diff options
27 files changed, 484 insertions, 335 deletions
diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl index 4598421eb3bc..c3f3b1ced33c 100644 --- a/core/java/android/app/supervision/ISupervisionManager.aidl +++ b/core/java/android/app/supervision/ISupervisionManager.aidl @@ -22,4 +22,5 @@ package android.app.supervision; */ interface ISupervisionManager { boolean isSupervisionEnabledForUser(int userId); + String getActiveSupervisionAppPackage(int userId); } diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index 92241f3634e8..12432ddd0eb9 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -16,6 +16,7 @@ package android.app.supervision; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; @@ -98,4 +99,20 @@ public class SupervisionManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the package name of the app that is acting as the active supervision app or null if + * supervision is disabled. + * + * @hide + */ + @UserHandleAware + @Nullable + public String getActiveSupervisionAppPackage() { + try { + return mService.getActiveSupervisionAppPackage(mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 6da2a073ec19..1cf42820f356 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -18,13 +18,6 @@ flag { } flag { - namespace: "virtual_devices" - name: "media_projection_keyguard_restrictions" - description: "Auto-stop MP when the device locks" - bug: "348335290" -} - -flag { namespace: "virtual_devices" name: "virtual_display_insets" description: "APIs for specifying virtual display insets (via cutout)" diff --git a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java index de93234445ca..d3fb93588762 100644 --- a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java +++ b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java @@ -19,6 +19,7 @@ package android.hardware.biometrics; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.DevicePolicyManager; +import android.app.supervision.SupervisionManager; import android.content.ComponentName; import android.content.Context; import android.os.Build; @@ -55,27 +56,44 @@ public class ParentalControlsUtilsInternal { return null; } - public static boolean parentConsentRequired(@NonNull Context context, - @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality, + /** @return true if parental consent is required in order for biometric sensors to be used. */ + public static boolean parentConsentRequired( + @NonNull Context context, + @NonNull DevicePolicyManager dpm, + @Nullable SupervisionManager sm, + @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) { if (getTestComponentName(context, userHandle.getIdentifier()) != null) { return true; } - return parentConsentRequired(dpm, modality, userHandle); + return parentConsentRequired(dpm, sm, modality, userHandle); } /** * @return true if parental consent is required in order for biometric sensors to be used. */ - public static boolean parentConsentRequired(@NonNull DevicePolicyManager dpm, - @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) { - final ComponentName cn = getSupervisionComponentName(dpm, userHandle); - if (cn == null) { - return false; + public static boolean parentConsentRequired( + @NonNull DevicePolicyManager dpm, + @Nullable SupervisionManager sm, + @BiometricAuthenticator.Modality int modality, + @NonNull UserHandle userHandle) { + final int keyguardDisabledFeatures; + + if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) { + if (sm != null && !sm.isSupervisionEnabledForUser(userHandle.getIdentifier())) { + return false; + } + // Check for keyguard features disabled by any admin. + keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(/* admin= */ null); + } else { + final ComponentName cn = getSupervisionComponentName(dpm, userHandle); + if (cn == null) { + return false; + } + keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn); } - final int keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn); final boolean dpmFpDisabled = containsFlag(keyguardDisabledFeatures, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); final boolean dpmFaceDisabled = containsFlag(keyguardDisabledFeatures, @@ -97,7 +115,9 @@ public class ParentalControlsUtilsInternal { return consentRequired; } + /** @deprecated Use {@link SupervisionManager} to check for supervision. */ @Nullable + @Deprecated public static ComponentName getSupervisionComponentName(@NonNull DevicePolicyManager dpm, @NonNull UserHandle userHandle) { return dpm.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle); diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index e35c3b80a58b..222088e8a8b9 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -583,3 +583,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_start_launch_transition_from_taskbar_bugfix" + namespace: "lse_desktop_experience" + description: "Enables starting a launch transition directly from the taskbar if desktop tasks are visible." + bug: "361366053" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java index e3d7902f34b2..00973811dbf0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java @@ -84,7 +84,11 @@ public final class ActionDisabledByAdminControllerFactory { return false; } DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); - return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, + final SupervisionManager sm = + android.app.supervision.flags.Flags.deprecateDpmSupervisionApis() + ? context.getSystemService(SupervisionManager.class) + : null; + return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, sm, BiometricAuthenticator.TYPE_ANY_BIOMETRIC, new UserHandle(UserHandle.myUserId())); } diff --git a/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml index f7dac13888c0..ea494b4642d5 100644 --- a/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml +++ b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml @@ -21,7 +21,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/keyguard_lock_padding" - android:focusable="true" + android:focusable="false" /> <com.android.keyguard.BouncerKeyguardMessageArea @@ -30,6 +30,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/secondary_message_padding" - android:focusable="true" /> + android:focusable="false" /> </merge> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml index 2a8f1b596711..f231df2f1a10 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml @@ -66,7 +66,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml index 76f6f599c54c..04457229d573 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -31,7 +31,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml index 5879c110d8a1..b184344f2f24 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml @@ -67,7 +67,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml index 3f7b02835357..0e15ff66f3ee 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml @@ -35,7 +35,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml index b464fb3bafed..f6ac02aee657 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml @@ -74,7 +74,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml index 21580731aed2..ba4da794d777 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml @@ -32,7 +32,7 @@ <com.android.systemui.bouncer.ui.BouncerMessageView android:id="@+id/bouncer_message_view" - android:importantForAccessibility="noHideDescendants" + android:screenReaderFocusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 7f176de547bc..0e9d8fec9717 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; + import static com.android.systemui.Flags.pinInputFieldStyledFocusState; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -164,6 +166,8 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB layoutParams.height = (int) getResources().getDimension( R.dimen.keyguard_pin_field_height); } + + mPasswordEntry.sendAccessibilityEvent(TYPE_VIEW_FOCUSED); } private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) { diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 69b7e892a380..9795cda97f37 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -49,8 +49,10 @@ import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton +import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.isFourFingerTouchpadSwipe import com.android.systemui.touchpad.tutorial.ui.gesture.isThreeFingerTouchpadSwipe @@ -80,6 +82,7 @@ fun TutorialSelectionScreen( } ), ) { + val padding = if (hasCompactWindowSize()) 24.dp else 60.dp val configuration = LocalConfiguration.current when (configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { @@ -88,7 +91,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(60.dp), + modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, ) } @@ -98,7 +101,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(60.dp), + modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, ) } @@ -106,7 +109,7 @@ fun TutorialSelectionScreen( // because other composables have weight 1, Done button will be positioned first DoneButton( onDoneButtonClicked = onDoneButtonClicked, - modifier = Modifier.padding(horizontal = 60.dp), + modifier = Modifier.padding(horizontal = padding), ) } } @@ -146,7 +149,7 @@ private fun VerticalSelectionButtons( lastSelectedScreen: Screen, ) { Column( - verticalArrangement = Arrangement.spacedBy(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { @@ -244,8 +247,13 @@ private fun TutorialButton( modifier = Modifier.width(30.dp).height(30.dp), tint = iconColor, ) - Spacer(modifier = Modifier.height(16.dp)) - Text(text = text, style = MaterialTheme.typography.headlineLarge, color = iconColor) + if (!hasCompactWindowSize()) Spacer(modifier = Modifier.height(16.dp)) + Text( + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineLarge, + color = iconColor, + ) } } } diff --git a/services/core/java/com/android/server/display/plugin/PluginManager.java b/services/core/java/com/android/server/display/plugin/PluginManager.java index d4099975cafa..cb0a4574361a 100644 --- a/services/core/java/com/android/server/display/plugin/PluginManager.java +++ b/services/core/java/com/android/server/display/plugin/PluginManager.java @@ -74,15 +74,17 @@ public class PluginManager { /** * Adds change listener for particular plugin type */ - public <T> void subscribe(PluginType<T> type, PluginChangeListener<T> listener) { - mPluginStorage.addListener(type, listener); + public <T> void subscribe(PluginType<T> type, String uniqueDisplayId, + PluginChangeListener<T> listener) { + mPluginStorage.addListener(type, uniqueDisplayId, listener); } /** * Removes change listener */ - public <T> void unsubscribe(PluginType<T> type, PluginChangeListener<T> listener) { - mPluginStorage.removeListener(type, listener); + public <T> void unsubscribe(PluginType<T> type, String uniqueDisplayId, + PluginChangeListener<T> listener) { + mPluginStorage.removeListener(type, uniqueDisplayId, listener); } /** diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java index dd3415fb614d..5102c2709329 100644 --- a/services/core/java/com/android/server/display/plugin/PluginStorage.java +++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java @@ -20,10 +20,13 @@ import android.annotation.Nullable; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.tools.r8.keepanno.annotations.KeepForApi; import java.io.PrintWriter; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -35,42 +38,97 @@ import java.util.Set; public class PluginStorage { private static final String TAG = "PluginStorage"; + // Special ID used to indicate that given value is to be applied globally, rather than to a + // specific display. If both GLOBAL and specific display values are present - specific display + // value is selected. + @VisibleForTesting + static final String GLOBAL_ID = "GLOBAL"; + private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<PluginType<?>, Object> mValues = new HashMap<>(); + private final Map<PluginType<?>, ValuesContainer<?>> mValues = new HashMap<>(); @GuardedBy("mLock") private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>(); @GuardedBy("mLock") - private final PluginEventStorage mPluginEventStorage = new PluginEventStorage(); + private final Map<String, PluginEventStorage> mPluginEventStorages = new HashMap<>(); + + /** + * Updates value in storage and forwards it to corresponding listeners for all displays + * that does not have display specific value. + * Should be called by OEM Plugin implementation in order to communicate with Framework + */ + @KeepForApi + public <T> void updateGlobalValue(PluginType<T> type, @Nullable T value) { + updateValue(type, GLOBAL_ID, value); + } /** - * Updates value in storage and forwards it to corresponding listeners. - * Should be called by OEM Plugin implementation in order to provide communicate with Framework + * Updates value in storage and forwards it to corresponding listeners for specific display. + * Should be called by OEM Plugin implementation in order to communicate with Framework + * @param type - plugin type, that need to be updated + * @param uniqueDisplayId - uniqueDisplayId that this type/value should be applied to + * @param value - plugin value for particular type and display */ @KeepForApi - public <T> void updateValue(PluginType<T> type, @Nullable T value) { - Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value); + public <T> void updateValue(PluginType<T> type, String uniqueDisplayId, @Nullable T value) { + Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value + + "; displayId=" + uniqueDisplayId); Set<PluginManager.PluginChangeListener<T>> localListeners; + T valueToNotify; synchronized (mLock) { - mValues.put(type, value); - mPluginEventStorage.onValueUpdated(type); - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - localListeners = new LinkedHashSet<>(container.mListeners); + ValuesContainer<T> valuesByType = getValuesContainerLocked(type); + valuesByType.updateValueLocked(uniqueDisplayId, value); + // if value was set to null, we might need to notify with GLOBAL value instead + valueToNotify = valuesByType.getValueLocked(uniqueDisplayId); + + PluginEventStorage storage = mPluginEventStorages.computeIfAbsent(uniqueDisplayId, + d -> new PluginEventStorage()); + storage.onValueUpdated(type); + + localListeners = getListenersForUpdateLocked(type, uniqueDisplayId); } Slog.d(TAG, "updateValue, notifying listeners=" + localListeners); - localListeners.forEach(l -> l.onChanged(value)); + localListeners.forEach(l -> l.onChanged(valueToNotify)); + } + + @GuardedBy("mLock") + private <T> Set<PluginManager.PluginChangeListener<T>> getListenersForUpdateLocked( + PluginType<T> type, String uniqueDisplayId) { + ListenersContainer<T> listenersContainer = getListenersContainerLocked(type); + Set<PluginManager.PluginChangeListener<T>> localListeners = new LinkedHashSet<>(); + // if GLOBAL value change we need to notify only listeners for displays that does not + // have display specific value + if (GLOBAL_ID.equals(uniqueDisplayId)) { + ValuesContainer<T> valuesContainer = getValuesContainerLocked(type); + Set<String> excludedDisplayIds = valuesContainer.getNonGlobalDisplaysLocked(); + listenersContainer.mListeners.forEach((localDisplayId, listeners) -> { + if (!excludedDisplayIds.contains(localDisplayId)) { + localListeners.addAll(listeners); + } + }); + } else { + localListeners.addAll( + listenersContainer.mListeners.getOrDefault(uniqueDisplayId, Set.of())); + } + return localListeners; } /** * Adds listener for PluginType. If storage already has value for this type, listener will * be notified immediately. */ - <T> void addListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) { + <T> void addListener(PluginType<T> type, String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + if (GLOBAL_ID.equals(uniqueDisplayId)) { + Slog.d(TAG, "addListener ignored for GLOBAL_ID, type=" + type.mName); + return; + } T value = null; synchronized (mLock) { - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - if (container.mListeners.add(listener)) { - value = getValueForTypeLocked(type); + ListenersContainer<T> container = getListenersContainerLocked(type); + if (container.addListenerLocked(uniqueDisplayId, listener)) { + ValuesContainer<T> valuesContainer = getValuesContainerLocked(type); + value = valuesContainer.getValueLocked(uniqueDisplayId); } } if (value != null) { @@ -81,10 +139,15 @@ public class PluginStorage { /** * Removes listener */ - <T> void removeListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) { + <T> void removeListener(PluginType<T> type, String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + if (GLOBAL_ID.equals(uniqueDisplayId)) { + Slog.d(TAG, "removeListener ignored for GLOBAL_ID, type=" + type.mName); + return; + } synchronized (mLock) { - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - container.mListeners.remove(listener); + ListenersContainer<T> container = getListenersContainerLocked(type); + container.removeListenerLocked(uniqueDisplayId, listener); } } @@ -92,53 +155,106 @@ public class PluginStorage { * Print the object's state and debug information into the given stream. */ void dump(PrintWriter pw) { - Map<PluginType<?>, Object> localValues; + Map<PluginType<?>, Map<String, Object>> localValues = new HashMap<>(); @SuppressWarnings("rawtypes") - Map<PluginType, Set> localListeners = new HashMap<>(); - List<PluginEventStorage.TimeFrame> timeFrames; + Map<PluginType, Map<String, Set>> localListeners = new HashMap<>(); + Map<String, List<PluginEventStorage.TimeFrame>> timeFrames = new HashMap<>(); synchronized (mLock) { - timeFrames = mPluginEventStorage.getTimeFrames(); - localValues = new HashMap<>(mValues); - mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners)); + mPluginEventStorages.forEach((displayId, storage) -> { + timeFrames.put(displayId, storage.getTimeFrames()); + }); + mValues.forEach((type, valueContainer) -> { + localValues.put(type, new HashMap<>(valueContainer.mValues)); + }); + mListeners.forEach((type, container) -> { + localListeners.put(type, new HashMap<>(container.mListeners)); + }); } pw.println("PluginStorage:"); pw.println("values=" + localValues); pw.println("listeners=" + localListeners); pw.println("PluginEventStorage:"); - for (PluginEventStorage.TimeFrame timeFrame: timeFrames) { - timeFrame.dump(pw); + for (Map.Entry<String, List<PluginEventStorage.TimeFrame>> timeFrameEntry : + timeFrames.entrySet()) { + pw.println("TimeFrames for displayId=" + timeFrameEntry.getKey()); + for (PluginEventStorage.TimeFrame timeFrame : timeFrameEntry.getValue()) { + timeFrame.dump(pw); + } } } @GuardedBy("mLock") @SuppressWarnings("unchecked") - private <T> T getValueForTypeLocked(PluginType<T> type) { - Object value = mValues.get(type); - if (value == null) { - return null; - } else if (type.mType == value.getClass()) { - return (T) value; + private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) { + ListenersContainer<?> container = mListeners.get(type); + if (container == null) { + ListenersContainer<T> lc = new ListenersContainer<>(); + mListeners.put(type, lc); + return lc; } else { - Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName() - + ", expected=" + type.mType.getName()); - return null; + return (ListenersContainer<T>) container; } } @GuardedBy("mLock") @SuppressWarnings("unchecked") - private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) { - ListenersContainer<?> container = mListeners.get(type); + private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) { + ValuesContainer<?> container = mValues.get(type); if (container == null) { - ListenersContainer<T> lc = new ListenersContainer<>(); - mListeners.put(type, lc); - return lc; + ValuesContainer<T> vc = new ValuesContainer<>(); + mValues.put(type, vc); + return vc; } else { - return (ListenersContainer<T>) container; + return (ValuesContainer<T>) container; } } private static final class ListenersContainer<T> { - private final Set<PluginManager.PluginChangeListener<T>> mListeners = new LinkedHashSet<>(); + private final Map<String, Set<PluginManager.PluginChangeListener<T>>> mListeners = + new LinkedHashMap<>(); + + private boolean addListenerLocked( + String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener) { + Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = + mListeners.computeIfAbsent(uniqueDisplayId, k -> new LinkedHashSet<>()); + return listenersForDisplay.add(listener); + } + + private void removeListenerLocked(String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = mListeners.get( + uniqueDisplayId); + if (listenersForDisplay == null) { + return; + } + + listenersForDisplay.remove(listener); + + if (listenersForDisplay.isEmpty()) { + mListeners.remove(uniqueDisplayId); + } + } + } + + private static final class ValuesContainer<T> { + private final Map<String, T> mValues = new HashMap<>(); + + private void updateValueLocked(String uniqueDisplayId, @Nullable T value) { + if (value == null) { + mValues.remove(uniqueDisplayId); + } else { + mValues.put(uniqueDisplayId, value); + } + } + + private Set<String> getNonGlobalDisplaysLocked() { + Set<String> keys = new HashSet<>(mValues.keySet()); + keys.remove(GLOBAL_ID); + return keys; + } + + private @Nullable T getValueLocked(String displayId) { + return mValues.getOrDefault(displayId, mValues.get(GLOBAL_ID)); + } } } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java index 2e0bb4f88485..18f2f48b80a3 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.telecom.TelecomManager; import android.telephony.TelephonyCallback; @@ -38,6 +39,7 @@ import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemConfig; +import java.util.List; import java.util.function.Consumer; /** @@ -60,21 +62,35 @@ public class MediaProjectionStopController { private final TelephonyManager mTelephonyManager; private final AppOpsManager mAppOpsManager; private final PackageManager mPackageManager; - private final RoleManager mRoleManager; + private final RoleHolderProvider mRoleHolderProvider; private final ContentResolver mContentResolver; private boolean mIsInCall; private long mLastCallStartTimeMillis; + + @VisibleForTesting + interface RoleHolderProvider { + List<String> getRoleHoldersAsUser(String roleName, UserHandle user); + } + public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) { + this(context, stopReasonConsumer, + (roleName, user) -> context.getSystemService(RoleManager.class) + .getRoleHoldersAsUser(roleName, user)); + } + + @VisibleForTesting + MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer, + RoleHolderProvider roleHolderProvider) { mStopReasonConsumer = stopReasonConsumer; mKeyguardManager = context.getSystemService(KeyguardManager.class); mTelecomManager = context.getSystemService(TelecomManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); mPackageManager = context.getPackageManager(); - mRoleManager = context.getSystemService(RoleManager.class); mContentResolver = context.getContentResolver(); + mRoleHolderProvider = roleHolderProvider; } /** @@ -146,8 +162,9 @@ public class MediaProjectionStopController { Slog.v(TAG, "Continuing MediaProjection for package with OP_PROJECT_MEDIA AppOp "); return true; } - if (mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - projectionGrant.userHandle).contains(projectionGrant.packageName)) { + if (mRoleHolderProvider.getRoleHoldersAsUser( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, projectionGrant.userHandle) + .contains(projectionGrant.packageName)) { Slog.v(TAG, "Continuing MediaProjection for package holding app streaming role."); return true; } @@ -177,10 +194,6 @@ public class MediaProjectionStopController { */ public boolean isStartForbidden( MediaProjectionManagerService.MediaProjection projectionGrant) { - if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { - return false; - } - if (!mKeyguardManager.isKeyguardLocked()) { return false; } @@ -194,9 +207,6 @@ public class MediaProjectionStopController { @VisibleForTesting void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { if (!isKeyguardLocked) return; - if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { - return; - } mStopReasonConsumer.accept(STOP_REASON_KEYGUARD); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e761e024b3ca..883d8f95b612 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1680,26 +1680,27 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * Sets the specified orientation of this container. It percolates this change upward along the * hierarchy to let each level of the hierarchy a chance to respond to it. * - * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}. + * @param requestedOrientation the specified orientation. Needs to be one of + * {@link ScreenOrientation}. * @param requestingContainer the container which orientation request has changed. Mostly used * to ensure it gets correct configuration. * @return the resolved override orientation of this window container. */ @ScreenOrientation - int setOrientation(@ScreenOrientation int orientation, + int setOrientation(@ScreenOrientation int requestedOrientation, @Nullable WindowContainer requestingContainer) { - if (getOverrideOrientation() == orientation) { - return orientation; + if (getOverrideOrientation() == requestedOrientation) { + return requestedOrientation; } - setOverrideOrientation(orientation); + setOverrideOrientation(requestedOrientation); final WindowContainer parent = getParent(); if (parent == null) { - return orientation; + return requestedOrientation; } // The derived class can return a result that is different from the given orientation. - final int resolvedOrientation = getOverrideOrientation(); + final int actualOverrideOrientation = getOverrideOrientation(); if (getConfiguration().orientation != getRequestedConfigurationOrientation( - false /* forDisplay */, resolvedOrientation) + false /* forDisplay */, actualOverrideOrientation) // Update configuration directly only if the change won't be dispatched from // ancestor. This prevents from computing intermediate configuration when the // parent also needs to be updated from the ancestor. E.g. the app requests @@ -1707,12 +1708,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // the task can be updated to portrait first so the configuration can be // computed in a consistent environment. && (inMultiWindowMode() - || !handlesOrientationChangeFromDescendant(orientation))) { + || !handlesOrientationChangeFromDescendant(requestedOrientation))) { // Resolve the requested orientation. onConfigurationChanged(parent.getConfiguration()); } onDescendantOrientationChanged(requestingContainer); - return resolvedOrientation; + return actualOverrideOrientation; } @ScreenOrientation diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index 073ee31ddd60..4f9859d33d74 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -51,7 +51,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.pm.UserManagerInternal; import java.io.FileDescriptor; @@ -62,17 +61,17 @@ import java.util.List; public class SupervisionService extends ISupervisionManager.Stub { private static final String LOG_TAG = "SupervisionService"; - private final Context mContext; - // TODO(b/362756788): Does this need to be a LockGuard lock? private final Object mLockDoNoUseDirectly = new Object(); @GuardedBy("getLockObject()") private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); + private final Context mContext; private final DevicePolicyManagerInternal mDpmInternal; private final PackageManager mPackageManager; private final UserManagerInternal mUserManagerInternal; + final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); public SupervisionService(Context context) { mContext = context.createAttributionContext(LOG_TAG); @@ -82,6 +81,12 @@ public class SupervisionService extends ISupervisionManager.Stub { mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener()); } + /** + * Returns whether supervision is enabled for the given user. + * + * <p>Supervision is automatically enabled when the supervision app becomes the profile owner or + * explicitly enabled via an internal call to {@link #setSupervisionEnabledForUser}. + */ @Override public boolean isSupervisionEnabledForUser(@UserIdInt int userId) { if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { @@ -92,6 +97,20 @@ public class SupervisionService extends ISupervisionManager.Stub { } } + /** + * Returns the package name of the active supervision app or null if supervision is disabled. + */ + @Override + @Nullable + public String getActiveSupervisionAppPackage(@UserIdInt int userId) { + if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { + enforcePermission(INTERACT_ACROSS_USERS); + } + synchronized (getLockObject()) { + return getUserDataLocked(userId).supervisionAppPackage; + } + } + @Override public void onShellCommand( @Nullable FileDescriptor in, @@ -140,35 +159,53 @@ public class SupervisionService extends ISupervisionManager.Stub { return data; } - void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { + /** + * Sets supervision as enabled or disabled for the given user and, in case supervision is being + * enabled, the package of the active supervision app. + */ + private void setSupervisionEnabledForUser( + @UserIdInt int userId, boolean enabled, @Nullable String supervisionAppPackage) { synchronized (getLockObject()) { - getUserDataLocked(userId).supervisionEnabled = enabled; + SupervisionUserData data = getUserDataLocked(userId); + data.supervisionEnabled = enabled; + data.supervisionAppPackage = enabled ? supervisionAppPackage : null; } } - /** Ensures that supervision is enabled when supervision app is the profile owner. */ + /** Ensures that supervision is enabled when the supervision app is the profile owner. */ private void syncStateWithDevicePolicyManager(@UserIdInt int userId) { - if (isProfileOwner(userId)) { - setSupervisionEnabledForUser(userId, true); + final ComponentName po = + mDpmInternal != null ? mDpmInternal.getProfileOwnerAsUser(userId) : null; + + if (po != null && po.getPackageName().equals(getSystemSupervisionPackage())) { + setSupervisionEnabledForUser(userId, true, po.getPackageName()); + } else if (po != null && po.equals(getSupervisionProfileOwnerComponent())) { + // TODO(b/392071637): Consider not enabling supervision in case profile owner is given + // to the legacy supervision profile owner component. + setSupervisionEnabledForUser(userId, true, po.getPackageName()); } else { // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner. // This might only be possible after introducing specific and public APIs to enable - // supervision. - setSupervisionEnabledForUser(userId, false); + // and disable supervision. + setSupervisionEnabledForUser(userId, false, /* supervisionAppPackage= */ null); } } - /** Returns whether the supervision app has profile owner status. */ - private boolean isProfileOwner(@UserIdInt int userId) { - ComponentName profileOwner = - mDpmInternal != null ? mDpmInternal.getProfileOwnerAsUser(userId) : null; - return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName()); + /** + * Returns the {@link ComponentName} of the supervision profile owner component. + * + * <p>This component is used to give GMS Kids Module permission to supervise the device and may + * still be active during the transition to the {@code SYSTEM_SUPERVISION} role. + */ + private ComponentName getSupervisionProfileOwnerComponent() { + return ComponentName.unflattenFromString( + mContext.getResources() + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)); } - /** Returns whether the given package name belongs to the supervision role holder. */ - private boolean isSupervisionAppPackage(String packageName) { - return packageName.equals( - mContext.getResources().getString(R.string.config_systemSupervision)); + /** Returns the package assigned to the {@code SYSTEM_SUPERVISION} role. */ + private String getSystemSupervisionPackage() { + return mContext.getResources().getString(R.string.config_systemSupervision); } /** Enforces that the caller has the given permission. */ @@ -228,19 +265,21 @@ public class SupervisionService extends ISupervisionManager.Stub { } } - final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); - private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal { @Override public boolean isActiveSupervisionApp(int uid) { - String[] packages = mPackageManager.getPackagesForUid(uid); - if (packages == null) { + int userId = UserHandle.getUserId(uid); + String supervisionAppPackage = getActiveSupervisionAppPackage(userId); + if (supervisionAppPackage == null) { return false; } - for (var packageName : packages) { - if (SupervisionService.this.isSupervisionAppPackage(packageName)) { - int userId = UserHandle.getUserId(uid); - return SupervisionService.this.isSupervisionEnabledForUser(userId); + + String[] packages = mPackageManager.getPackagesForUid(uid); + if (packages != null) { + for (var packageName : packages) { + if (supervisionAppPackage.equals(packageName)) { + return true; + } } } return false; @@ -253,7 +292,8 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { - SupervisionService.this.setSupervisionEnabledForUser(userId, enabled); + SupervisionService.this.setSupervisionEnabledForUser( + userId, enabled, getSystemSupervisionPackage()); } @Override diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java index 2adaae3943f1..976642bd563d 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java @@ -32,16 +32,18 @@ public class SupervisionServiceShellCommand extends ShellCommand { return handleDefaultCommands(null); } switch (cmd) { - case "enable": return setEnabled(true); - case "disable": return setEnabled(false); - default: return handleDefaultCommands(cmd); + case "enable": + return setEnabled(true); + case "disable": + return setEnabled(false); + default: + return handleDefaultCommands(cmd); } } private int setEnabled(boolean enabled) { - final var pw = getOutPrintWriter(); final var userId = UserHandle.parseUserArg(getNextArgRequired()); - mService.setSupervisionEnabledForUser(userId, enabled); + mService.mInternal.setSupervisionEnabledForUser(userId, enabled); return 0; } diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java index 1dd48f581bf4..06acb91509a1 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java @@ -26,6 +26,7 @@ import android.util.IndentingPrintWriter; public class SupervisionUserData { public final @UserIdInt int userId; public boolean supervisionEnabled; + @Nullable public String supervisionAppPackage; public boolean supervisionLockScreenEnabled; @Nullable public PersistableBundle supervisionLockScreenOptions; @@ -38,6 +39,7 @@ public class SupervisionUserData { pw.println("User " + userId + ":"); pw.increaseIndent(); pw.println("supervisionEnabled: " + supervisionEnabled); + pw.println("supervisionAppPackage: " + supervisionAppPackage); pw.println("supervisionLockScreenEnabled: " + supervisionLockScreenEnabled); pw.println("supervisionLockScreenOptions: " + supervisionLockScreenOptions); pw.decreaseIndent(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt index 01061f16c279..d9224eaf66ca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt @@ -29,6 +29,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever private val TEST_PLUGIN_TYPE = PluginType(Int::class.java, "test_type") +private val DISPLAY_ID = "display_id" @SmallTest class PluginManagerTest { @@ -62,18 +63,18 @@ class PluginManagerTest { fun testSubscribe() { val pluginManager = createPluginManager() - pluginManager.subscribe(TEST_PLUGIN_TYPE, mockListener) + pluginManager.subscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) - verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, mockListener) + verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) } @Test fun testUnsubscribe() { val pluginManager = createPluginManager() - pluginManager.unsubscribe(TEST_PLUGIN_TYPE, mockListener) + pluginManager.unsubscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) - verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, mockListener) + verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) } private fun createPluginManager(enabled: Boolean = true): PluginManager { diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt index 218e34134e93..8eb3e9fbf9b0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt @@ -23,6 +23,8 @@ import org.junit.Test private val TEST_PLUGIN_TYPE1 = PluginType(String::class.java, "test_type1") private val TEST_PLUGIN_TYPE2 = PluginType(String::class.java, "test_type2") +private val DISPLAY_ID_1 = "display_1" +private val DISPLAY_ID_2 = "display_2" @SmallTest class PluginStorageTest { @@ -33,9 +35,9 @@ class PluginStorageTest { fun testUpdateValue() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -44,9 +46,9 @@ class PluginStorageTest { fun testAddListener() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -55,10 +57,10 @@ class PluginStorageTest { fun testRemoveListener() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) - storage.removeListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) + storage.removeListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener.receivedValue).isNull() } @@ -68,10 +70,10 @@ class PluginStorageTest { val type1Value = "value1" val type2Value = "value2" val testChangeListener = TestPluginChangeListener<String>() - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) - storage.updateValue(TEST_PLUGIN_TYPE2, type2Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, type2Value) - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -81,15 +83,62 @@ class PluginStorageTest { val type1Value = "value1" val testChangeListener1 = TestPluginChangeListener<String>() val testChangeListener2 = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener1) - storage.addListener(TEST_PLUGIN_TYPE2, testChangeListener2) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, testChangeListener2) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) assertThat(testChangeListener2.receivedValue).isNull() } + @Test + fun testUpdateGlobal_noDisplaySpecificValue() { + val type1Value = "value1" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1Value) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1Value) + } + + @Test + fun testUpdateGlobal_withDisplaySpecificValue() { + val type1Value = "value1" + val type1GlobalValue = "value1Global" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue) + } + + @Test + fun testUpdateGlobal_withDisplaySpecificValueRemoved() { + val type1Value = "value1" + val type1GlobalValue = "value1Global" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, null) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1GlobalValue) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue) + } + private class TestPluginChangeListener<T> : PluginChangeListener<T> { var receivedValue: T? = null diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 3ced56a04138..a58a9cd2a28f 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -34,7 +34,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -53,15 +52,11 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; -import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; import android.app.AppOpsManager; -import android.app.Instrumentation; import android.app.KeyguardManager; -import android.app.role.RoleManager; -import android.companion.AssociationRequest; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -76,11 +71,9 @@ import android.media.projection.StopReason; import android.os.Binder; import android.os.IBinder; import android.os.Looper; -import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.test.TestLooper; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -99,7 +92,6 @@ import com.android.server.testutils.OffsettableClock; import com.android.server.wm.WindowManagerInternal; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -110,7 +102,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -292,8 +283,6 @@ public class MediaProjectionManagerServiceTest { assertThat(stoppedCallback2).isFalse(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked() throws Exception { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); @@ -308,8 +297,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_packageAllowlisted() throws NameNotFoundException { @@ -325,8 +312,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_AppOpMediaProjection() throws NameNotFoundException { @@ -347,50 +332,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) - @Test - public void testCreateProjection_keyguardLocked_RoleHeld() { - runWithRole( - AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - () -> { - try { - mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED; - doReturn(mAppInfo) - .when(mPackageManager) - .getApplicationInfoAsUser( - anyString(), - any(ApplicationInfoFlags.class), - any(UserHandle.class)); - MediaProjectionManagerService.MediaProjection projection = - mService.createProjectionInternal( - Process.myUid(), - mContext.getPackageName(), - TYPE_MIRRORING, - /* isPermanentGrant= */ false, - UserHandle.CURRENT, - DEFAULT_DISPLAY); - doReturn(true).when(mKeyguardManager).isKeyguardLocked(); - doReturn(PackageManager.PERMISSION_DENIED) - .when(mPackageManager) - .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); - - projection.start(mIMediaProjectionCallback); - projection.notifyVirtualDisplayCreated(10); - - // The projection was started because it was allowed to capture the - // keyguard. - assertWithMessage("Failed to run projection") - .that(mService.getActiveProjectionInfo()) - .isNotNull(); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - }); - } - - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_screenshareProtectionsDisabled() throws NameNotFoundException { @@ -416,8 +357,6 @@ public class MediaProjectionManagerServiceTest { } } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_noDisplayCreated() throws NameNotFoundException { @@ -509,8 +448,6 @@ public class MediaProjectionManagerServiceTest { assertThat(secondProjection).isNotEqualTo(projection); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testReuseProjection_keyguardNotLocked_startConsentDialog() throws NameNotFoundException { @@ -527,8 +464,6 @@ public class MediaProjectionManagerServiceTest { verify(mContext).startActivityAsUser(any(), any()); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); @@ -1302,48 +1237,6 @@ public class MediaProjectionManagerServiceTest { return mService.getProjectionInternal(UID, PACKAGE_NAME); } - /** - * Run the provided block giving the current context's package the provided role. - */ - @SuppressWarnings("SameParameterValue") - private void runWithRole(String role, Runnable block) { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - String packageName = mContext.getPackageName(); - UserHandle user = instrumentation.getTargetContext().getUser(); - RoleManager roleManager = Objects.requireNonNull( - mContext.getSystemService(RoleManager.class)); - try { - CountDownLatch latch = new CountDownLatch(1); - instrumentation.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.MANAGE_ROLE_HOLDERS, - Manifest.permission.BYPASS_ROLE_QUALIFICATION); - - roleManager.setBypassingRoleQualification(true); - roleManager.addRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), success -> { - if (success) { - latch.countDown(); - } else { - Assert.fail("Couldn't set role for test (failure) " + role); - } - }); - assertWithMessage("Couldn't set role for test (timeout) : " + role) - .that(latch.await(1, TimeUnit.SECONDS)).isTrue(); - block.run(); - - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - roleManager.removeRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), (aBool) -> {}); - roleManager.setBypassingRoleQualification(false); - instrumentation.getUiAutomation() - .dropShellPermissionIdentity(); - } - } - private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub { CountDownLatch mLatch = new CountDownLatch(1); @Override diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java index 379079a0018c..10ac0495d69a 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java @@ -22,7 +22,6 @@ import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_ import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -37,13 +36,10 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; -import android.app.Instrumentation; import android.app.KeyguardManager; -import android.app.role.RoleManager; import android.companion.AssociationRequest; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -69,7 +65,6 @@ import com.android.server.SystemConfig; import com.android.server.wm.WindowManagerInternal; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -79,9 +74,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.List; import java.util.function.Consumer; /** @@ -123,6 +116,8 @@ public class MediaProjectionStopControllerTest { private KeyguardManager mKeyguardManager; @Mock private TelecomManager mTelecomManager; + @Mock + private MediaProjectionStopController.RoleHolderProvider mRoleManager; private AppOpsManager mAppOpsManager; @Mock @@ -145,7 +140,7 @@ public class MediaProjectionStopControllerTest { mContext.addMockSystemService(TelecomManager.class, mTelecomManager); mContext.setMockPackageManager(mPackageManager); - mStopController = new MediaProjectionStopController(mContext, mStopConsumer); + mStopController = new MediaProjectionStopController(mContext, mStopConsumer, mRoleManager); mService = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); @@ -170,8 +165,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testMediaProjectionNotRestricted() throws Exception { when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); @@ -180,8 +173,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testMediaProjectionRestricted() throws Exception { MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(); mediaProjection.notifyVirtualDisplayCreated(1); @@ -239,21 +230,13 @@ public class MediaProjectionStopControllerTest { @Test public void testExemptFromStoppingHasAppStreamingRole() throws Exception { - runWithRole( - AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - () -> { - try { - MediaProjectionManagerService.MediaProjection mediaProjection = - createMediaProjection(); - doReturn(PackageManager.PERMISSION_DENIED).when( - mPackageManager).checkPermission( - RECORD_SENSITIVE_CONTENT, mediaProjection.packageName); - assertThat(mStopController.isExemptFromStopping(mediaProjection, - MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(); + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + RECORD_SENSITIVE_CONTENT, mediaProjection.packageName); + doReturn(List.of(mediaProjection.packageName)).when(mRoleManager).getRoleHoldersAsUser( + eq(AssociationRequest.DEVICE_PROFILE_APP_STREAMING), any(UserHandle.class)); + assertThat(mStopController.isExemptFromStopping(mediaProjection, + MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue(); } @Test @@ -316,8 +299,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_unlocked() { mStopController.onKeyguardLockedStateChanged(false); @@ -325,8 +306,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_locked() { mStopController.onKeyguardLockedStateChanged(true); @@ -438,47 +417,4 @@ public class MediaProjectionStopControllerTest { MediaProjectionManager.TYPE_SCREEN_CAPTURE, false, mContext.getUser(), INVALID_DISPLAY); } - - /** - * Run the provided block giving the current context's package the provided role. - */ - @SuppressWarnings("SameParameterValue") - private void runWithRole(String role, Runnable block) { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - String packageName = mContext.getPackageName(); - UserHandle user = instrumentation.getTargetContext().getUser(); - RoleManager roleManager = Objects.requireNonNull( - mContext.getSystemService(RoleManager.class)); - try { - CountDownLatch latch = new CountDownLatch(1); - instrumentation.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.MANAGE_ROLE_HOLDERS, - Manifest.permission.BYPASS_ROLE_QUALIFICATION); - - roleManager.setBypassingRoleQualification(true); - roleManager.addRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), success -> { - if (success) { - latch.countDown(); - } else { - Assert.fail("Couldn't set role for test (failure) " + role); - } - }); - assertWithMessage("Couldn't set role for test (timeout) : " + role) - .that(latch.await(1, TimeUnit.SECONDS)).isTrue(); - block.run(); - - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - roleManager.removeRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), (aBool) -> { - }); - roleManager.setBypassingRoleQualification(false); - instrumentation.getUiAutomation() - .dropShellPermissionIdentity(); - } - } } diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt index 5862ac65eba9..af50effb7c8e 100644 --- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt @@ -92,6 +92,21 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(systemSupervisionPackage) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM) + fun onUserStarting_legacyProfileOwnerComponent_enablesSupervision() { + whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID)) + .thenReturn(supervisionProfileOwnerComponent) + + simulateUserStarting(USER_ID) + + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(supervisionProfileOwnerComponent.packageName) } @Test @@ -103,6 +118,7 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID, preCreated = true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test @@ -114,6 +130,7 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test @@ -125,6 +142,21 @@ class SupervisionServiceTest { broadcastProfileOwnerChanged(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(systemSupervisionPackage) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM) + fun profileOwnerChanged_legacyProfileOwnerComponent_enablesSupervision() { + whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID)) + .thenReturn(supervisionProfileOwnerComponent) + + broadcastProfileOwnerChanged(USER_ID) + + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(supervisionProfileOwnerComponent.packageName) } @Test @@ -136,13 +168,14 @@ class SupervisionServiceTest { broadcastProfileOwnerChanged(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() { whenever(mockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(arrayOf(systemSupervisionPackage)) - service.setSupervisionEnabledForUser(USER_ID, true) + service.mInternal.setSupervisionEnabledForUser(USER_ID, true) assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue() } @@ -151,7 +184,7 @@ class SupervisionServiceTest { fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() { whenever(mockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(arrayOf(systemSupervisionPackage)) - service.setSupervisionEnabledForUser(USER_ID, false) + service.mInternal.setSupervisionEnabledForUser(USER_ID, false) assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse() } @@ -167,15 +200,15 @@ class SupervisionServiceTest { fun setSupervisionEnabledForUser() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() - service.setSupervisionEnabledForUser(USER_ID, true) + service.mInternal.setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() - service.setSupervisionEnabledForUser(USER_ID, false) + service.mInternal.setSupervisionEnabledForUser(USER_ID, false) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() } @Test - fun supervisionEnabledForUser_internal() { + fun setSupervisionEnabledForUser_internal() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() service.mInternal.setSupervisionEnabledForUser(USER_ID, true) @@ -205,6 +238,13 @@ class SupervisionServiceTest { private val systemSupervisionPackage: String get() = context.getResources().getString(R.string.config_systemSupervision) + private val supervisionProfileOwnerComponent: ComponentName + get() = + context + .getResources() + .getString(R.string.config_defaultSupervisionProfileOwnerComponent) + .let(ComponentName::unflattenFromString)!! + private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) { val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0) userInfo.preCreated = preCreated |