diff options
39 files changed, 797 insertions, 259 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index e0fc9590f9f7..6964866db7f3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -45145,7 +45145,7 @@ package android.telephony { field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; - field @FlaggedApi("com.android.internal.telephony.flags.hide_roaming_icon") public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; + field public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool"; field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool"; field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool"; diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 4a579a4c0e85..4e6fb8d3a8e7 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -500,6 +500,16 @@ flag { } flag { + name: "get_user_switchability_permission" + namespace: "multiuser" + description: "Update permissions for getUserSwitchability" + bug: "390458180" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles" namespace: "profile_experiences" description: "Use user states to check the state of quiet mode for managed profiles only" diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 62126963cba4..79323bf2f2f7 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -225,3 +225,13 @@ flag { description: "Removes modifiers from the original key event that activated the fallback, ensuring that only the intended fallback event is sent." bug: "382545048" } + +flag { + name: "abort_slow_multi_press" + namespace: "wear_frameworks" + description: "If a press that's a part of a multipress takes too long, the multipress gesture will be cancelled." + bug: "370095426" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 1041041b2a27..1cf293d46350 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -45,8 +45,7 @@ import java.util.function.BiFunction; * {@link PersistableBundle} subclass. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass -@SuppressWarnings("HiddenSuperclass") -public class BaseBundle implements Parcel.ClassLoaderProvider { +public class BaseBundle { /** @hide */ protected static final String TAG = "Bundle"; static final boolean DEBUG = false; @@ -300,9 +299,8 @@ public class BaseBundle implements Parcel.ClassLoaderProvider { /** * Return the ClassLoader currently associated with this Bundle. - * @hide */ - public ClassLoader getClassLoader() { + ClassLoader getClassLoader() { return mClassLoader; } @@ -416,9 +414,6 @@ public class BaseBundle implements Parcel.ClassLoaderProvider { if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) { Intent.maybeMarkAsMissingCreatorToken(object); } - } else if (object instanceof Bundle) { - Bundle bundle = (Bundle) object; - bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); } return (clazz != null) ? clazz.cast(object) : (T) object; } @@ -492,7 +487,7 @@ public class BaseBundle implements Parcel.ClassLoaderProvider { int[] numLazyValues = new int[]{0}; try { parcelledData.readArrayMap(map, count, !parcelledByNative, - /* lazy */ ownsParcel, this, numLazyValues); + /* lazy */ ownsParcel, mClassLoader, numLazyValues); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 55bfd451d97a..819d58d9f059 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -141,8 +141,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { STRIPPED.putInt("STRIPPED", 1); } - private boolean isFirstRetrievedFromABundle = false; - /** * Constructs a new, empty Bundle. */ @@ -1022,9 +1020,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { return null; } try { - Bundle bundle = (Bundle) o; - bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); - return bundle; + return (Bundle) o; } catch (ClassCastException e) { typeWarning(key, o, "Bundle", e); return null; @@ -1032,21 +1028,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** - * Set the ClassLoader of a bundle to its container bundle. This is necessary so that when a - * bundle's ClassLoader is changed, it can be propagated to its children. Do this only when it - * is retrieved from the container bundle first time though. Once it is accessed outside of its - * container, its ClassLoader should no longer be changed by its container anymore. - * - * @param containerBundle the bundle this bundle is retrieved from. - */ - void setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(BaseBundle containerBundle) { - if (!isFirstRetrievedFromABundle) { - setClassLoader(containerBundle.getClassLoader()); - isFirstRetrievedFromABundle = true; - } - } - - /** * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a {@code null} * value is explicitly associated with the key. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 3c4139d39762..e58934746c14 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -4661,7 +4661,7 @@ public final class Parcel { * @hide */ @Nullable - private Object readLazyValue(@Nullable ClassLoaderProvider loaderProvider) { + public Object readLazyValue(@Nullable ClassLoader loader) { int start = dataPosition(); int type = readInt(); if (isLengthPrefixed(type)) { @@ -4672,17 +4672,12 @@ public final class Parcel { int end = MathUtils.addOrThrow(dataPosition(), objectLength); int valueLength = end - start; setDataPosition(end); - return new LazyValue(this, start, valueLength, type, loaderProvider); + return new LazyValue(this, start, valueLength, type, loader); } else { - return readValue(type, getClassLoader(loaderProvider), /* clazz */ null); + return readValue(type, loader, /* clazz */ null); } } - @Nullable - private static ClassLoader getClassLoader(@Nullable ClassLoaderProvider loaderProvider) { - return loaderProvider == null ? null : loaderProvider.getClassLoader(); - } - private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> { /** @@ -4696,12 +4691,7 @@ public final class Parcel { private final int mPosition; private final int mLength; private final int mType; - // this member is set when a bundle that includes a LazyValue is unparceled. But it is used - // when apply method is called. Between these 2 events, the bundle's ClassLoader could have - // changed. Let the bundle be a ClassLoaderProvider allows the bundle provides its current - // ClassLoader at the time apply method is called. - @NonNull - private final ClassLoaderProvider mLoaderProvider; + @Nullable private final ClassLoader mLoader; @Nullable private Object mObject; /** @@ -4712,13 +4702,12 @@ public final class Parcel { */ @Nullable private volatile Parcel mSource; - LazyValue(Parcel source, int position, int length, int type, - @NonNull ClassLoaderProvider loaderProvider) { + LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) { mSource = requireNonNull(source); mPosition = position; mLength = length; mType = type; - mLoaderProvider = loaderProvider; + mLoader = loader; } @Override @@ -4731,8 +4720,7 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoaderProvider.getClassLoader(), clazz, - itemTypes); + mObject = source.readValue(mLoader, clazz, itemTypes); } finally { source.setDataPosition(restore); } @@ -4805,8 +4793,7 @@ public final class Parcel { return Objects.equals(mObject, value.mObject); } // Better safely fail here since this could mean we get different objects. - if (!Objects.equals(mLoaderProvider.getClassLoader(), - value.mLoaderProvider.getClassLoader())) { + if (!Objects.equals(mLoader, value.mLoader)) { return false; } // Otherwise compare metadata prior to comparing payload. @@ -4820,24 +4807,10 @@ public final class Parcel { @Override public int hashCode() { // Accessing mSource first to provide memory barrier for mObject - return Objects.hash(mSource == null, mObject, mLoaderProvider.getClassLoader(), mType, - mLength); + return Objects.hash(mSource == null, mObject, mLoader, mType, mLength); } } - /** - * Provides a ClassLoader. - * @hide - */ - public interface ClassLoaderProvider { - /** - * Returns a ClassLoader. - * - * @return ClassLoader - */ - ClassLoader getClassLoader(); - } - /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { // Avoids allocating Class[0] array @@ -5578,8 +5551,8 @@ public final class Parcel { } private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, - int size, @Nullable ClassLoaderProvider loaderProvider) { - readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null); + int size, @Nullable ClassLoader loader) { + readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader, null); } /** @@ -5593,12 +5566,11 @@ public final class Parcel { * @hide */ void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, - boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) { + boolean lazy, @Nullable ClassLoader loader, int[] lazyValueCount) { ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, size); while (size > 0) { String key = readString(); - Object value = (lazy) ? readLazyValue(loaderProvider) : readValue( - getClassLoader(loaderProvider)); + Object value = (lazy) ? readLazyValue(loader) : readValue(loader); if (value instanceof LazyValue) { lazyValueCount[0]++; } @@ -5619,12 +5591,12 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal, - @Nullable ClassLoaderProvider loaderProvider) { + @Nullable ClassLoader loader) { final int N = readInt(); if (N < 0) { return; } - readArrayMapInternal(outVal, N, loaderProvider); + readArrayMapInternal(outVal, N, loader); } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2e231e3957c6..f8a90bbc6994 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13788,6 +13788,16 @@ public final class Settings { = "enable_freeform_support"; /** + * Whether to override the availability of the desktop experiences features on the + * device. With desktop experiences enabled, secondary displays can be used to run + * apps, in desktop mode by default. Otherwise they can only be used for mirroring. + * @hide + */ + @Readable + public static final String DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES = + "override_desktop_experience_features"; + + /** * Whether to override the availability of the desktop mode on the main display of the * device. If on, users can make move an app to the desktop, allowing a freeform windowing * experience. diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java index a337ba2a57fb..e0d4ec1ca826 100644 --- a/core/java/android/text/style/TtsSpan.java +++ b/core/java/android/text/style/TtsSpan.java @@ -108,11 +108,13 @@ public class TtsSpan implements ParcelableSpan { /** * The text associated with this span is a time, consisting of a number of - * hours and minutes, specified with {@link #ARG_HOURS} and - * {@link #ARG_MINUTES}. + * hours, minutes, and seconds specified with {@link #ARG_HOURS}, {@link #ARG_MINUTES}, and + * {@link #ARG_SECONDS}. * Also accepts the arguments {@link #ARG_GENDER}, * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and - * {@link #ARG_CASE}. + * {@link #ARG_CASE}. This is different from {@link #TYPE_DURATION}. This should be used to + * convey a particular moment in time, such as a clock time, while {@link #TYPE_DURATION} should + * be used to convey an interval of time. */ public static final String TYPE_TIME = "android.type.time"; @@ -310,16 +312,18 @@ public class TtsSpan implements ParcelableSpan { public static final String ARG_UNIT = "android.arg.unit"; /** - * Argument used to specify the hours of a time. The hours should be - * provided as an integer in the range from 0 up to and including 24. - * Can be used with {@link #TYPE_TIME}. + * Argument used to specify the hours of a time or duration. The hours should be + * provided as an integer in the range from 0 up to and including 24 for + * {@link #TYPE_TIME}. + * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}. */ public static final String ARG_HOURS = "android.arg.hours"; /** - * Argument used to specify the minutes of a time. The minutes should be - * provided as an integer in the range from 0 up to and including 59. - * Can be used with {@link #TYPE_TIME}. + * Argument used to specify the minutes of a time or duration. The minutes should be + * provided as an integer in the range from 0 up to and including 59 for + * {@link #TYPE_TIME}. + * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}. */ public static final String ARG_MINUTES = "android.arg.minutes"; diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 7f2f0e8863df..cfb4835a13f7 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -194,7 +194,7 @@ public class InsetsSourceControl implements Parcelable { } public void release(Consumer<SurfaceControl> surfaceReleaseConsumer) { - if (mLeash != null) { + if (mLeash != null && mLeash.isValid()) { surfaceReleaseConsumer.accept(mLeash); } } diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index be69d3da3874..d44b941082b5 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -20,12 +20,13 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.app.Application; import android.content.ContentResolver; +import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import com.android.window.flags.Flags; -import java.util.function.Supplier; +import java.util.function.BooleanSupplier; /** * Checks desktop mode flag state. @@ -90,9 +91,35 @@ public enum DesktopModeFlags { INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true); - private static final String TAG = "DesktopModeFlagsUtil"; + /** + * Flag class, to be used in case the enum cannot be used because the flag is not accessible. + * + * <p> This class will still use the process-wide cache. + */ + public static class DesktopModeFlag { + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + public DesktopModeFlag(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop mode developer option + * overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + + } + + private static final String TAG = "DesktopModeFlags"; // Function called to obtain aconfig flag value. - private final Supplier<Boolean> mFlagFunction; + private final BooleanSupplier mFlagFunction; // Whether the flag state should be affected by developer option. private final boolean mShouldOverrideByDevOption; @@ -100,7 +127,9 @@ public enum DesktopModeFlags { // be refreshed only on reboots as overridden state is expected to take effect on reboots. private static ToggleOverride sCachedToggleOverride; - DesktopModeFlags(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) { + public static final String SYSTEM_PROPERTY_NAME = "persist.wm.debug.desktop_experience_devopts"; + + DesktopModeFlags(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { this.mFlagFunction = flagFunction; this.mShouldOverrideByDevOption = shouldOverrideByDevOption; } @@ -110,24 +139,42 @@ public enum DesktopModeFlags { * overrides. */ public boolean isTrue() { - Application application = ActivityThread.currentApplication(); - if (!Flags.showDesktopWindowingDevOption() - || !mShouldOverrideByDevOption - || application == null) { - return mFlagFunction.get(); - } else { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + + private static boolean isFlagTrue(BooleanSupplier flagFunction, + boolean shouldOverrideByDevOption) { + if (!shouldOverrideByDevOption) return flagFunction.getAsBoolean(); + if (Flags.showDesktopExperienceDevOption()) { + return switch (getToggleOverride(null)) { + case OVERRIDE_UNSET, OVERRIDE_OFF -> flagFunction.getAsBoolean(); + case OVERRIDE_ON -> true; + }; + } + if (Flags.showDesktopWindowingDevOption()) { + Application application = ActivityThread.currentApplication(); + if (application == null) { + Log.w(TAG, "Could not get the current application."); + return flagFunction.getAsBoolean(); + } + ContentResolver contentResolver = application.getContentResolver(); + if (contentResolver == null) { + Log.w(TAG, "Could not get the content resolver for the application."); + return flagFunction.getAsBoolean(); + } boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode(); - return switch (getToggleOverride(application.getContentResolver())) { - case OVERRIDE_UNSET -> mFlagFunction.get(); + return switch (getToggleOverride(contentResolver)) { + case OVERRIDE_UNSET -> flagFunction.getAsBoolean(); // When toggle override matches its default state, don't override flags. This // helps users reset their feature overrides. - case OVERRIDE_OFF -> !shouldToggleBeEnabledByDefault && mFlagFunction.get(); - case OVERRIDE_ON -> shouldToggleBeEnabledByDefault ? mFlagFunction.get() : true; + case OVERRIDE_OFF -> !shouldToggleBeEnabledByDefault && flagFunction.getAsBoolean(); + case OVERRIDE_ON -> !shouldToggleBeEnabledByDefault || flagFunction.getAsBoolean(); }; } + return flagFunction.getAsBoolean(); } - private ToggleOverride getToggleOverride(ContentResolver contentResolver) { + private static ToggleOverride getToggleOverride(@Nullable ContentResolver contentResolver) { // If cached, return it if (sCachedToggleOverride != null) { return sCachedToggleOverride; @@ -143,12 +190,21 @@ public enum DesktopModeFlags { /** * Returns {@link ToggleOverride} from Settings.Global set by toggle. */ - private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) { - int settingValue = Settings.Global.getInt( - contentResolver, - Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, - ToggleOverride.OVERRIDE_UNSET.getSetting() - ); + private static ToggleOverride getToggleOverrideFromSystem( + @Nullable ContentResolver contentResolver) { + int settingValue; + if (Flags.showDesktopExperienceDevOption()) { + settingValue = SystemProperties.getInt( + SYSTEM_PROPERTY_NAME, + ToggleOverride.OVERRIDE_UNSET.getSetting() + ); + } else { + settingValue = Settings.Global.getInt( + contentResolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + ToggleOverride.OVERRIDE_UNSET.getSetting() + ); + } return ToggleOverride.fromSetting(settingValue, ToggleOverride.OVERRIDE_UNSET); } diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index b4e7675402b9..73ebcdd8a07b 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -555,4 +555,11 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "show_desktop_experience_dev_option" + namespace: "lse_desktop_experience" + description: "Replace the freeform windowing dev options with a desktop experience one." + bug: "389092752" +} diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 7baaa6d590f2..a0c4c13a8702 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -137,6 +137,14 @@ <public name="wantsRoleHolderPriority"/> <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) --> <public name="minSdkVersionFull"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorFill"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorFillInverse"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorStroke"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorStrokeInverse"/> </staging-public-group> <staging-public-group type="id" first-id="0x01b60000"> diff --git a/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java index b28e2b04b342..49927be65ae5 100644 --- a/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java +++ b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java @@ -21,33 +21,42 @@ import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF; import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_ON; import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET; import static android.window.DesktopModeFlags.ToggleOverride.fromSetting; -import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE; -import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS; +import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION; import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + import android.content.ContentResolver; import android.content.Context; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import android.support.test.uiautomator.UiDevice; +import android.window.DesktopModeFlags.DesktopModeFlag; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.window.flags.Flags; + import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.lang.reflect.Field; +import java.util.List; /** * Test class for {@link android.window.DesktopModeFlags} @@ -57,21 +66,39 @@ import java.lang.reflect.Field; */ @SmallTest @Presubmit -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class DesktopModeFlagsTest { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION); + } + @Rule - public SetFlagsRule setFlagsRule = new SetFlagsRule(); + public SetFlagsRule mSetFlagsRule; + private UiDevice mUiDevice; private Context mContext; + private boolean mLocalFlagValue = false; + private final DesktopModeFlag mOverriddenLocalFlag = new DesktopModeFlag( + () -> mLocalFlagValue, true); + private final DesktopModeFlag mNotOverriddenLocalFlag = new DesktopModeFlag( + () -> mLocalFlagValue, false); private static final int OVERRIDE_OFF_SETTING = 0; private static final int OVERRIDE_ON_SETTING = 1; private static final int OVERRIDE_UNSET_SETTING = -1; + public DesktopModeFlagsTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(flags); + } + @Before - public void setUp() { + public void setUp() throws Exception { mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + setOverride(null); } @After @@ -80,26 +107,35 @@ public class DesktopModeFlagsTest { } @Test - @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { + public void isTrue_overrideOff_featureFlagOn() throws Exception { setOverride(OVERRIDE_OFF_SETTING); - // In absence of dev options, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); + + if (showDesktopWindowingDevOpts()) { + // DW Dev Opts turns off flags when ON + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); + } else { + // DE Dev Opts doesn't turn flags OFF + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); + } } @Test - @DisableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_overrideOn_featureFlagOff() throws Exception { setOverride(OVERRIDE_ON_SETTING); - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); + if (showAnyDevOpts()) { + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); + } else { + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); + } } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_overrideUnset_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_overrideUnset_featureFlagOn() throws Exception { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag @@ -107,9 +143,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_overrideUnset_featureFlagOff_returnsFalse() { + public void isTrue_overrideUnset_featureFlagOff() throws Exception { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag @@ -117,8 +152,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_noOverride_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_noOverride_featureFlagOn_returnsTrue() throws Exception { setOverride(null); // For overridableFlag, in absence of overrides, follow flag @@ -126,9 +161,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_noOverride_featureFlagOff_returnsFalse() { + public void isTrue_noOverride_featureFlagOff_returnsFalse() throws Exception { setOverride(null); // For overridableFlag, in absence of overrides, follow flag @@ -136,8 +170,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_unrecognizableOverride_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_unrecognizableOverride_featureFlagOn_returnsTrue() throws Exception { setOverride(-2); // For overridableFlag, for unrecognized overrides, follow flag @@ -145,9 +179,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_unrecognizableOverride_featureFlagOff_returnsFalse() { + public void isTrue_unrecognizableOverride_featureFlagOff_returnsFalse() throws Exception { setOverride(-2); // For overridableFlag, for unrecognizable overrides, follow flag @@ -155,27 +188,10 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_overrideOff_featureFlagOn_returnsFalse() { - setOverride(OVERRIDE_OFF_SETTING); - - // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_overrideOn_featureFlagOff_returnsTrue() { - setOverride(OVERRIDE_ON_SETTING); - - // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); - } + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() throws Exception { + assumeTrue(showDesktopWindowingDevOpts()); - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist @@ -188,9 +204,9 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() { + public void isTrue_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() throws Exception { + assumeTrue(showAnyDevOpts()); setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist @@ -203,146 +219,144 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS}) - public void isTrue_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() { + @EnableFlags({FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) + public void isTrue_dwFlagOn_overrideUnset_featureFlagOn() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) - public void isTrue_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideUnset_featureFlagOff() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() { + @EnableFlags({FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) + public void isTrue_dwFlagOn_overrideOn_featureFlagOn() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) - public void isTrue_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideOn_featureFlagOff() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_ON_SETTING); - // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + if (showDesktopExperienceDevOpts()) { + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + } else { + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + } + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideOff_featureFlagOn() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_OFF_SETTING); - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + if (showDesktopWindowingDevOpts()) { + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + } else { + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + } + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) - public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags({ - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags({ - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOff_overrideOn_featureFlagOff_returnFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOff_overrideOn_featureFlagOff() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_ON_SETTING); - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + if (showAnyDevOpts()) { + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + } else { + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + } + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags({ - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_OFF_SETTING); - // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test @@ -365,7 +379,9 @@ public class DesktopModeFlagsTest { assertThat(OVERRIDE_UNSET.getSetting()).isEqualTo(-1); } - private void setOverride(Integer setting) { + private void setOverride(Integer setting) throws Exception { + setSysProp(setting); + ContentResolver contentResolver = mContext.getContentResolver(); String key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES; @@ -376,11 +392,35 @@ public class DesktopModeFlagsTest { } } + private void setSysProp(Integer value) throws Exception { + if (value == null) { + resetSysProp(); + } else { + mUiDevice.executeShellCommand( + "setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " " + value); + } + } + + private void resetSysProp() throws Exception { + mUiDevice.executeShellCommand("setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " ''"); + } + private void resetCache() throws Exception { Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField( "sCachedToggleOverride"); cachedToggleOverride.setAccessible(true); cachedToggleOverride.set(null, null); - setOverride(OVERRIDE_UNSET_SETTING); + } + + private boolean showDesktopWindowingDevOpts() { + return Flags.showDesktopWindowingDevOption() && !Flags.showDesktopExperienceDevOption(); + } + + private boolean showDesktopExperienceDevOpts() { + return Flags.showDesktopExperienceDevOption(); + } + + private boolean showAnyDevOpts() { + return Flags.showDesktopWindowingDevOption() || Flags.showDesktopExperienceDevOption(); } } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 2fed1380b635..1ee71ca78815 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -218,6 +218,13 @@ public class DesktopModeStatus { return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption(); } + /** + * Return {@code true} if desktop mode dev option should be shown on current device + */ + public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) { + return Flags.showDesktopExperienceDevOption(); + } + /** Returns if desktop mode dev option should be enabled if there is no user override. */ public static boolean shouldDevOptionBeEnabledByDefault() { return Flags.enableDesktopWindowingMode(); @@ -290,7 +297,7 @@ public class DesktopModeStatus { /** * Return {@code true} if desktop mode is unrestricted and is supported in the device. */ - private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { + public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { return !enforceDeviceRestrictions() || isDesktopModeSupported(context); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 4e7f87c48a86..f1f49eda75b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -633,8 +633,6 @@ public class BubbleStackView extends FrameLayout mMagneticTarget, mIndividualBubbleMagnetListener); - hideCurrentInputMethod(); - // Save the magnetized individual bubble so we can dispatch touch events to it. mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut(); } else { @@ -671,6 +669,10 @@ public class BubbleStackView extends FrameLayout return; } + if (mPositioner.isImeVisible()) { + hideCurrentInputMethod(); + } + // Show the dismiss target, if we haven't already. mDismissView.show(); diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig index fa1349c61c4c..6d4f0b4f47d5 100644 --- a/media/java/android/media/flags/projection.aconfig +++ b/media/java/android/media/flags/projection.aconfig @@ -29,3 +29,13 @@ flag { is_exported: true } +flag { + namespace: "media_projection" + name: "show_stop_dialog_post_call_end" + description: "Shows a stop dialog for MediaProjection sessions that started during call and remain active after a call ends" + bug: "390343524" + metadata { + purpose: PURPOSE_BUGFIX + } + is_exported: true +} diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl index e46d34e81483..3baf4d7efd65 100644 --- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl +++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl @@ -18,6 +18,7 @@ package android.media.projection; import android.media.projection.MediaProjectionInfo; import android.view.ContentRecordingSession; +import android.media.projection.MediaProjectionEvent; /** {@hide} */ oneway interface IMediaProjectionWatcherCallback { @@ -35,4 +36,19 @@ oneway interface IMediaProjectionWatcherCallback { in MediaProjectionInfo info, in @nullable ContentRecordingSession session ); + + /** + * Called when a specific {@link MediaProjectionEvent} occurs during the media projection session. + * + * @param event contains the event type, which describes the nature/context of the event. + * @param info optional {@link MediaProjectionInfo} containing details about the media + projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + void onMediaProjectionEvent( + in MediaProjectionEvent event, + in @nullable MediaProjectionInfo info, + in @nullable ContentRecordingSession session + ); } diff --git a/media/java/android/media/projection/MediaProjectionEvent.aidl b/media/java/android/media/projection/MediaProjectionEvent.aidl new file mode 100644 index 000000000000..34359900ce81 --- /dev/null +++ b/media/java/android/media/projection/MediaProjectionEvent.aidl @@ -0,0 +1,3 @@ +package android.media.projection; + +parcelable MediaProjectionEvent;
\ No newline at end of file diff --git a/media/java/android/media/projection/MediaProjectionEvent.java b/media/java/android/media/projection/MediaProjectionEvent.java new file mode 100644 index 000000000000..6922560c8abe --- /dev/null +++ b/media/java/android/media/projection/MediaProjectionEvent.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2025 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, + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.projection; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** @hide */ +public final class MediaProjectionEvent implements Parcelable { + + /** + * Represents various media projection events. + */ + @IntDef({PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL}) + @Retention(RetentionPolicy.SOURCE) + public @interface EventType {} + + /** Event type for when a call ends but the session is still active. */ + public static final int PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL = 0; + + private final @EventType int mEventType; + private final long mTimestampMillis; + + public MediaProjectionEvent(@EventType int eventType, long timestampMillis) { + mEventType = eventType; + mTimestampMillis = timestampMillis; + } + + private MediaProjectionEvent(Parcel in) { + mEventType = in.readInt(); + mTimestampMillis = in.readLong(); + } + + public @EventType int getEventType() { + return mEventType; + } + + public long getTimestampMillis() { + return mTimestampMillis; + } + + @Override + public boolean equals(Object o) { + if (o instanceof MediaProjectionEvent other) { + return mEventType == other.mEventType && mTimestampMillis == other.mTimestampMillis; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mEventType, mTimestampMillis); + } + + @Override + public String toString() { + return "MediaProjectionEvent{mEventType=" + mEventType + ", mTimestampMillis=" + + mTimestampMillis + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mEventType); + out.writeLong(mTimestampMillis); + } + + public static final Parcelable.Creator<MediaProjectionEvent> CREATOR = + new Parcelable.Creator<>() { + @Override + public MediaProjectionEvent createFromParcel(Parcel in) { + return new MediaProjectionEvent(in); + } + + @Override + public MediaProjectionEvent[] newArray(int size) { + return new MediaProjectionEvent[size]; + } + }; +} diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 9cc2cca441a4..9036bf385d96 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -363,6 +363,19 @@ public final class MediaProjectionManager { @Nullable ContentRecordingSession session ) { } + + /** + * Called when a specific {@link MediaProjectionEvent} occurs during the media projection + * session. + * + * @param event the media projection event details. + * @param info optional details about the media projection host. + * @param session optional associated recording session details. + */ + public void onMediaProjectionEvent( + final MediaProjectionEvent event, + @Nullable MediaProjectionInfo info, + @Nullable final ContentRecordingSession session) {} } /** @hide */ @@ -405,5 +418,13 @@ public final class MediaProjectionManager { ) { mHandler.post(() -> mCallback.onRecordingSessionSet(info, session)); } + + @Override + public void onMediaProjectionEvent( + final MediaProjectionEvent event, + @Nullable MediaProjectionInfo info, + @Nullable final ContentRecordingSession session) { + mHandler.post(() -> mCallback.onMediaProjectionEvent(event, info, session)); + } } } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index b7269256a449..0d6d32a22dae 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -51,7 +51,6 @@ import java.util.function.Consumer; @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) @SystemService(Context.MEDIA_QUALITY_SERVICE) public final class MediaQualityManager { - // TODO: unhide the APIs for api review private static final String TAG = "MediaQualityManager"; private final IMediaQualityManager mService; @@ -123,7 +122,6 @@ public final class MediaQualityManager { public void onPictureProfileAdded(String profileId, PictureProfile profile) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postPictureProfileAdded(profileId, profile); } } @@ -132,7 +130,6 @@ public final class MediaQualityManager { public void onPictureProfileUpdated(String profileId, PictureProfile profile) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postPictureProfileUpdated(profileId, profile); } } @@ -141,7 +138,6 @@ public final class MediaQualityManager { public void onPictureProfileRemoved(String profileId, PictureProfile profile) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postPictureProfileRemoved(profileId, profile); } } @@ -151,7 +147,6 @@ public final class MediaQualityManager { String profileId, List<ParameterCapability> caps) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postParameterCapabilitiesChanged(profileId, caps); } } @@ -160,7 +155,6 @@ public final class MediaQualityManager { public void onError(String profileId, int err) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postError(profileId, err); } } @@ -171,7 +165,6 @@ public final class MediaQualityManager { public void onSoundProfileAdded(String profileId, SoundProfile profile) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postSoundProfileAdded(profileId, profile); } } @@ -180,7 +173,6 @@ public final class MediaQualityManager { public void onSoundProfileUpdated(String profileId, SoundProfile profile) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postSoundProfileUpdated(profileId, profile); } } @@ -189,7 +181,6 @@ public final class MediaQualityManager { public void onSoundProfileRemoved(String profileId, SoundProfile profile) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postSoundProfileRemoved(profileId, profile); } } @@ -199,7 +190,6 @@ public final class MediaQualityManager { String profileId, List<ParameterCapability> caps) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postParameterCapabilitiesChanged(profileId, caps); } } @@ -208,7 +198,6 @@ public final class MediaQualityManager { public void onError(String profileId, int err) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postError(profileId, err); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9aad5d5f8367..246aa7158cab 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -178,6 +178,7 @@ public class SettingsBackupTest { Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES, Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, Settings.Global.DEVELOPMENT_FORCE_RTL, Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp index 0ff856e0b91e..1d74774c7c11 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp @@ -5,7 +5,7 @@ package { aconfig_declarations { name: "com_android_a11y_menu_flags", package: "com.android.systemui.accessibility.accessibilitymenu", - container: "system", + container: "system_ext", srcs: [ "accessibility.aconfig", ], diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig index 6d790114803a..bdf6d4242e68 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig @@ -1,5 +1,5 @@ package: "com.android.systemui.accessibility.accessibilitymenu" -container: "system" +container: "system_ext" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt index c7930549abe8..44c375d6ac5e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt @@ -272,9 +272,8 @@ private fun calculateNumCellsWidth(width: Dp) = } private fun calculateNumCellsHeight(height: Dp) = - // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes when { - height >= 900.dp -> 3 + height >= 1000.dp -> 3 height >= 480.dp -> 2 else -> 1 } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt index 953cf88feccb..943cbe87c8c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt @@ -49,6 +49,7 @@ data class KeyguardFingerprintListenModel( var systemUser: Boolean = false, var udfps: Boolean = false, var userDoesNotHaveTrust: Boolean = false, + var communalShowing: Boolean = false, ) : KeyguardListenModel() { /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */ @@ -81,6 +82,7 @@ data class KeyguardFingerprintListenModel( systemUser.toString(), udfps.toString(), userDoesNotHaveTrust.toString(), + communalShowing.toString(), ) } @@ -122,6 +124,7 @@ data class KeyguardFingerprintListenModel( systemUser = model.systemUser udfps = model.udfps userDoesNotHaveTrust = model.userDoesNotHaveTrust + communalShowing = model.communalShowing } } @@ -170,6 +173,7 @@ data class KeyguardFingerprintListenModel( "systemUser", "underDisplayFingerprint", "userDoesNotHaveTrust", + "communalShowing", ) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 101fcdfce7e5..c266a5b47cff 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -43,6 +43,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.systemui.Flags.glanceableHubV2; import static com.android.systemui.Flags.simPinBouncerReset; import static com.android.systemui.Flags.simPinUseSlotId; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; @@ -128,6 +129,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -294,6 +296,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final Provider<JavaAdapter> mJavaAdapter; private final Provider<SceneInteractor> mSceneInteractor; private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor; + private final CommunalSceneInteractor mCommunalSceneInteractor; private final AuthController mAuthController; private final UiEventLogger mUiEventLogger; private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage; @@ -404,6 +407,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; private boolean mFingerprintDetectRunning; private boolean mIsDreaming; + private boolean mCommunalShowing; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; @@ -2205,7 +2209,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab IActivityTaskManager activityTaskManagerService, Provider<AlternateBouncerInteractor> alternateBouncerInteractor, Provider<JavaAdapter> javaAdapter, - Provider<SceneInteractor> sceneInteractor) { + Provider<SceneInteractor> sceneInteractor, + CommunalSceneInteractor communalSceneInteractor) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2254,6 +2259,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mAlternateBouncerInteractor = alternateBouncerInteractor; mJavaAdapter = javaAdapter; mSceneInteractor = sceneInteractor; + mCommunalSceneInteractor = communalSceneInteractor; mHandler = new Handler(mainLooper) { @Override @@ -2535,6 +2541,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab ); } + if (glanceableHubV2()) { + mJavaAdapter.get().alwaysCollectFlow( + mCommunalSceneInteractor.isCommunalVisible(), + this::onCommunalShowingChanged + ); + } + // start() can be invoked in the middle of user switching, so check for this state and issue // the call manually as that important event was missed. if (mUserTracker.isUserSwitching()) { @@ -2837,6 +2850,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Sets whether the communal hub is showing. + */ + @VisibleForTesting + void onCommunalShowingChanged(boolean showing) { + mCommunalShowing = showing; + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + } + + /** * Whether the alternate bouncer is showing. */ public void setAlternateBouncerShowing(boolean showing) { @@ -2998,11 +3020,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); final boolean shouldListenBouncerState = !strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing(); + final boolean isUdfpsAuthRequiredOnCommunal = + !mCommunalShowing || isAlternateBouncerShowing(); final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer && !strongerAuthRequired - && userDoesNotHaveTrust); + && userDoesNotHaveTrust + && (!glanceableHubV2() || isUdfpsAuthRequiredOnCommunal)); boolean shouldListen = shouldListenKeyguardState && shouldListenUserState @@ -3033,7 +3058,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mSwitchingUser, mIsSystemUser, isUdfps, - userDoesNotHaveTrust)); + userDoesNotHaveTrust, + mCommunalShowing)); return shouldListen; } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2645811fa4ad..312d2ffd74e4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -38,6 +38,7 @@ import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELL import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_STOPPED; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; +import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -139,6 +140,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; @@ -180,6 +182,9 @@ import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -189,9 +194,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper @@ -304,6 +306,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private JavaAdapter mJavaAdapter; @Mock private SceneInteractor mSceneInteractor; + @Mock + private CommunalSceneInteractor mCommunalSceneInteractor; @Captor private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener; @@ -1084,6 +1088,49 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void udfpsStopsListeningWhenCommunalShowing() { + // GIVEN keyguard showing + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue(); + + // WHEN communal is shown + mKeyguardUpdateMonitor.onCommunalShowingChanged(true); + + // THEN shouldn't listen for fingerprint + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isFalse(); + + // WHEN alternate bouncer shows on top of communal, we should listen for fingerprint + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true); + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue(); + + // WHEN communal is hidden + mKeyguardUpdateMonitor.onCommunalShowingChanged(false); + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(false); + + // THEN listen for fingerprint + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue(); + } + + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void sfpsNotAffectedByCommunalShowing() { + // GIVEN keyguard showing + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + + // WHEN communal is shown + mKeyguardUpdateMonitor.onCommunalShowingChanged(true); + + // THEN we should still listen for fingerprint if not UDFPS + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + } + + @Test public void testFingerprintPowerPressed_restartsFingerprintListeningStateWithDelay() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, ""); @@ -2669,7 +2716,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager, () -> mAlternateBouncerInteractor, () -> mJavaAdapter, - () -> mSceneInteractor); + () -> mSceneInteractor, + mCommunalSceneInteractor); setAlternateBouncerVisibility(false); setPrimaryBouncerVisibility(false); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index c0dbfa546a94..89f0d0edbf2b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -710,7 +710,9 @@ public class HdmiControlService extends SystemService { // Register ContentObserver to monitor the settings change. registerContentObserver(); } - mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); + if (mMhlController != null) { + mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index c428f39fd9d0..34a6cb951d46 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -58,6 +58,7 @@ import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.media.projection.IMediaProjectionManager; import android.media.projection.IMediaProjectionWatcherCallback; +import android.media.projection.MediaProjectionEvent; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.media.projection.ReviewGrantedConsentResult; @@ -80,6 +81,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.media.projection.flags.Flags; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; @@ -177,9 +179,31 @@ public final class MediaProjectionManagerService extends SystemService private void maybeStopMediaProjection(int reason) { synchronized (mLock) { - if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) { - Slog.d(TAG, "Content Recording: Stopping MediaProjection due to " - + MediaProjectionStopController.stopReasonToString(reason)); + if (mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) { + return; + } + + if (Flags.showStopDialogPostCallEnd() + && mMediaProjectionStopController.isStopReasonCallEnd(reason)) { + MediaProjectionEvent event = + new MediaProjectionEvent( + MediaProjectionEvent + .PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL, + System.currentTimeMillis()); + Slog.d( + TAG, + "Scheduling event: " + + event.getEventType() + + " for reason: " + + MediaProjectionStopController.stopReasonToString(reason)); + + // Post the PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL event with a delay. + mHandler.postDelayed(() -> dispatchEvent(event), 500); + } else { + Slog.d( + TAG, + "Stopping MediaProjection due to reason: " + + MediaProjectionStopController.stopReasonToString(reason)); mProjectionGrant.stop(StopReason.STOP_DEVICE_LOCKED); } } @@ -388,6 +412,24 @@ public final class MediaProjectionManagerService extends SystemService mCallbackDelegate.dispatchSession(projectionInfo, session); } + private void dispatchEvent(@NonNull MediaProjectionEvent event) { + if (!Flags.showStopDialogPostCallEnd()) { + Slog.d( + TAG, + "Event dispatch skipped. Reason: Flag showStopDialogPostCallEnd " + + "is disabled. Event details: " + + event); + return; + } + MediaProjectionInfo projectionInfo; + ContentRecordingSession session; + synchronized (mLock) { + projectionInfo = mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null; + session = mProjectionGrant != null ? mProjectionGrant.mSession : null; + } + mCallbackDelegate.dispatchEvent(event, projectionInfo, session); + } + /** * Returns {@code true} when updating the current mirroring session on WM succeeded, and * {@code false} otherwise. @@ -1467,6 +1509,25 @@ public final class MediaProjectionManagerService extends SystemService } } + private void dispatchEvent( + @NonNull MediaProjectionEvent event, + @Nullable MediaProjectionInfo info, + @Nullable ContentRecordingSession session) { + if (!Flags.showStopDialogPostCallEnd()) { + Slog.d( + TAG, + "Event dispatch skipped. Reason: Flag showStopDialogPostCallEnd " + + "is disabled. Event details: " + + event); + return; + } + synchronized (mLock) { + for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { + mHandler.post(new WatcherEventCallback(callback, event, info, session)); + } + } + } + public void dispatchSession( @NonNull MediaProjectionInfo projectionInfo, @Nullable ContentRecordingSession session) { @@ -1593,6 +1654,41 @@ public final class MediaProjectionManagerService extends SystemService } } + private static final class WatcherEventCallback implements Runnable { + private final IMediaProjectionWatcherCallback mCallback; + private final MediaProjectionEvent mEvent; + private final MediaProjectionInfo mProjectionInfo; + private final ContentRecordingSession mSession; + + WatcherEventCallback( + @NonNull IMediaProjectionWatcherCallback callback, + @NonNull MediaProjectionEvent event, + @Nullable MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallback = callback; + mEvent = event; + mProjectionInfo = projectionInfo; + mSession = session; + } + + @Override + public void run() { + if (!Flags.showStopDialogPostCallEnd()) { + Slog.d( + TAG, + "Not running WatcherEventCallback. Reason: Flag " + + "showStopDialogPostCallEnd is disabled. " + ); + return; + } + try { + mCallback.onMediaProjectionEvent(mEvent, mProjectionInfo, mSession); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify MediaProjectionEvent change", e); + } + } + } + private static final class WatcherSessionCallback implements Runnable { private final IMediaProjectionWatcherCallback mCallback; private final MediaProjectionInfo mProjectionInfo; 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 c018e6bc1dc7..2e0bb4f88485 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java @@ -95,6 +95,11 @@ public class MediaProjectionStopController { } } + /** Checks if the given stop reason corresponds to a call ending. */ + public boolean isStopReasonCallEnd(int stopReason) { + return stopReason == STOP_REASON_CALL_END; + } + /** * Checks whether the given projection grant is exempt from stopping restrictions. */ diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 57ccab027c29..a2c53e56b9c9 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2922,7 +2922,16 @@ public class UserManagerService extends IUserManager.Stub { * switchable. */ public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) { - checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserSwitchability"); + if (Flags.getUserSwitchabilityPermission()) { + if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) { + throw new SecurityException( + "You need MANAGE_USERS or INTERACT_ACROSS_USERS permission to " + + "getUserSwitchability"); + } + } else { + checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, + "getUserSwitchability"); + } final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("getUserSwitchability-" + userId); diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 441d3eaf2348..142d919da455 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -24,6 +24,8 @@ import android.util.Log; import android.view.KeyEvent; import android.view.ViewConfiguration; +import com.android.hardware.input.Flags; + import java.io.PrintWriter; import java.util.ArrayList; @@ -355,6 +357,19 @@ public final class SingleKeyGestureDetector { } if (event.getKeyCode() == mActiveRule.mKeyCode) { + if (Flags.abortSlowMultiPress() + && (event.getEventTime() - mLastDownTime + >= mActiveRule.getLongPressTimeoutMs())) { + // In this case, we are either on a first long press (but long press behavior is not + // supported for this rule), or, on a non-first press that is at least as long as + // the long-press duration. Thus, we will cancel the multipress gesture. + if (DEBUG) { + Log.d(TAG, "The duration of the press is too slow. Resetting."); + } + reset(); + return false; + } + // key-up action should always be triggered if not processed by long press. MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, mKeyPressCounter, event); diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java index efc68aac0323..00e1c01bbadb 100644 --- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java +++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java @@ -22,6 +22,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR; import android.media.projection.IMediaProjectionManager; import android.media.projection.IMediaProjectionWatcherCallback; +import android.media.projection.MediaProjectionEvent; import android.media.projection.MediaProjectionInfo; import android.os.Binder; import android.os.IBinder; @@ -84,6 +85,12 @@ public class ScreenRecordingCallbackController { public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo, ContentRecordingSession contentRecordingSession) { } + + @Override + public void onMediaProjectionEvent( + MediaProjectionEvent event, + MediaProjectionInfo mediaProjectionInfo, + ContentRecordingSession session) {} } ScreenRecordingCallbackController(WindowManagerService wms) { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 0ecd0251ca94..3b79c54f1c73 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -89,8 +89,9 @@ class WallpaperWindowToken extends WindowToken { // Similar to Task.prepareSurfaces, outside of transitions we need to apply visibility // changes directly. In transitions the transition player will take care of applying the // visibility change. - if (!mTransitionController.inTransition(this)) { - getSyncTransaction().setVisibility(mSurfaceControl, isVisible()); + if (!mTransitionController.isCollecting(this) + && !mTransitionController.isPlayingTarget(this)) { + getPendingTransaction().setVisibility(mSurfaceControl, isVisible()); } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 5de0e9b6ed93..3a1d652f82d4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -309,6 +309,7 @@ import android.window.ActivityWindowInfo; import android.window.AddToSurfaceSyncGroupResult; import android.window.ClientWindowFrames; import android.window.ConfigurationChangeSetting; +import android.window.DesktopModeFlags; import android.window.IGlobalDragListener; import android.window.IScreenRecordingCallback; import android.window.ISurfaceSyncGroupCompletedListener; @@ -820,6 +821,8 @@ public class WindowManagerService extends IWindowManager.Stub DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH); private final Uri mMaximumObscuringOpacityForTouchUri = Settings.Global.getUriFor( Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH); + private final Uri mDevelopmentOverrideDesktopExperienceUri = Settings.Global.getUriFor( + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES); public SettingsObserver() { super(new Handler()); @@ -847,6 +850,8 @@ public class WindowManagerService extends IWindowManager.Stub UserHandle.USER_ALL); resolver.registerContentObserver(mMaximumObscuringOpacityForTouchUri, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(mDevelopmentOverrideDesktopExperienceUri, false, this, + UserHandle.USER_ALL); } @Override @@ -890,6 +895,11 @@ public class WindowManagerService extends IWindowManager.Stub return; } + if (mDevelopmentOverrideDesktopExperienceUri.equals(uri)) { + updateDevelopmentOverrideDesktopExperience(); + return; + } + @UpdateAnimationScaleMode final int mode; if (mWindowAnimationScaleUri.equals(uri)) { @@ -956,6 +966,16 @@ public class WindowManagerService extends IWindowManager.Stub mAtmService.mForceResizableActivities = forceResizable; } + void updateDevelopmentOverrideDesktopExperience() { + ContentResolver resolver = mContext.getContentResolver(); + final int overrideDesktopMode = Settings.Global.getInt(resolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES, + DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET.getSetting()); + + SystemProperties.set(DesktopModeFlags.SYSTEM_PROPERTY_NAME, + Integer.toString(overrideDesktopMode)); + } + void updateDevEnableNonResizableMultiWindow() { ContentResolver resolver = mContext.getContentResolver(); final boolean devEnableNonResizableMultiWindow = Settings.Global.getInt(resolver, 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 affcfc14034e..379079a0018c 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 @@ -300,6 +300,22 @@ public class MediaProjectionStopControllerTest { } @Test + public void isStopReasonCallEnd_stopReasonCallEnd_returnsTrue() { + boolean result = + mStopController.isStopReasonCallEnd( + MediaProjectionStopController.STOP_REASON_CALL_END); + assertThat(result).isTrue(); + } + + @Test + public void isStopReasonCallEnd_stopReasonKeyguard_returnsFalse() { + boolean result = + mStopController.isStopReasonCallEnd( + MediaProjectionStopController.STOP_REASON_KEYGUARD); + assertThat(result).isFalse(); + } + + @Test @EnableFlags( android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_unlocked() { diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index ff8b6d3c1962..08eb1451bd6f 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -23,6 +23,8 @@ import static android.view.KeyEvent.KEYCODE_POWER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.hardware.input.Flags.FLAG_ABORT_SLOW_MULTI_PRESS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -35,9 +37,13 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Process; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.KeyEvent; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.util.concurrent.BlockingQueue; @@ -52,6 +58,8 @@ import java.util.concurrent.TimeUnit; * atest WmTests:SingleKeyGestureTests */ public class SingleKeyGestureTests { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private SingleKeyGestureDetector mDetector; private int mMaxMultiPressCount = 3; @@ -260,6 +268,44 @@ public class SingleKeyGestureTests { } @Test + @EnableFlags(FLAG_ABORT_SLOW_MULTI_PRESS) + public void testMultipress_noLongPressBehavior_longPressCancelsMultiPress() + throws InterruptedException { + mLongPressOnPowerBehavior = false; + + pressKey(KEYCODE_POWER, 0 /* pressTime */); + pressKey(KEYCODE_POWER, mLongPressTime /* pressTime */); + + assertFalse(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + @EnableFlags(FLAG_ABORT_SLOW_MULTI_PRESS) + public void testMultipress_noVeryLongPressBehavior_veryLongPressCancelsMultiPress() + throws InterruptedException { + mLongPressOnPowerBehavior = false; + mVeryLongPressOnPowerBehavior = false; + + pressKey(KEYCODE_POWER, 0 /* pressTime */); + pressKey(KEYCODE_POWER, mVeryLongPressTime /* pressTime */); + + assertFalse(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + @DisableFlags(FLAG_ABORT_SLOW_MULTI_PRESS) + public void testMultipress_flagDisabled_noLongPressBehavior_longPressDoesNotCancelMultiPress() + throws InterruptedException { + mLongPressOnPowerBehavior = false; + mExpectedMultiPressCount = 2; + + pressKey(KEYCODE_POWER, 0 /* pressTime */); + pressKey(KEYCODE_POWER, mLongPressTime /* pressTime */); + + assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test public void testMultiPress() throws InterruptedException { // Double presses. mExpectedMultiPressCount = 2; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index d3f98d1a1d70..0b3d720bf52a 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3219,7 +3219,6 @@ public class CarrierConfigManager { * The roaming indicator will be shown if this is {@code true} and will not be shown if this is * {@code false}. */ - @FlaggedApi(Flags.FLAG_HIDE_ROAMING_ICON) public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; /** |