diff options
85 files changed, 2273 insertions, 483 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 4c235e44fd1d..1233fa1ab773 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -172,7 +172,6 @@ java_aconfig_library { // DeviceStateManager aconfig_declarations { name: "android.hardware.devicestate.feature.flags-aconfig", - exportable: true, package: "android.hardware.devicestate.feature.flags", container: "system", srcs: ["core/java/android/hardware/devicestate/feature/*.aconfig"], @@ -187,7 +186,6 @@ java_aconfig_library { // Input aconfig_declarations { name: "com.android.hardware.input.input-aconfig", - exportable: true, package: "com.android.hardware.input", container: "system", srcs: ["core/java/android/hardware/input/*.aconfig"], @@ -472,7 +470,6 @@ cc_aconfig_library { // Hardware aconfig_declarations { name: "android.hardware.flags-aconfig", - exportable: true, package: "android.hardware.flags", container: "system", srcs: ["core/java/android/hardware/flags/*.aconfig"], @@ -570,7 +567,6 @@ java_aconfig_library { // Media Editing aconfig_declarations { name: "com.android.media.flags.editing-aconfig", - exportable: true, package: "com.android.media.editing.flags", container: "system", srcs: [ @@ -618,7 +614,6 @@ java_aconfig_library { // Media TV aconfig_declarations { name: "android.media.tv.flags-aconfig", - exportable: true, package: "android.media.tv.flags", container: "system", srcs: ["media/java/android/media/tv/flags/media_tv.aconfig"], @@ -633,7 +628,6 @@ java_aconfig_library { // OnDeviceIntelligence aconfig_declarations { name: "android.app.ondeviceintelligence-aconfig", - exportable: true, package: "android.app.ondeviceintelligence.flags", container: "system", srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"], @@ -688,7 +682,6 @@ cc_aconfig_library { // Biometrics aconfig_declarations { name: "android.hardware.biometrics.flags-aconfig", - exportable: true, package: "android.hardware.biometrics", container: "system", srcs: ["core/java/android/hardware/biometrics/flags.aconfig"], @@ -769,7 +762,6 @@ java_aconfig_library { // Broadcast Radio aconfig_declarations { name: "android.hardware.radio.flags-aconfig", - exportable: true, package: "android.hardware.radio", container: "system", srcs: ["core/java/android/hardware/radio/*.aconfig"], @@ -806,7 +798,6 @@ java_aconfig_library { // Content Protection aconfig_declarations { name: "android.view.contentprotection.flags-aconfig", - exportable: true, package: "android.view.contentprotection.flags", container: "system", srcs: ["core/java/android/view/contentprotection/flags/*.aconfig"], @@ -835,7 +826,6 @@ java_aconfig_library { // App prediction aconfig_declarations { name: "android.service.appprediction.flags-aconfig", - exportable: true, package: "android.service.appprediction.flags", container: "system", srcs: ["core/java/android/service/appprediction/flags/*.aconfig"], @@ -850,7 +840,6 @@ java_aconfig_library { // Controls aconfig_declarations { name: "android.service.controls.flags-aconfig", - exportable: true, package: "android.service.controls.flags", container: "system", srcs: ["core/java/android/service/controls/flags/*.aconfig"], @@ -865,7 +854,6 @@ java_aconfig_library { // Voice aconfig_declarations { name: "android.service.voice.flags-aconfig", - exportable: true, package: "android.service.voice.flags", container: "system", srcs: ["core/java/android/service/voice/flags/*.aconfig"], @@ -897,7 +885,6 @@ java_aconfig_library { // Companion aconfig_declarations { name: "android.companion.flags-aconfig", - exportable: true, package: "android.companion", container: "system", srcs: ["core/java/android/companion/*.aconfig"], @@ -912,7 +899,6 @@ java_aconfig_library { // Networking aconfig_declarations { name: "android.net.platform.flags-aconfig", - exportable: true, package: "android.net.platform.flags", container: "system", srcs: ["core/java/android/net/flags.aconfig"], @@ -922,7 +908,6 @@ aconfig_declarations { // Thread network aconfig_declarations { name: "com.android.net.thread.platform.flags-aconfig", - exportable: true, package: "com.android.net.thread.platform.flags", container: "system", srcs: ["core/java/android/net/thread/flags.aconfig"], @@ -1026,7 +1011,6 @@ aconfig_declarations { name: "framework-jobscheduler-job.flags-aconfig", package: "android.app.job", container: "system", - exportable: true, srcs: ["apex/jobscheduler/framework/aconfig/job.aconfig"], } @@ -1094,7 +1078,6 @@ java_aconfig_library { // Smartspace aconfig_declarations { name: "android.app.smartspace.flags-aconfig", - exportable: true, package: "android.app.smartspace.flags", container: "system", srcs: ["core/java/android/app/smartspace/flags.aconfig"], @@ -1130,7 +1113,6 @@ java_aconfig_library { // USB aconfig_declarations { name: "android.hardware.usb.flags-aconfig", - exportable: true, package: "android.hardware.usb.flags", container: "system", srcs: ["core/java/android/hardware/usb/flags/*.aconfig"], @@ -1216,7 +1198,6 @@ java_aconfig_library { // Provider aconfig_declarations { name: "android.provider.flags-aconfig", - exportable: true, package: "android.provider", container: "system", srcs: ["core/java/android/provider/*.aconfig"], @@ -1238,7 +1219,6 @@ java_aconfig_library { // Speech aconfig_declarations { name: "android.speech.flags-aconfig", - exportable: true, package: "android.speech.flags", container: "system", srcs: ["core/java/android/speech/flags/*.aconfig"], @@ -1260,7 +1240,6 @@ java_aconfig_library { // Content aconfig_declarations { name: "android.content.flags-aconfig", - exportable: true, package: "android.content.flags", container: "system", srcs: ["core/java/android/content/flags/flags.aconfig"], @@ -1289,7 +1268,6 @@ java_aconfig_library { // CrashRecovery Module aconfig_declarations { name: "android.crashrecovery.flags-aconfig", - exportable: true, package: "android.crashrecovery.flags", container: "system", srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"], @@ -1330,7 +1308,6 @@ java_aconfig_library { // Wearable Sensing aconfig_declarations { name: "android.app.wearable.flags-aconfig", - exportable: true, package: "android.app.wearable", container: "system", srcs: ["core/java/android/app/wearable/*.aconfig"], diff --git a/core/api/current.txt b/core/api/current.txt index b19c3ab96bb5..13d5e03da7ed 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9877,7 +9877,7 @@ package android.companion { ctor public ObservingDevicePresenceRequest.Builder(); method @NonNull public android.companion.ObservingDevicePresenceRequest build(); method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int); - method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid); + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid); } public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ee0225fc8918..e3380e0bf12a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -21,12 +21,14 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.StrictMode.vmIncorrectContextUseEnabled; import static android.view.WindowManager.LayoutParams.WindowType; +import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UiContext; +import android.companion.virtual.VirtualDevice; import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; @@ -2288,7 +2290,35 @@ class ContextImpl extends Context { Log.v(TAG, "Treating renounced permission " + permission + " as denied"); return PERMISSION_DENIED; } - return PermissionManager.checkPermission(permission, pid, uid, getDeviceId()); + + // When checking a device-aware permission on a remote device, if the permission is CAMERA + // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote + // device doesn't have capability fall back to checking permission on the default device. + // Note: we only perform permission check redirection when the device id is not explicitly + // set in the context. + int deviceId = getDeviceId(); + if (deviceId != Context.DEVICE_ID_DEFAULT + && !mIsExplicitDeviceId + && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) { + VirtualDeviceManager virtualDeviceManager = + getSystemService(VirtualDeviceManager.class); + VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId); + if (virtualDevice != null) { + if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO) + && !virtualDevice.hasCustomAudioInputSupport()) + || (Objects.equals(permission, Manifest.permission.CAMERA) + && !virtualDevice.hasCustomCameraSupport())) { + deviceId = Context.DEVICE_ID_DEFAULT; + } + } else { + Slog.e( + TAG, + "virtualDevice is not found when device id is not default. deviceId = " + + deviceId); + } + } + + return PermissionManager.checkPermission(permission, pid, uid, deviceId); } /** @hide */ diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java index f1d594e80bda..11ea735dff4f 100644 --- a/core/java/android/companion/ObservingDevicePresenceRequest.java +++ b/core/java/android/companion/ObservingDevicePresenceRequest.java @@ -183,7 +183,11 @@ public final class ObservingDevicePresenceRequest implements Parcelable { * @param uuid The ParcelUuid for observing device presence. */ @NonNull - @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) + @RequiresPermission(allOf = { + android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE, + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_SCAN + }) public Builder setUuid(@NonNull ParcelUuid uuid) { checkNotUsed(); this.mUuid = uuid; diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index 2fe115f49099..5b711c9d8401 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -25,8 +25,6 @@ import android.util.Printer; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; -import dalvik.annotation.optimization.CriticalNative; - import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -80,7 +78,6 @@ public final class MessageQueue { private native static void nativeDestroy(long ptr); @UnsupportedAppUsage private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ - @CriticalNative private native static void nativeWake(long ptr); private native static boolean nativeIsPolling(long ptr); private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 3441244d6c58..fe3fa8cf34f5 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -240,6 +240,16 @@ public final class PermissionManager { public static final String EXTRA_PERMISSION_USAGES = "android.permission.extra.PERMISSION_USAGES"; + /** + * Specify what permissions are device aware. Only device aware permissions can be granted to + * a remote device. + * @hide + */ + public static final Set<String> DEVICE_AWARE_PERMISSIONS = + Flags.deviceAwarePermissionsEnabled() + ? Set.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) + : Collections.emptySet(); + private final @NonNull Context mContext; private final IPackageManager mPackageManager; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index da6cd40a0c8c..c5a4d677e70e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -9587,6 +9587,8 @@ public final class ViewRootImpl implements ViewParent, } mRemoved = true; mOnBackInvokedDispatcher.detachFromWindow(); + removeVrrMessages(); + if (mAdded) { dispatchDetachedFromWindow(); } @@ -12551,8 +12553,8 @@ public final class ViewRootImpl implements ViewParent, if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, frameRateCategory, false).applyAsyncUnsafe(); - mLastPreferredFrameRateCategory = frameRateCategory; } + mLastPreferredFrameRateCategory = frameRateCategory; } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate category", e); @@ -12612,8 +12614,8 @@ public final class ViewRootImpl implements ViewParent, if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate, mFrameRateCompatibility).applyAsyncUnsafe(); - mLastPreferredFrameRate = preferredFrameRate; } + mLastPreferredFrameRate = preferredFrameRate; } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate", e); @@ -12855,4 +12857,10 @@ public final class ViewRootImpl implements ViewParent, mHasIdledMessage = true; } } + + private void removeVrrMessages() { + mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); + mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); + mHandler.removeMessages(MSG_FRAME_RATE_SETTING); + } } diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp index 9525605a6a8c..30d9ea19be39 100644 --- a/core/jni/android_os_MessageQueue.cpp +++ b/core/jni/android_os_MessageQueue.cpp @@ -225,7 +225,7 @@ static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, nativeMessageQueue->pollOnce(env, obj, timeoutMillis); } -static void android_os_MessageQueue_nativeWake(jlong ptr) { +static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->wake(); } diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index 6f20adf74ee2..8891b50352d1 100644 --- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java @@ -39,13 +39,13 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.BackgroundThread; import android.util.LongArrayQueue; import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java new file mode 100644 index 000000000000..a6ae68f62f10 --- /dev/null +++ b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java @@ -0,0 +1,103 @@ +/* + * * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.HandlerThread; + +import com.android.internal.annotations.GuardedBy; + +import java.util.concurrent.Executor; + +/** + * Thread for asynchronous event processing. This thread is configured as + * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU + * resources will be dedicated to it, and it will "have less chance of impacting + * the responsiveness of the user interface." + * <p> + * This thread is best suited for tasks that the user is not actively waiting + * for, or for tasks that the user expects to be executed eventually. + * + * @see com.android.internal.os.BackgroundThread + * + * TODO: b/326916057 depend on modules-utils-backgroundthread instead + * @hide + */ +public final class BackgroundThread extends HandlerThread { + private static final Object sLock = new Object(); + + @GuardedBy("sLock") + private static BackgroundThread sInstance; + @GuardedBy("sLock") + private static Handler sHandler; + @GuardedBy("sLock") + private static HandlerExecutor sHandlerExecutor; + + private BackgroundThread() { + super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND); + } + + @GuardedBy("sLock") + private static void ensureThreadLocked() { + if (sInstance == null) { + sInstance = new BackgroundThread(); + sInstance.start(); + sHandler = new Handler(sInstance.getLooper()); + sHandlerExecutor = new HandlerExecutor(sHandler); + } + } + + /** + * Get the singleton instance of this class. + * + * @return the singleton instance of this class + */ + @NonNull + public static BackgroundThread get() { + synchronized (sLock) { + ensureThreadLocked(); + return sInstance; + } + } + + /** + * Get the singleton {@link Handler} for this class. + * + * @return the singleton {@link Handler} for this class. + */ + @NonNull + public static Handler getHandler() { + synchronized (sLock) { + ensureThreadLocked(); + return sHandler; + } + } + + /** + * Get the singleton {@link Executor} for this class. + * + * @return the singleton {@link Executor} for this class. + */ + @NonNull + public static Executor getExecutor() { + synchronized (sLock) { + ensureThreadLocked(); + return sHandlerExecutor; + } + } +} diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java new file mode 100644 index 000000000000..948ebcca0263 --- /dev/null +++ b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.annotation.NonNull; +import android.os.Handler; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +/** + * An adapter {@link Executor} that posts all executed tasks onto the given + * {@link Handler}. + * + * TODO: b/326916057 depend on modules-utils-backgroundthread instead + * @hide + */ +public class HandlerExecutor implements Executor { + private final Handler mHandler; + + public HandlerExecutor(@NonNull Handler handler) { + mHandler = Objects.requireNonNull(handler); + } + + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } + } +} diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index bc35a85e48f8..9fd386f38684 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -68,6 +68,13 @@ <string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string> <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] --> <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string> + <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] --> + <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string> + <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] --> + <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string> + <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] --> + <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) --> + <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string> <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] --> <string name="passkey">passkey</string> <string name="password">password</string> diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index f2c252ec6422..b408c1553d94 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -51,6 +51,8 @@ import com.android.credentialmanager.TAG import com.android.credentialmanager.model.BiometricRequestInfo import com.android.credentialmanager.model.EntryInfo +const val CREDENTIAL_ENTRY_PREFIX = "androidx.credentials.provider.credentialEntry." + fun EntryInfo.getIntentSenderRequest( isAutoSelected: Boolean = false ): IntentSenderRequest? { @@ -140,7 +142,8 @@ private fun getCredentialOptionInfoList( isDefaultIconPreferredAsSingleProvider = credentialEntry.isDefaultIconPreferredAsSingleProvider, affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), - biometricRequest = predetermineAndValidateBiometricFlow(it), + biometricRequest = predetermineAndValidateBiometricFlow(it, + CREDENTIAL_ENTRY_PREFIX), ) ) } @@ -169,7 +172,8 @@ private fun getCredentialOptionInfoList( isDefaultIconPreferredAsSingleProvider = credentialEntry.isDefaultIconPreferredAsSingleProvider, affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), - biometricRequest = predetermineAndValidateBiometricFlow(it), + biometricRequest = predetermineAndValidateBiometricFlow(it, + CREDENTIAL_ENTRY_PREFIX), ) ) } @@ -197,7 +201,8 @@ private fun getCredentialOptionInfoList( isDefaultIconPreferredAsSingleProvider = credentialEntry.isDefaultIconPreferredAsSingleProvider, affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), - biometricRequest = predetermineAndValidateBiometricFlow(it), + biometricRequest = predetermineAndValidateBiometricFlow(it, + CREDENTIAL_ENTRY_PREFIX), ) ) } @@ -217,21 +222,26 @@ private fun getCredentialOptionInfoList( * Note that the required values, such as the provider info's icon or display name, or the entries * credential type or userName, and finally the display info's app name, are non-null and must * exist to run through the flow. + * + * @param hintPrefix a string prefix indicating the type of entry being utilized, since both create + * and get flows utilize slice params; includes the final '.' before the name of the type (e.g. + * androidx.credentials.provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS must have + * 'hintPrefix' up to "androidx.credentials.provider.credentialEntry.") * // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never * // expected to be used. When it is added, it should be lightly validated. */ -private fun predetermineAndValidateBiometricFlow( - it: Entry +fun predetermineAndValidateBiometricFlow( + entry: Entry, + hintPrefix: String, ): BiometricRequestInfo? { // TODO(b/326243754) : When available, use the official jetpack structured type - val allowedAuthenticators: Int? = it.slice.items.firstOrNull { - it.hasHint("androidx.credentials." + - "provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS") + val allowedAuthenticators: Int? = entry.slice.items.firstOrNull { + it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS") }?.int // This is optional and does not affect validating the biometric flow in any case - val opId: Int? = it.slice.items.firstOrNull { - it.hasHint("androidx.credentials.provider.credentialEntry.SLICE_HINT_CRYPTO_OP_ID") + val opId: Int? = entry.slice.items.firstOrNull { + it.hasHint(hintPrefix + "SLICE_HINT_CRYPTO_OP_ID") }?.int if (allowedAuthenticators != null) { return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 28c40479962e..a03975375e8a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -18,6 +18,7 @@ package com.android.credentialmanager import android.app.Activity import android.hardware.biometrics.BiometricPrompt +import android.hardware.biometrics.BiometricPrompt.AuthenticationResult import android.os.IBinder import android.text.TextUtils import android.util.Log @@ -39,6 +40,7 @@ import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.createflow.ActiveEntry import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateScreenState +import com.android.credentialmanager.createflow.findBiometricFlowEntry import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetScreenState import com.android.credentialmanager.logging.LifecycleEvent @@ -304,7 +306,11 @@ class CredentialSelectorViewModel( uiState = uiState.copy( createCredentialUiState = uiState.createCredentialUiState?.copy( currentScreenState = - if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds + // An autoselect flow never makes it to the more options screen + if (findBiometricFlowEntry(activeEntry = activeEntry, + isAutoSelectFlow = false) != null) CreateScreenState.BIOMETRIC_SELECTION + else if ( + uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds ?.contains(activeEntry.activeProvider.id) ?: true || !(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider ?: false) || @@ -330,7 +336,10 @@ class CredentialSelectorViewModel( ) } - fun createFlowOnEntrySelected(selectedEntry: EntryInfo) { + fun createFlowOnEntrySelected( + selectedEntry: EntryInfo, + authResult: AuthenticationResult? = null + ) { val providerId = selectedEntry.providerId val entryKey = selectedEntry.entryKey val entrySubkey = selectedEntry.entrySubkey @@ -341,6 +350,9 @@ class CredentialSelectorViewModel( uiState = uiState.copy( selectedEntry = selectedEntry, providerActivityState = ProviderActivityState.READY_TO_LAUNCH, + biometricState = if (authResult == null) uiState.biometricState else uiState + .biometricState.copy(biometricResult = BiometricResult( + biometricAuthenticationResult = authResult)) ) } else { credManRepo.onOptionSelected( @@ -367,9 +379,4 @@ class CredentialSelectorViewModel( fun logUiEvent(uiEventEnum: UiEventEnum) { this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName) } - - companion object { - // TODO(b/326243754) : Replace/remove once all failure flows added in - const val TEMPORARY_FAILURE_CODE = Integer.MIN_VALUE - } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index fd6fc6a44c7c..358ebfa1ec90 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -52,10 +52,11 @@ import androidx.credentials.provider.CreateEntry import androidx.credentials.provider.RemoteEntry import org.json.JSONObject import android.credentials.flags.Flags +import com.android.credentialmanager.createflow.isBiometricFlow import com.android.credentialmanager.getflow.TopBrandingContent +import com.android.credentialmanager.ktx.predetermineAndValidateBiometricFlow import java.time.Instant - fun getAppLabel( pm: PackageManager, appPackageName: String @@ -237,6 +238,9 @@ class GetFlowUtils { class CreateFlowUtils { companion object { + + private const val CREATE_ENTRY_PREFIX = "androidx.credentials.provider.createEntry." + /** * Note: caller required handle empty list due to parsing error. */ @@ -417,12 +421,21 @@ class CreateFlowUtils { } } val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser + val sortedCreateOptionsPairs = createOptionsPairs.sortedWith( + compareByDescending { it.first.lastUsedTime } + ) + val activeEntry = toActiveEntry( + defaultProvider = defaultProvider, + sortedCreateOptionsPairs = sortedCreateOptionsPairs, + remoteEntry = remoteEntry, + remoteEntryProvider = remoteEntryProvider, + ) + val isBiometricFlow = if (activeEntry == null) false else isBiometricFlow(activeEntry, + sortedCreateOptionsPairs, requestDisplayInfo) val initialScreenState = toCreateScreenState( createOptionSize = createOptionsPairs.size, remoteEntry = remoteEntry, - ) - val sortedCreateOptionsPairs = createOptionsPairs.sortedWith( - compareByDescending { it.first.lastUsedTime } + isBiometricFlow = isBiometricFlow ) return CreateCredentialUiState( enabledProviders = enabledProviders, @@ -430,12 +443,7 @@ class CreateFlowUtils { currentScreenState = initialScreenState, requestDisplayInfo = requestDisplayInfo, sortedCreateOptionsPairs = sortedCreateOptionsPairs, - activeEntry = toActiveEntry( - defaultProvider = defaultProvider, - sortedCreateOptionsPairs = sortedCreateOptionsPairs, - remoteEntry = remoteEntry, - remoteEntryProvider = remoteEntryProvider, - ), + activeEntry = activeEntry, remoteEntry = remoteEntry, foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null, ) @@ -444,9 +452,12 @@ class CreateFlowUtils { fun toCreateScreenState( createOptionSize: Int, remoteEntry: RemoteInfo?, + isBiometricFlow: Boolean, ): CreateScreenState { return if (createOptionSize == 0 && remoteEntry != null) { CreateScreenState.EXTERNAL_ONLY_SELECTION + } else if (isBiometricFlow) { + CreateScreenState.BIOMETRIC_SELECTION } else { CreateScreenState.CREATION_OPTION_SELECTION } @@ -503,8 +514,8 @@ class CreateFlowUtils { it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" + "SELECT_ALLOWED") }?.text == "true", - // TODO(b/326243754) : Handle this when the create flow is added; for now the - // create flow does not support biometric values + biometricRequest = predetermineAndValidateBiometricFlow(it, + CREATE_ENTRY_PREFIX), ) ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index a30956ecf5a5..fa177351be30 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -26,11 +26,14 @@ import androidx.core.content.ContextCompat.getMainExecutor import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.R import com.android.credentialmanager.createflow.EnabledProviderInfo +import com.android.credentialmanager.createflow.getCreateTitleResCode import com.android.credentialmanager.getflow.ProviderDisplayInfo import com.android.credentialmanager.getflow.RequestDisplayInfo import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode import com.android.credentialmanager.model.BiometricRequestInfo +import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.model.EntryInfo +import com.android.credentialmanager.model.creation.CreateOptionInfo import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.model.get.ProviderInfo import java.lang.Exception @@ -39,14 +42,30 @@ import java.lang.Exception * Aggregates common display information used for the Biometric Flow. * Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the * [providerName], which represents the name of the provider, the [displayTitleText] which is - * the large text displaying the flow in progress, and the [descriptionAboveBiometricButton], which + * the large text displaying the flow in progress, and the [descriptionForCredential], which * describes details of where the credential is being saved, and how. + * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com: + * + * 'get' flow: + * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon) + * - [displayTitleText] = "Use your saved passkey for Any Provider?" + * - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with + * Your@Email.com" + * + * 'create' flow: + * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon) + * - [displayTitleText] = "Create passkey to sign in to Any Provider?" + * - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?" + * ). + * + * The above are examples; the credential type can change depending on scenario. + * // TODO(b/326243891) : Finalize once all the strings and create flow is iterated to completion */ data class BiometricDisplayInfo( val providerIcon: Bitmap, val providerName: String, val displayTitleText: String, - val descriptionAboveBiometricButton: String, + val descriptionForCredential: String, val biometricRequestInfo: BiometricRequestInfo, ) @@ -56,10 +75,7 @@ data class BiometricDisplayInfo( * additional states that may improve the flow. */ data class BiometricState( - val biometricResult: BiometricResult? = null, - val biometricError: BiometricError? = null, - val biometricHelp: BiometricHelp? = null, - val biometricAcquireInfo: Int? = null, + val biometricResult: BiometricResult? = null ) /** @@ -108,18 +124,20 @@ fun runBiometricFlow( .RequestDisplayInfo? = null, createProviderInfo: EnabledProviderInfo? = null, ) { + // TODO(b/330396089) : Add rotation configuration fix with state machine var biometricDisplayInfo: BiometricDisplayInfo? = null + var flowType = FlowType.GET if (getRequestDisplayInfo != null) { biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(getRequestDisplayInfo, getProviderInfoList, getProviderDisplayInfo, context, biometricEntry) } else if (createRequestDisplayInfo != null) { - // TODO(b/326243754) : Create Flow to be implemented in follow up - biometricDisplayInfo = validateBiometricCreateFlow( + flowType = FlowType.CREATE + biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo( createRequestDisplayInfo, - createProviderInfo - ) + createProviderInfo, + context, biometricEntry) } if (biometricDisplayInfo == null) { @@ -128,7 +146,7 @@ fun runBiometricFlow( } val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage, - biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators) + biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators, flowType) val callback: BiometricPrompt.AuthenticationCallback = setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry, @@ -154,23 +172,21 @@ fun runBiometricFlow( /** * Sets up the biometric prompt with the UI specific bits. * // TODO(b/326243754) : Pass in opId once dependency is confirmed via CryptoObject - * // TODO(b/326243754) : Given fallbacks aren't allowed, for now we validate that device creds - * // are NOT allowed to be passed in to avoid throwing an error. Later, however, once target - * // alignments occur, we should add the bit back properly. */ private fun setupBiometricPrompt( context: Context, biometricDisplayInfo: BiometricDisplayInfo, openMoreOptionsPage: () -> Unit, requestAllowedAuthenticators: Int, + flowType: FlowType, ): BiometricPrompt { val finalAuthenticators = removeDeviceCredential(requestAllowedAuthenticators) val biometricPrompt = BiometricPrompt.Builder(context) .setTitle(biometricDisplayInfo.displayTitleText) // TODO(b/326243754) : Migrate to using new methods recently aligned upon - .setNegativeButton(context.getString(R.string - .dropdown_presentation_more_sign_in_options_text), + .setNegativeButton(context.getString(if (flowType == FlowType.GET) R.string + .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options), getMainExecutor(context)) { _, _ -> openMoreOptionsPage() } @@ -178,7 +194,7 @@ private fun setupBiometricPrompt( .setConfirmationRequired(true) .setLogoBitmap(biometricDisplayInfo.providerIcon) .setLogoDescription(biometricDisplayInfo.providerName) - .setDescription(biometricDisplayInfo.descriptionAboveBiometricButton) + .setDescription(biometricDisplayInfo.descriptionForCredential) .build() return biometricPrompt @@ -294,14 +310,16 @@ private fun validateAndRetrieveBiometricGetDisplayInfo( * checking between the two. The reason for this method matches the logic for the * [validateBiometricGetFlow] with the only difference being that this is for the create flow. */ -private fun validateBiometricCreateFlow( +private fun validateAndRetrieveBiometricCreateDisplayInfo( createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo?, createProviderInfo: EnabledProviderInfo?, + context: Context, + selectedEntry: EntryInfo, ): BiometricDisplayInfo? { if (createRequestDisplayInfo != null && createProviderInfo != null) { - } else if (createRequestDisplayInfo != null && createProviderInfo != null) { - // TODO(b/326243754) : Create Flow to be implemented in follow up - return createFlowDisplayValues() + if (selectedEntry !is CreateOptionInfo) { return null } + return createBiometricDisplayValues(createRequestDisplayInfo, createProviderInfo, context, + selectedEntry) } return null } @@ -346,17 +364,47 @@ private fun getBiometricDisplayValues( username ) return BiometricDisplayInfo(providerIcon = icon, providerName = providerName, - displayTitleText = displayTitleText, descriptionAboveBiometricButton = descriptionText, + displayTitleText = displayTitleText, descriptionForCredential = descriptionText, biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo) } /** - * Handles the biometric sign in via the 'create credentials' flow, or early validates this flow - * needs to fallback. + * Handles the biometric sign in via the create credentials flow. Stricter in the get flow in that + * if this is called, a result is guaranteed. Specifically, this is guaranteed to return a non-null + * value unlike the get counterpart. */ -private fun createFlowDisplayValues(): BiometricDisplayInfo? { - // TODO(b/326243754) : Create Flow to be implemented in follow up - return null +private fun createBiometricDisplayValues( + createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo, + createProviderInfo: EnabledProviderInfo, + context: Context, + selectedEntry: CreateOptionInfo, +): BiometricDisplayInfo { + val icon: Bitmap? + val providerName: String? + val displayTitleText: String? + icon = createProviderInfo.icon.toBitmap() + providerName = createProviderInfo.displayName + displayTitleText = context.getString( + getCreateTitleResCode(createRequestDisplayInfo), + createRequestDisplayInfo.appName + ) + val descriptionText: String = context.getString( + when (createRequestDisplayInfo.type) { + CredentialType.PASSKEY -> + R.string.choose_create_single_tap_passkey_title + + CredentialType.PASSWORD -> + R.string.choose_create_single_tap_password_title + + CredentialType.UNKNOWN -> + R.string.choose_create_single_tap_sign_in_title + }, + createRequestDisplayInfo.appName, + ) + // TODO(b/327620327) : Add a subtitle and any other recently aligned ideas + return BiometricDisplayInfo(providerIcon = icon, providerName = providerName, + displayTitleText = displayTitleText, descriptionForCredential = descriptionText, + biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo) } /** diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt new file mode 100644 index 000000000000..f6140f51b7b5 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.common + +enum class FlowType { + GET, + CREATE +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index af78573ee9e9..25fb477cbf38 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -17,6 +17,7 @@ package com.android.credentialmanager.createflow import android.credentials.flags.Flags.selectorUiImprovementsEnabled +import android.hardware.biometrics.BiometricPrompt import android.text.TextUtils import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult @@ -26,7 +27,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.Divider import androidx.compose.material.icons.Icons @@ -38,6 +38,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -49,6 +50,7 @@ import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.material.ModalBottomSheetDefaults +import com.android.credentialmanager.common.runBiometricFlow import com.android.credentialmanager.common.ui.ActionButton import com.android.credentialmanager.common.ui.BodyMediumText import com.android.credentialmanager.common.ui.BodySmallText @@ -95,6 +97,22 @@ fun CreateCredentialScreen( viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection, onLog = { viewModel.logUiEvent(it) }, ) + CreateScreenState.BIOMETRIC_SELECTION -> + BiometricSelectionPage( + biometricEntry = createCredentialUiState + .activeEntry?.activeEntryInfo, + onCancelFlowAndFinish = viewModel::onUserCancel, + onIllegalScreenStateAndFinish = viewModel::onIllegalUiState, + onMoreOptionSelected = + viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection, + requestDisplayInfo = createCredentialUiState.requestDisplayInfo, + enabledProviderInfo = createCredentialUiState + .activeEntry?.activeProvider!!, + onBiometricEntrySelected = + viewModel::createFlowOnEntrySelected, + fallbackToOriginalFlow = + viewModel::getFlowOnBackToPrimarySelectionScreen, + ) CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard( requestDisplayInfo = createCredentialUiState.requestDisplayInfo, enabledProviderList = createCredentialUiState.enabledProviders, @@ -313,20 +331,9 @@ fun CreationSelectionCard( item { Divider(thickness = 16.dp, color = Color.Transparent) } item { HeadlineText( - text = when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> stringResource( - R.string.choose_create_option_passkey_title, - requestDisplayInfo.appName - ) - CredentialType.PASSWORD -> stringResource( - R.string.choose_create_option_password_title, - requestDisplayInfo.appName - ) - CredentialType.UNKNOWN -> stringResource( - R.string.choose_create_option_sign_in_title, - requestDisplayInfo.appName - ) - } + text = stringResource( + getCreateTitleResCode(requestDisplayInfo), + requestDisplayInfo.appName) ) } item { Divider(thickness = 24.dp, color = Color.Transparent) } @@ -560,4 +567,32 @@ fun RemoteEntryRow( iconImageVector = Icons.Outlined.QrCodeScanner, entryHeadlineText = stringResource(R.string.another_device), ) -}
\ No newline at end of file +} + +@Composable +internal fun BiometricSelectionPage( + biometricEntry: EntryInfo?, + onMoreOptionSelected: () -> Unit, + requestDisplayInfo: RequestDisplayInfo, + enabledProviderInfo: EnabledProviderInfo, + onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit, + onCancelFlowAndFinish: () -> Unit, + onIllegalScreenStateAndFinish: (String) -> Unit, + fallbackToOriginalFlow: () -> Unit, +) { + if (biometricEntry == null) { + fallbackToOriginalFlow() + return + } + runBiometricFlow( + biometricEntry = biometricEntry, + context = LocalContext.current, + openMoreOptionsPage = onMoreOptionSelected, + sendDataToProvider = onBiometricEntrySelected, + onCancelFlowAndFinish = onCancelFlowAndFinish, + createRequestDisplayInfo = requestDisplayInfo, + createProviderInfo = enabledProviderInfo, + onBiometricFailureFallback = fallbackToOriginalFlow, + onIllegalStateAndFinish = onIllegalScreenStateAndFinish, + ) +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 617a981fc4ba..1d262ba5261a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -16,9 +16,11 @@ package com.android.credentialmanager.createflow +import android.credentials.flags.Flags.credmanBiometricApiEnabled import android.graphics.drawable.Drawable -import com.android.credentialmanager.model.EntryInfo +import com.android.credentialmanager.R import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.model.creation.CreateOptionInfo import com.android.credentialmanager.model.creation.RemoteInfo @@ -33,14 +35,99 @@ data class CreateCredentialUiState( val foundCandidateFromUserDefaultProvider: Boolean, ) +/** + * Checks if this create flow is a biometric flow. Note that this flow differs slightly from the + * autoselect 'get' flow. Namely, given there can be multiple providers, rather than multiple + * accounts, the idea is that autoselect is ever only enabled for a single provider (or even, in + * that case, a single 'type' (family only, or work only) for a provider). However, for all other + * cases, the biometric screen should always show up if that entry contains the biometric bit. + */ +internal fun findBiometricFlowEntry( + activeEntry: ActiveEntry, + isAutoSelectFlow: Boolean, +): CreateOptionInfo? { + if (!credmanBiometricApiEnabled()) { + return null + } + if (isAutoSelectFlow) { + // Since this is the create flow, auto select will only ever be true for a single provider. + // However, for all other cases, biometric should be used if that bit is opted into. If + // they clash, autoselect is always preferred, but that's only if there's a single provider. + return null + } + val biometricEntry = getCreateEntry(activeEntry) + return if (biometricEntry?.biometricRequest != null) biometricEntry else null +} + +/** + * Retrieves the activeEntry by validating it is a [CreateOptionInfo]. This is done by ensuring + * that the [activeEntry] exists as a [CreateOptionInfo] to retrieve its [EntryInfo]. + */ +internal fun getCreateEntry( + activeEntry: ActiveEntry?, +): CreateOptionInfo? { + val entry = activeEntry?.activeEntryInfo + if (entry !is CreateOptionInfo) { + return null + } + return entry +} + +/** +* Determines if the flow is a biometric flow by taking into account autoselect criteria. +*/ +internal fun isBiometricFlow( + activeEntry: ActiveEntry, + sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, + requestDisplayInfo: RequestDisplayInfo, +) = findBiometricFlowEntry(activeEntry, isFlowAutoSelectable( + requestDisplayInfo = requestDisplayInfo, + activeEntry = activeEntry, + sortedCreateOptionsPairs = sortedCreateOptionsPairs +)) != null + +/** + * This utility presents the correct resource string for the create flows title conditionally. + * Similar to generateDisplayTitleTextResCode in the 'get' flow, but for the create flow instead. + * This is for the title, and is a shared resource, unlike the specific unlock request text. + * E.g. this will look something like: "Create passkey to sign in to Tribank." + * // TODO(b/330396140) : Validate approach and add dynamic auth strings + */ +internal fun getCreateTitleResCode(createRequestDisplayInfo: RequestDisplayInfo): Int = + when (createRequestDisplayInfo.type) { + CredentialType.PASSKEY -> + R.string.choose_create_option_passkey_title + + CredentialType.PASSWORD -> + R.string.choose_create_option_password_title + + CredentialType.UNKNOWN -> + R.string.choose_create_option_sign_in_title + } + internal fun isFlowAutoSelectable( uiState: CreateCredentialUiState ): Boolean { - return uiState.requestDisplayInfo.isAutoSelectRequest && - uiState.sortedCreateOptionsPairs.size == 1 && - uiState.activeEntry?.activeEntryInfo?.let { - it is CreateOptionInfo && it.allowAutoSelect - } ?: false + return isFlowAutoSelectable(uiState.requestDisplayInfo, uiState.activeEntry, + uiState.sortedCreateOptionsPairs) +} + +/** + * When initializing, the [CreateCredentialUiState] is generated after the initial screen is set. + * This overloaded method allows identifying if the flow is auto selectable prior to the creation + * of the [CreateCredentialUiState]. + */ +internal fun isFlowAutoSelectable( + requestDisplayInfo: RequestDisplayInfo, + activeEntry: ActiveEntry?, + sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>> +): Boolean { + val isAutoSelectRequest = requestDisplayInfo.isAutoSelectRequest + if (sortedCreateOptionsPairs.size != 1) { + return false + } + val singleEntry = getCreateEntry(activeEntry) + return isAutoSelectRequest && singleEntry?.allowAutoSelect == true } internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean { @@ -95,6 +182,7 @@ data class ActiveEntry ( /** The name of the current screen. */ enum class CreateScreenState { CREATION_OPTION_SELECTION, + BIOMETRIC_SELECTION, MORE_OPTIONS_SELECTION, DEFAULT_PROVIDER_CONFIRMATION, EXTERNAL_ONLY_SELECTION, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 4d7272c7716e..6d1a3dd98210 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -144,8 +144,6 @@ fun GetCredentialScreen( } else if (credmanBiometricApiEnabled() && getCredentialUiState .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) { BiometricSelectionPage( - // TODO(b/326243754) : Utilize expected entry for this flow, confirm - // activeEntry will always be what represents the single tap flow biometricEntry = getCredentialUiState.activeEntry, onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected, onCancelFlowAndFinish = viewModel::onUserCancel, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 6d5b52a7a5f9..ac776af4f627 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -238,6 +238,7 @@ fun toProviderDisplayInfo( /** * This generates the res code for the large display title text for the selector. For example, it * retrieves the resource for strings like: "Use your saved passkey for *rpName*". + * TODO(b/330396140) : Validate approach and add dynamic auth strings */ internal fun generateDisplayTitleTextResCode( singleEntryType: CredentialType, diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index f6616dbfe11a..6810aac92925 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -724,3 +724,10 @@ flag { " Compose for the UI." bug: "325099249" } + +flag { + name: "keyboard_docking_indicator" + namespace: "systemui" + description: "Glow bar indicator reveals upon keyboard docking." + bug: "324600132" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt new file mode 100644 index 000000000000..ca6b3434d90e --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.composable + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.modifiers.height +import com.android.compose.modifiers.width +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater +import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel + +@Composable +fun BrightnessMirror( + viewModel: BrightnessMirrorViewModel, + qsSceneAdapter: QSSceneAdapter, + modifier: Modifier = Modifier, +) { + val isShowing by viewModel.isShowing.collectAsState() + val mirrorAlpha by + animateFloatAsState( + targetValue = if (isShowing) 1f else 0f, + label = "alphaAnimationBrightnessMirrorShowing", + ) + val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState() + val offset = IntOffset(0, mirrorOffsetAndSize.yOffset) + + Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) { + QuickSettingsTheme { + // The assumption for using this AndroidView is that there will be only one in view at + // a given time (which is a reasonable assumption). Because `QSSceneAdapter` (actually + // `BrightnessSliderController` only supports a single mirror). + // The benefit of doing it like this is that if the configuration changes or QSImpl is + // re-inflated, it's not relevant to the composable, as we'll always get a new one. + AndroidView( + modifier = + Modifier.align(Alignment.TopCenter) + .offset { offset } + .width { mirrorOffsetAndSize.width } + .height { mirrorOffsetAndSize.height }, + factory = { context -> + val (view, controller) = + BrightnessMirrorInflater.inflate(context, viewModel.sliderControllerFactory) + viewModel.setToggleSlider(controller) + view + }, + update = { qsSceneAdapter.setBrightnessMirrorController(viewModel) }, + onRelease = { qsSceneAdapter.setBrightnessMirrorController(null) } + ) + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 244f48019dc5..a3768346e7c6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.ui.composable import android.view.ViewGroup import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -49,6 +50,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource @@ -120,8 +122,20 @@ private fun SceneScope.QuickSettingsScene( statusBarIconController: StatusBarIconController, modifier: Modifier = Modifier, ) { + val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() + val contentAlpha by + animateFloatAsState( + targetValue = if (brightnessMirrorShowing) 0f else 1f, + label = "alphaAnimationBrightnessMirrorContentHiding", + ) + + BrightnessMirror( + viewModel = viewModel.brightnessMirrorViewModel, + qsSceneAdapter = viewModel.qsSceneAdapter + ) + // TODO(b/280887232): implement the real UI. - Box(modifier = modifier.fillMaxSize()) { + Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) { val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() BackHandler( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 85798acd0dcd..9bd6f817cff3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade.ui.composable import android.view.ViewGroup +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.clipScrollableContainer @@ -33,6 +34,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -45,6 +47,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalDensity @@ -70,6 +73,7 @@ import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.notifications.ui.composable.NotificationScrollingStack import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility +import com.android.systemui.qs.ui.composable.BrightnessMirror import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes @@ -302,12 +306,25 @@ private fun SceneScope.SplitShade( } } + val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() + val contentAlpha by + animateFloatAsState( + targetValue = if (brightnessMirrorShowing) 0f else 1f, + label = "alphaAnimationBrightnessMirrorContentHiding", + ) + + val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha } + Box( modifier = modifier .fillMaxSize() .element(Shade.Elements.BackgroundScrim) - .background(colorResource(R.color.shade_scrim_background_dark)) + // Cannot set the alpha of the whole element to 0, because the mirror should be + // in the QS column. + .background( + colorResource(R.color.shade_scrim_background_dark).copy(alpha = contentAlpha) + ) ) { Column( modifier = Modifier.fillMaxSize(), @@ -317,61 +334,80 @@ private fun SceneScope.SplitShade( createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, - modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding) + modifier = + Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding) + .then(brightnessMirrorShowingModifier) ) Row(modifier = Modifier.fillMaxWidth().weight(1f)) { - Column( - verticalArrangement = Arrangement.Top, - modifier = - Modifier.weight(1f).fillMaxSize().thenIf(!isCustomizing) { - Modifier.padding(bottom = navBarBottomHeight) - }, - ) { + Box(modifier = Modifier.weight(1f)) { + BrightnessMirror( + viewModel = viewModel.brightnessMirrorViewModel, + qsSceneAdapter = viewModel.qsSceneAdapter, + // Need to remove the offset of the header height, as the mirror uses + // the position of the Brightness slider in the window + modifier = Modifier.offset(y = -ShadeHeader.Dimensions.CollapsedHeight) + ) Column( + verticalArrangement = Arrangement.Top, modifier = - Modifier.fillMaxSize().weight(1f).thenIf(!isCustomizing) { - Modifier.verticalNestedScrollToScene() - .verticalScroll( - quickSettingsScrollState, - enabled = isScrollable - ) - .clipScrollableContainer(Orientation.Horizontal) - } + Modifier.fillMaxSize().thenIf(!isCustomizing) { + Modifier.padding(bottom = navBarBottomHeight) + }, ) { - Box( + Column( modifier = - Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings) + Modifier.fillMaxSize() + .weight(1f) + .thenIf(!isCustomizing) { + Modifier.verticalNestedScrollToScene() + .verticalScroll( + quickSettingsScrollState, + enabled = isScrollable + ) + .clipScrollableContainer(Orientation.Horizontal) + } + .then(brightnessMirrorShowingModifier) ) { - QuickSettings( - qsSceneAdapter = viewModel.qsSceneAdapter, - heightProvider = { viewModel.qsSceneAdapter.qsHeight }, - isSplitShade = true, + Box( + modifier = + Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings) + ) { + QuickSettings( + qsSceneAdapter = viewModel.qsSceneAdapter, + heightProvider = { viewModel.qsSceneAdapter.qsHeight }, + isSplitShade = true, + modifier = Modifier.fillMaxWidth(), + squishiness = tileSquishiness, + ) + } + + MediaIfVisible( + viewModel = viewModel, + mediaCarouselController = mediaCarouselController, + mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), - squishiness = tileSquishiness, ) } - - MediaIfVisible( - viewModel = viewModel, - mediaCarouselController = mediaCarouselController, - mediaHost = mediaHost, - modifier = Modifier.fillMaxWidth(), + FooterActionsWithAnimatedVisibility( + viewModel = footerActionsViewModel, + isCustomizing = isCustomizing, + lifecycleOwner = lifecycleOwner, + modifier = + Modifier.align(Alignment.CenterHorizontally) + .then(brightnessMirrorShowingModifier), ) } - FooterActionsWithAnimatedVisibility( - viewModel = footerActionsViewModel, - isCustomizing = isCustomizing, - lifecycleOwner = lifecycleOwner, - modifier = Modifier.align(Alignment.CenterHorizontally), - ) } NotificationScrollingStack( viewModel = viewModel.notifications, maxScrimTop = { 0f }, modifier = - Modifier.weight(1f).fillMaxHeight().padding(bottom = navBarBottomHeight), + Modifier.weight(1f) + .fillMaxHeight() + .padding(bottom = navBarBottomHeight) + .then(brightnessMirrorShowingModifier), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 36919d0c74a4..9a13bb261aa7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -827,7 +827,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() = + fun isAuthenticatedIsResetToFalseWhenTransitioningToGone() = testScope.runTest { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -840,7 +840,13 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authenticated()).isTrue() - keyguardRepository.keyguardDoneAnimationsFinished() + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + ) + ) assertThat(authenticated()).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt index 1a9ad3c285e6..f2eb7f44600e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.qs.QSImpl import com.android.systemui.qs.dagger.QSComponent import com.android.systemui.qs.dagger.QSSceneComponent +import com.android.systemui.settings.brightness.MirrorController import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shared.model.ShadeMode @@ -508,4 +509,21 @@ class QSSceneAdapterImplTest : SysuiTestCase() { underTest.requestCloseCustomizer() verify(qsImpl!!).closeCustomizer() } + + @Test + fun setBrightnessMirrorController() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + val mirrorController = mock<MirrorController>() + underTest.setBrightnessMirrorController(mirrorController) + + verify(qsImpl!!).setBrightnessMirrorController(mirrorController) + + underTest.setBrightnessMirrorController(null) + verify(qsImpl!!).setBrightnessMirrorController(null) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index d9ab3b172d2a..426f94d3e96a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -108,6 +109,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { underTest = QuickSettingsSceneViewModel( + brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, notifications = kosmos.notificationsPlaceholderViewModel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 98cbda2efd2b..9856f9050c4b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -74,6 +74,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.settings.FakeDisplayTracker +import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -259,6 +260,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, notifications = kosmos.notificationsPlaceholderViewModel, + brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, mediaDataManager = mediaDataManager, shadeInteractor = kosmos.shadeInteractor, footerActionsController = kosmos.footerActionsController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt new file mode 100644 index 000000000000..a1af70b316ee --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BrightnessMirrorShowingRepositoryTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private val underTest = BrightnessMirrorShowingRepository() + + @Test + fun isShowing_setAndFlow() = + kosmos.testScope.runTest { + val isShowing by collectLastValue(underTest.isShowing) + + assertThat(isShowing).isFalse() + + underTest.setMirrorShowing(true) + assertThat(isShowing).isTrue() + + underTest.setMirrorShowing(false) + assertThat(isShowing).isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt new file mode 100644 index 000000000000..31d6df250af3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BrightnessMirrorShowingInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private val underTest = + BrightnessMirrorShowingInteractor(kosmos.brightnessMirrorShowingRepository) + + @Test + fun isShowing_setAndFlow() = + kosmos.testScope.runTest { + val isShowing by collectLastValue(underTest.isShowing) + + assertThat(isShowing).isFalse() + + underTest.setMirrorShowing(true) + assertThat(isShowing).isTrue() + + underTest.setMirrorShowing(false) + assertThat(isShowing).isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt new file mode 100644 index 000000000000..6de7f403a745 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.ui.binder + +import android.content.applicationContext +import android.view.ContextThemeWrapper +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R +import com.android.systemui.settings.brightnessSliderControllerFactory +import com.android.systemui.testKosmos +import com.android.systemui.util.Assert +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BrightnessMirrorInflaterTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val themedContext = + ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings) + + @Test + fun inflate_sliderViewAddedToFrame() { + Assert.setTestThread(Thread.currentThread()) + + val (frame, sliderController) = + BrightnessMirrorInflater.inflate( + themedContext, + kosmos.brightnessSliderControllerFactory + ) + + assertThat(sliderController.rootView.parent).isSameInstanceAs(frame) + + Assert.setTestThread(null) + } + + @Test + fun inflate_frameAndSliderViewVisible() { + Assert.setTestThread(Thread.currentThread()) + + val (frame, sliderController) = + BrightnessMirrorInflater.inflate( + themedContext, + kosmos.brightnessSliderControllerFactory, + ) + + assertThat(frame.visibility).isEqualTo(View.VISIBLE) + assertThat(sliderController.rootView.visibility).isEqualTo(View.VISIBLE) + + Assert.setTestThread(null) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt new file mode 100644 index 000000000000..09fc6f9ad96d --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.ui.viewmodel + +import android.content.applicationContext +import android.content.res.mainResources +import android.view.ContextThemeWrapper +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R +import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater +import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel +import com.android.systemui.settings.brightness.ui.viewModel.LocationAndSize +import com.android.systemui.settings.brightnessSliderControllerFactory +import com.android.systemui.testKosmos +import com.android.systemui.util.Assert +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BrightnessMirrorViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private val themedContext = + ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings) + + private val underTest = + with(kosmos) { + BrightnessMirrorViewModel( + brightnessMirrorShowingInteractor, + mainResources, + brightnessSliderControllerFactory, + ) + } + + @Test + fun showHideMirror_isShowing() = + with(kosmos) { + testScope.runTest { + val showing by collectLastValue(underTest.isShowing) + + assertThat(showing).isFalse() + + underTest.showMirror() + assertThat(showing).isTrue() + + underTest.hideMirror() + assertThat(showing).isFalse() + } + } + + @Test + fun setLocationInWindow_correctLocationAndSize() = + with(kosmos) { + testScope.runTest { + val locationAndSize by collectLastValue(underTest.locationAndSize) + + val x = 20 + val y = 100 + val height = 50 + val width = 200 + val padding = + mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding) + + val mockView = + mock<View> { + whenever(getLocationInWindow(any())).then { + it.getArgument<IntArray>(0).apply { + this[0] = x + this[1] = y + } + Unit + } + + whenever(measuredHeight).thenReturn(height) + whenever(measuredWidth).thenReturn(width) + } + + underTest.setLocationAndSize(mockView) + + assertThat(locationAndSize) + .isEqualTo( + // Adjust for padding around the view + LocationAndSize( + yOffset = y - padding, + width = width + 2 * padding, + height = height + 2 * padding, + ) + ) + } + } + + @Test + fun setLocationInWindow_paddingSetToRootView() = + with(kosmos) { + Assert.setTestThread(Thread.currentThread()) + val padding = + mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding) + + val view = mock<View>() + + val (_, sliderController) = + BrightnessMirrorInflater.inflate( + themedContext, + brightnessSliderControllerFactory, + ) + underTest.setToggleSlider(sliderController) + + underTest.setLocationAndSize(view) + + with(sliderController.rootView) { + assertThat(paddingBottom).isEqualTo(padding) + assertThat(paddingTop).isEqualTo(padding) + assertThat(paddingLeft).isEqualTo(padding) + assertThat(paddingRight).isEqualTo(padding) + } + + Assert.setTestThread(null) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 77109d65fadc..7a681b383aad 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor @@ -127,6 +128,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsSceneAdapter, notifications = kosmos.notificationsPlaceholderViewModel, + brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, mediaDataManager = mediaDataManager, shadeInteractor = kosmos.shadeInteractor, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt index 7dd883b89db7..f7ea25c8c0ca 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt @@ -46,8 +46,25 @@ inline fun <reified T : View> View.getNearestParent(): T? { } /** Adds a [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */ -fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle { - val listener = View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> onLayoutChanged(v) } +fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle = + onLayoutChanged { v, _, _, _, _, _, _, _, _ -> + onLayoutChanged(v) + } + +/** Adds the [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */ +fun View.onLayoutChanged(listener: View.OnLayoutChangeListener): DisposableHandle { addOnLayoutChangeListener(listener) return DisposableHandle { removeOnLayoutChangeListener(listener) } } + +/** Adds a [View.OnApplyWindowInsetsListener] and provides a [DisposableHandle] for teardown. */ +fun View.onApplyWindowInsets(listener: View.OnApplyWindowInsetsListener): DisposableHandle { + setOnApplyWindowInsetsListener(listener) + return DisposableHandle { setOnApplyWindowInsetsListener(null) } +} + +/** Adds a [View.OnTouchListener] and provides a [DisposableHandle] for teardown. */ +fun View.onTouchListener(listener: View.OnTouchListener): DisposableHandle { + setOnTouchListener(listener) + return DisposableHandle { setOnTouchListener(null) } +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index baae986c494d..0e04d15d8680 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt @@ -40,7 +40,6 @@ import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationSta import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.dump.DumpManager -import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.BiometricType import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository @@ -313,11 +312,7 @@ constructor( // or device starts going to sleep. merge( powerInteractor.isAsleep, - if (KeyguardWmStateRefactor.isEnabled) { - keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE) - } else { - keyguardRepository.keyguardDoneAnimationsFinished.map { true } - }, + keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE), userRepository.selectedUser.map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 0249abd684cc..44fd58267250 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -41,6 +41,9 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.common.ui.view.onApplyWindowInsets +import com.android.systemui.common.ui.view.onLayoutChanged +import com.android.systemui.common.ui.view.onTouchListener import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.KeyguardBottomAreaRefactor @@ -64,6 +67,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo +import com.android.systemui.util.kotlin.DisposableHandles import com.android.systemui.util.ui.AnimatedValue import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating @@ -99,16 +103,19 @@ object KeyguardRootViewBinder { keyguardViewMediator: KeyguardViewMediator?, mainImmediateDispatcher: CoroutineDispatcher, ): DisposableHandle { - var onLayoutChangeListener: OnLayoutChange? = null + val disposables = DisposableHandles() val childViews = mutableMapOf<Int, View>() if (KeyguardBottomAreaRefactor.isEnabled) { - view.setOnTouchListener { _, event -> - if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) { - viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt())) + disposables += + view.onTouchListener { _, event -> + if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) { + viewModel.setRootViewLastTapPosition( + Point(event.x.toInt(), event.y.toInt()) + ) + } + false } - false - } } val burnInParams = MutableStateFlow(BurnInParameters()) @@ -117,7 +124,7 @@ object KeyguardRootViewBinder { alpha = { view.alpha }, ) - val disposableHandle = + disposables += view.repeatWhenAttached(mainImmediateDispatcher) { repeatOnLifecycle(Lifecycle.State.CREATED) { launch("$TAG#occludingAppDeviceEntryMessageViewModel.message") { @@ -346,8 +353,7 @@ object KeyguardRootViewBinder { } } - onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams) - view.addOnLayoutChangeListener(onLayoutChangeListener) + disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams)) // Views will be added or removed after the call to bind(). This is needed to avoid many // calls to findViewById @@ -362,24 +368,21 @@ object KeyguardRootViewBinder { } } ) - - view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets -> - val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() - burnInParams.update { current -> - current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top) - } - insets + disposables += DisposableHandle { + view.setOnHierarchyChangeListener(null) + childViews.clear() } - return object : DisposableHandle { - override fun dispose() { - disposableHandle.dispose() - view.removeOnLayoutChangeListener(onLayoutChangeListener) - view.setOnHierarchyChangeListener(null) - view.setOnApplyWindowInsetsListener(null) - childViews.clear() + disposables += + view.onApplyWindowInsets { _: View, insets: WindowInsets -> + val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() + burnInParams.update { current -> + current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top) + } + insets } - } + + return disposables } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java index 3b3844a4be7b..e4249757d737 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java @@ -31,7 +31,7 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.res.R; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; +import com.android.systemui.settings.brightness.MirrorController; import com.android.systemui.util.LifecycleFragment; import java.util.function.Consumer; @@ -182,7 +182,7 @@ public class QSFragmentLegacy extends LifecycleFragment implements QS { } public void setBrightnessMirrorController( - BrightnessMirrorController brightnessMirrorController) { + MirrorController brightnessMirrorController) { if (mQsImpl != null) { mQsImpl.setBrightnessMirrorController(brightnessMirrorController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index a000d63a2ee3..a0607e9f859a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -61,6 +61,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.settings.brightness.MirrorController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; @@ -68,7 +69,6 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.util.Utils; @@ -544,7 +544,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } public void setBrightnessMirrorController( - BrightnessMirrorController brightnessMirrorController) { + @Nullable MirrorController brightnessMirrorController) { mQSPanelController.setBrightnessMirror(brightnessMirrorController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index cd6511979375..55dc4859cf90 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -24,6 +24,8 @@ import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER import android.view.MotionEvent; import android.view.View; +import androidx.annotation.Nullable; + import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.dump.DumpManager; @@ -38,9 +40,9 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.settings.brightness.MirrorController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.tuner.TunerService; @@ -139,6 +141,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mBrightnessMirrorHandler.onQsPanelAttached(); PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout()); pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener); + maybeReinflateBrightnessSlider(); } @Override @@ -157,15 +160,18 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override protected void onConfigurationChanged() { mView.updateResources(); + maybeReinflateBrightnessSlider(); + if (mView.isListening()) { + refreshAllTiles(); + } + } + + private void maybeReinflateBrightnessSlider() { int newDensity = mView.getResources().getConfiguration().densityDpi; if (newDensity != mLastDensity) { mLastDensity = newDensity; reinflateBrightnessSlider(); } - - if (mView.isListening()) { - refreshAllTiles(); - } } private void reinflateBrightnessSlider() { @@ -210,7 +216,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } } - public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) { + public void setBrightnessMirror(@Nullable MirrorController brightnessMirrorController) { mBrightnessMirrorHandler.setController(brightnessMirrorController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index 8d5aeab54343..3d86e3c084f8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -34,6 +34,7 @@ import com.android.systemui.qs.QSContainerImpl import com.android.systemui.qs.QSImpl import com.android.systemui.qs.dagger.QSSceneComponent import com.android.systemui.res.R +import com.android.systemui.settings.brightness.MirrorController import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.util.kotlin.sample @@ -68,6 +69,9 @@ interface QSSceneAdapter { */ val qsView: Flow<View> + /** Sets the [MirrorController] in [QSImpl]. Set to `null` to remove. */ + fun setBrightnessMirrorController(mirrorController: MirrorController?) + /** * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in * [qsView]. Re-inflations due to configuration changes will use the last used [context]. @@ -206,7 +210,7 @@ constructor( applicationScope.launch { launch { state.sample(_isCustomizing, ::Pair).collect { (state, customizing) -> - _qsImpl.value?.apply { + qsImpl.value?.apply { if (state != QSSceneAdapter.State.QS && customizing) { this@apply.closeCustomizerImmediately() } @@ -284,6 +288,10 @@ constructor( qsImpl.value?.closeCustomizer() } + override fun setBrightnessMirrorController(mirrorController: MirrorController?) { + qsImpl.value?.setBrightnessMirrorController(mirrorController) + } + private fun QSImpl.applyState(state: QSSceneAdapter.State) { setQsVisible(state.isVisible) setExpanded(state.isVisible && state.expansion > 0f) diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 62ed49150eec..4c8647191cb6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -26,6 +26,7 @@ import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import java.util.concurrent.atomic.AtomicBoolean @@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.map class QuickSettingsSceneViewModel @Inject constructor( + val brightnessMirrorViewModel: BrightnessMirrorViewModel, val shadeHeaderViewModel: ShadeHeaderViewModel, val qsSceneAdapter: QSSceneAdapter, val notifications: NotificationsPlaceholderViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 1e513b200ac4..5cec5e7e62d6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -395,10 +395,10 @@ public class ScreenshotController { Assert.isMainThread(); mCurrentRequestCallback = requestCallback; - if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) { + if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN + && screenshot.getBitmap() == null) { Rect bounds = getFullScreenRect(); - screenshot.setBitmap( - mImageCapture.captureDisplay(mDisplayId, bounds)); + screenshot.setBitmap(mImageCapture.captureDisplay(mDisplayId, bounds)); screenshot.setScreenBounds(bounds); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt index 796457d88cc2..3ad4075a2b89 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt @@ -17,13 +17,14 @@ package com.android.systemui.screenshot /** Processes a screenshot request sent from [ScreenshotHelper]. */ -interface ScreenshotRequestProcessor { +fun interface ScreenshotRequestProcessor { /** * Inspects the incoming ScreenshotData, potentially modifying it based upon policy. * - * @param screenshot the screenshot to process + * @param original the screenshot to process + * @return a potentially modified screenshot data */ - suspend fun process(screenshot: ScreenshotData): ScreenshotData + suspend fun process(original: ScreenshotData): ScreenshotData } /** Exception thrown by [RequestProcessor] if something goes wrong. */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 92d3e550dc0f..ec7707c83980 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -92,14 +92,14 @@ constructor( // Let's wait before logging "screenshot requested", as we should log the processed // ScreenshotData. val screenshotData = - try { - screenshotRequestProcessor.process(rawScreenshotData) - } catch (e: RequestProcessorException) { - Log.e(TAG, "Failed to process screenshot request!", e) - logScreenshotRequested(rawScreenshotData) - onFailedScreenshotRequest(rawScreenshotData, callback) - return - } + runCatching { screenshotRequestProcessor.process(rawScreenshotData) } + .onFailure { + Log.e(TAG, "Failed to process screenshot request!", it) + logScreenshotRequested(rawScreenshotData) + onFailedScreenshotRequest(rawScreenshotData, callback) + } + .getOrNull() + ?: return logScreenshotRequested(screenshotData) Log.d(TAG, "Screenshot request: $screenshotData") diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt new file mode 100644 index 000000000000..c380db0ca3a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.data.model + +import android.content.ComponentName +import android.graphics.Rect + +/** A child task within a RootTaskInfo */ +data class ChildTaskModel( + /** The task identifier */ + val id: Int, + /** The task name */ + val name: String, + /** The location and size of the task */ + val bounds: Rect, + /** The user which created the task. */ + val userId: Int, +) { + val componentName: ComponentName? + get() = ComponentName.unflattenFromString(name) +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt index 837a661230cb..2048b7c0c142 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt @@ -24,6 +24,6 @@ data class DisplayContentModel( val displayId: Int, /** Information about the current System UI state which can affect capture. */ val systemUiState: SystemUiState, - /** A list of root tasks on the display, ordered from bottom to top along the z-axis */ + /** A list of root tasks on the display, ordered from top to bottom along the z-axis */ val rootTasks: List<RootTaskInfo>, ) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt index 9c81b322a2b7..48e813d89af7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt @@ -18,7 +18,7 @@ package com.android.systemui.screenshot.data.repository import com.android.systemui.screenshot.data.model.DisplayContentModel /** Provides information about tasks related to a display. */ -interface DisplayContentRepository { +fun interface DisplayContentRepository { /** Provides information about the tasks and content presented on a given display. */ suspend fun getDisplayContent(displayId: Int): DisplayContentModel } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt new file mode 100644 index 000000000000..5e2b57651de7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.policy + +import android.content.ComponentName +import android.os.UserHandle + +/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */ +data class CaptureParameters( + /** How should the content be captured? */ + val type: CaptureType, + /** The focused or top component at the time of the screenshot. */ + val component: ComponentName?, + /** Which user should receive the screenshot file? */ + val owner: UserHandle, +) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt new file mode 100644 index 000000000000..4a88180d8f73 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.policy + +import com.android.systemui.screenshot.data.model.DisplayContentModel + +/** Contains logic to determine when and how an adjust to screenshot behavior applies. */ +fun interface CapturePolicy { + /** + * Test the policy against the current display task state. If the policy applies, Returns a + * non-null [CaptureParameters] describing how the screenshot request should be augmented. + */ + suspend fun apply(content: DisplayContentModel): CaptureParameters? +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt new file mode 100644 index 000000000000..6ca2e9d6d5e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.policy + +import android.graphics.Rect + +/** What to capture */ +sealed interface CaptureType { + /** Capture the entire screen contents. */ + class FullScreen(val displayId: Int) : CaptureType + + /** Capture the contents of the task only. */ + class IsolatedTask( + val taskId: Int, + val taskBounds: Rect?, + ) : CaptureType +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt new file mode 100644 index 000000000000..2c0a0dbf8ea9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.policy + +import android.content.ComponentName +import android.graphics.Bitmap +import android.graphics.Rect +import android.os.UserHandle +import android.util.Log +import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN +import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.screenshot.ImageCapture +import com.android.systemui.screenshot.ScreenshotData +import com.android.systemui.screenshot.ScreenshotRequestProcessor +import com.android.systemui.screenshot.data.repository.DisplayContentRepository +import com.android.systemui.screenshot.policy.CaptureType.FullScreen +import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +private const val TAG = "PolicyRequestProcessor" + +/** A [ScreenshotRequestProcessor] which supports general policy rule matching. */ +class PolicyRequestProcessor( + @Background private val background: CoroutineDispatcher, + private val capture: ImageCapture, + private val displayTasks: DisplayContentRepository, + private val policies: List<CapturePolicy>, +) : ScreenshotRequestProcessor { + override suspend fun process(original: ScreenshotData): ScreenshotData { + if (original.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) { + // The request contains an already captured screenshot, accept it as is. + Log.i(TAG, "Screenshot bitmap provided. No modifications applied.") + return original + } + + val tasks = displayTasks.getDisplayContent(original.displayId) + + // If policies yield explicit modifications, apply them and return the result + Log.i(TAG, "Applying policy checks....") + policies + .firstNotNullOfOrNull { policy -> policy.apply(tasks) } + ?.let { + Log.i(TAG, "Modifying screenshot: $it") + return apply(it, original) + } + + // Otherwise capture normally, filling in additional information as needed. + return replaceWithScreenshot( + original = original, + componentName = original.topComponent ?: tasks.rootTasks.firstOrNull()?.topActivity, + owner = original.userHandle, + displayId = original.displayId + ) + } + + /** Produce a new [ScreenshotData] using [CaptureParameters] */ + suspend fun apply(updates: CaptureParameters, original: ScreenshotData): ScreenshotData { + // Update and apply bitmap capture depending on the parameters. + val updated = + when (val type = updates.type) { + is IsolatedTask -> + replaceWithTaskSnapshot( + original, + updates.component, + updates.owner, + type.taskId, + type.taskBounds + ) + is FullScreen -> + replaceWithScreenshot( + original, + updates.component, + updates.owner, + type.displayId + ) + } + return updated + } + + suspend fun replaceWithTaskSnapshot( + original: ScreenshotData, + componentName: ComponentName?, + owner: UserHandle, + taskId: Int, + taskBounds: Rect?, + ): ScreenshotData { + val taskSnapshot = capture.captureTask(taskId) + return original.copy( + type = TAKE_SCREENSHOT_PROVIDED_IMAGE, + bitmap = taskSnapshot, + userHandle = owner, + taskId = taskId, + topComponent = componentName, + screenBounds = taskBounds + ) + } + + suspend fun replaceWithScreenshot( + original: ScreenshotData, + componentName: ComponentName?, + owner: UserHandle?, + displayId: Int, + ): ScreenshotData { + val screenshot = captureDisplay(displayId) + return original.copy( + type = TAKE_SCREENSHOT_FULLSCREEN, + bitmap = screenshot, + userHandle = owner, + topComponent = componentName, + screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0) + ) + } + + /** TODO: Move to ImageCapture (existing function is non-suspending) */ + private suspend fun captureDisplay(displayId: Int): Bitmap? { + return withContext(background) { capture.captureDisplay(displayId) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt new file mode 100644 index 000000000000..221e64782894 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.policy + +import android.os.UserHandle +import com.android.systemui.screenshot.data.model.DisplayContentModel +import com.android.systemui.screenshot.data.model.ProfileType +import com.android.systemui.screenshot.data.repository.ProfileTypeRepository +import com.android.systemui.screenshot.policy.CaptureType.FullScreen +import javax.inject.Inject +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.firstOrNull + +/** + * Condition: When any visible task belongs to a private user. + * + * Parameters: Capture the whole screen, owned by the private user. + */ +class PrivateProfilePolicy +@Inject +constructor( + private val profileTypes: ProfileTypeRepository, +) : CapturePolicy { + override suspend fun apply(content: DisplayContentModel): CaptureParameters? { + // Find the first visible rootTaskInfo with a child task owned by a private user + val (rootTask, childTask) = + content.rootTasks + .filter { it.isVisible } + .firstNotNullOfOrNull { root -> + root + .childTasksTopDown() + .firstOrNull { + profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE + } + ?.let { root to it } + } + ?: return null + + // If matched, return parameters needed to modify the request. + return CaptureParameters( + type = FullScreen(content.displayId), + component = childTask.componentName ?: rootTask.topActivity, + owner = UserHandle.of(childTask.userId), + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt new file mode 100644 index 000000000000..d2f4d9e039f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.policy + +import android.app.ActivityTaskManager.RootTaskInfo +import com.android.systemui.screenshot.data.model.ChildTaskModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.map + +internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> { + return ((numActivities - 1) downTo 0).asFlow().map { index -> + ChildTaskModel( + childTaskIds[index], + childTaskNames[index], + childTaskBounds[index], + childTaskUserIds[index] + ) + } +} + +internal suspend fun RootTaskInfo.firstChildTaskOrNull( + filter: suspend (Int) -> Boolean +): Pair<RootTaskInfo, Int>? { + // Child tasks are provided in bottom-up order + // Filtering is done top-down, so iterate backwards here. + for (index in numActivities - 1 downTo 0) { + if (filter(index)) { + return (this to index) + } + } + return null +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt index bc71ab71b626..63d15087d12c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt @@ -16,7 +16,9 @@ package com.android.systemui.screenshot.policy +import com.android.systemui.Flags.screenshotPrivateProfile import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.screenshot.ImageCapture import com.android.systemui.screenshot.RequestProcessor import com.android.systemui.screenshot.ScreenshotPolicy @@ -29,6 +31,7 @@ import dagger.Binds import dagger.Module import dagger.Provides import javax.inject.Provider +import kotlinx.coroutines.CoroutineDispatcher @Module interface ScreenshotPolicyModule { @@ -37,18 +40,42 @@ interface ScreenshotPolicyModule { @SysUISingleton fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository + @Binds + @SysUISingleton + fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository + companion object { + @JvmStatic + @Provides + @SysUISingleton + fun bindCapturePolicyList( + privateProfilePolicy: PrivateProfilePolicy, + workProfilePolicy: WorkProfilePolicy, + ): List<CapturePolicy> { + // In order of priority. The first matching policy applies. + return listOf(workProfilePolicy, privateProfilePolicy) + } + + @JvmStatic @Provides @SysUISingleton fun bindScreenshotRequestProcessor( + @Background background: CoroutineDispatcher, imageCapture: ImageCapture, policyProvider: Provider<ScreenshotPolicy>, + displayContentRepoProvider: Provider<DisplayContentRepository>, + policyListProvider: Provider<List<CapturePolicy>>, ): ScreenshotRequestProcessor { - return RequestProcessor(imageCapture, policyProvider.get()) + return if (screenshotPrivateProfile()) { + PolicyRequestProcessor( + background, + imageCapture, + displayContentRepoProvider.get(), + policyListProvider.get() + ) + } else { + RequestProcessor(imageCapture, policyProvider.get()) + } } } - - @Binds - @SysUISingleton - fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt new file mode 100644 index 000000000000..d6b5d6dfda25 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.policy + +import android.app.WindowConfiguration.WINDOWING_MODE_PINNED +import android.os.UserHandle +import com.android.systemui.screenshot.data.model.DisplayContentModel +import com.android.systemui.screenshot.data.model.ProfileType +import com.android.systemui.screenshot.data.repository.ProfileTypeRepository +import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask +import javax.inject.Inject +import kotlinx.coroutines.flow.first + +/** + * Condition: When the top visible task (excluding PIP mode) belongs to a work user. + * + * Parameters: Capture only the foreground task, owned by the work user. + */ +class WorkProfilePolicy +@Inject +constructor( + private val profileTypes: ProfileTypeRepository, +) : CapturePolicy { + override suspend fun apply(content: DisplayContentModel): CaptureParameters? { + // Find the first non PiP rootTask with a top child task owned by a work user + val (rootTask, childTask) = + content.rootTasks + .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED } + .map { it to it.childTasksTopDown().first() } + .firstOrNull { (_, child) -> + profileTypes.getProfileType(child.userId) == ProfileType.WORK + } + ?: return null + + // If matched, return parameters needed to modify the request. + return CaptureParameters( + type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds), + component = childTask.componentName ?: rootTask.topActivity, + owner = UserHandle.of(childTask.userId), + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 92d6ec97ad83..8397d9f1e7b9 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -52,7 +52,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.settings.SecureSettings; import dagger.assisted.Assisted; @@ -107,7 +106,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig private ValueAnimator mSliderAnimator; @Override - public void setMirror(BrightnessMirrorController controller) { + public void setMirror(@Nullable MirrorController controller) { mControl.setMirrorControllerAndMirror(controller); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt index 701d814a843b..073279be5af2 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt @@ -16,12 +16,9 @@ package com.android.systemui.settings.brightness -import com.android.systemui.statusbar.policy.BrightnessMirrorController -import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener - class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController) { - var mirrorController: BrightnessMirrorController? = null + var mirrorController: MirrorController? = null private set var brightnessController: MirroredBrightnessController = brightnessController @@ -30,7 +27,8 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController updateBrightnessMirror() } - private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() } + private val brightnessMirrorListener = + MirrorController.BrightnessMirrorListener { updateBrightnessMirror() } fun onQsPanelAttached() { mirrorController?.addCallback(brightnessMirrorListener) @@ -40,7 +38,7 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController mirrorController?.removeCallback(brightnessMirrorListener) } - fun setController(controller: BrightnessMirrorController?) { + fun setController(controller: MirrorController?) { mirrorController?.removeCallback(brightnessMirrorListener) mirrorController = controller mirrorController?.addCallback(brightnessMirrorListener) @@ -48,6 +46,6 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController } private fun updateBrightnessMirror() { - mirrorController?.let { brightnessController.setMirror(it) } + brightnessController.setMirror(mirrorController) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index 539b0c2dd599..b425fb997d9e 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -57,8 +57,10 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV ToggleSlider { private Listener mListener; + @Nullable private ToggleSlider mMirror; - private BrightnessMirrorController mMirrorController; + @Nullable + private MirrorController mMirrorController; private boolean mTracking; private final FalsingManager mFalsingManager; private final UiEventLogger mUiEventLogger; @@ -108,6 +110,9 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV protected void onViewAttached() { mView.setOnSeekBarChangeListener(mSeekListener); mView.setOnInterceptListener(mOnInterceptListener); + if (mMirror != null) { + mView.setOnDispatchTouchEventListener(this::mirrorTouchEvent); + } } @Override @@ -129,7 +134,10 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private boolean copyEventToMirror(MotionEvent ev) { MotionEvent copy = ev.copy(); - boolean out = mMirror.mirrorTouchEvent(copy); + boolean out = false; + if (mMirror != null) { + out = mMirror.mirrorTouchEvent(copy); + } copy.recycle(); return out; } @@ -166,9 +174,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV * @param c */ @Override - public void setMirrorControllerAndMirror(BrightnessMirrorController c) { + public void setMirrorControllerAndMirror(@Nullable MirrorController c) { mMirrorController = c; - setMirror(c.getToggleSlider()); + if (c != null) { + setMirror(c.getToggleSlider()); + } else { + setMirror(null); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt new file mode 100644 index 000000000000..6a9af26ba4a7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness + +import android.view.View +import com.android.systemui.settings.brightness.MirrorController.BrightnessMirrorListener +import com.android.systemui.statusbar.policy.CallbackController + +interface MirrorController : CallbackController<BrightnessMirrorListener> { + + /** + * Get the [ToggleSlider] currently associated with this controller, or `null` if none currently + */ + fun getToggleSlider(): ToggleSlider? + + /** + * Indicate to this controller that the user is dragging on the brightness view and the mirror + * should show + */ + fun showMirror() + + /** + * Indicate to this controller that the user has stopped dragging on the brightness view and the + * mirror should hide + */ + fun hideMirror() + + /** + * Set the location and size of the current brightness [view] in QS so it can be properly + * adapted to show the mirror in the same location and with the same size. + */ + fun setLocationAndSize(view: View) + + fun interface BrightnessMirrorListener { + fun onBrightnessMirrorReinflated(brightnessMirror: View?) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt index 8d857dec2108..b1a532baa710 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt @@ -22,5 +22,5 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController * Indicates controller that has brightness slider and uses [BrightnessMirrorController] */ interface MirroredBrightnessController { - fun setMirror(controller: BrightnessMirrorController) + fun setMirror(controller: MirrorController?) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java index 648e33b1d228..24bc67047a47 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java @@ -19,7 +19,6 @@ package com.android.systemui.settings.brightness; import android.view.MotionEvent; import com.android.settingslib.RestrictedLockUtils; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; public interface ToggleSlider { interface Listener { @@ -27,7 +26,7 @@ public interface ToggleSlider { } void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin); - void setMirrorControllerAndMirror(BrightnessMirrorController c); + void setMirrorControllerAndMirror(MirrorController c); boolean mirrorTouchEvent(MotionEvent ev); void setOnChangedListener(Listener l); diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt new file mode 100644 index 000000000000..a0c9be46b6e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.data.repository + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +@SysUISingleton +class BrightnessMirrorShowingRepository @Inject constructor() { + private val _isShowing = MutableStateFlow(false) + val isShowing = _isShowing.asStateFlow() + + fun setMirrorShowing(showing: Boolean) { + _isShowing.value = showing + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt new file mode 100644 index 000000000000..ef6e72f76ae6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository +import javax.inject.Inject + +@SysUISingleton +class BrightnessMirrorShowingInteractor +@Inject +constructor( + private val brightnessMirrorShowingRepository: BrightnessMirrorShowingRepository, +) { + val isShowing = brightnessMirrorShowingRepository.isShowing + + fun setMirrorShowing(showing: Boolean) { + brightnessMirrorShowingRepository.setMirrorShowing(showing) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt new file mode 100644 index 000000000000..468a87325507 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.ui.binder + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.android.systemui.res.R +import com.android.systemui.settings.brightness.BrightnessSliderController + +object BrightnessMirrorInflater { + + fun inflate( + context: Context, + sliderControllerFactory: BrightnessSliderController.Factory, + ): Pair<View, BrightnessSliderController> { + val frame = + (LayoutInflater.from(context).inflate(R.layout.brightness_mirror_container, null) + as ViewGroup) + .apply { isVisible = true } + val sliderController = sliderControllerFactory.create(context, frame) + sliderController.init() + frame.addView( + sliderController.rootView, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + return frame to sliderController + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt new file mode 100644 index 000000000000..2651a994bb55 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.ui.viewModel + +import android.content.res.Resources +import android.view.View +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.settings.brightness.BrightnessSliderController +import com.android.systemui.settings.brightness.MirrorController +import com.android.systemui.settings.brightness.ToggleSlider +import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +@SysUISingleton +class BrightnessMirrorViewModel +@Inject +constructor( + private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor, + @Main private val resources: Resources, + val sliderControllerFactory: BrightnessSliderController.Factory, +) : MirrorController { + + private val tempPosition = IntArray(2) + + private var _toggleSlider: BrightnessSliderController? = null + + val isShowing = brightnessMirrorShowingInteractor.isShowing + + private val _locationAndSize: MutableStateFlow<LocationAndSize> = + MutableStateFlow(LocationAndSize()) + val locationAndSize = _locationAndSize.asStateFlow() + + override fun getToggleSlider(): ToggleSlider? { + return _toggleSlider + } + + fun setToggleSlider(toggleSlider: BrightnessSliderController) { + _toggleSlider = toggleSlider + } + + override fun showMirror() { + brightnessMirrorShowingInteractor.setMirrorShowing(true) + } + + override fun hideMirror() { + brightnessMirrorShowingInteractor.setMirrorShowing(false) + } + + override fun setLocationAndSize(view: View) { + view.getLocationInWindow(tempPosition) + val padding = resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding) + _toggleSlider?.rootView?.setPadding(padding, padding, padding, padding) + // Account for desired padding + _locationAndSize.value = + LocationAndSize( + yOffset = tempPosition[1] - padding, + width = view.measuredWidth + 2 * padding, + height = view.measuredHeight + 2 * padding, + ) + } + + // Callbacks are used for indicating reinflation when the config changes in some ways (like + // density). However, we don't need that as we recompose the view anyway + override fun addCallback(listener: MirrorController.BrightnessMirrorListener) {} + + override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {} +} + +data class LocationAndSize( + val yOffset: Int = 0, + val width: Int = 0, + val height: Int = 0, +) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 9362cd058929..980f665ae61f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -33,6 +33,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel @@ -57,6 +58,7 @@ constructor( val qsSceneAdapter: QSSceneAdapter, val shadeHeaderViewModel: ShadeHeaderViewModel, val notifications: NotificationsPlaceholderViewModel, + val brightnessMirrorViewModel: BrightnessMirrorViewModel, val mediaDataManager: MediaDataManager, shadeInteractor: ShadeInteractor, private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index cfd19bacdc69..4059565d9865 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -21,6 +21,8 @@ import android.view.WindowInsets import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.Flags.communalHub +import com.android.systemui.common.ui.view.onApplyWindowInsets +import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters @@ -168,22 +170,16 @@ constructor( controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() } disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) } - view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets -> - val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() - burnInParams.update { current -> - current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top) + disposables += + view.onApplyWindowInsets { _: View, insets: WindowInsets -> + val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() + burnInParams.update { current -> + current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top) + } + insets } - insets - } - disposables += DisposableHandle { view.setOnApplyWindowInsetsListener(null) } - // Required to capture keyguard media changes and ensure the notification count is correct - val layoutChangeListener = - View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - viewModel.notificationStackChanged() - } - view.addOnLayoutChangeListener(layoutChangeListener) - disposables += DisposableHandle { view.removeOnLayoutChangeListener(layoutChangeListener) } + disposables += view.onLayoutChanged { viewModel.notificationStackChanged() } return disposables } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index a1fae03a2090..2651d2eed404 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -103,8 +103,8 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { /** * Returns an ActivityOptions bundle created using the given parameters. * - * @param displayId The ID of the display to launch the activity in. Typically this would - * be the display the status bar is on. + * @param displayId The ID of the display to launch the activity in. Typically this would + * be the display the status bar is on. * @param animationAdapter The animation adapter used to start this activity, or {@code null} * for the default animation. */ @@ -197,7 +197,7 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void onKeyguardViewManagerStatesUpdated(); - /** */ + /** */ boolean getCommandQueuePanelsEnabled(); void showWirelessChargingAnimation(int batteryLevel); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 5baf6a069602..1d6b744a7bf0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -168,6 +168,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -594,6 +595,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final SceneContainerFlags mSceneContainerFlags; + private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor; + /** * Public constructor for CentralSurfaces. * @@ -705,7 +708,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { UserTracker userTracker, Provider<FingerprintManager> fingerprintManager, ActivityStarter activityStarter, - SceneContainerFlags sceneContainerFlags + SceneContainerFlags sceneContainerFlags, + BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor ) { mContext = context; mNotificationsController = notificationsController; @@ -801,6 +805,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; mSceneContainerFlags = sceneContainerFlags; + mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -1083,6 +1088,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mJavaAdapter.alwaysCollectFlow( mCommunalInteractor.isIdleOnCommunal(), mIdleOnCommunalConsumer); + if (mSceneContainerFlags.isEnabled()) { + mJavaAdapter.alwaysCollectFlow( + mBrightnessMirrorShowingInteractor.isShowing(), + this::setBrightnessMirrorShowing + ); + } } /** @@ -1284,10 +1295,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mShadeSurface, mNotificationShadeDepthControllerLazy.get(), mBrightnessSliderFactory, - (visible) -> { - mBrightnessMirrorVisible = visible; - updateScrimController(); - }); + this::setBrightnessMirrorShowing); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; if (qs instanceof QSFragmentLegacy) { @@ -1346,6 +1354,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f)); } + private void setBrightnessMirrorShowing(boolean showing) { + mBrightnessMirrorVisible = showing; + updateScrimController(); + } /** * When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index 13f76feca9fd..7d920eab73fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -27,6 +27,7 @@ import android.widget.FrameLayout; import com.android.systemui.res.R; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.settings.brightness.MirrorController; import com.android.systemui.settings.brightness.ToggleSlider; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeViewController; @@ -38,8 +39,7 @@ import java.util.function.Consumer; /** * Controls showing and hiding of the brightness mirror. */ -public class BrightnessMirrorController - implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> { +public class BrightnessMirrorController implements MirrorController { private final NotificationShadeWindowView mStatusBarWindow; private final Consumer<Boolean> mVisibilityCallback; @@ -71,6 +71,7 @@ public class BrightnessMirrorController updateResources(); } + @Override public void showMirror() { mBrightnessMirror.setVisibility(View.VISIBLE); mVisibilityCallback.accept(true); @@ -78,16 +79,14 @@ public class BrightnessMirrorController mDepthController.setBrightnessMirrorVisible(true); } + @Override public void hideMirror() { mVisibilityCallback.accept(false); mNotificationPanel.setAlpha(255, true /* animate */); mDepthController.setBrightnessMirrorVisible(false); } - /** - * Set the location and size of the mirror container to match that of the slider in QS - * @param original the original view in QS - */ + @Override public void setLocationAndSize(View original) { original.getLocationInWindow(mInt2Cache); @@ -112,6 +111,7 @@ public class BrightnessMirrorController } } + @Override public ToggleSlider getToggleSlider() { return mToggleSliderController; } @@ -176,8 +176,4 @@ public class BrightnessMirrorController public void onUiModeChanged() { reinflate(); } - - public interface BrightnessMirrorListener { - void onBrightnessMirrorReinflated(View brightnessMirror); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt index 25ba09a0ce90..6a22d8648d91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt @@ -84,7 +84,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - whenever(mirrorController.toggleSlider).thenReturn(mirror) + whenever(mirrorController.getToggleSlider()).thenReturn(mirror) whenever(motionEvent.copy()).thenReturn(motionEvent) whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0)) @@ -129,7 +129,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { @Test fun testNullMirrorNotTrackingTouch() { - whenever(mirrorController.toggleSlider).thenReturn(null) + whenever(mirrorController.getToggleSlider()).thenReturn(null) mController.setMirrorControllerAndMirror(mirrorController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index ed29665c6e16..2f153d8b7003 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -25,6 +25,8 @@ import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -119,9 +121,9 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; @@ -331,7 +333,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final DumpManager mDumpManager = new DumpManager(); private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager); - private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags(); + private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags(); + + private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor = + mKosmos.getBrightnessMirrorShowingInteractor(); @Before public void setup() throws Exception { @@ -553,7 +558,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, () -> mFingerprintManager, mActivityStarter, - mSceneContainerFlags + mSceneContainerFlags, + mBrightnessMirrorShowingInteractor ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); @@ -1084,6 +1090,34 @@ public class CentralSurfacesImplTest extends SysuiTestCase { verify(mStatusBarWindowController).refreshStatusBarHeight(); } + @Test + public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() { + mSceneContainerFlags.setEnabled(true); + mCentralSurfaces.registerCallbacks(); + + mBrightnessMirrorShowingInteractor.setMirrorShowing(true); + mTestScope.getTestScheduler().runCurrent(); + verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR); + + mBrightnessMirrorShowingInteractor.setMirrorShowing(false); + mTestScope.getTestScheduler().runCurrent(); + ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class); + // The default is to call the one with the callback argument + verify(mScrimController).transitionTo(captor.capture(), any()); + assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR); + } + + @Test + public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() { + mSceneContainerFlags.setEnabled(false); + mCentralSurfaces.registerCallbacks(); + + mBrightnessMirrorShowingInteractor.setMirrorShowing(true); + mTestScope.getTestScheduler().runCurrent(); + verify(mScrimController, never()).transitionTo(ScrimState.BRIGHTNESS_MIRROR); + verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any()); + } + /** * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard} * to reconfigure the keyguard to reflect the requested showing/occluded states. diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 1b23296ec4d3..afc8f309f6d2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -49,6 +49,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.sceneDataSource +import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.phone.screenOffAnimationController import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository @@ -106,6 +107,7 @@ class KosmosJavaAdapter( val sharedNotificationContainerInteractor by lazy { kosmos.sharedNotificationContainerInteractor } + val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor } init { kosmos.applicationContext = testCase.context diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt index df08e4a78178..a654d6fc239a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.ui.adapter import android.content.Context import android.view.View +import com.android.systemui.settings.brightness.MirrorController import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -41,6 +42,9 @@ class FakeQSSceneAdapter( private val _navBarPadding = MutableStateFlow<Int>(0) val navBarPadding = _navBarPadding.asStateFlow() + var brightnessMirrorController: MirrorController? = null + private set + override var isQsFullyCollapsed: Boolean = true override suspend fun inflate(context: Context) { @@ -64,4 +68,8 @@ class FakeQSSceneAdapter( override fun requestCloseCustomizer() { _customizing.value = false } + + override fun setBrightnessMirrorController(mirrorController: MirrorController?) { + brightnessMirrorController = mirrorController + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt new file mode 100644 index 000000000000..8b7e5d8f54c5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings + +import com.android.internal.logging.uiEventLogger +import com.android.systemui.classifier.falsingManager +import com.android.systemui.haptics.vibratorHelper +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.activityStarter +import com.android.systemui.settings.brightness.BrightnessSliderController +import com.android.systemui.util.time.systemClock + +/** This factory creates empty mocks. */ +var Kosmos.brightnessSliderControllerFactory by + Kosmos.Fixture<BrightnessSliderController.Factory> { + BrightnessSliderController.Factory( + falsingManager, + uiEventLogger, + vibratorHelper, + systemClock, + activityStarter, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt new file mode 100644 index 000000000000..6db46499cea9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.brightnessMirrorShowingRepository by + Kosmos.Fixture { BrightnessMirrorShowingRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt new file mode 100644 index 000000000000..8f6b829f0021 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository + +val Kosmos.brightnessMirrorShowingInteractor by + Kosmos.Fixture { BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt new file mode 100644 index 000000000000..8fb370caee09 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings.brightness.ui.viewmodel + +import android.content.res.mainResources +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel +import com.android.systemui.settings.brightnessSliderControllerFactory + +val Kosmos.brightnessMirrorViewModel by + Kosmos.Fixture { + BrightnessMirrorViewModel( + brightnessMirrorShowingInteractor, + mainResources, + brightnessSliderControllerFactory, + ) + } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 0811c872d2eb..bbbc4aef55f3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2734,10 +2734,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.mComponentNameToServiceMap; boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId); + // Store the list of installed services. + mTempComponentNameSet.clear(); for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) { AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i); ComponentName componentName = ComponentName.unflattenFromString( installedService.getId()); + mTempComponentNameSet.add(componentName); AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName); @@ -2797,6 +2800,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub audioManager.setAccessibilityServiceUids(mTempIntArray); } mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray); + + // If any services have been removed, remove them from the enabled list and the touch + // exploration granted list. + boolean anyServiceRemoved = + userState.mEnabledServices.removeIf((comp) -> !mTempComponentNameSet.contains(comp)) + || userState.mTouchExplorationGrantedServices.removeIf( + (comp) -> !mTempComponentNameSet.contains(comp)); + if (anyServiceRemoved) { + // Update the enabled services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userState.mEnabledServices, + userState.mUserId); + // Update the touch exploration granted services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + userState.mTouchExplorationGrantedServices, + userState.mUserId); + } updateAccessibilityEnabledSettingLocked(userState); } diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java index d7e766eed209..f397814f7ad7 100644 --- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java @@ -16,6 +16,8 @@ package com.android.server.companion.utils; +import static android.Manifest.permission.BLUETOOTH_CONNECT; +import static android.Manifest.permission.BLUETOOTH_SCAN; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED; @@ -209,7 +211,9 @@ public final class PermissionsUtils { */ public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) { if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) - != PERMISSION_GRANTED) { + != PERMISSION_GRANTED + || context.checkCallingPermission(BLUETOOTH_SCAN) != PERMISSION_GRANTED + || context.checkCallingPermission(BLUETOOTH_CONNECT) != PERMISSION_GRANTED) { throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have " + "permissions to request observing device presence base on the UUID"); } diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index ac19d8bc897f..4694e9fd44bc 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -70,6 +70,8 @@ public final class SensitiveContentProtectionManagerService extends SystemServic NotificationListener mNotificationListener; @Nullable private MediaProjectionManager mProjectionManager; + + @GuardedBy("mSensitiveContentProtectionLock") @Nullable private MediaProjectionSession mMediaProjectionSession; @@ -98,6 +100,48 @@ public final class SensitiveContentProtectionManagerService extends SystemServic mIsExempted = isExempted; mSessionId = sessionId; } + + public void logProjectionSessionStart() { + FrameworkStatsLog.write( + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION, + mSessionId, + mUid, + mIsExempted, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS + ); + } + + public void logProjectionSessionStop() { + FrameworkStatsLog.write( + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION, + mSessionId, + mUid, + mIsExempted, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS + ); + } + + public void logAppBlocked(int uid) { + FrameworkStatsLog.write( + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION, + mSessionId, + uid, + mUid, + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED + ); + } + + public void logAppUnblocked(int uid) { + FrameworkStatsLog.write( + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION, + mSessionId, + uid, + mUid, + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED + ); + } } private final MediaProjectionManager.Callback mProjectionCallback = @@ -112,28 +156,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } - FrameworkStatsLog.write( - SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION, - mMediaProjectionSession.mSessionId, - mMediaProjectionSession.mUid, - mMediaProjectionSession.mIsExempted, - SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START, - SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS - ); } @Override public void onStop(MediaProjectionInfo info) { if (DEBUG) Log.d(TAG, "onStop projection: " + info); - FrameworkStatsLog.write( - SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION, - mMediaProjectionSession.mSessionId, - mMediaProjectionSession.mUid, - mMediaProjectionSession.mIsExempted, - SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP, - SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS - ); - Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "SensitiveContentProtectionManagerService.onProjectionStop"); try { @@ -242,16 +269,18 @@ public final class SensitiveContentProtectionManagerService extends SystemServic DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0; int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0, projectionInfo.getUserHandle().getIdentifier()); - mMediaProjectionSession = new MediaProjectionSession( - uid, isPackageExempted || isFeatureDisabled, new Random().nextLong()); - - if (isPackageExempted || isFeatureDisabled) { - Log.w(TAG, "projection session is exempted, package =" - + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled); - return; - } - synchronized (mSensitiveContentProtectionLock) { + mMediaProjectionSession = new MediaProjectionSession( + uid, isPackageExempted || isFeatureDisabled, new Random().nextLong()); + mMediaProjectionSession.logProjectionSessionStart(); + + if (isPackageExempted || isFeatureDisabled) { + Log.w(TAG, "projection session is exempted, package =" + + projectionInfo.getPackageName() + ", isFeatureDisabled=" + + isFeatureDisabled); + return; + } + mProjectionActive = true; if (sensitiveNotificationAppProtection()) { updateAppsThatShouldBlockScreenCapture(); @@ -266,7 +295,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic private void onProjectionEnd() { synchronized (mSensitiveContentProtectionLock) { mProjectionActive = false; - mMediaProjectionSession = null; + if (mMediaProjectionSession != null) { + mMediaProjectionSession.logProjectionSessionStop(); + mMediaProjectionSession = null; + } // notify windowmanager to clear any sensitive notifications observed during projection // session @@ -437,22 +469,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic packageInfos.add(packageInfo); if (isShowingSensitiveContent) { mWindowManager.addBlockScreenCaptureForApps(packageInfos); - FrameworkStatsLog.write( - FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION, - mMediaProjectionSession.mSessionId, - uid, - mMediaProjectionSession.mUid, - FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED - ); + if (mMediaProjectionSession != null) { + mMediaProjectionSession.logAppBlocked(uid); + } } else { mWindowManager.removeBlockScreenCaptureForApps(packageInfos); - FrameworkStatsLog.write( - FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION, - mMediaProjectionSession.mSessionId, - uid, - mMediaProjectionSession.mUid, - FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED - ); + if (mMediaProjectionSession != null) { + mMediaProjectionSession.logAppUnblocked(uid); + } } } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0a7f49da0c31..3c6b500d9ced 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -108,29 +108,50 @@ import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockSettingsInternal; +import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accounts.AccountManagerService; import com.android.server.adaptiveauth.AdaptiveAuthService; +import com.android.server.adb.AdbService; +import com.android.server.alarm.AlarmManagerService; import com.android.server.am.ActivityManagerService; +import com.android.server.ambientcontext.AmbientContextManagerService; +import com.android.server.app.GameManagerService; import com.android.server.appbinding.AppBindingService; +import com.android.server.apphibernation.AppHibernationService; import com.android.server.appop.AppOpMigrationHelper; import com.android.server.appop.AppOpMigrationHelperImpl; +import com.android.server.appprediction.AppPredictionManagerService; +import com.android.server.appwidget.AppWidgetService; import com.android.server.art.ArtModuleServiceInitializer; import com.android.server.art.DexUseManagerLocal; import com.android.server.attention.AttentionManagerService; import com.android.server.audio.AudioService; +import com.android.server.autofill.AutofillManagerService; +import com.android.server.backup.BackupManagerService; import com.android.server.biometrics.AuthService; import com.android.server.biometrics.BiometricService; import com.android.server.biometrics.sensors.face.FaceService; import com.android.server.biometrics.sensors.fingerprint.FingerprintService; import com.android.server.biometrics.sensors.iris.IrisService; +import com.android.server.blob.BlobStoreManagerService; import com.android.server.broadcastradio.BroadcastRadioService; import com.android.server.camera.CameraServiceProxy; import com.android.server.clipboard.ClipboardService; +import com.android.server.companion.CompanionDeviceManagerService; +import com.android.server.companion.virtual.VirtualDeviceManagerService; import com.android.server.compat.PlatformCompat; import com.android.server.compat.PlatformCompatNative; +import com.android.server.compat.overrides.AppCompatOverridesService; +import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.PacProxyService; +import com.android.server.content.ContentService; import com.android.server.contentcapture.ContentCaptureManagerInternal; +import com.android.server.contentcapture.ContentCaptureManagerService; +import com.android.server.contentsuggestions.ContentSuggestionsManagerService; +import com.android.server.contextualsearch.ContextualSearchManagerService; import com.android.server.coverage.CoverageService; import com.android.server.cpu.CpuMonitorService; +import com.android.server.credentials.CredentialManagerService; import com.android.server.criticalevents.CriticalEventLog; import com.android.server.devicepolicy.DevicePolicyManagerService; import com.android.server.devicestate.DeviceStateManagerService; @@ -147,14 +168,20 @@ import com.android.server.incident.IncidentCompanionService; import com.android.server.input.InputManagerService; import com.android.server.inputmethod.InputMethodManagerService; import com.android.server.integrity.AppIntegrityManagerService; +import com.android.server.job.JobSchedulerService; import com.android.server.lights.LightsService; import com.android.server.locales.LocaleManagerService; import com.android.server.location.LocationManagerService; import com.android.server.location.altitude.AltitudeService; +import com.android.server.locksettings.LockSettingsService; import com.android.server.logcat.LogcatManagerService; +import com.android.server.media.MediaResourceMonitorService; import com.android.server.media.MediaRouterService; +import com.android.server.media.MediaSessionService; import com.android.server.media.metrics.MediaMetricsManagerService; import com.android.server.media.projection.MediaProjectionManagerService; +import com.android.server.midi.MidiService; +import com.android.server.musicrecognition.MusicRecognitionManagerService; import com.android.server.net.NetworkManagementService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.watchlist.NetworkWatchlistService; @@ -195,12 +222,16 @@ import com.android.server.power.ShutdownThread; import com.android.server.power.ThermalManagerService; import com.android.server.power.hint.HintManagerService; import com.android.server.powerstats.PowerStatsService; +import com.android.server.print.PrintManagerService; import com.android.server.profcollect.ProfcollectForwardingService; import com.android.server.recoverysystem.RecoverySystemService; import com.android.server.resources.ResourcesManagerService; import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.role.RoleServicePlatformHelper; +import com.android.server.rollback.RollbackManagerService; import com.android.server.rotationresolver.RotationResolverManagerService; +import com.android.server.search.SearchManagerService; +import com.android.server.searchui.SearchUiManagerService; import com.android.server.security.AttestationVerificationManagerService; import com.android.server.security.FileIntegrityService; import com.android.server.security.KeyAttestationApplicationIdProviderService; @@ -210,16 +241,28 @@ import com.android.server.selinux.SelinuxAuditLogsService; import com.android.server.sensorprivacy.SensorPrivacyService; import com.android.server.sensors.SensorService; import com.android.server.signedconfig.SignedConfigService; +import com.android.server.slice.SliceManagerService; +import com.android.server.smartspace.SmartspaceManagerService; import com.android.server.soundtrigger.SoundTriggerService; import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService; +import com.android.server.speech.SpeechRecognitionManagerService; +import com.android.server.stats.bootstrap.StatsBootstrapAtomService; +import com.android.server.stats.pull.StatsPullAtomService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; +import com.android.server.systemcaptions.SystemCaptionsManagerService; import com.android.server.telecom.TelecomLoaderService; import com.android.server.testharness.TestHarnessModeService; import com.android.server.textclassifier.TextClassificationManagerService; import com.android.server.textservices.TextServicesManagerService; +import com.android.server.texttospeech.TextToSpeechManagerService; +import com.android.server.timedetector.GnssTimeUpdateService; import com.android.server.timedetector.NetworkTimeUpdateService; +import com.android.server.timedetector.TimeDetectorService; +import com.android.server.timezonedetector.TimeZoneDetectorService; +import com.android.server.timezonedetector.location.LocationTimeZoneManagerService; import com.android.server.tracing.TracingServiceProxy; +import com.android.server.translation.TranslationManagerService; import com.android.server.trust.TrustManagerService; import com.android.server.tv.TvInputManagerService; import com.android.server.tv.TvRemoteService; @@ -227,10 +270,15 @@ import com.android.server.tv.interactive.TvInteractiveAppManagerService; import com.android.server.tv.tunerresourcemanager.TunerResourceManagerService; import com.android.server.twilight.TwilightService; import com.android.server.uri.UriGrantsManagerService; +import com.android.server.usage.StorageStatsService; import com.android.server.usage.UsageStatsService; +import com.android.server.usb.UsbService; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.vibrator.VibratorManagerService; +import com.android.server.voiceinteraction.VoiceInteractionManagerService; import com.android.server.vr.VrManagerService; +import com.android.server.wallpaper.WallpaperManagerService; +import com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService; import com.android.server.wearable.WearableSensingManagerService; import com.android.server.webkit.WebViewUpdateService; import com.android.server.wm.ActivityTaskManagerService; @@ -268,44 +316,20 @@ public final class SystemServer implements Dumpable { * Implementation class names. TODO: Move them to a codegen class or load * them from the build system somehow. */ - private static final String BACKUP_MANAGER_SERVICE_CLASS = - "com.android.server.backup.BackupManagerService$Lifecycle"; - private static final String APPWIDGET_SERVICE_CLASS = - "com.android.server.appwidget.AppWidgetService"; private static final String ARC_NETWORK_SERVICE_CLASS = "com.android.server.arc.net.ArcNetworkService"; private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS = "com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService"; private static final String ARC_SYSTEM_HEALTH_SERVICE = "com.android.server.arc.health.ArcSystemHealthService"; - private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS = - "com.android.server.voiceinteraction.VoiceInteractionManagerService"; - private static final String APP_HIBERNATION_SERVICE_CLASS = - "com.android.server.apphibernation.AppHibernationService"; - private static final String PRINT_MANAGER_SERVICE_CLASS = - "com.android.server.print.PrintManagerService"; - private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS = - "com.android.server.companion.CompanionDeviceManagerService"; - private static final String VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS = - "com.android.server.companion.virtual.VirtualDeviceManagerService"; private static final String STATS_COMPANION_APEX_PATH = "/apex/com.android.os.statsd/javalib/service-statsd.jar"; + private static final String STATS_COMPANION_LIFECYCLE_CLASS = + "com.android.server.stats.StatsCompanion$Lifecycle"; private static final String SCHEDULING_APEX_PATH = "/apex/com.android.scheduling/javalib/service-scheduling.jar"; private static final String REBOOT_READINESS_LIFECYCLE_CLASS = "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle"; - private static final String CONNECTIVITY_SERVICE_APEX_PATH = - "/apex/com.android.tethering/javalib/service-connectivity.jar"; - private static final String STATS_COMPANION_LIFECYCLE_CLASS = - "com.android.server.stats.StatsCompanion$Lifecycle"; - private static final String STATS_PULL_ATOM_SERVICE_CLASS = - "com.android.server.stats.pull.StatsPullAtomService"; - private static final String STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS = - "com.android.server.stats.bootstrap.StatsBootstrapAtomService$Lifecycle"; - private static final String USB_SERVICE_CLASS = - "com.android.server.usb.UsbService$Lifecycle"; - private static final String MIDI_SERVICE_CLASS = - "com.android.server.midi.MidiService$Lifecycle"; private static final String WIFI_APEX_SERVICE_JAR_PATH = "/apex/com.android.wifi/javalib/service-wifi.jar"; private static final String WIFI_SERVICE_CLASS = @@ -320,16 +344,6 @@ public final class SystemServer implements Dumpable { "com.android.server.wifi.p2p.WifiP2pService"; private static final String LOWPAN_SERVICE_CLASS = "com.android.server.lowpan.LowpanService"; - private static final String JOB_SCHEDULER_SERVICE_CLASS = - "com.android.server.job.JobSchedulerService"; - private static final String LOCK_SETTINGS_SERVICE_CLASS = - "com.android.server.locksettings.LockSettingsService$Lifecycle"; - private static final String STORAGE_MANAGER_SERVICE_CLASS = - "com.android.server.StorageManagerService$Lifecycle"; - private static final String STORAGE_STATS_SERVICE_CLASS = - "com.android.server.usage.StorageStatsService$Lifecycle"; - private static final String SEARCH_MANAGER_SERVICE_CLASS = - "com.android.server.search.SearchManagerService$Lifecycle"; private static final String THERMAL_OBSERVER_CLASS = "com.android.clockwork.ThermalObserver"; private static final String WEAR_CONNECTIVITY_SERVICE_CLASS = @@ -354,91 +368,26 @@ public final class SystemServer implements Dumpable { "com.android.clockwork.settings.WearSettingsService"; private static final String WRIST_ORIENTATION_SERVICE_CLASS = "com.android.clockwork.wristorientation.WristOrientationService"; - private static final String ACCOUNT_SERVICE_CLASS = - "com.android.server.accounts.AccountManagerService$Lifecycle"; - private static final String CONTENT_SERVICE_CLASS = - "com.android.server.content.ContentService$Lifecycle"; - private static final String WALLPAPER_SERVICE_CLASS = - "com.android.server.wallpaper.WallpaperManagerService$Lifecycle"; - private static final String AUTO_FILL_MANAGER_SERVICE_CLASS = - "com.android.server.autofill.AutofillManagerService"; - private static final String CREDENTIAL_MANAGER_SERVICE_CLASS = - "com.android.server.credentials.CredentialManagerService"; - private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS = - "com.android.server.contentcapture.ContentCaptureManagerService"; - private static final String TRANSLATION_MANAGER_SERVICE_CLASS = - "com.android.server.translation.TranslationManagerService"; - private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS = - "com.android.server.musicrecognition.MusicRecognitionManagerService"; - private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS = - "com.android.server.ambientcontext.AmbientContextManagerService"; - private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS = - "com.android.server.systemcaptions.SystemCaptionsManagerService"; - private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS = - "com.android.server.texttospeech.TextToSpeechManagerService"; + private static final String IOT_SERVICE_CLASS = "com.android.things.server.IoTSystemService"; - private static final String SLICE_MANAGER_SERVICE_CLASS = - "com.android.server.slice.SliceManagerService$Lifecycle"; private static final String CAR_SERVICE_HELPER_SERVICE_CLASS = "com.android.internal.car.CarServiceHelperService"; - private static final String TIME_DETECTOR_SERVICE_CLASS = - "com.android.server.timedetector.TimeDetectorService$Lifecycle"; - private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS = - "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle"; - private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS = - "com.android.server.timezonedetector.location.LocationTimeZoneManagerService$Lifecycle"; - private static final String GNSS_TIME_UPDATE_SERVICE_CLASS = - "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle"; - private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS = - "com.android.server.accessibility.AccessibilityManagerService$Lifecycle"; - private static final String ADB_SERVICE_CLASS = - "com.android.server.adb.AdbService$Lifecycle"; - private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS = - "com.android.server.speech.SpeechRecognitionManagerService"; - private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS = - "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService"; - private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS = - "com.android.server.appprediction.AppPredictionManagerService"; - private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS = - "com.android.server.contentsuggestions.ContentSuggestionsManagerService"; - private static final String SEARCH_UI_MANAGER_SERVICE_CLASS = - "com.android.server.searchui.SearchUiManagerService"; - private static final String SMARTSPACE_MANAGER_SERVICE_CLASS = - "com.android.server.smartspace.SmartspaceManagerService"; - private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS = - "com.android.server.contextualsearch.ContextualSearchManagerService"; - private static final String DEVICE_IDLE_CONTROLLER_CLASS = - "com.android.server.DeviceIdleController"; - private static final String BLOB_STORE_MANAGER_SERVICE_CLASS = - "com.android.server.blob.BlobStoreManagerService"; private static final String APPSEARCH_MODULE_LIFECYCLE_CLASS = "com.android.server.appsearch.AppSearchModule$Lifecycle"; private static final String ISOLATED_COMPILATION_SERVICE_CLASS = "com.android.server.compos.IsolatedCompilationService"; - private static final String ROLLBACK_MANAGER_SERVICE_CLASS = - "com.android.server.rollback.RollbackManagerService"; - private static final String ALARM_MANAGER_SERVICE_CLASS = - "com.android.server.alarm.AlarmManagerService"; - private static final String MEDIA_SESSION_SERVICE_CLASS = - "com.android.server.media.MediaSessionService"; - private static final String MEDIA_RESOURCE_MONITOR_SERVICE_CLASS = - "com.android.server.media.MediaResourceMonitorService"; + private static final String CONNECTIVITY_SERVICE_APEX_PATH = + "/apex/com.android.tethering/javalib/service-connectivity.jar"; private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS = "com.android.server.ConnectivityServiceInitializer"; private static final String NETWORK_STATS_SERVICE_INITIALIZER_CLASS = "com.android.server.NetworkStatsServiceInitializer"; - private static final String IP_CONNECTIVITY_METRICS_CLASS = - "com.android.server.connectivity.IpConnectivityMetrics"; private static final String MEDIA_COMMUNICATION_SERVICE_CLASS = "com.android.server.media.MediaCommunicationService"; - private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS = - "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle"; private static final String HEALTHCONNECT_MANAGER_SERVICE_CLASS = "com.android.server.healthconnect.HealthConnectManagerService"; private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService"; - private static final String GAME_MANAGER_SERVICE_CLASS = - "com.android.server.app.GameManagerService$Lifecycle"; private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS = "com.android.ecm.EnhancedConfirmationService"; @@ -461,6 +410,7 @@ public final class SystemServer implements Dumpable { + "OnDevicePersonalizationSystemService$Lifecycle"; private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS = "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle"; + private static final String DEVICE_LOCK_SERVICE_CLASS = "com.android.server.devicelock.DeviceLockService"; private static final String DEVICE_LOCK_APEX_PATH = @@ -1435,7 +1385,7 @@ public final class SystemServer implements Dumpable { // Manages apk rollbacks. t.traceBegin("StartRollbackManagerService"); - mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(RollbackManagerService.class); t.traceEnd(); // Tracks native tombstones. @@ -1580,11 +1530,11 @@ public final class SystemServer implements Dumpable { // The AccountManager must come before the ContentService t.traceBegin("StartAccountManagerService"); - mSystemServiceManager.startService(ACCOUNT_SERVICE_CLASS); + mSystemServiceManager.startService(AccountManagerService.Lifecycle.class); t.traceEnd(); t.traceBegin("StartContentService"); - mSystemServiceManager.startService(CONTENT_SERVICE_CLASS); + mSystemServiceManager.startService(ContentService.Lifecycle.class); t.traceEnd(); t.traceBegin("InstallSystemProviders"); @@ -1639,7 +1589,7 @@ public final class SystemServer implements Dumpable { // TODO(aml-jobscheduler): Think about how to do it properly. t.traceBegin("StartAlarmManagerService"); - mSystemServiceManager.startService(ALARM_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(AlarmManagerService.class); t.traceEnd(); t.traceBegin("StartInputManagerService"); @@ -1721,7 +1671,7 @@ public final class SystemServer implements Dumpable { } t.traceBegin("IpConnectivityMetrics"); - mSystemServiceManager.startService(IP_CONNECTIVITY_METRICS_CLASS); + mSystemServiceManager.startService(IpConnectivityMetrics.class); t.traceEnd(); t.traceBegin("NetworkWatchlistService"); @@ -1796,7 +1746,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartAccessibilityManagerService"); try { - mSystemServiceManager.startService(ACCESSIBILITY_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(AccessibilityManagerService.Lifecycle.class); } catch (Throwable e) { reportWtf("starting Accessibility Manager", e); } @@ -1819,7 +1769,7 @@ public final class SystemServer implements Dumpable { * NotificationManagerService is dependant on StorageManagerService, * (for media / usb notifications) so we must start StorageManagerService first. */ - mSystemServiceManager.startService(STORAGE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(StorageManagerService.Lifecycle.class); storageManager = IStorageManager.Stub.asInterface( ServiceManager.getService("mount")); } catch (Throwable e) { @@ -1829,7 +1779,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartStorageStatsService"); try { - mSystemServiceManager.startService(STORAGE_STATS_SERVICE_CLASS); + mSystemServiceManager.startService(StorageStatsService.Lifecycle.class); } catch (Throwable e) { reportWtf("starting StorageStatsService", e); } @@ -1860,7 +1810,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); t.traceBegin("StartAppHibernationService"); - mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS); + mSystemServiceManager.startService(AppHibernationService.class); t.traceEnd(); t.traceBegin("ArtManagerLocal"); @@ -1892,7 +1842,7 @@ public final class SystemServer implements Dumpable { } else { t.traceBegin("StartLockSettingsService"); try { - mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS); + mSystemServiceManager.startService(LockSettingsService.Lifecycle.class); lockSettings = ILockSettings.Stub.asInterface( ServiceManager.getService("lock_settings")); } catch (Throwable e) { @@ -1925,7 +1875,7 @@ public final class SystemServer implements Dumpable { } t.traceBegin("StartDeviceIdleController"); - mSystemServiceManager.startService(DEVICE_IDLE_CONTROLLER_CLASS); + mSystemServiceManager.startService(DeviceIdleController.class); t.traceEnd(); // Always start the Device Policy Manager, so that the API is compatible with @@ -1948,7 +1898,7 @@ public final class SystemServer implements Dumpable { if (deviceHasConfigString(context, R.string.config_defaultMusicRecognitionService)) { t.traceBegin("StartMusicRecognitionManagerService"); - mSystemServiceManager.startService(MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(MusicRecognitionManagerService.class); t.traceEnd(); } else { Slog.d(TAG, @@ -1966,7 +1916,7 @@ public final class SystemServer implements Dumpable { if (deviceHasConfigString( context, R.string.config_defaultAmbientContextDetectionService)) { t.traceBegin("StartAmbientContextService"); - mSystemServiceManager.startService(AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(AmbientContextManagerService.class); t.traceEnd(); } else { Slog.d(TAG, "AmbientContextManagerService not defined by OEM or disabled by flag"); @@ -1974,13 +1924,13 @@ public final class SystemServer implements Dumpable { // System Speech Recognition Service t.traceBegin("StartSpeechRecognitionManagerService"); - mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(SpeechRecognitionManagerService.class); t.traceEnd(); // App prediction manager service if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) { t.traceBegin("StartAppPredictionService"); - mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(AppPredictionManagerService.class); t.traceEnd(); } else { Slog.d(TAG, "AppPredictionService not defined by OEM"); @@ -1989,7 +1939,7 @@ public final class SystemServer implements Dumpable { // Content suggestions manager service if (deviceHasConfigString(context, R.string.config_defaultContentSuggestionsService)) { t.traceBegin("StartContentSuggestionsService"); - mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS); + mSystemServiceManager.startService(ContentSuggestionsManagerService.class); t.traceEnd(); } else { Slog.d(TAG, "ContentSuggestionsService not defined by OEM"); @@ -1998,14 +1948,14 @@ public final class SystemServer implements Dumpable { // Search UI manager service if (deviceHasConfigString(context, R.string.config_defaultSearchUiService)) { t.traceBegin("StartSearchUiService"); - mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(SearchUiManagerService.class); t.traceEnd(); } // Smartspace manager service if (deviceHasConfigString(context, R.string.config_defaultSmartspaceService)) { t.traceBegin("StartSmartspaceService"); - mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(SmartspaceManagerService.class); t.traceEnd(); } else { Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag"); @@ -2015,7 +1965,7 @@ public final class SystemServer implements Dumpable { if (deviceHasConfigString(context, R.string.config_defaultContextualSearchPackageName)) { t.traceBegin("StartContextualSearchService"); - mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(ContextualSearchManagerService.class); t.traceEnd(); } else { Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag"); @@ -2214,7 +2164,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartTimeDetectorService"); try { - mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS); + mSystemServiceManager.startService(TimeDetectorService.Lifecycle.class); } catch (Throwable e) { reportWtf("starting TimeDetectorService service", e); } @@ -2235,7 +2185,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartTimeZoneDetectorService"); try { - mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS); + mSystemServiceManager.startService(TimeZoneDetectorService.Lifecycle.class); } catch (Throwable e) { reportWtf("starting TimeZoneDetectorService service", e); } @@ -2251,7 +2201,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartLocationTimeZoneManagerService"); try { - mSystemServiceManager.startService(LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(LocationTimeZoneManagerService.Lifecycle.class); } catch (Throwable e) { reportWtf("starting LocationTimeZoneManagerService service", e); } @@ -2260,7 +2210,7 @@ public final class SystemServer implements Dumpable { if (context.getResources().getBoolean(R.bool.config_enableGnssTimeUpdateService)) { t.traceBegin("StartGnssTimeUpdateService"); try { - mSystemServiceManager.startService(GNSS_TIME_UPDATE_SERVICE_CLASS); + mSystemServiceManager.startService(GnssTimeUpdateService.Lifecycle.class); } catch (Throwable e) { reportWtf("starting GnssTimeUpdateService service", e); } @@ -2270,7 +2220,7 @@ public final class SystemServer implements Dumpable { if (!isWatch) { t.traceBegin("StartSearchManagerService"); try { - mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(SearchManagerService.Lifecycle.class); } catch (Throwable e) { reportWtf("starting Search Service", e); } @@ -2279,7 +2229,7 @@ public final class SystemServer implements Dumpable { if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) { t.traceBegin("StartWallpaperManagerService"); - mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS); + mSystemServiceManager.startService(WallpaperManagerService.Lifecycle.class); t.traceEnd(); } else { Slog.i(TAG, "Wallpaper service disabled by config"); @@ -2289,8 +2239,7 @@ public final class SystemServer implements Dumpable { if (deviceHasConfigString(context, R.string.config_defaultWallpaperEffectsGenerationService)) { t.traceBegin("StartWallpaperEffectsGenerationService"); - mSystemServiceManager.startService( - WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(WallpaperEffectsGenerationManagerService.class); t.traceEnd(); } @@ -2345,14 +2294,14 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) { // Start MIDI Manager service t.traceBegin("StartMidiManager"); - mSystemServiceManager.startService(MIDI_SERVICE_CLASS); + mSystemServiceManager.startService(MidiService.Lifecycle.class); t.traceEnd(); } // Start ADB Debugging Service t.traceBegin("StartAdbService"); try { - mSystemServiceManager.startService(ADB_SERVICE_CLASS); + mSystemServiceManager.startService(AdbService.Lifecycle.class); } catch (Throwable e) { Slog.e(TAG, "Failure starting AdbService"); } @@ -2364,7 +2313,7 @@ public final class SystemServer implements Dumpable { || Build.IS_EMULATOR) { // Manage USB host and device support t.traceBegin("StartUsbService"); - mSystemServiceManager.startService(USB_SERVICE_CLASS); + mSystemServiceManager.startService(UsbService.Lifecycle.class); t.traceEnd(); } @@ -2396,7 +2345,7 @@ public final class SystemServer implements Dumpable { // TODO(aml-jobscheduler): Think about how to do it properly. t.traceBegin("StartJobScheduler"); - mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS); + mSystemServiceManager.startService(JobSchedulerService.class); t.traceEnd(); t.traceBegin("StartSoundTrigger"); @@ -2409,14 +2358,14 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) { t.traceBegin("StartBackupManager"); - mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(BackupManagerService.Lifecycle.class); t.traceEnd(); } if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS) || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) { t.traceBegin("StartAppWidgetService"); - mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS); + mSystemServiceManager.startService(AppWidgetService.class); t.traceEnd(); } @@ -2425,7 +2374,7 @@ public final class SystemServer implements Dumpable { // of initializing various settings. It will internally modify its behavior // based on that feature. t.traceBegin("StartVoiceRecognitionManager"); - mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(VoiceInteractionManagerService.class); t.traceEnd(); if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) { @@ -2486,7 +2435,7 @@ public final class SystemServer implements Dumpable { } t.traceBegin(START_BLOB_STORE_SERVICE); - mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(BlobStoreManagerService.class); t.traceEnd(); // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode) @@ -2507,7 +2456,7 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) { t.traceBegin("StartPrintManager"); - mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(PrintManagerService.class); t.traceEnd(); } @@ -2517,13 +2466,13 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) { t.traceBegin("StartCompanionDeviceManager"); - mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(CompanionDeviceManagerService.class); t.traceEnd(); } if (context.getResources().getBoolean(R.bool.config_enableVirtualDeviceManager)) { t.traceBegin("StartVirtualDeviceManager"); - mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(VirtualDeviceManagerService.class); t.traceEnd(); } @@ -2532,7 +2481,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); t.traceBegin("StartMediaSessionService"); - mSystemServiceManager.startService(MEDIA_SESSION_SERVICE_CLASS); + mSystemServiceManager.startService(MediaSessionService.class); t.traceEnd(); if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { @@ -2563,7 +2512,7 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) { t.traceBegin("StartMediaResourceMonitor"); - mSystemServiceManager.startService(MEDIA_RESOURCE_MONITOR_SERVICE_CLASS); + mSystemServiceManager.startService(MediaResourceMonitorService.class); t.traceEnd(); } @@ -2735,7 +2684,7 @@ public final class SystemServer implements Dumpable { if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) { t.traceBegin("StartSliceManagerService"); - mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(SliceManagerService.Lifecycle.class); t.traceEnd(); } @@ -2759,12 +2708,12 @@ public final class SystemServer implements Dumpable { // Statsd pulled atoms t.traceBegin("StartStatsPullAtomService"); - mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS); + mSystemServiceManager.startService(StatsPullAtomService.class); t.traceEnd(); // Log atoms to statsd from bootstrap processes. t.traceBegin("StatsBootstrapAtomService"); - mSystemServiceManager.startService(STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS); + mSystemServiceManager.startService(StatsBootstrapAtomService.Lifecycle.class); t.traceEnd(); // Incidentd and dumpstated helper @@ -2811,7 +2760,7 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOFILL)) { t.traceBegin("StartAutoFillService"); - mSystemServiceManager.startService(AUTO_FILL_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(AutofillManagerService.class); t.traceEnd(); } @@ -2820,13 +2769,12 @@ public final class SystemServer implements Dumpable { DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL, CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true); if (credentialManagerEnabled) { - if(isWatch && - !android.credentials.flags.Flags.wearCredentialManagerEnabled()) { - Slog.d(TAG, "CredentialManager disabled on wear."); + if (isWatch && !android.credentials.flags.Flags.wearCredentialManagerEnabled()) { + Slog.d(TAG, "CredentialManager disabled on wear."); } else { - t.traceBegin("StartCredentialManagerService"); - mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS); - t.traceEnd(); + t.traceBegin("StartCredentialManagerService"); + mSystemServiceManager.startService(CredentialManagerService.class); + t.traceEnd(); } } else { Slog.d(TAG, "CredentialManager disabled."); @@ -2836,7 +2784,7 @@ public final class SystemServer implements Dumpable { // Translation manager service if (deviceHasConfigString(context, R.string.config_defaultTranslationService)) { t.traceBegin("StartTranslationManagerService"); - mSystemServiceManager.startService(TRANSLATION_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(TranslationManagerService.class); t.traceEnd(); } else { Slog.d(TAG, "TranslationService not defined by OEM"); @@ -2980,7 +2928,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); t.traceBegin("GameManagerService"); - mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(GameManagerService.Lifecycle.class); t.traceEnd(); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) { @@ -3012,7 +2960,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); t.traceBegin("AppCompatOverridesService"); - mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS); + mSystemServiceManager.startService(AppCompatOverridesService.Lifecycle.class); t.traceEnd(); t.traceBegin("HealthConnectManagerService"); @@ -3397,14 +3345,14 @@ public final class SystemServer implements Dumpable { } t.traceBegin("StartSystemCaptionsManagerService"); - mSystemServiceManager.startService(SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(SystemCaptionsManagerService.class); t.traceEnd(); } private void startTextToSpeechManagerService(@NonNull Context context, @NonNull TimingsTraceAndSlog t) { t.traceBegin("StartTextToSpeechManagerService"); - mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(TextToSpeechManagerService.class); t.traceEnd(); } @@ -3439,7 +3387,7 @@ public final class SystemServer implements Dumpable { } t.traceBegin("StartContentCaptureService"); - mSystemServiceManager.startService(CONTENT_CAPTURE_MANAGER_SERVICE_CLASS); + mSystemServiceManager.startService(ContentCaptureManagerService.class); ContentCaptureManagerInternal ccmi = LocalServices.getService(ContentCaptureManagerInternal.class); diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 11893e7d7577..44ed3df34f24 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -1601,7 +1601,7 @@ class PermissionService(private val service: AccessCheckingService) : ) { with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { - if (permissionName !in DEVICE_AWARE_PERMISSIONS) { + if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) { Slog.i( LOG_TAG, "$permissionName is not device aware permission, " + @@ -1626,7 +1626,7 @@ class PermissionService(private val service: AccessCheckingService) : ) { with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { - if (permissionName !in DEVICE_AWARE_PERMISSIONS) { + if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) { Slog.i( LOG_TAG, "$permissionName is not device aware permission, " + @@ -2847,15 +2847,6 @@ class PermissionService(private val service: AccessCheckingService) : PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER - /** These permissions are supported for virtual devices. */ - // TODO: b/298661870 - Use new API to get the list of device aware permissions. - val DEVICE_AWARE_PERMISSIONS = - if (Flags.deviceAwarePermissionsEnabled()) { - setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) - } else { - emptySet<String>() - } - fun getFullerPermission(permissionName: String): String? = FULLER_PERMISSIONS[permissionName] } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 13550923cf3d..10eae577f706 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -46,6 +46,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; +import static android.server.wm.ActivityManagerTestBase.isTablet; import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -75,6 +76,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -122,6 +124,7 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; import com.android.server.wm.utils.MockTracker; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.Before; @@ -1295,6 +1298,12 @@ public class ActivityStarterTests extends WindowTestsBase { */ @Test public void testDeliverIntentToTopActivityOfNonTopDisplay() { + // TODO(b/330152508): Remove check once legacy multi-display behaviour can coexist with + // desktop windowing mode + // Ignore test if desktop windowing is enabled on tablets as legacy multi-display + // behaviour will not be respected + assumeFalse(Flags.enableDesktopWindowingMode() && isTablet()); + final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */); |