diff options
208 files changed, 4709 insertions, 2081 deletions
diff --git a/Android.bp b/Android.bp index cf73451d444e..6fa66506cbe6 100644 --- a/Android.bp +++ b/Android.bp @@ -636,7 +636,6 @@ java_library { "core/java/com/android/internal/util/AsyncService.java", "core/java/com/android/internal/util/Protocol.java", "telephony/java/android/telephony/Annotation.java", - ":net-utils-framework-wifi-common-srcs", ], libs: [ "framework-annotations-lib", diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fd0262ef5771..7674246fba6a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6718,6 +6718,14 @@ package android.hardware.soundtrigger { field public static final int STATUS_OK = 0; // 0x0 } + @FlaggedApi("android.media.soundtrigger.sound_trigger_generic_model_api") public static final class SoundTrigger.GenericSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable { + ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int); + ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.GenericSoundModel> CREATOR; + } + public static final class SoundTrigger.Keyphrase implements android.os.Parcelable { ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]); method public int describeContents(); diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index bfff4dbdd627..e33a5c9e979a 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -29,6 +29,7 @@ import static android.system.OsConstants.EPIPE; import static java.util.Objects.requireNonNull; import android.annotation.ElapsedRealtimeLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -874,10 +875,9 @@ public class SoundTrigger { /***************************************************************************** * A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound * patterns. - * - * @hide ****************************************************************************/ - public static class GenericSoundModel extends SoundModel implements Parcelable { + @FlaggedApi(android.media.soundtrigger.Flags.FLAG_SOUND_TRIGGER_GENERIC_MODEL_API) + public static final class GenericSoundModel extends SoundModel implements Parcelable { public static final @android.annotation.NonNull Parcelable.Creator<GenericSoundModel> CREATOR = new Parcelable.Creator<GenericSoundModel>() { @@ -890,12 +890,27 @@ public class SoundTrigger { } }; + /** + * Constructor for {@link GenericSoundModel} with version. + * + * @param uuid Unique identifier for this sound model. + * @param vendorUuid Unique vendor identifier for this sound model. + * @param data Opaque data for this sound model. + * @param version Vendor-specific version number of this sound model. + */ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, int version) { - super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version); + super(uuid, Objects.requireNonNull(vendorUuid, "vendorUuid cannot be null"), + TYPE_GENERIC_SOUND, data, version); } - @UnsupportedAppUsage + /** + * Constructor for {@link GenericSoundModel} without version. The version is set to -1. + * + * @param uuid Unique identifier for this sound model. + * @param vendorUuid Unique vendor identifier for this sound model. + * @param data Opaque data for this sound model. + */ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data) { this(uuid, vendorUuid, data, -1); @@ -919,7 +934,7 @@ public class SoundTrigger { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(getUuid().toString()); if (getVendorUuid() == null) { dest.writeInt(-1); diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 2447ff93fdbc..000a537cf3c3 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -66,6 +66,7 @@ public abstract class BatteryConsumer { POWER_COMPONENT_WAKELOCK, POWER_COMPONENT_MEMORY, POWER_COMPONENT_PHONE, + POWER_COMPONENT_AMBIENT_DISPLAY, POWER_COMPONENT_IDLE, POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS, }) diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index b2f333ae58e8..cdffea4e9ede 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2491,7 +2491,7 @@ public abstract class BatteryStats { public static final int SCREEN_BRIGHTNESS_LIGHT = 3; public static final int SCREEN_BRIGHTNESS_BRIGHT = 4; - static final String[] SCREEN_BRIGHTNESS_NAMES = { + public static final String[] SCREEN_BRIGHTNESS_NAMES = { "dark", "dim", "medium", "light", "bright" }; diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 44edf298beeb..475984e4a751 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -345,7 +345,10 @@ public abstract class VibrationEffect implements Parcelable { @FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS) @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS) public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) { - return new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH, VendorEffect.DEFAULT_SCALE); + VibrationEffect vendorEffect = new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH, + VendorEffect.DEFAULT_SCALE); + vendorEffect.validate(); + return vendorEffect; } /** @@ -1204,9 +1207,7 @@ public abstract class VibrationEffect implements Parcelable { } return mEffectStrength == other.mEffectStrength && (Float.compare(mLinearScale, other.mLinearScale) == 0) - // Make sure it calls unparcel for both before calling BaseBundle.kindofEquals. - && mVendorData.size() == other.mVendorData.size() - && BaseBundle.kindofEquals(mVendorData, other.mVendorData); + && isPersistableBundleEquals(mVendorData, other.mVendorData); } @Override @@ -1243,6 +1244,55 @@ public abstract class VibrationEffect implements Parcelable { out.writeFloat(mLinearScale); } + /** + * Compares two {@link PersistableBundle} objects are equals. + */ + private static boolean isPersistableBundleEquals( + PersistableBundle first, PersistableBundle second) { + if (first == second) { + return true; + } + if (first == null || second == null || first.size() != second.size()) { + return false; + } + for (String key : first.keySet()) { + if (!isPersistableBundleSupportedValueEquals(first.get(key), second.get(key))) { + return false; + } + } + return true; + } + + /** + * Compares two values which type is supported by {@link PersistableBundle}. + * + * <p>If the type isn't supported. The equality is done by {@link Object#equals(Object)}. + */ + private static boolean isPersistableBundleSupportedValueEquals( + Object first, Object second) { + if (first == second) { + return true; + } else if (first == null || second == null + || !first.getClass().equals(second.getClass())) { + return false; + } else if (first instanceof PersistableBundle) { + return isPersistableBundleEquals( + (PersistableBundle) first, (PersistableBundle) second); + } else if (first instanceof int[]) { + return Arrays.equals((int[]) first, (int[]) second); + } else if (first instanceof long[]) { + return Arrays.equals((long[]) first, (long[]) second); + } else if (first instanceof double[]) { + return Arrays.equals((double[]) first, (double[]) second); + } else if (first instanceof boolean[]) { + return Arrays.equals((boolean[]) first, (boolean[]) second); + } else if (first instanceof String[]) { + return Arrays.equals((String[]) first, (String[]) second); + } else { + return Objects.equals(first, second); + } + } + @NonNull public static final Creator<VendorEffect> CREATOR = new Creator<VendorEffect>() { diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 99bd67bd96ff..161cce0293e7 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -516,7 +516,7 @@ public abstract class Vibrator { */ @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(@NonNull VibrationEffect vibe, - @NonNull VibrationAttributes attributes, @NonNull String reason) { + @NonNull VibrationAttributes attributes, String reason) { vibrate(Process.myUid(), mPackageName, vibe, reason, attributes); } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index fbeab84fa96d..d4547167ce87 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -266,9 +266,9 @@ public class DreamService extends Service implements Window.Callback { private boolean mDozing; private boolean mWindowless; private boolean mPreviewMode; - private int mDozeScreenState = Display.STATE_UNKNOWN; - private @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN; - private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; + private volatile int mDozeScreenState = Display.STATE_UNKNOWN; + private volatile @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN; + private volatile int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; private boolean mDebug = false; diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 4281da12720b..5ac0c50a312e 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -1689,6 +1689,10 @@ public class PhoneStateListener { public final void onCarrierRoamingNtnModeChanged(boolean active) { // not supported on the deprecated interface - Use TelephonyCallback instead } + + public final void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) { + // not supported on the deprecated interface - Use TelephonyCallback instead + } } private void log(String s) { diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index b8b84d93c97c..c360e64c8c1a 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -653,6 +653,27 @@ public class TelephonyCallback { public static final int EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED = 42; /** + * Event for listening to changes in carrier roaming non-terrestrial network eligibility. + * + * @see CarrierRoamingNtnModeListener + * + * Device is eligible for satellite communication if all the following conditions are met: + * <ul> + * <li>Any subscription on the device supports P2P satellite messaging which is defined by + * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li> + * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to + * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li> + * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi, + * and the hysteresis timer defined by {@link CarrierConfigManager + * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. + * </li> + * </ul> + * + * @hide + */ + public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43; + + /** * @hide */ @IntDef(prefix = {"EVENT_"}, value = { @@ -697,7 +718,8 @@ public class TelephonyCallback { EVENT_MEDIA_QUALITY_STATUS_CHANGED, EVENT_EMERGENCY_CALLBACK_MODE_CHANGED, EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED, - EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED + EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED, + EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface TelephonyEvent { @@ -1711,6 +1733,23 @@ public class TelephonyCallback { * {code false} otherwise. */ void onCarrierRoamingNtnModeChanged(boolean active); + + /** + * Callback invoked when carrier roaming non-terrestrial network eligibility changes. + * + * @param eligible {@code true} when the device is eligible for satellite + * communication if all the following conditions are met: + * <ul> + * <li>Any subscription on the device supports P2P satellite messaging which is defined by + * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li> + * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to + * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li> + * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi, + * and the hysteresis timer defined by {@link CarrierConfigManager + * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li> + * </ul> + */ + default void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {} } /** @@ -2125,5 +2164,16 @@ public class TelephonyCallback { Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> listener.onCarrierRoamingNtnModeChanged(active))); } + + public void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) { + if (!Flags.carrierRoamingNbIotNtn()) return; + + CarrierRoamingNtnModeListener listener = + (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity(() -> mExecutor.execute( + () -> listener.onCarrierRoamingNtnEligibleStateChanged(eligible))); + } } } diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 6160fdb223f2..10f03c15310c 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -1091,6 +1091,34 @@ public class TelephonyRegistryManager { } /** + * Notify external listeners that device eligibility to connect to carrier roaming + * non-terrestrial network changed. + * + * @param subId subscription ID. + * @param eligible {@code true} when the device is eligible for satellite + * communication if all the following conditions are met: + * <ul> + * <li>Any subscription supports P2P satellite messaging which is defined by + * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li> + * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to + * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li> + * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi, + * and the hysteresis timer defined by {@link CarrierConfigManager + * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li> + * </ul> + * + * @hide + */ + public void notifyCarrierRoamingNtnEligibleStateChanged(int subId, boolean eligible) { + try { + sRegistry.notifyCarrierRoamingNtnEligibleStateChanged(subId, eligible); + } catch (RemoteException ex) { + // system server crash + throw ex.rethrowFromSystemServer(); + } + } + + /** * Processes potential event changes from the provided {@link TelephonyCallback}. * * @param telephonyCallback callback for monitoring callback changes to the telephony state. @@ -1246,6 +1274,11 @@ public class TelephonyRegistryManager { if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) { eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED); } + + if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) { + eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED); + } + return eventList; } diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 792c22348d77..f177e1473b6a 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -82,4 +82,5 @@ oneway interface IPhoneStateListener { void onCallBackModeStopped(int type, int reason); void onSimultaneousCallingStateChanged(in int[] subIds); void onCarrierRoamingNtnModeChanged(in boolean active); + void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 04332cd758a6..e500a37abb53 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -121,4 +121,5 @@ interface ITelephonyRegistry { void notifyCallbackModeStarted(int phoneId, int subId, int type); void notifyCallbackModeStopped(int phoneId, int subId, int type, int reason); void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active); + void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible); } diff --git a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java index 92b1c04ef359..1d360cc5811a 100644 --- a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java +++ b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java @@ -18,7 +18,6 @@ package android.content; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; -import org.junit.Ignore; import android.app.ActivityManager; import android.app.activity.LocalProvider; @@ -33,13 +32,14 @@ import android.os.UserHandle; import android.os.UserManager; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.SystemUtil; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/ApexEnvironmentTest.java b/core/tests/coretests/src/android/content/ApexEnvironmentTest.java index 438c5ae1b023..f3803d24d395 100644 --- a/core/tests/coretests/src/android/content/ApexEnvironmentTest.java +++ b/core/tests/coretests/src/android/content/ApexEnvironmentTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import android.os.UserHandle; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java index 407c6c3e2e2c..a9e781c9c253 100644 --- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java +++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java @@ -19,8 +19,8 @@ package android.content; import static org.junit.Assert.fail; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java index c9a6d222dcba..4ecd2daec826 100644 --- a/core/tests/coretests/src/android/content/ContentProviderTest.java +++ b/core/tests/coretests/src/android/content/ContentProviderTest.java @@ -26,7 +26,7 @@ import android.net.Uri; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index c8015d43b404..dfde0bcac243 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -39,7 +39,7 @@ import android.platform.test.annotations.Presubmit; import android.util.Size; import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/core/tests/coretests/src/android/content/PermissionCheckerTest.java b/core/tests/coretests/src/android/content/PermissionCheckerTest.java index cb04a7497d3b..65d8e2b01a44 100644 --- a/core/tests/coretests/src/android/content/PermissionCheckerTest.java +++ b/core/tests/coretests/src/android/content/PermissionCheckerTest.java @@ -23,7 +23,7 @@ import android.app.UiAutomation; import android.os.Binder; import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Test; diff --git a/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java index 4366e02cdf23..77991855cd6c 100644 --- a/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java +++ b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java @@ -22,7 +22,7 @@ import android.os.Parcel; import android.platform.test.annotations.AppModeFull; import android.text.TextUtils; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java index 86e958320a04..ad3a16b2b159 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java @@ -19,15 +19,14 @@ package android.content.pm; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertThrows; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; import android.content.pm.PackageManager.Property; import android.os.Bundle; import android.platform.test.annotations.Presubmit; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java index 20421d105db8..b60d61408054 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java @@ -18,8 +18,8 @@ package android.content.pm; import static com.google.common.truth.Truth.assertThat; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java index 61a3a11f1122..dbd6c2bdeb0a 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java @@ -24,8 +24,8 @@ import android.os.Bundle; import android.os.Parcel; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java index 2986d61d46fc..6c23ea3e0fa4 100644 --- a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java +++ b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java @@ -24,7 +24,7 @@ import android.content.pm.PackagePartitions.SystemPartition; import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java index 8f8488f8b287..ec5e205d9a3d 100644 --- a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java +++ b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java @@ -23,7 +23,7 @@ import android.content.LocusId; import android.os.Parcel; import android.platform.test.annotations.Presubmit; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.collect.Lists; diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java index af36dbb36379..edeea6d85ca6 100644 --- a/core/tests/coretests/src/android/content/pm/UserInfoTest.java +++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java @@ -20,8 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import android.os.UserHandle; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java index 9aef2ca104bd..85f5d698d516 100644 --- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java +++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java @@ -30,8 +30,8 @@ import android.app.Instrumentation; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.coretests.R; diff --git a/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java b/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java index f7f9569c413e..ac69a0f3687c 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java @@ -27,8 +27,8 @@ import android.graphics.drawable.LayerDrawable; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.coretests.R; diff --git a/core/tests/coretests/src/android/database/CursorWindowTest.java b/core/tests/coretests/src/android/database/CursorWindowTest.java index 255020a84893..64e4d3b260f0 100644 --- a/core/tests/coretests/src/android/database/CursorWindowTest.java +++ b/core/tests/coretests/src/android/database/CursorWindowTest.java @@ -23,8 +23,8 @@ import static org.junit.Assert.assertTrue; import android.database.sqlite.SQLiteException; import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java index e25fdf9f2291..c00c171c9124 100644 --- a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java +++ b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java @@ -16,20 +16,19 @@ package android.database; -import static android.database.DatabaseUtils.bindSelection; -import static android.database.DatabaseUtils.getSqlStatementType; -import static android.database.DatabaseUtils.getSqlStatementTypeExtended; -import static android.database.DatabaseUtils.STATEMENT_COMMENT; import static android.database.DatabaseUtils.STATEMENT_CREATE; import static android.database.DatabaseUtils.STATEMENT_DDL; import static android.database.DatabaseUtils.STATEMENT_OTHER; import static android.database.DatabaseUtils.STATEMENT_SELECT; import static android.database.DatabaseUtils.STATEMENT_UPDATE; import static android.database.DatabaseUtils.STATEMENT_WITH; +import static android.database.DatabaseUtils.bindSelection; +import static android.database.DatabaseUtils.getSqlStatementType; +import static android.database.DatabaseUtils.getSqlStatementTypeExtended; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/database/RedactingCursorTest.java b/core/tests/coretests/src/android/database/RedactingCursorTest.java index e2d2bae0e8b2..470d4a9b74af 100644 --- a/core/tests/coretests/src/android/database/RedactingCursorTest.java +++ b/core/tests/coretests/src/android/database/RedactingCursorTest.java @@ -24,7 +24,7 @@ import android.content.Context; import android.net.Uri; import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java index 9bad6d2167ba..efa9b7ad9fe9 100644 --- a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java +++ b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java @@ -29,8 +29,8 @@ import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java index 09c380a49920..e20a806748e8 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java @@ -20,14 +20,12 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.content.Context; -import android.database.sqlite.SQLiteCantOpenDatabaseException; -import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.OpenParams; import android.util.Log; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java index 82bd588ea9ff..a4dedc55e8b6 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java @@ -25,8 +25,8 @@ import android.content.Context; import android.database.DatabaseUtils; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Test; diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java index 2b663bdb7861..dbe7a9a6f44c 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java @@ -24,8 +24,8 @@ import android.os.HandlerThread; import android.util.Log; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index c4695d95d756..bd9c4b8f8bd7 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -25,16 +25,13 @@ import static org.junit.Assert.fail; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; -import android.os.SystemClock; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.test.AndroidTestCase; -import android.util.Log; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -46,11 +43,9 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Phaser; -import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java index 24d27c484912..832ebe534c67 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java @@ -29,9 +29,9 @@ import android.database.DatabaseUtils; import android.os.SystemClock; import android.util.Log; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java index a9d148289262..ced18469c6d0 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java @@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java index fa824b140caa..169074193848 100644 --- a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java +++ b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java @@ -23,8 +23,8 @@ import static org.junit.Assert.fail; import android.net.Uri; import android.util.Xml; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java index ce0bf30473f6..938147c6a3d1 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals; import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java index 37fe22fb532d..242a27358832 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals; import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.google.common.collect.Lists; diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java index 2b37b52c8bf9..d310b76423ef 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals; import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java index 3b8f71585099..29001ae69cb7 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.fail; import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java b/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java index 2b15d73b6f01..7677c3c5f0af 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java @@ -20,8 +20,8 @@ import static android.security.keystore.recovery.TrustedRootCertificates.getRoot import static org.junit.Assert.assertTrue; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java index aec54e190114..07353f8cf054 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals; import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java index ba5e74ab61e8..7f91d0382990 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java @@ -21,8 +21,8 @@ import static android.security.keystore.recovery.X509CertificateParsingUtils.dec import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java index 1e444adbc362..711ff9458e11 100644 --- a/core/tests/coretests/src/android/util/ArrayMapTest.java +++ b/core/tests/coretests/src/android/util/ArrayMapTest.java @@ -19,8 +19,8 @@ import static org.junit.Assert.fail; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/ArraySetTest.java b/core/tests/coretests/src/android/util/ArraySetTest.java index 51de6341179b..8888991ffcda 100644 --- a/core/tests/coretests/src/android/util/ArraySetTest.java +++ b/core/tests/coretests/src/android/util/ArraySetTest.java @@ -18,8 +18,8 @@ package android.util; import static org.junit.Assert.fail; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java index b64826611f81..3b322c2c9f67 100644 --- a/core/tests/coretests/src/android/util/Base64Test.java +++ b/core/tests/coretests/src/android/util/Base64Test.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Ignore; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java index da29828383b6..96c79013a8ec 100644 --- a/core/tests/coretests/src/android/util/BinaryXmlTest.java +++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java @@ -30,7 +30,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.os.PersistableBundle; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/core/tests/coretests/src/android/util/CharsetUtilsTest.java b/core/tests/coretests/src/android/util/CharsetUtilsTest.java index fbbe311d44fb..33936e9147bc 100644 --- a/core/tests/coretests/src/android/util/CharsetUtilsTest.java +++ b/core/tests/coretests/src/android/util/CharsetUtilsTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.util.HexDump; diff --git a/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java index 72bd578ad8b9..4587f5b19eaf 100644 --- a/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java +++ b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java index f748acda0630..a52c9ac3aa86 100644 --- a/core/tests/coretests/src/android/util/FloatMathTest.java +++ b/core/tests/coretests/src/android/util/FloatMathTest.java @@ -18,7 +18,7 @@ package android.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/KeyValueListParserTest.java b/core/tests/coretests/src/android/util/KeyValueListParserTest.java index f65c4c7c0bdf..eaf8d194a936 100644 --- a/core/tests/coretests/src/android/util/KeyValueListParserTest.java +++ b/core/tests/coretests/src/android/util/KeyValueListParserTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/LogNullabilityTest.java b/core/tests/coretests/src/android/util/LogNullabilityTest.java index 475e347141df..5aa2626b6de7 100644 --- a/core/tests/coretests/src/android/util/LogNullabilityTest.java +++ b/core/tests/coretests/src/android/util/LogNullabilityTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.fail; import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java index 15caac93f9ae..0e44e68c87c3 100644 --- a/core/tests/coretests/src/android/util/LogTest.java +++ b/core/tests/coretests/src/android/util/LogTest.java @@ -19,8 +19,8 @@ package android.util; import android.os.SystemProperties; import android.test.PerformanceTestCase; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.Suppress; -import androidx.test.runner.AndroidJUnit4; import junit.framework.TestCase; diff --git a/core/tests/coretests/src/android/util/LogWriterTest.java b/core/tests/coretests/src/android/util/LogWriterTest.java index 890a401c3654..739fbcd3b327 100644 --- a/core/tests/coretests/src/android/util/LogWriterTest.java +++ b/core/tests/coretests/src/android/util/LogWriterTest.java @@ -16,7 +16,7 @@ package android.util; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/LongArrayQueueTest.java b/core/tests/coretests/src/android/util/LongArrayQueueTest.java index 77e8d608810f..f696f70ba4e9 100644 --- a/core/tests/coretests/src/android/util/LongArrayQueueTest.java +++ b/core/tests/coretests/src/android/util/LongArrayQueueTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java index 9dbaae0d0555..fb0b2e351093 100644 --- a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java +++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java @@ -18,8 +18,8 @@ package android.util; import static org.junit.Assert.assertEquals; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/LruCacheTest.java b/core/tests/coretests/src/android/util/LruCacheTest.java index 10e8308e7964..1c6dcdffd172 100644 --- a/core/tests/coretests/src/android/util/LruCacheTest.java +++ b/core/tests/coretests/src/android/util/LruCacheTest.java @@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java index 06f970fcf77c..cb34c9878eb0 100644 --- a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java +++ b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/MutableTest.java b/core/tests/coretests/src/android/util/MutableTest.java index dfdff4dc5e94..1f73c1602843 100644 --- a/core/tests/coretests/src/android/util/MutableTest.java +++ b/core/tests/coretests/src/android/util/MutableTest.java @@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java index a180ec3f9e35..8d0785f799cc 100644 --- a/core/tests/coretests/src/android/util/PatternsTest.java +++ b/core/tests/coretests/src/android/util/PatternsTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/PoolsTest.java b/core/tests/coretests/src/android/util/PoolsTest.java index bdbc9b11e6ac..e31ab7827fc2 100644 --- a/core/tests/coretests/src/android/util/PoolsTest.java +++ b/core/tests/coretests/src/android/util/PoolsTest.java @@ -19,7 +19,7 @@ package android.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/PrefixPrinterTest.java b/core/tests/coretests/src/android/util/PrefixPrinterTest.java index a8d48ee57552..8199155fcf77 100644 --- a/core/tests/coretests/src/android/util/PrefixPrinterTest.java +++ b/core/tests/coretests/src/android/util/PrefixPrinterTest.java @@ -18,7 +18,7 @@ package android.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/RecurrenceRuleTest.java b/core/tests/coretests/src/android/util/RecurrenceRuleTest.java index 32548b4cff19..8b2068c7936b 100644 --- a/core/tests/coretests/src/android/util/RecurrenceRuleTest.java +++ b/core/tests/coretests/src/android/util/RecurrenceRuleTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/core/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java index 6ca1751a675b..e5ee04c2cfc9 100644 --- a/core/tests/coretests/src/android/util/SequenceUtilsTest.java +++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java @@ -29,8 +29,8 @@ import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/SingletonTest.java b/core/tests/coretests/src/android/util/SingletonTest.java index 8c5a9639c23a..31ae650e936a 100644 --- a/core/tests/coretests/src/android/util/SingletonTest.java +++ b/core/tests/coretests/src/android/util/SingletonTest.java @@ -18,7 +18,7 @@ package android.util; import static org.junit.Assert.assertTrue; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java index ba9c8d92e173..cc5ffbefbe5b 100644 --- a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java @@ -18,8 +18,8 @@ package android.util; import static org.junit.Assert.assertEquals; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java index b29b6f1f8e9d..4038d88b654b 100644 --- a/core/tests/coretests/src/android/util/SparseLongArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertTrue; import android.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java index a8dce7032fa3..48ea3a15d129 100644 --- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java @@ -19,8 +19,8 @@ import static com.google.common.truth.Truth.assertThat; import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/StateSetTest.java b/core/tests/coretests/src/android/util/StateSetTest.java index dfd1523465ae..14e4e2000a65 100644 --- a/core/tests/coretests/src/android/util/StateSetTest.java +++ b/core/tests/coretests/src/android/util/StateSetTest.java @@ -22,8 +22,8 @@ import static org.junit.Assert.assertTrue; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/util/TeeWriterTest.java b/core/tests/coretests/src/android/util/TeeWriterTest.java index c78376a99a30..cc1c0913e998 100644 --- a/core/tests/coretests/src/android/util/TeeWriterTest.java +++ b/core/tests/coretests/src/android/util/TeeWriterTest.java @@ -18,7 +18,7 @@ package android.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/util/XmlTest.java b/core/tests/coretests/src/android/util/XmlTest.java index 91ebc2a1e1d5..540f118099d8 100644 --- a/core/tests/coretests/src/android/util/XmlTest.java +++ b/core/tests/coretests/src/android/util/XmlTest.java @@ -26,7 +26,7 @@ import static org.xmlpull.v1.XmlPullParser.TEXT; import android.os.PersistableBundle; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; diff --git a/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java index 729a555e02eb..e74027e9ee1d 100644 --- a/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java +++ b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java @@ -21,8 +21,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java index 7872810717b8..0fdc23903657 100644 --- a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java +++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java @@ -23,8 +23,8 @@ import static org.testng.Assert.assertThrows; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java index 0d1dde38ca8f..2c66330def9f 100644 --- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java +++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java @@ -44,8 +44,8 @@ import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.view.DisplayCutout.ParcelableWrapper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java index 77dd8bd7f976..7778ba1b628a 100644 --- a/core/tests/coretests/src/android/view/DisplayShapeTest.java +++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java @@ -27,8 +27,8 @@ import android.graphics.Path; import android.graphics.RectF; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 1682135e2769..668487dcc490 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -42,7 +42,7 @@ import android.view.SurfaceControl.Transaction; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.animation.LinearInterpolator; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java index 61e05dac2371..c3bd0657c511 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -32,7 +32,7 @@ import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 75749c7eff09..1144ee17fadb 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -61,7 +61,7 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.util.SparseIntArray; import android.view.WindowInsets.Type; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.window.flags.Flags; diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java index bad048523053..d0f9a38720bf 100644 --- a/core/tests/coretests/src/android/view/MotionEventTest.java +++ b/core/tests/coretests/src/android/view/MotionEventTest.java @@ -33,8 +33,8 @@ import android.platform.test.annotations.Presubmit; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java index b5b2d0c05daf..8ac9292390b0 100644 --- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java @@ -37,7 +37,7 @@ import android.platform.test.annotations.Presubmit; import android.view.WindowInsetsController.OnControllableInsetsChangedListener; import android.view.animation.LinearInterpolator; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java index 43490213c29a..3992aa1bbcbc 100644 --- a/core/tests/coretests/src/android/view/RoundedCornerTest.java +++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java @@ -23,8 +23,8 @@ import static org.junit.Assert.assertThat; import android.graphics.Point; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java index ec665ad042ae..c26d94501a52 100644 --- a/core/tests/coretests/src/android/view/RoundedCornersTest.java +++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java @@ -42,8 +42,8 @@ import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.util.Pair; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java index 5f258949857a..bee5dc4bf3c0 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java @@ -38,8 +38,8 @@ import android.os.ICancellationSignal; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java index dc43204c35d6..726ee85dddd5 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java @@ -35,8 +35,8 @@ import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java index 71bdce4ecb0e..5a5510cc57d9 100644 --- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java +++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java @@ -22,8 +22,8 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; @@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.platform.test.annotations.Presubmit; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.AfterClass; import org.junit.BeforeClass; diff --git a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java index 65dd34f723c3..9ab14af58982 100644 --- a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java +++ b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java @@ -21,16 +21,16 @@ import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + @RunWith(AndroidJUnit4.class) @MediumTest @Presubmit diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java index 218047c5f45e..9144cd60b8ef 100644 --- a/core/tests/coretests/src/android/view/ViewCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java @@ -26,9 +26,9 @@ import android.view.ViewDebug.CanvasProvider; import android.view.ViewDebug.HardwareCanvasProvider; import android.view.ViewDebug.SoftwareCanvasProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.coretests.R; diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 62291d405a60..18364ad5e2b6 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -50,10 +50,10 @@ import android.widget.FrameLayout; import android.widget.ProgressBar; import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.coretests.R; diff --git a/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java index 54524b2c8e70..f5c71f8da005 100644 --- a/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java @@ -25,9 +25,9 @@ import android.graphics.Canvas; import android.widget.FrameLayout; import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Rule; diff --git a/core/tests/coretests/src/android/view/ViewInvalidateTest.java b/core/tests/coretests/src/android/view/ViewInvalidateTest.java index c25a2deb2a05..d4181d3e459b 100644 --- a/core/tests/coretests/src/android/view/ViewInvalidateTest.java +++ b/core/tests/coretests/src/android/view/ViewInvalidateTest.java @@ -31,9 +31,9 @@ import android.widget.FrameLayout; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Assert; diff --git a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java index e1b403fdefcf..c5e0ebd7216a 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java @@ -26,8 +26,8 @@ import android.graphics.drawable.Icon; import android.os.Bundle; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java index 46e3a4c26dde..1c26da79ff52 100644 --- a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java index e4cfc53cc53a..4635e9ba84ac 100644 --- a/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java @@ -20,11 +20,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; - import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java index 20a876809ba0..07fe12fe81e9 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java @@ -20,8 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import android.provider.DeviceConfig; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index 8225afc5f510..2ed016cb75ad 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -38,8 +38,8 @@ import android.os.Parcel; import android.view.View; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java index 11eb567df488..d92da6e4d019 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java @@ -19,8 +19,8 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java index 011866de4848..ec46426a3033 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java @@ -20,8 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java index 31f8029550dd..de6f1d2850a1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java @@ -24,8 +24,8 @@ import android.icu.util.ULocale; import android.os.Bundle; import android.os.Parcel; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java index 4f0b44bfb864..bd1f7e1fb139 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java @@ -25,8 +25,8 @@ import android.os.LocaleList; import android.os.Parcel; import android.util.ArrayMap; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java index 14c077ce06f4..61e6738af27d 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java @@ -36,8 +36,8 @@ import android.os.LocaleList; import android.os.Parcel; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java index 4eccbe5f1dc4..a466caf8360e 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.content.om.OverlayConfigParser; diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java index a0e9947cf5d3..43cff8d0b699 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java @@ -28,7 +28,7 @@ import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.frameworks.coretests.R; import com.android.internal.content.om.OverlayConfig; diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java index a978e3b9e739..7b9ea556fc20 100644 --- a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java @@ -21,9 +21,9 @@ import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.coretests.R; diff --git a/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java index fdde36ad8e35..fdba811f3eaa 100644 --- a/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java @@ -30,8 +30,8 @@ import static com.android.internal.util.BitUtils.unpackBits; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java index ac954d6ad150..32c2d639832a 100644 --- a/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java index e6ebfefd2aea..aa59afe6dd7a 100644 --- a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java +++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java @@ -33,8 +33,8 @@ import android.text.style.ForegroundColorSpan; import android.text.style.TextAppearanceSpan; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java index d2d3c134f390..7bd062aa3de9 100644 --- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java @@ -31,7 +31,7 @@ import static org.junit.Assert.assertTrue; import android.content.ComponentName; import android.util.SparseArray; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java index 61d4e3da5036..9259181f5277 100644 --- a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java +++ b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java @@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.util.Dumpable; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.util.dump.DumpableContainerImpl; diff --git a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java index 6bd67ea486d1..aee352bc3a79 100644 --- a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java @@ -28,7 +28,7 @@ import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import android.provider.DeviceConfig; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/core/tests/coretests/src/com/android/internal/util/FastMathTest.java b/core/tests/coretests/src/com/android/internal/util/FastMathTest.java index dd263345022b..bedcf4c0a3bb 100644 --- a/core/tests/coretests/src/com/android/internal/util/FastMathTest.java +++ b/core/tests/coretests/src/com/android/internal/util/FastMathTest.java @@ -18,7 +18,7 @@ package com.android.internal.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java index 8456161f9709..a0eb0586e321 100644 --- a/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertArrayEquals; import android.util.EmptyArray; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java index dcffa1cf6975..9adf607f810c 100644 --- a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java +++ b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java @@ -22,7 +22,7 @@ import static com.android.internal.util.HexDump.toHexString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/IntPairTest.java b/core/tests/coretests/src/com/android/internal/util/IntPairTest.java index af6503ff6cac..527be8ff94b9 100644 --- a/core/tests/coretests/src/com/android/internal/util/IntPairTest.java +++ b/core/tests/coretests/src/com/android/internal/util/IntPairTest.java @@ -18,7 +18,7 @@ package com.android.internal.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java index 010f72466f3a..ce265a3178d3 100644 --- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java @@ -29,7 +29,7 @@ import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import android.provider.DeviceConfig; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.util.LatencyTracker.ActionProperties; diff --git a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java index e6418fae7dca..93262f0f0076 100644 --- a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java +++ b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java @@ -18,7 +18,7 @@ package com.android.internal.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java index d24cbfef9f10..b22014e058ef 100644 --- a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java @@ -18,7 +18,7 @@ package com.android.internal.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java index 0d213357c9a5..e0d5499835a8 100644 --- a/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java +++ b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Rule; diff --git a/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java b/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java index d7a100a30ac8..4497770ef40d 100644 --- a/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java +++ b/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java b/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java index efef7fffb208..dbd6fc1c5b78 100644 --- a/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java +++ b/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java @@ -18,7 +18,7 @@ package com.android.internal.util; import static org.junit.Assert.assertEquals; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java index ef579fe07af5..43ee3c5f0e03 100644 --- a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java +++ b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue; import android.os.SystemClock; import android.text.format.DateUtils; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt new file mode 100644 index 000000000000..a34d7bed497b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.graphics.Rect +import android.view.InsetsSource +import android.view.InsetsState +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener + +abstract class ImeListener( + private val mDisplayController: DisplayController, + private val mDisplayId: Int +) : OnInsetsChangedListener { + // The last insets state + private val mInsetsState = InsetsState() + private val mTmpBounds = Rect() + + override fun insetsChanged(insetsState: InsetsState) { + if (mInsetsState == insetsState) { + return + } + + // Get the stable bounds that account for display cutout and system bars to calculate the + // relative IME height + val layout = mDisplayController.getDisplayLayout(mDisplayId) + if (layout == null) { + return + } + layout.getStableBounds(mTmpBounds) + + val wasVisible = getImeVisibilityAndHeight(mInsetsState).first + val oldHeight = getImeVisibilityAndHeight(mInsetsState).second + + val isVisible = getImeVisibilityAndHeight(insetsState).first + val newHeight = getImeVisibilityAndHeight(insetsState).second + + mInsetsState.set(insetsState, true) + if (wasVisible != isVisible || oldHeight != newHeight) { + onImeVisibilityChanged(isVisible, newHeight) + } + } + + private fun getImeVisibilityAndHeight( + insetsState: InsetsState): Pair<Boolean, Int> { + val source = insetsState.peekSource(InsetsSource.ID_IME) + val frame = if (source != null && source.isVisible) source.frame else null + val height = if (frame != null) mTmpBounds.bottom - frame.top else 0 + val visible = source?.isVisible ?: false + return Pair(visible, height) + } + + /** + * To be overridden by implementations to handle IME changes. + */ + protected abstract fun onImeVisibilityChanged(imeVisible: Boolean, imeHeight: Int) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 5097ed8866c9..19a109e9a28c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -31,6 +31,7 @@ import android.animation.ValueAnimator; import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -201,7 +202,7 @@ public class SplitDecorManager extends WindowlessWindowManager { /** Showing resizing hint. */ public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, - boolean immediately, float[] veilColor) { + boolean immediately) { if (mResizingIconView == null) { return; } @@ -234,7 +235,7 @@ public class SplitDecorManager extends WindowlessWindowManager { if (mBackgroundLeash == null) { mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession); - t.setColor(mBackgroundLeash, veilColor) + t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask)) .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1); } @@ -245,7 +246,7 @@ public class SplitDecorManager extends WindowlessWindowManager { mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession); // Fill up another side bounds area. - t.setColor(mGapBackgroundLeash, veilColor) + t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask)) .setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2) .setPosition(mGapBackgroundLeash, left, top) .setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height()); @@ -486,4 +487,9 @@ public class SplitDecorManager extends WindowlessWindowManager { mIcon = null; } } + + private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { + final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); + return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java index e8226051b672..f9259e79472e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -16,6 +16,8 @@ package com.android.wm.shell.common.split; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED; + import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -24,18 +26,25 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import android.app.ActivityManager; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Intent; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Color; import android.graphics.Rect; +import android.os.UserHandle; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.util.ArrayUtils; import com.android.wm.shell.Flags; import com.android.wm.shell.ShellTaskOrganizer; +import java.util.Arrays; +import java.util.List; + /** Helper utility class for split screen components to use. */ public class SplitScreenUtils { /** Reverse the split position. */ @@ -128,10 +137,4 @@ public class SplitScreenUtils { return isLandscape; } } - - /** Returns the specified background color that matches a RunningTaskInfo. */ - public static Color getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { - final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); - return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index f32683db533a..d289ef239354 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -95,7 +95,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { mCallback = callback; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; if (DESKTOP_WINDOWING_MODE.isEnabled(mContext) - && DesktopModeFlags.THEMED_APP_HEADERS.isEnabled(context)) { + && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { // Don't show the SCM button for freeform tasks mHasSizeCompat &= !taskInfo.isFreeform(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 247cc42e51ed..4299841da0fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -32,77 +32,77 @@ import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer -/** Keeps track of task data related to desktop mode. */ +/** Tracks task data for Desktop Mode. */ class DesktopModeTaskRepository { - /** Task data that is tracked per display */ - private data class DisplayData( - /** - * Set of task ids that are marked as active in desktop mode. Active tasks in desktop mode - * are freeform tasks that are visible or have been visible after desktop mode was - * activated. Task gets removed from this list when it vanishes. Or when desktop mode is - * turned off. - */ + /** + * Task data tracked per desktop. + * + * @property activeTasks task ids of active tasks currently or previously visible in Desktop + * mode session. Tasks become inactive when task closes or when desktop mode session ends. + * @property visibleTasks task ids for active freeform tasks that are currently visible. There + * might be other active tasks in desktop mode that are not visible. + * @property minimizedTasks task ids for active freeform tasks that are currently minimized. + * @property closingTasks task ids for tasks that are going to close, but are currently visible. + * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom + * (top is at index 0). + */ + private data class DesktopTaskData( val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), - // Tasks that are closing, but are still visible // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver val closingTasks: ArraySet<Int> = ArraySet(), - // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), ) - // Token of the current wallpaper activity, used to remove it when the last task is removed + /* Current wallpaper activity token to remove wallpaper activity when last task is removed. */ var wallpaperActivityToken: WindowContainerToken? = null + private val activeTasksListeners = ArraySet<ActiveTasksListener>() - // Track visible tasks separately because a task may be part of the desktop but not visible. private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>() - // Track corner/caption regions of desktop tasks, used to determine gesture exclusion + + /* Tracks corner/caption regions of desktop tasks, used to determine gesture exclusion. */ private val desktopExclusionRegions = SparseArray<Region>() - // Track last bounds of task before toggled to stable bounds + + /* Tracks last bounds of task before toggled to stable bounds. */ private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>() + private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null - private val displayData = - object : SparseArray<DisplayData>() { - /** - * Get the [DisplayData] associated with this [displayId] - * - * Creates a new instance if one does not exist - */ - fun getOrCreate(displayId: Int): DisplayData { - if (!contains(displayId)) { - put(displayId, DisplayData()) - } - return get(displayId) - } - } + private val desktopTaskDataByDisplayId = object : SparseArray<DesktopTaskData>() { + /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */ + fun getOrCreate(displayId: Int): DesktopTaskData = + this[displayId] ?: DesktopTaskData().also { this[displayId] = it } + } - /** Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */ + /** Adds [activeTasksListener] to be notified of updates to active tasks. */ fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) { activeTasksListeners.add(activeTasksListener) } - /** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */ + /** Adds [visibleTasksListener] to be notified of updates to visible tasks. */ fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) { visibleTasksListeners[visibleTasksListener] = executor - displayData.keyIterator().forEach { + desktopTaskDataByDisplayId.keyIterator().forEach { + val visibleTaskCount = getVisibleTaskCount(it) executor.execute { - visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount(it)) + visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) } } } - /** Returns a list of all [DisplayData]. */ - private fun displayDataList(): Sequence<DisplayData> = - displayData.valueIterator().asSequence() + /** Updates tasks changes on all the active task listeners for given display id. */ + private fun updateActiveTasksListeners(displayId: Int) { + activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } + } - /** - * Add a Consumer which will inform other classes of changes to exclusion regions for all - * Desktop tasks. - */ + /** Returns a list of all [DesktopTaskData] in the repository. */ + private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> = + desktopTaskDataByDisplayId.valueIterator().asSequence() + + /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */ fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) { desktopGestureExclusionListener = regionListener desktopGestureExclusionExecutor = executor @@ -111,7 +111,7 @@ class DesktopModeTaskRepository { } } - /** Create a new merged region representative of all exclusion regions in all desktop tasks. */ + /** Creates a new merged region representative of all exclusion regions in all desktop tasks. */ private fun calculateDesktopExclusionRegion(): Region { val desktopExclusionRegion = Region() desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion -> @@ -120,192 +120,120 @@ class DesktopModeTaskRepository { return desktopExclusionRegion } - /** Remove a previously registered [ActiveTasksListener] */ + /** Remove the previously registered [activeTasksListener] */ fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) { activeTasksListeners.remove(activeTasksListener) } - /** Remove a previously registered [VisibleTasksListener] */ + /** Removes the previously registered [visibleTasksListener]. */ fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) { visibleTasksListeners.remove(visibleTasksListener) } - /** - * Mark a task with given [taskId] as active on given [displayId] - * - * @return `true` if the task was not active on given [displayId] - */ - fun addActiveTask(displayId: Int, taskId: Int): Boolean { - // Check if task is active on another display, if so, remove it - displayData.forEach { id, data -> - if (id != displayId && data.activeTasks.remove(taskId)) { - activeTasksListeners.onEach { it.onActiveTasksChanged(id) } - } - } + /** Adds task with [taskId] to the list of active tasks on [displayId]. */ + fun addActiveTask(displayId: Int, taskId: Int) { + // Removes task if it is active on another display excluding [displayId]. + removeActiveTask(taskId, excludedDisplayId = displayId) - val added = displayData.getOrCreate(displayId).activeTasks.add(taskId) - if (added) { - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: add active task=%d displayId=%d", - taskId, - displayId - ) - activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } + if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) { + logD("Adds active task=%d displayId=%d", taskId, displayId) + updateActiveTasksListeners(displayId) } - return added } - /** - * Remove task with given [taskId] from active tasks. - * - * @return `true` if the task was active - */ - fun removeActiveTask(taskId: Int): Boolean { - var result = false - displayData.forEach { displayId, data -> - if (data.activeTasks.remove(taskId)) { - activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } - result = true + /** Removes task from active task list of displays excluding the [excludedDisplayId]. */ + fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) { + desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData -> + if ((displayId != excludedDisplayId) + && desktopTaskData.activeTasks.remove(taskId)) { + logD("Removed active task=%d displayId=%d", taskId, displayId) + updateActiveTasksListeners(displayId) } } - if (result) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId) - } - return result } - /** - * Mark a task with given [taskId] as closing on given [displayId] - * - * @return `true` if the task was not closing on given [displayId] - */ - fun addClosingTask(displayId: Int, taskId: Int): Boolean { - val added = displayData.getOrCreate(displayId).closingTasks.add(taskId) - if (added) { - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: added closing task=%d displayId=%d", - taskId, - displayId - ) + /** Adds given task to the closing task list for [displayId]. */ + fun addClosingTask(displayId: Int, taskId: Int) { + if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) { + logD("Added closing task=%d displayId=%d", taskId, displayId) + } else { + // If the task hasn't been removed from closing list after it disappeared. + logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId) } - return added } - /** - * Remove task with given [taskId] from closing tasks. - * - * @return `true` if the task was closing - */ - fun removeClosingTask(taskId: Int): Boolean { - var removed = false - displayData.forEach { _, data -> - if (data.closingTasks.remove(taskId)) { - removed = true + /** Removes task from the list of closing tasks for [displayId]. */ + fun removeClosingTask(taskId: Int) { + desktopTaskDataByDisplayId.forEach { displayId, taskInfo -> + if (taskInfo.closingTasks.remove(taskId)) { + logD("Removed closing task=%d displayId=%d", taskId, displayId) } } - if (removed) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId) - } - return removed } - fun isActiveTask(taskId: Int) = displayDataList().any { taskId in it.activeTasks } - fun isClosingTask(taskId: Int) = displayDataList().any { taskId in it.closingTasks } - fun isVisibleTask(taskId: Int) = displayDataList().any { taskId in it.visibleTasks } - fun isMinimizedTask(taskId: Int) = displayDataList().any { taskId in it.minimizedTasks } + fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks } + fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks } + fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks } + fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks } - /** - * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task - * on its display - */ + /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */ fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean = - displayDataList().any { data -> - data.visibleTasks - .subtract(data.closingTasks) - .subtract(data.minimizedTasks) - .singleOrNull() == taskId + desktopTaskDataSequence().any { it.visibleTasks + .subtract(it.closingTasks) + .subtract(it.minimizedTasks) + .singleOrNull() == taskId } - /** Get a set of the active tasks for given [displayId] */ - fun getActiveTasks(displayId: Int): ArraySet<Int> { - return ArraySet(displayData[displayId]?.activeTasks) - } + fun getActiveTasks(displayId: Int): ArraySet<Int> = + ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks) - /** Returns the minimized tasks for the given [displayId]. */ fun getMinimizedTasks(displayId: Int): ArraySet<Int> = - ArraySet(displayData[displayId]?.minimizedTasks) + ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks) - /** - * Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display, - * ordered from front to back. - */ - fun getActiveNonMinimizedTasksOrderedFrontToBack(displayId: Int): List<Int> { - val activeTasks = getActiveTasks(displayId) - val allTasksInZOrder = getFreeformTasksInZOrder(displayId) - return activeTasks - // Don't show already minimized Tasks - .filter { taskId -> !isMinimizedTask(taskId) } - .sortedBy { taskId -> allTasksInZOrder.indexOf(taskId) } - } + /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */ + fun getActiveNonMinimizedOrderedTasks(displayId: Int): List<Int> = + getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) } - /** Get a list of freeform tasks, ordered from top-bottom (top at index 0). */ + /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */ fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> = - ArrayList(displayData[displayId]?.freeformTasksInZOrder ?: emptyList()) + ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList()) + + /** Removes task from visible tasks of all displays except [excludedDisplayId]. */ + private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) { + desktopTaskDataByDisplayId.forEach { displayId, data -> + if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) { + notifyVisibleTaskListeners(displayId, data.visibleTasks.size) + } + } + } /** - * Updates whether a freeform task with this id is visible or not and notifies listeners. + * Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners. * - * If the task was visible on a different display with a different displayId, it is removed from - * the set of visible tasks on that display. Listeners will be notified. + * If task was visible on a different display with a different [displayId], removes from + * the set of visible tasks on that display and notifies listeners. */ fun updateVisibleFreeformTasks(displayId: Int, taskId: Int, visible: Boolean) { if (visible) { - // Task is visible. Check if we need to remove it from any other display. - val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId } - for (otherDisplayId in otherDisplays) { - if (displayData[otherDisplayId].visibleTasks.remove(taskId)) { - notifyVisibleTaskListeners( - otherDisplayId, - displayData[otherDisplayId].visibleTasks.size - ) - } - } + // If task is visible, remove it from any other display besides [displayId]. + removeVisibleTask(taskId, excludedDisplayId = displayId) } else if (displayId == INVALID_DISPLAY) { // Task has vanished. Check which display to remove the task from. - displayData.forEach { displayId, data -> - if (data.visibleTasks.remove(taskId)) { - notifyVisibleTaskListeners(displayId, data.visibleTasks.size) - } - } + removeVisibleTask(taskId) return } - - val prevCount = visibleTaskCount(displayId) + val prevCount = getVisibleTaskCount(displayId) if (visible) { - displayData.getOrCreate(displayId).visibleTasks.add(taskId) + desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId) unminimizeTask(displayId, taskId) } else { - displayData[displayId]?.visibleTasks?.remove(taskId) + desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId) } - val newCount = visibleTaskCount(displayId) - - // Check if count changed + val newCount = getVisibleTaskCount(displayId) if (prevCount != newCount) { - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d", - taskId, - visible, - displayId - ) - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: visibleTaskCount has changed from %d to %d", - prevCount, - newCount - ) + logD("Update task visibility taskId=%d visible=%b displayId=%d", + taskId, visible, displayId) + logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount) notifyVisibleTaskListeners(displayId, newCount) } } @@ -316,72 +244,46 @@ class DesktopModeTaskRepository { } } - /** Get number of tasks that are marked as visible on given [displayId] */ - fun visibleTaskCount(displayId: Int): Int { - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: visibleTaskCount= %d", - displayData[displayId]?.visibleTasks?.size ?: 0 - ) - return displayData[displayId]?.visibleTasks?.size ?: 0 - } + /** Gets number of visible tasks on given [displayId] */ + fun getVisibleTaskCount(displayId: Int): Int = + desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size ?: 0.also { + logD("getVisibleTaskCount=$it") + } - /** Add (or move if it already exists) the task to the top of the ordered list. */ - // TODO(b/342417921): Identify if there is additional checks needed to move tasks for - // multi-display scenarios. + /** Adds task (or moves if it already exists) to the top of the ordered list. */ fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) { - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: add or move task to top: display=%d, taskId=%d", - displayId, - taskId - ) - displayData[displayId]?.freeformTasksInZOrder?.remove(taskId) - displayData.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId) + logD("Add or move task to top: display=%d taskId=%d", taskId, displayId) + desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId) + desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId) } - /** Mark a Task as minimized. */ + /** Minimizes the task for [taskId] and [displayId] */ fun minimizeTask(displayId: Int, taskId: Int) { - ProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopModeTaskRepository: minimize Task: display=%d, task=%d", - displayId, - taskId - ) - displayData.getOrCreate(displayId).minimizedTasks.add(taskId) + logD("Minimize Task: display=%d, task=%d", displayId, taskId) + desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId) } - /** Mark a Task as non-minimized. */ + /** Unminimizes the task for [taskId] and [displayId] */ fun unminimizeTask(displayId: Int, taskId: Int) { - ProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d", - displayId, - taskId - ) - displayData[displayId]?.minimizedTasks?.remove(taskId) + logD("Unminimize Task: display=%d, task=%d", displayId, taskId) + desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) ?: + logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId) } - /** Remove the task from the ordered list. */ + /** Removes task from the ordered list. */ fun removeFreeformTask(displayId: Int, taskId: Int) { - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d", - displayId, - taskId - ) - displayData[displayId]?.freeformTasksInZOrder?.remove(taskId) + logD("Removes freeform task: taskId=%d", taskId) + desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId) boundsBeforeMaximizeByTaskId.remove(taskId) - ProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: remaining freeform tasks: %s", - displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: "" - ) + logD("Remaining freeform tasks: %d", + desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString() ?: "") } /** - * Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been - * accepted by desktopGestureExclusionListener, it will be updated in the appropriate classes. + * Updates active desktop gesture exclusion regions. + * + * If [desktopExclusionRegions] is accepted by [desktopGestureExclusionListener], updates it in + * appropriate classes. */ fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) { desktopExclusionRegions.put(taskId, taskExclusionRegions) @@ -391,9 +293,10 @@ class DesktopModeTaskRepository { } /** - * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion has - * been accepted by desktopGestureExclusionListener, it will be updated in the appropriate - * classes. + * Removes desktop gesture exclusion region for the specified task. + * + * If [desktopExclusionRegions] is accepted by [desktopGestureExclusionListener], updates it in + * appropriate classes. */ fun removeExclusionRegion(taskId: Int) { desktopExclusionRegions.delete(taskId) @@ -403,26 +306,24 @@ class DesktopModeTaskRepository { } /** Removes and returns the bounds saved before maximizing the given task. */ - fun removeBoundsBeforeMaximize(taskId: Int): Rect? { - return boundsBeforeMaximizeByTaskId.removeReturnOld(taskId) - } + fun removeBoundsBeforeMaximize(taskId: Int): Rect? = + boundsBeforeMaximizeByTaskId.removeReturnOld(taskId) /** Saves the bounds of the given task before maximizing. */ - fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) { + fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) = boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds)) - } internal fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopModeTaskRepository") - dumpDisplayData(pw, innerPrefix) + dumpDesktopTaskData(pw, innerPrefix) pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}") pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}") } - private fun dumpDisplayData(pw: PrintWriter, prefix: String) { + private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " - displayData.forEach { displayId, data -> + desktopTaskDataByDisplayId.forEach { displayId, data -> pw.println("${prefix}Display $displayId:") pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}") pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}") @@ -432,23 +333,28 @@ class DesktopModeTaskRepository { } } - /** - * Defines interface for classes that can listen to changes for active tasks in desktop mode. - */ + /** Listens to changes for active tasks in desktop mode. */ interface ActiveTasksListener { - /** Called when the active tasks change in desktop mode. */ fun onActiveTasksChanged(displayId: Int) {} } - /** - * Defines interface for classes that can listen to changes for visible tasks in desktop mode. - */ + /** Listens to changes for visible tasks in desktop mode. */ interface VisibleTasksListener { - /** Called when the desktop changes the number of visible freeform tasks. */ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {} } -} -private fun <T> Iterable<T>.toDumpString(): String { - return joinToString(separator = ", ", prefix = "[", postfix = "]") + private fun logD(msg: String, vararg arguments: Any?) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + private fun logW(msg: String, vararg arguments: Any?) { + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + companion object { + private const val TAG = "DesktopModeTaskRepository" + } } + +private fun <T> Iterable<T>.toDumpString(): String = + joinToString(separator = ", ", prefix = "[", postfix = "]") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 2ef045d9fb89..9d3b2e5d4a32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -255,7 +255,7 @@ class DesktopTasksController( /** Gets number of visible tasks in [displayId]. */ fun visibleTaskCount(displayId: Int): Int = - desktopModeTaskRepository.visibleTaskCount(displayId) + desktopModeTaskRepository.getVisibleTaskCount(displayId) /** Returns true if any tasks are visible in Desktop Mode. */ fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0 @@ -446,14 +446,7 @@ class DesktopTasksController( if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) { removeWallpaperActivity(wct) } - if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) { - // Could happen if the task hasn't been removed from closing list after it disappeared - ProtoLog.w( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: the task with taskId=%d is already closing!", - taskId - ) - } + desktopModeTaskRepository.addClosingTask(displayId, taskId) } /** Move a task with given `taskId` to fullscreen */ @@ -794,7 +787,7 @@ class DesktopTasksController( } val nonMinimizedTasksOrderedFrontToBack = - desktopModeTaskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId) + desktopModeTaskRepository.getActiveNonMinimizedOrderedTasks(displayId) // If we're adding a new Task we might need to minimize an old one val taskToMinimize: RunningTaskInfo? = if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) { @@ -812,7 +805,7 @@ class DesktopTasksController( .filter { taskId -> taskId != taskToMinimize?.taskId } .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) } .reversed() // Start from the back so the front task is brought forward last - .forEach { task -> wct.reorder(task.token, true /* onTop */) } + .forEach { task -> wct.reorder(task.token, /* onTop= */ true) } return taskToMinimize } @@ -820,7 +813,7 @@ class DesktopTasksController( shellTaskOrganizer .getRunningTasks(context.displayId) .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME } - ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) } + ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) } } private fun addWallpaperActivity(wct: WindowContainerTransaction) { @@ -1069,14 +1062,7 @@ class DesktopTasksController( // Remove wallpaper activity when the last active task is removed removeWallpaperActivity(wct) } - if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) { - // Could happen if the task hasn't been removed from closing list after it disappeared - ProtoLog.w( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: the task with taskId=%d is already closing!", - task.taskId - ) - } + desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId) // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task. if (Flags.enableDesktopWindowingBackNavigation() && desktopModeTaskRepository.isVisibleTask(task.taskId)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index c5ed1be004e5..a011ff5636d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -65,10 +65,10 @@ class DesktopTasksLimiter ( } override fun onTransitionReady( - transition: IBinder, - info: TransitionInfo, - startTransaction: SurfaceControl.Transaction, - finishTransaction: SurfaceControl.Transaction + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction ) { val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return @@ -129,8 +129,7 @@ class DesktopTasksLimiter ( } fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) { - if (taskRepository - .getActiveNonMinimizedTasksOrderedFrontToBack(displayId).isNotEmpty()) { + if (taskRepository.getActiveNonMinimizedOrderedTasks(displayId).isNotEmpty()) { return } val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId) @@ -178,7 +177,7 @@ class DesktopTasksLimiter ( "DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d", newFrontTaskInfo.taskId) val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront( - taskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId), + taskRepository.getActiveNonMinimizedOrderedTasks(displayId), newFrontTaskInfo.taskId) val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack) if (taskToMinimize != null) { @@ -242,7 +241,5 @@ class DesktopTasksLimiter ( } @VisibleForTesting - fun getTransitionObserver(): TransitionObserver { - return minimizeTransitionObserver - } + fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index e4aa115347eb..d03a561cd3ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -21,11 +21,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; @@ -40,6 +40,7 @@ import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; @@ -290,7 +291,7 @@ public class DragLayout extends LinearLayout final int activityType = taskInfo1.getActivityType(); if (activityType == ACTIVITY_TYPE_STANDARD) { Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); - int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb(); + int bgColor1 = getResizingBackgroundColor(taskInfo1); mDropZoneView1.setAppInfo(bgColor1, icon1); mDropZoneView2.setAppInfo(bgColor1, icon1); mDropZoneView1.setForceIgnoreBottomMargin(false); @@ -312,10 +313,10 @@ public class DragLayout extends LinearLayout mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); if (topOrLeftTask != null && bottomOrRightTask != null) { Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo); - int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask).toArgb(); + int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask); Drawable bottomOrRightIcon = mIconProvider.getIcon( bottomOrRightTask.topActivityInfo); - int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask).toArgb(); + int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask); mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon); mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon); } @@ -586,6 +587,11 @@ public class DragLayout extends LinearLayout } } + private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { + final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); + return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); + } + /** * Dumps information about this drag layout. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 4531967d6f93..229d972b2834 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -101,12 +101,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId); repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); if (taskInfo.isVisible) { - if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "Adding active freeform task: #%d", taskInfo.taskId); - } + repository.addActiveTask(taskInfo.displayId, taskInfo.taskId); repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, - true); + true); } }); } @@ -122,10 +119,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mDesktopModeTaskRepository.ifPresent(repository -> { repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); - if (repository.removeActiveTask(taskInfo.taskId)) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "Removing active freeform task: #%d", taskInfo.taskId); - } + repository.removeActiveTask(taskInfo.taskId, /* excludedDisplayId= */ null); repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false); }); } @@ -146,14 +140,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (DesktopModeStatus.canEnterDesktopMode(mContext)) { mDesktopModeTaskRepository.ifPresent(repository -> { if (taskInfo.isVisible) { - if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "Adding active freeform task: #%d", taskInfo.taskId); - } - } else if (repository.isClosingTask(taskInfo.taskId) - && repository.removeClosingTask(taskInfo.taskId)) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "Removing closing freeform task: #%d", taskInfo.taskId); + repository.addActiveTask(taskInfo.displayId, taskInfo.taskId); + } else if (repository.isClosingTask(taskInfo.taskId)) { + repository.removeClosingTask(taskInfo.taskId); } repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index b939b169d8bd..8aa093379ee7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -44,6 +44,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ExternalInterfaceBinder; +import com.android.wm.shell.common.ImeListener; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; @@ -56,7 +57,6 @@ import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -201,6 +201,11 @@ public class PipController implements ConfigurationChangeListener, .getDisplayLayout(mPipDisplayLayoutState.getDisplayId())); } }); + mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), + new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) { + @Override + public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {} + }); // Allow other outside processes to bind to PiP controller using the key below. mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index d7ee563b562c..2531ff150f43 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -41,7 +41,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString; -import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; @@ -2458,13 +2457,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, updateSurfaceBounds(layout, t, shouldUseParallaxEffect); getMainStageBounds(mTempRect1); getSideStageBounds(mTempRect2); - // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both - // sides match. When b/307490004 is fixed, this code can be reverted. - float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents(); - mMainStage.onResizing( - mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor); - mSideStage.onResizing( - mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor); + mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately); + mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately); t.apply(); mTransactionPool.release(t); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 1076eca60369..d1ab3e96d4c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -314,10 +314,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, - int offsetY, boolean immediately, float[] veilColor) { + int offsetY, boolean immediately) { if (mSplitDecorManager != null && mRootTaskInfo != null) { mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX, - offsetY, immediately, veilColor); + offsetY, immediately); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt new file mode 100644 index 000000000000..3b0a0722968b --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.content.res.Configuration +import android.content.res.Resources +import android.graphics.Insets +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.view.DisplayCutout +import android.view.DisplayInfo +import android.view.InsetsSource.ID_IME +import android.view.InsetsState +import android.view.Surface +import android.view.WindowInsets.Type +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.wm.shell.ShellTestCase +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.kotlin.whenever + + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ImeListenerTest : ShellTestCase() { + private lateinit var imeListener: CachingImeListener + private lateinit var displayLayout: DisplayLayout + + @Mock private lateinit var displayController: DisplayController + @Before + fun setUp() { + val resources = createResources(40, 50, false) + val displayInfo = createDisplayInfo(1000, 1500, 0, Surface.ROTATION_0) + displayLayout = DisplayLayout(displayInfo, resources, false, false) + whenever(displayController.getDisplayLayout(DEFAULT_DISPLAY_ID)).thenReturn(displayLayout) + imeListener = CachingImeListener(displayController, DEFAULT_DISPLAY_ID) + } + + @Test + fun testImeAppears() { + val insetsState = createInsetsStateWithIme(true, DEFAULT_IME_HEIGHT) + imeListener.insetsChanged(insetsState) + assertTrue("Ime insets source should become visible", imeListener.cachedImeVisible) + assertEquals(DEFAULT_IME_HEIGHT, imeListener.cachedImeHeight) + } + + @Test + fun testImeAppears_thenDisappears() { + // Send insetsState with an IME as a visible source. + val insetsStateWithIme = createInsetsStateWithIme(true, DEFAULT_IME_HEIGHT) + imeListener.insetsChanged(insetsStateWithIme) + + // Send insetsState without IME. + val insetsStateWithoutIme = createInsetsStateWithIme(false, 0) + imeListener.insetsChanged(insetsStateWithoutIme) + + assertFalse("Ime insets source should become invisible", + imeListener.cachedImeVisible) + assertEquals(0, imeListener.cachedImeHeight) + } + + private fun createInsetsStateWithIme(isVisible: Boolean, imeHeight: Int): InsetsState { + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + val insetsState = InsetsState() + + val insetsSource = insetsState.getOrCreateSource(ID_IME, Type.ime()) + insetsSource.setVisible(isVisible) + insetsSource.setFrame(stableBounds.left, stableBounds.bottom - imeHeight, + stableBounds.right, stableBounds.bottom) + return insetsState + } + + private fun createDisplayInfo(width: Int, height: Int, cutoutHeight: Int, + rotation: Int): DisplayInfo { + val info = DisplayInfo() + info.logicalWidth = width + info.logicalHeight = height + info.rotation = rotation + if (cutoutHeight > 0) { + info.displayCutout = DisplayCutout( + Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */, + null /* boundLeft */, + Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight, + cutoutHeight) /* boundTop */, null /* boundRight */, + null /* boundBottom */) + } else { + info.displayCutout = DisplayCutout.NO_CUTOUT + } + info.logicalDensityDpi = 300 + return info + } + + private fun createResources(navLand: Int, navPort: Int, navMoves: Boolean): Resources { + val cfg = Configuration() + cfg.uiMode = Configuration.UI_MODE_TYPE_NORMAL + val res = Mockito.mock(Resources::class.java) + Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( + R.dimen.navigation_bar_height_landscape_car_mode) + Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize( + R.dimen.navigation_bar_height_car_mode) + Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( + R.dimen.navigation_bar_width_car_mode) + Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( + R.dimen.navigation_bar_height_landscape) + Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize( + R.dimen.navigation_bar_height) + Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( + R.dimen.navigation_bar_width) + Mockito.doReturn(navMoves).whenever(res).getBoolean(R.bool.config_navBarCanMove) + Mockito.doReturn(cfg).whenever(res).configuration + return res + } + + private class CachingImeListener( + displayController: DisplayController, + displayId: Int + ) : ImeListener(displayController, displayId) { + var cachedImeVisible = false + var cachedImeHeight = 0 + public override fun onImeVisibilityChanged(imeVisible: Boolean, imeHeight: Int) { + cachedImeVisible = imeVisible + cachedImeHeight = imeHeight + } + } + + companion object { + private const val DEFAULT_DISPLAY_ID = 0 + private const val DEFAULT_IME_HEIGHT = 500 + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 18b08bfb0f47..0a5672d45f33 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -41,32 +41,44 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun addActiveTask_listenerNotifiedAndTaskIsActive() { + fun addActiveTask_notifiesListener() { val listener = TestListener() repo.addActiveTaskListener(listener) repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun addActiveTask_taskIsActive() { + val listener = TestListener() + repo.addActiveTaskListener(listener) + + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + assertThat(repo.isActiveTask(1)).isTrue() } @Test - fun addActiveTask_sameTaskDoesNotNotify() { + fun addSameActiveTaskTwice_notifiesOnce() { val listener = TestListener() repo.addActiveTaskListener(listener) repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1) } @Test - fun addActiveTask_multipleTasksAddedNotifiesForEach() { + fun addActiveTask_multipleTasksAdded_notifiesForAllTasks() { val listener = TestListener() repo.addActiveTaskListener(listener) repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) } @@ -84,22 +96,35 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun removeActiveTask_listenerNotifiedAndTaskNotActive() { + fun removeActiveTask_notifiesListener() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.removeActiveTask(1) + // Notify once for add and once for remove assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) + } + + @Test + fun removeActiveTask_taskNotActive() { + val listener = TestListener() + repo.addActiveTaskListener(listener) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + + repo.removeActiveTask(1) + assertThat(repo.isActiveTask(1)).isFalse() } @Test - fun removeActiveTask_removeNotExistingTaskDoesNotNotify() { + fun removeActiveTask_nonExistingTask_doesNotNotify() { val listener = TestListener() repo.addActiveTaskListener(listener) + repo.removeActiveTask(99) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(0) } @@ -108,32 +133,38 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.removeActiveTask(1) + assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(0) assertThat(repo.isActiveTask(1)).isFalse() } @Test - fun isActiveTask_notExistingTaskReturnsFalse() { + fun isActiveTask_nonExistingTask_returnsFalse() { assertThat(repo.isActiveTask(99)).isFalse() } @Test - fun isOnlyVisibleNonClosingTask_noTasks() { + fun isOnlyVisibleNonClosingTask_noTasks_returnsFalse() { // No visible tasks assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + } + + @Test + fun isClosingTask_noTasks_returnsFalse() { + // No visible tasks assertThat(repo.isClosingTask(1)).isFalse() } @Test - fun isOnlyVisibleNonClosingTask_singleVisibleNonClosingTask() { + fun updateVisibleFreeformTasks_singleVisibleNonClosingTask_updatesTasksCorrectly() { repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) - // The only visible task assertThat(repo.isVisibleTask(1)).isTrue() assertThat(repo.isClosingTask(1)).isFalse() assertThat(repo.isOnlyVisibleNonClosingTask(1)).isTrue() - // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() assertThat(repo.isClosingTask(99)).isFalse() assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() @@ -207,10 +238,11 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun addListener_notifiesVisibleFreeformTask() { + fun addVisibleTasksListener_notifiesVisibleFreeformTask() { repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) val listener = TestVisibilityListener() val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) executor.flushAll() @@ -236,6 +268,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) executor.flushAll() @@ -303,6 +336,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { executor.flushAll() assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) executor.flushAll() @@ -329,6 +363,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { executor.flushAll() assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2) + repo.updateVisibleFreeformTasks(INVALID_DISPLAY, taskId = 1, visible = false) executor.flushAll() @@ -337,65 +372,73 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun visibleTaskCount_defaultDisplay_returnsCorrectCount() { + fun getVisibleTaskCount_defaultDisplay_returnsCorrectCount() { // No tasks, count is 0 - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) // New task increments count to 1 repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Visibility update to same task does not increase count repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Second task visible increments count repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) // Hiding a task decrements count repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Hiding all tasks leaves count at 0 repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false) - assertThat(repo.visibleTaskCount(displayId = 9)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0) // Hiding a not existing task, count remains at 0 repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) } @Test - fun visibleTaskCount_multipleDisplays_returnsCorrectCount() { - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) - assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) + fun getVisibleTaskCount_multipleDisplays_returnsCorrectCount() { + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) // New task on default display increments count for that display only repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) - assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) // New task on secondary display, increments count for that display only repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) - assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) // Marking task visible on another display, updates counts for both displays repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) - assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) // Marking task that is on secondary display, hidden on default display, does not affect // secondary display repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) - assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) // Hiding a task on that display, decrements count repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false) - assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) - assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) + + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) } @Test @@ -428,7 +471,9 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { fun removeFreeformTask_removesTaskBoundsBeforeMaximize() { val taskId = 1 repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200)) + repo.removeFreeformTask(THIRD_DISPLAY, taskId) + assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull() } @@ -436,7 +481,9 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { fun saveBoundsBeforeMaximize_boundsSavedByTaskId() { val taskId = 1 val bounds = Rect(0, 0, 200, 200) + repo.saveBoundsBeforeMaximize(taskId, bounds) + assertThat(repo.removeBoundsBeforeMaximize(taskId)).isEqualTo(bounds) } @@ -446,17 +493,20 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val bounds = Rect(0, 0, 200, 200) repo.saveBoundsBeforeMaximize(taskId, bounds) repo.removeBoundsBeforeMaximize(taskId) - assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull() + + val boundsBeforeMaximize = repo.removeBoundsBeforeMaximize(taskId) + + assertThat(boundsBeforeMaximize).isNull() } @Test - fun minimizeTaskNotCalled_noTasksMinimized() { + fun isMinimizedTask_minimizeTaskNotCalled_noTasksMinimized() { assertThat(repo.isMinimizedTask(taskId = 0)).isFalse() assertThat(repo.isMinimizedTask(taskId = 1)).isFalse() } @Test - fun minimizeTask_onlyThatTaskIsMinimized() { + fun minimizeTask_minimizesCorrectTask() { repo.minimizeTask(displayId = 0, taskId = 0) assertThat(repo.isMinimizedTask(taskId = 0)).isTrue() @@ -465,8 +515,9 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun unminimizeTask_taskNoLongerMinimized() { + fun unminimizeTask_unminimizesTask() { repo.minimizeTask(displayId = 0, taskId = 0) + repo.unminimizeTask(displayId = 0, taskId = 0) assertThat(repo.isMinimizedTask(taskId = 0)).isFalse() @@ -478,6 +529,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { fun unminimizeTask_nonExistentTask_doesntCrash() { repo.unminimizeTask(displayId = 0, taskId = 0) + // No change assertThat(repo.isMinimizedTask(taskId = 0)).isFalse() assertThat(repo.isMinimizedTask(taskId = 1)).isFalse() assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() @@ -485,41 +537,44 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test - fun updateVisibleFreeformTasks_toVisible_taskIsUnminimized() { + fun updateVisibleFreeformTasks_minimizedTaskBecomesVisible_unminimizesTask() { repo.minimizeTask(displayId = 10, taskId = 2) - repo.updateVisibleFreeformTasks(displayId = 10, taskId = 2, visible = true) - assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() + val isMinimizedTask = repo.isMinimizedTask(taskId = 2) + + assertThat(isMinimizedTask).isFalse() } @Test - fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() { + fun getActiveNonMinimizedOrderedTasks_returnsFreeformTasksInCorrectOrder() { repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1) repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2) repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3) - // The front-most task will be the one added last through addOrMoveFreeformTaskToTop + // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop` repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3) repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 2) repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 1) - assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0)) - .containsExactly(1, 2, 3).inOrder() + val tasks = repo.getActiveNonMinimizedOrderedTasks(displayId = 0) + + assertThat(tasks).containsExactly(1, 2, 3).inOrder() } @Test - fun getActiveNonMinimizedTasksOrderedFrontToBack_minimizedTaskNotIncluded() { + fun getActiveNonMinimizedOrderedTasks_excludesMinimizedTasks() { repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1) repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2) repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3) - // The front-most task will be the one added last through addOrMoveFreeformTaskToTop + // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop` repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3) repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 2) repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 1) repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2) - assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack( - displayId = DEFAULT_DISPLAY)).containsExactly(1, 3).inOrder() + val tasks = repo.getActiveNonMinimizedOrderedTasks(displayId = DEFAULT_DISPLAY) + + assertThat(tasks).containsExactly(1, 3).inOrder() } diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 1d6e38d2a510..8a877b843e9d 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -165,7 +165,7 @@ public final class MediaSession { /** * Creates a new session. The session will automatically be registered with - * the system but will not be published until {@link #setActive(boolean) + * the system, but will not be published until {@link #setActive(boolean) * setActive(true)} is called. You must call {@link #release()} when * finished with the session. * <p> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig index ba842877fc79..c1e43c9abb33 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig @@ -19,3 +19,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "action_bar_wrap_content" + namespace: "accessibility" + description: "Applies WRAP_CONTENT to the action bar in A11yMenu settings to better fit large fonts" + bug: "347911378" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java index ab8f97ad9c3d..c71ef8360008 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.provider.Browser; import android.provider.Settings; import android.view.View; +import android.view.ViewGroup; import android.widget.TextView; import android.window.OnBackInvokedCallback; @@ -35,6 +36,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceManager; +import com.android.systemui.accessibility.accessibilitymenu.Flags; import com.android.systemui.accessibility.accessibilitymenu.R; /** @@ -60,6 +62,18 @@ public class A11yMenuSettingsActivity extends FragmentActivity { ((TextView) findViewById(R.id.action_bar_title)).setText( getResources().getString(R.string.accessibility_menu_settings_name) ); + if (Flags.actionBarWrapContent()) { + setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar)); + setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar_container)); + } + } + + private void setHeightWrapContent(View view) { + if (view != null) { + ViewGroup.LayoutParams params = view.getLayoutParams(); + params.height = ViewGroup.LayoutParams.WRAP_CONTENT; + view.setLayoutParams(params); + } } @Override diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 462db340bdaa..e4f27aa35ac5 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -601,6 +601,13 @@ flag { } flag { + name: "screenshot_ui_controller_refactor" + namespace: "systemui" + description: "Simplify and refactor ScreenshotController" + bug: "354711957" +} + +flag { name: "run_fingerprint_detect_on_dismissible_keyguard" namespace: "systemui" description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible." diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 11c104e8542e..b4e513c5137f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -216,7 +216,9 @@ fun CommunalHub( val screenWidth = windowMetrics.bounds.width() val layoutDirection = LocalLayoutDirection.current - if (!viewModel.isEditMode) { + if (viewModel.isEditMode) { + ScrollOnNewWidgetAddedEffect(communalContent, gridState) + } else { ScrollOnUpdatedLiveContentEffect(communalContent, gridState) } @@ -547,6 +549,36 @@ private fun ScrollOnUpdatedLiveContentEffect( } } +/** Observes communal content and scrolls to a newly added widget if any. */ +@Composable +private fun ScrollOnNewWidgetAddedEffect( + communalContent: List<CommunalContentModel>, + gridState: LazyGridState, +) { + val coroutineScope = rememberCoroutineScope() + val widgetKeys = remember { mutableListOf<String>() } + + LaunchedEffect(communalContent) { + val oldWidgetKeys = widgetKeys.toList() + widgetKeys.clear() + widgetKeys.addAll(communalContent.filter { it.isWidgetContent() }.map { it.key }) + + // Do nothing if there is no new widget + val indexOfFirstNewWidget = widgetKeys.indexOfFirst { !oldWidgetKeys.contains(it) } + if (indexOfFirstNewWidget < 0) { + return@LaunchedEffect + } + + // Scroll if the new widget is not visible + val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index + if (lastVisibleItemIndex != null && indexOfFirstNewWidget > lastVisibleItemIndex) { + // Launching with a scope to prevent the job from being canceled in the case of a + // recomposition during scrolling + coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstNewWidget) } + } + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable private fun BoxScope.CommunalHubLazyGrid( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt index 5a56cea5eb8f..7b497e84db62 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt @@ -49,14 +49,16 @@ object MediaScenePicker : ElementScenePicker { transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Communal) -> { Scenes.Lockscreen } - - // TODO: 345467290 - update with the actual scene picking transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> { Scenes.QuickSettings } - - // TODO: 340216785 - update with the actual scene picking - else -> pickSingleSceneIn(scenes, transition, element) + else -> { + when { + scenes.contains(transition.toScene) -> transition.toScene + scenes.contains(transition.fromScene) -> transition.fromScene + else -> null + } + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 069f2f555a75..18ca0f75245e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -87,6 +87,7 @@ import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState +import com.android.systemui.media.dagger.MediaModule.QS_PANEL import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.notifications.ui.composable.NotificationScrollingStack import com.android.systemui.notifications.ui.composable.NotificationStackCutoffGuideline @@ -150,7 +151,8 @@ constructor( private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, private val mediaCarouselController: MediaCarouselController, - @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost, + @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost, + @Named(QS_PANEL) private val qsMediaHost: MediaHost, ) : ComposableScene { override val key = Scenes.Shade @@ -174,15 +176,20 @@ constructor( createBatteryMeterViewController = batteryMeterViewControllerFactory::create, statusBarIconController = statusBarIconController, mediaCarouselController = mediaCarouselController, - mediaHost = mediaHost, + qqsMediaHost = qqsMediaHost, + qsMediaHost = qsMediaHost, modifier = modifier, shadeSession = shadeSession, ) init { - mediaHost.expansion = MediaHostState.EXPANDED - mediaHost.showsOnlyActiveMedia = true - mediaHost.init(MediaHierarchyManager.LOCATION_QQS) + qqsMediaHost.expansion = MediaHostState.EXPANDED + qqsMediaHost.showsOnlyActiveMedia = true + qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS) + + qsMediaHost.expansion = MediaHostState.EXPANDED + qsMediaHost.showsOnlyActiveMedia = false + qsMediaHost.init(MediaHierarchyManager.LOCATION_QS) } } @@ -195,7 +202,8 @@ private fun SceneScope.ShadeScene( createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, statusBarIconController: StatusBarIconController, mediaCarouselController: MediaCarouselController, - mediaHost: MediaHost, + qqsMediaHost: MediaHost, + qsMediaHost: MediaHost, modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { @@ -210,7 +218,7 @@ private fun SceneScope.ShadeScene( createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, mediaCarouselController = mediaCarouselController, - mediaHost = mediaHost, + mediaHost = qqsMediaHost, modifier = modifier, shadeSession = shadeSession, ) @@ -223,7 +231,7 @@ private fun SceneScope.ShadeScene( createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, mediaCarouselController = mediaCarouselController, - mediaHost = mediaHost, + mediaHost = qsMediaHost, modifier = modifier, shadeSession = shadeSession, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt index 56b3679b6835..42db96e917ee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt @@ -23,8 +23,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository +import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType -import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec @@ -42,6 +42,8 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class GridConsistencyInteractorTest : SysuiTestCase() { + data object NoopGridLayoutType : GridLayoutType + private val kosmos = testKosmos().apply { defaultLargeTilesRepository = @@ -54,6 +56,11 @@ class GridConsistencyInteractorTest : SysuiTestCase() { TileSpec.create("largeD"), ) } + gridConsistencyInteractorsMap = + mapOf( + Pair(NoopGridLayoutType, noopGridConsistencyInteractor), + Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor) + ) } private val underTest = with(kosmos) { gridConsistencyInteractor } @@ -71,7 +78,7 @@ class GridConsistencyInteractorTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Using the no-op grid consistency interactor - gridLayoutTypeRepository.setLayout(PartitionedGridLayoutType) + gridLayoutTypeRepository.setLayout(NoopGridLayoutType) // Setting an invalid layout with holes // [ Large A ] [ sa ] diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt deleted file mode 100644 index 55b7454fa2c1..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.panels.ui.compose - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.testScope -import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository -import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository -import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel -import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.testKosmos -import com.google.common.truth.Truth -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidJUnit4::class) -class PartitionedGridLayoutTest : SysuiTestCase() { - private val kosmos = - testKosmos().apply { - defaultLargeTilesRepository = - object : DefaultLargeTilesRepository { - override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large")) - } - } - - private val underTest = with(kosmos) { PartitionedGridLayout(partitionedGridViewModel) } - - @Test - fun correctPagination_underOnePage_partitioned_sameRelativeOrder() = - with(kosmos) { - testScope.runTest { - val rows = 3 - val columns = 4 - - val tiles = - listOf( - largeTile(), - smallTile(), - smallTile(), - largeTile(), - largeTile(), - smallTile() - ) - val (smallTiles, largeTiles) = - tiles.partition { iconTilesViewModel.isIconTile(it.spec) } - - // [L L] [L L] - // [L L] - // [S] [S] [S] - - val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) - - Truth.assertThat(pages).hasSize(1) - Truth.assertThat(pages[0]).isEqualTo(largeTiles + smallTiles) - } - } - - @Test - fun correctPagination_twoPages_partitioned_sameRelativeOrder() = - with(kosmos) { - testScope.runTest { - val rows = 3 - val columns = 4 - - val tiles = - listOf( - largeTile(), - smallTile(), - smallTile(), - largeTile(), - smallTile(), - smallTile(), - largeTile(), - smallTile(), - smallTile(), - ) - // --- Page 1 --- - // [L L] [L L] - // [L L] - // [S] [S] [S] [S] - // --- Page 2 --- - // [S] [S] - - val (smallTiles, largeTiles) = - tiles.partition { iconTilesViewModel.isIconTile(it.spec) } - - val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) - - val expectedPage0 = largeTiles + smallTiles.take(4) - val expectedPage1 = smallTiles.drop(4) - - Truth.assertThat(pages).hasSize(2) - Truth.assertThat(pages[0]).isEqualTo(expectedPage0) - Truth.assertThat(pages[1]).isEqualTo(expectedPage1) - } - } - - companion object { - fun largeTile() = MockTileViewModel(TileSpec.create("large")) - - fun smallTile() = MockTileViewModel(TileSpec.create("small")) - } -} diff --git a/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml b/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml new file mode 100644 index 000000000000..7eab340c09cf --- /dev/null +++ b/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="48dp" + android:drawablePadding="4dp" + android:ellipsize="end" + android:gravity="center_vertical" + android:paddingHorizontal="8dp" + android:textColor="?android:textColorSecondary" /> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 3904ee144b05..b1cba2fb3ab8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -915,7 +915,12 @@ constructor( event: MotionEvent, touchExplorationEnabled: Boolean, ): Boolean { - if (bpTalkback() && modalities.first().hasUdfps && touchExplorationEnabled) { + if ( + bpTalkback() && + modalities.first().hasUdfps && + touchExplorationEnabled && + !isAuthenticated.first().isAuthenticated + ) { // TODO(b/315184924): Remove uses of UdfpsUtils val scaledTouch = udfpsUtils.getTouchInNativeCoordinates( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt index b387855ad553..830ef3b87faa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt @@ -46,6 +46,7 @@ object KeyguardLongPressViewBinder { onSingleTap: () -> Unit, falsingManager: FalsingManager, ) { + view.contentDescription = view.resources.getString(R.string.accessibility_desc_lock_screen) view.accessibilityHintLongPressAction = AccessibilityNodeInfo.AccessibilityAction( AccessibilityNodeInfoCompat.ACTION_LONG_CLICK, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 78f4b4b32237..072d322d69a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -30,14 +30,10 @@ import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType import com.android.systemui.qs.panels.shared.model.PanelsLog -import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType -import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout -import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout -import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel @@ -102,22 +98,6 @@ interface PanelsModule { @Provides @IntoSet - fun provideStretchedGridLayout( - gridLayout: StretchedGridLayout - ): Pair<GridLayoutType, GridLayout> { - return Pair(StretchedGridLayoutType, gridLayout) - } - - @Provides - @IntoSet - fun providePartitionedGridLayout( - gridLayout: PartitionedGridLayout - ): Pair<GridLayoutType, GridLayout> { - return Pair(PartitionedGridLayoutType, gridLayout) - } - - @Provides - @IntoSet fun providePaginatedGridLayout( gridLayout: PaginatedGridLayout ): Pair<GridLayoutType, GridLayout> { @@ -148,22 +128,6 @@ interface PanelsModule { @Provides @IntoSet - fun provideStretchedGridConsistencyInteractor( - consistencyInteractor: NoopGridConsistencyInteractor - ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { - return Pair(StretchedGridLayoutType, consistencyInteractor) - } - - @Provides - @IntoSet - fun providePartitionedGridConsistencyInteractor( - consistencyInteractor: NoopGridConsistencyInteractor - ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { - return Pair(PartitionedGridLayoutType, consistencyInteractor) - } - - @Provides - @IntoSet fun providePaginatedGridConsistencyInteractor( @PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor, ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt index b1942fee9b6d..323f39b9f1cd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt @@ -26,14 +26,5 @@ interface GridLayoutType /** Grid type representing a scrollable vertical grid. */ data object InfiniteGridLayoutType : GridLayoutType -/** - * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty - * spaces. - */ -data object StretchedGridLayoutType : GridLayoutType - -/** Grid type grouping large tiles on top and icon tiles at the bottom. */ -data object PartitionedGridLayoutType : GridLayoutType - /** Grid type for a paginated list of tiles. It will delegate to some other layout type. */ data object PaginatedGridLayoutType : GridLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt deleted file mode 100644 index 6c84eddb5e38..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.panels.ui.compose - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.PathEffect -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.addOutline -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.modifiers.background -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.shared.model.SizedTile -import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel -import com.android.systemui.qs.panels.ui.viewmodel.PartitionedGridViewModel -import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel -import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.res.R -import javax.inject.Inject - -@SysUISingleton -class PartitionedGridLayout @Inject constructor(private val viewModel: PartitionedGridViewModel) : - PaginatableGridLayout { - @Composable - override fun TileGrid( - tiles: List<TileViewModel>, - modifier: Modifier, - editModeStart: () -> Unit, - ) { - DisposableEffect(tiles) { - val token = Any() - tiles.forEach { it.startListening(token) } - onDispose { tiles.forEach { it.stopListening(token) } } - } - val columns by viewModel.columns.collectAsStateWithLifecycle() - val showLabels by viewModel.showLabels.collectAsStateWithLifecycle() - val largeTileHeight = tileHeight() - val iconTileHeight = tileHeight(showLabels) - val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) } - - TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { - // Large tiles - items(largeTiles.size, span = { GridItemSpan(2) }) { index -> - Tile( - tile = largeTiles[index], - iconOnly = false, - modifier = Modifier.height(largeTileHeight) - ) - } - fillUpRow(nTiles = largeTiles.size, columns = columns / 2) - - // Small tiles - items(smallTiles.size) { index -> - Tile( - tile = smallTiles[index], - iconOnly = true, - showLabels = showLabels, - modifier = Modifier.height(iconTileHeight) - ) - } - } - } - - @Composable - override fun EditTileGrid( - tiles: List<EditTileViewModel>, - modifier: Modifier, - onAddTile: (TileSpec, Int) -> Unit, - onRemoveTile: (TileSpec) -> Unit, - ) { - val columns by viewModel.columns.collectAsStateWithLifecycle() - val showLabels by viewModel.showLabels.collectAsStateWithLifecycle() - - val listState = rememberEditListState(tiles) - val dragAndDropState = rememberDragAndDropState(listState) - - val (currentTiles, otherTiles) = listState.tiles.partition { it.isCurrent } - val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { - onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) - } - val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec -> - viewModel.resize(tileSpec, !viewModel.isIconTile(tileSpec)) - } - val largeTileHeight = tileHeight() - val iconTileHeight = tileHeight(showLabels) - val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical) - - Column( - verticalArrangement = Arrangement.spacedBy(tilePadding), - modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()) - ) { - Row( - modifier = - Modifier.background( - color = MaterialTheme.colorScheme.surfaceVariant, - alpha = { 1f }, - shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)) - ) - .padding(tilePadding) - ) { - Column(Modifier.padding(start = tilePadding)) { - Text( - text = "Show text labels", - color = MaterialTheme.colorScheme.onBackground, - fontWeight = FontWeight.Bold - ) - Text( - text = "Display names under each tile", - color = MaterialTheme.colorScheme.onBackground - ) - } - Spacer(modifier = Modifier.weight(1f)) - Switch(checked = showLabels, onCheckedChange = { viewModel.setShowLabels(it) }) - } - - CurrentTiles( - tiles = currentTiles, - largeTileHeight = largeTileHeight, - iconTileHeight = iconTileHeight, - tilePadding = tilePadding, - onAdd = onAddTile, - onRemove = onRemoveTile, - onDoubleTap = onDoubleTap, - isIconOnly = viewModel::isIconTile, - columns = columns, - showLabels = showLabels, - dragAndDropState = dragAndDropState, - ) - AvailableTiles( - tiles = otherTiles.filter { !dragAndDropState.isMoving(it.tileSpec) }, - largeTileHeight = largeTileHeight, - iconTileHeight = iconTileHeight, - tilePadding = tilePadding, - addTileToEnd = addTileToEnd, - onRemove = onRemoveTile, - onDoubleTap = onDoubleTap, - isIconOnly = viewModel::isIconTile, - showLabels = showLabels, - columns = columns, - dragAndDropState = dragAndDropState, - ) - } - } - - override fun splitIntoPages( - tiles: List<TileViewModel>, - rows: Int, - columns: Int, - ): List<List<TileViewModel>> { - val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) } - - val sizedLargeTiles = largeTiles.map { SizedTile(it, 2) } - val sizedSmallTiles = smallTiles.map { SizedTile(it, 1) } - val largeTilesRows = PaginatableGridLayout.splitInRows(sizedLargeTiles, columns) - val smallTilesRows = PaginatableGridLayout.splitInRows(sizedSmallTiles, columns) - return (largeTilesRows + smallTilesRows).chunked(rows).map { it.flatten().map { it.tile } } - } - - @Composable - private fun CurrentTiles( - tiles: List<EditTileViewModel>, - largeTileHeight: Dp, - iconTileHeight: Dp, - tilePadding: Dp, - onAdd: (TileSpec, Int) -> Unit, - onRemove: (TileSpec) -> Unit, - onDoubleTap: (TileSpec) -> Unit, - isIconOnly: (TileSpec) -> Boolean, - showLabels: Boolean, - columns: Int, - dragAndDropState: DragAndDropState, - ) { - val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) } - - val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding) - val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding) - - CurrentTilesContainer { - TileLazyGrid( - columns = GridCells.Fixed(columns), - modifier = - Modifier.height(largeGridHeight) - .dragAndDropTileList(dragAndDropState, { !isIconOnly(it) }, onAdd) - ) { - editTiles( - tiles = largeTiles, - clickAction = ClickAction.REMOVE, - onClick = onRemove, - onDoubleTap = onDoubleTap, - isIconOnly = { false }, - dragAndDropState = dragAndDropState, - acceptDrops = { !isIconOnly(it) }, - onDrop = onAdd, - indicatePosition = true, - ) - } - } - - CurrentTilesContainer { - TileLazyGrid( - columns = GridCells.Fixed(columns), - modifier = - Modifier.height(smallGridHeight) - .dragAndDropTileList(dragAndDropState, { isIconOnly(it) }, onAdd) - ) { - editTiles( - tiles = smallTiles, - clickAction = ClickAction.REMOVE, - onClick = onRemove, - onDoubleTap = onDoubleTap, - isIconOnly = { true }, - showLabels = showLabels, - dragAndDropState = dragAndDropState, - acceptDrops = { isIconOnly(it) }, - onDrop = onAdd, - indicatePosition = true, - ) - } - } - } - - @Composable - private fun AvailableTiles( - tiles: List<EditTileViewModel>, - largeTileHeight: Dp, - iconTileHeight: Dp, - tilePadding: Dp, - addTileToEnd: (TileSpec) -> Unit, - onRemove: (TileSpec) -> Unit, - onDoubleTap: (TileSpec) -> Unit, - isIconOnly: (TileSpec) -> Boolean, - showLabels: Boolean, - columns: Int, - dragAndDropState: DragAndDropState, - ) { - val (tilesStock, tilesCustom) = tiles.partition { it.appName == null } - val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) } - - val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding) - val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding) - val largeGridHeightCustom = - gridHeight(tilesCustom.size, iconTileHeight, columns, tilePadding) - - // Add up the height of all three grids and add padding in between - val gridHeight = - largeGridHeight + smallGridHeight + largeGridHeightCustom + (tilePadding * 2) - - val onDrop: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ -> - onRemove(tileSpec) - } - - AvailableTilesContainer { - TileLazyGrid( - columns = GridCells.Fixed(columns), - modifier = - Modifier.height(gridHeight) - .dragAndDropTileList(dragAndDropState, { true }, onDrop) - ) { - // Large tiles - editTiles( - largeTiles, - ClickAction.ADD, - addTileToEnd, - isIconOnly, - dragAndDropState, - onDoubleTap = onDoubleTap, - acceptDrops = { true }, - onDrop = onDrop, - ) - fillUpRow(nTiles = largeTiles.size, columns = columns / 2) - - // Small tiles - editTiles( - smallTiles, - ClickAction.ADD, - addTileToEnd, - isIconOnly, - dragAndDropState, - onDoubleTap = onDoubleTap, - showLabels = showLabels, - acceptDrops = { true }, - onDrop = onDrop, - ) - fillUpRow(nTiles = smallTiles.size, columns = columns) - - // Custom tiles, all icons - editTiles( - tilesCustom, - ClickAction.ADD, - addTileToEnd, - isIconOnly, - dragAndDropState, - onDoubleTap = onDoubleTap, - showLabels = showLabels, - acceptDrops = { true }, - onDrop = onDrop, - ) - } - } - } - - @Composable - private fun CurrentTilesContainer(content: @Composable () -> Unit) { - Box( - Modifier.fillMaxWidth() - .dashedBorder( - color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f), - shape = Dimensions.ContainerShape, - ) - .padding(dimensionResource(R.dimen.qs_tile_margin_vertical)) - ) { - content() - } - } - - @Composable - private fun AvailableTilesContainer(content: @Composable () -> Unit) { - Box( - Modifier.fillMaxWidth() - .background( - color = MaterialTheme.colorScheme.background, - alpha = { 1f }, - shape = Dimensions.ContainerShape, - ) - .padding(dimensionResource(R.dimen.qs_tile_margin_vertical)) - ) { - content() - } - } - - /** Fill up the rest of the row if it's not complete. */ - private fun LazyGridScope.fillUpRow(nTiles: Int, columns: Int) { - if (nTiles % columns != 0) { - item(span = { GridItemSpan(maxCurrentLineSpan) }) { Spacer(Modifier) } - } - } - - private fun Modifier.dashedBorder( - color: Color, - shape: Shape, - ): Modifier { - return this.drawWithContent { - val outline = shape.createOutline(size, layoutDirection, this) - val path = Path() - path.addOutline(outline) - val stroke = - Stroke( - width = 1.dp.toPx(), - pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f)) - ) - this.drawContent() - drawPath(path = path, style = stroke, color = color) - } - } - - private object Dimensions { - // Corner radius is half the height of a tile + padding - val ContainerShape = RoundedCornerShape(48.dp) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt deleted file mode 100644 index 3e48245a3d36..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.panels.ui.compose - -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.shared.model.SizedTile -import com.android.systemui.qs.panels.shared.model.TileRow -import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel -import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel -import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.res.R -import javax.inject.Inject - -@SysUISingleton -class StretchedGridLayout -@Inject -constructor( - private val iconTilesViewModel: IconTilesViewModel, - private val gridSizeViewModel: FixedColumnsSizeViewModel, -) : GridLayout { - - @Composable - override fun TileGrid( - tiles: List<TileViewModel>, - modifier: Modifier, - editModeStart: () -> Unit, - ) { - DisposableEffect(tiles) { - val token = Any() - tiles.forEach { it.startListening(token) } - onDispose { tiles.forEach { it.stopListening(token) } } - } - - // Tile widths [normal|stretched] - // Icon [3 | 4] - // Large [6 | 8] - val columns = 12 - val stretchedTiles = - remember(tiles) { - val sizedTiles = - tiles.map { - SizedTile( - it, - if (iconTilesViewModel.isIconTile(it.spec)) { - 3 - } else { - 6 - } - ) - } - splitInRows(sizedTiles, columns) - } - - TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) { - items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index -> - Tile( - tile = stretchedTiles[index].tile, - iconOnly = iconTilesViewModel.isIconTile(stretchedTiles[index].tile.spec), - modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) - ) - } - } - } - - @Composable - override fun EditTileGrid( - tiles: List<EditTileViewModel>, - modifier: Modifier, - onAddTile: (TileSpec, Int) -> Unit, - onRemoveTile: (TileSpec) -> Unit - ) { - val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() - - DefaultEditTileGrid( - tiles = tiles, - isIconOnly = iconTilesViewModel::isIconTile, - columns = columns, - modifier = modifier, - onAddTile = onAddTile, - onRemoveTile = onRemoveTile, - onResize = iconTilesViewModel::resize, - ) - } - - private fun splitInRows( - tiles: List<SizedTile<TileViewModel>>, - columns: Int - ): List<SizedTile<TileViewModel>> { - val row = TileRow<TileViewModel>(columns) - - return buildList { - for (tile in tiles) { - if (row.maybeAddTile(tile)) { - if (row.isFull()) { - // Row is full, no need to stretch tiles - addAll(row.tiles) - row.clear() - } - } else { - if (row.isFull()) { - addAll(row.tiles) - } else { - // Stretching tiles when row isn't full - addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) }) - } - row.clear() - row.maybeAddTile(tile) - } - } - addAll(row.tiles) - } - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt index 37c9552ded44..26405f0a2a57 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt @@ -14,11 +14,20 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.domain.interactor +package com.android.systemui.screenshot -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout -import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel +import android.view.Display -val Kosmos.partitionedGridLayout by - Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) } +interface InteractiveScreenshotHandler : ScreenshotHandler { + fun isPendingSharedTransition(): Boolean + + fun requestDismissal(event: ScreenshotEvent) + + fun removeWindow() + + fun onDestroy() + + interface Factory { + fun create(display: Display): InteractiveScreenshotHandler + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java new file mode 100644 index 000000000000..a2583e6cc08b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java @@ -0,0 +1,848 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; + +import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix; +import static com.android.systemui.Flags.screenshotSaveImageExporter; +import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; +import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; +import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; +import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; +import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; +import static com.android.systemui.screenshot.LogConfig.logTag; +import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER; +import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ICompatCameraControlCallback; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Insets; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.ScrollCaptureResponse; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewRootImpl; +import android.view.ViewTreeObserver; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.Toast; +import android.window.WindowContext; + +import com.android.internal.logging.UiEventLogger; +import com.android.internal.policy.PhoneWindow; +import com.android.settingslib.applications.InterestingConfigChanges; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.clipboardoverlay.ClipboardOverlayController; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; +import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; +import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor; +import com.android.systemui.util.Assert; + +import com.google.common.util.concurrent.ListenableFuture; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + +import kotlin.Unit; + +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +import javax.inject.Provider; + +/** + * Controls the state and flow for screenshots. + */ +public class LegacyScreenshotController implements InteractiveScreenshotHandler { + private static final String TAG = logTag(LegacyScreenshotController.class); + + // From WizardManagerHelper.java + private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; + + static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; + + private final WindowContext mContext; + private final FeatureFlags mFlags; + private final ScreenshotShelfViewProxy mViewProxy; + private final ScreenshotNotificationsController mNotificationsController; + private final ScreenshotSmartActions mScreenshotSmartActions; + private final UiEventLogger mUiEventLogger; + private final ImageExporter mImageExporter; + private final ImageCapture mImageCapture; + private final Executor mMainExecutor; + private final ExecutorService mBgExecutor; + private final BroadcastSender mBroadcastSender; + private final BroadcastDispatcher mBroadcastDispatcher; + private final ScreenshotActionsController mActionsController; + + private final WindowManager mWindowManager; + private final WindowManager.LayoutParams mWindowLayoutParams; + @Nullable + private final ScreenshotSoundController mScreenshotSoundController; + private final PhoneWindow mWindow; + private final Display mDisplay; + private final ScrollCaptureExecutor mScrollCaptureExecutor; + private final ScreenshotNotificationSmartActionsProvider + mScreenshotNotificationSmartActionsProvider; + private final TimeoutHandler mScreenshotHandler; + private final UserManager mUserManager; + private final AssistContentRequester mAssistContentRequester; + private final ActionExecutor mActionExecutor; + + + private final MessageContainerController mMessageContainerController; + private final AnnouncementResolver mAnnouncementResolver; + private Bitmap mScreenBitmap; + private SaveImageInBackgroundTask mSaveInBgTask; + private boolean mScreenshotTakenInPortrait; + private boolean mAttachRequested; + private boolean mDetachRequested; + private Animator mScreenshotAnimation; + private RequestCallback mCurrentRequestCallback; + private String mPackageName = ""; + private final BroadcastReceiver mCopyBroadcastReceiver; + + /** Tracks config changes that require re-creating UI */ + private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( + ActivityInfo.CONFIG_ORIENTATION + | ActivityInfo.CONFIG_LAYOUT_DIRECTION + | ActivityInfo.CONFIG_LOCALE + | ActivityInfo.CONFIG_UI_MODE + | ActivityInfo.CONFIG_SCREEN_LAYOUT + | ActivityInfo.CONFIG_ASSETS_PATHS); + + + @AssistedInject + LegacyScreenshotController( + Context context, + WindowManager windowManager, + FeatureFlags flags, + ScreenshotShelfViewProxy.Factory viewProxyFactory, + ScreenshotSmartActions screenshotSmartActions, + ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory, + UiEventLogger uiEventLogger, + ImageExporter imageExporter, + ImageCapture imageCapture, + @Main Executor mainExecutor, + ScrollCaptureExecutor scrollCaptureExecutor, + TimeoutHandler timeoutHandler, + BroadcastSender broadcastSender, + BroadcastDispatcher broadcastDispatcher, + ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, + ScreenshotActionsController.Factory screenshotActionsControllerFactory, + ActionExecutor.Factory actionExecutorFactory, + UserManager userManager, + AssistContentRequester assistContentRequester, + MessageContainerController messageContainerController, + Provider<ScreenshotSoundController> screenshotSoundController, + AnnouncementResolver announcementResolver, + @Assisted Display display + ) { + mScreenshotSmartActions = screenshotSmartActions; + mNotificationsController = screenshotNotificationsControllerFactory.create( + display.getDisplayId()); + mUiEventLogger = uiEventLogger; + mImageExporter = imageExporter; + mImageCapture = imageCapture; + mMainExecutor = mainExecutor; + mScrollCaptureExecutor = scrollCaptureExecutor; + mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider; + mBgExecutor = Executors.newSingleThreadExecutor(); + mBroadcastSender = broadcastSender; + mBroadcastDispatcher = broadcastDispatcher; + + mScreenshotHandler = timeoutHandler; + mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); + + mDisplay = display; + mWindowManager = windowManager; + final Context displayContext = context.createDisplayContext(display); + mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); + mFlags = flags; + mUserManager = userManager; + mMessageContainerController = messageContainerController; + mAssistContentRequester = assistContentRequester; + mAnnouncementResolver = announcementResolver; + + mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId()); + + mScreenshotHandler.setOnTimeoutRunnable(() -> { + if (DEBUG_UI) { + Log.d(TAG, "Corner timeout hit"); + } + mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT); + }); + + // Setup the window that we are going to use + mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); + mWindowLayoutParams.setTitle("ScreenshotAnimation"); + + mWindow = FloatingWindowUtil.getFloatingWindow(mContext); + mWindow.setWindowManager(mWindowManager, null, null); + + mConfigChanges.applyNewConfig(context.getResources()); + reloadAssets(); + + mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy, + () -> { + finishDismiss(); + return Unit.INSTANCE; + }); + mActionsController = screenshotActionsControllerFactory.getController(mActionExecutor); + + + // Sound is only reproduced from the controller of the default display. + if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) { + mScreenshotSoundController = screenshotSoundController.get(); + } else { + mScreenshotSoundController = null; + } + + mCopyBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) { + mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER); + } + } + }; + mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( + ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null, + Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION); + } + + @Override + public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, + RequestCallback requestCallback) { + Assert.isMainThread(); + + mCurrentRequestCallback = requestCallback; + if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN + && screenshot.getBitmap() == null) { + Rect bounds = getFullScreenRect(); + screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds)); + screenshot.setScreenBounds(bounds); + } + + if (screenshot.getBitmap() == null) { + Log.e(TAG, "handleScreenshot: Screenshot bitmap was null"); + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + if (mCurrentRequestCallback != null) { + mCurrentRequestCallback.reportError(); + } + return; + } + + mScreenBitmap = screenshot.getBitmap(); + String oldPackageName = mPackageName; + mPackageName = screenshot.getPackageNameString(); + + if (!isUserSetupComplete(Process.myUserHandle())) { + Log.w(TAG, "User setup not complete, displaying toast only"); + // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing + // and sharing shouldn't be exposed to the user. + saveScreenshotAndToast(screenshot, finisher); + return; + } + + mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), + ClipboardOverlayController.SELF_PERMISSION); + + mScreenshotTakenInPortrait = + mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; + + // Optimizations + mScreenBitmap.setHasAlpha(false); + mScreenBitmap.prepareToDraw(); + + prepareViewForNewScreenshot(screenshot, oldPackageName); + + final UUID requestId; + requestId = mActionsController.setCurrentScreenshot(screenshot); + saveScreenshotInBackground(screenshot, requestId, finisher, result -> { + if (result.uri != null) { + ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult( + result.uri, screenshot.getUserOrDefault(), result.timestamp); + mActionsController.setCompletedScreenshot(requestId, savedScreenshot); + } + }); + + if (screenshot.getTaskId() >= 0) { + mAssistContentRequester.requestAssistContent( + screenshot.getTaskId(), + assistContent -> + mActionsController.onAssistContent(requestId, assistContent)); + } else { + mActionsController.onAssistContent(requestId, null); + } + + // The window is focusable by default + setWindowFocusable(true); + mViewProxy.requestFocus(); + + enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); + + attachWindow(); + + boolean showFlash; + if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { + if (screenshot.getScreenBounds() != null + && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), + screenshot.getScreenBounds())) { + showFlash = false; + } else { + showFlash = true; + screenshot.setInsets(Insets.NONE); + screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(), + screenshot.getBitmap().getHeight())); + } + } else { + showFlash = true; + } + + mViewProxy.prepareEntranceAnimation( + () -> startAnimation(screenshot.getScreenBounds(), showFlash, + () -> mMessageContainerController.onScreenshotTaken(screenshot))); + + mViewProxy.setScreenshot(screenshot); + + // ignore system bar insets for the purpose of window layout + mWindow.getDecorView().setOnApplyWindowInsetsListener( + (v, insets) -> WindowInsets.CONSUMED); + } + + void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) { + withWindowAttached(() -> { + if (screenshotPrivateProfileAccessibilityAnnouncementFix()) { + mAnnouncementResolver.getScreenshotAnnouncement( + screenshot.getUserHandle().getIdentifier(), + mViewProxy::announceForAccessibility); + } else { + if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { + mViewProxy.announceForAccessibility(mContext.getResources().getString( + R.string.screenshot_saving_work_profile_title)); + } else { + mViewProxy.announceForAccessibility( + mContext.getResources().getString(R.string.screenshot_saving_title)); + } + } + }); + + mViewProxy.reset(); + + if (mViewProxy.isAttachedToWindow()) { + // if we didn't already dismiss for another reason + if (!mViewProxy.isDismissing()) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, + oldPackageName); + } + if (DEBUG_WINDOW) { + Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + + "(dismissing=" + mViewProxy.isDismissing() + ")"); + } + } + + mViewProxy.setPackageName(mPackageName); + } + + /** + * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already + * being dismissed) + */ + @Override + public void requestDismissal(ScreenshotEvent event) { + mViewProxy.requestDismissal(event); + } + + @Override + public boolean isPendingSharedTransition() { + return mActionExecutor.isPendingSharedTransition(); + } + + // Any cleanup needed when the service is being destroyed. + @Override + public void onDestroy() { + if (mSaveInBgTask != null) { + // just log success/failure for the pre-existing screenshot + mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); + } + removeWindow(); + releaseMediaPlayer(); + releaseContext(); + mBgExecutor.shutdown(); + } + + /** + * Release the constructed window context. + */ + private void releaseContext() { + mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver); + mContext.release(); + } + + private void releaseMediaPlayer() { + if (mScreenshotSoundController == null) return; + mScreenshotSoundController.releaseScreenshotSoundAsync(); + } + + /** + * Update resources on configuration change. Reinflate for theme/color changes. + */ + private void reloadAssets() { + if (DEBUG_UI) { + Log.d(TAG, "reloadAssets()"); + } + + mMessageContainerController.setView(mViewProxy.getView()); + mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() { + @Override + public void onUserInteraction() { + if (DEBUG_INPUT) { + Log.d(TAG, "onUserInteraction"); + } + mScreenshotHandler.resetTimeout(); + } + + @Override + public void onDismiss() { + finishDismiss(); + } + + @Override + public void onTouchOutside() { + // TODO(159460485): Remove this when focus is handled properly in the system + setWindowFocusable(false); + } + }); + + if (DEBUG_WINDOW) { + Log.d(TAG, "setContentView: " + mViewProxy.getView()); + } + mWindow.setContentView(mViewProxy.getView()); + } + + private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) { + // Wait until this window is attached to request because it is + // the reference used to locate the target window (below). + withWindowAttached(() -> { + requestScrollCapture(requestId, owner); + mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( + new ViewRootImpl.ActivityConfigCallback() { + @Override + public void onConfigurationChanged(Configuration overrideConfig, + int newDisplayId) { + if (mConfigChanges.applyNewConfig(mContext.getResources())) { + // Hide the scroll chip until we know it's available in this + // orientation + mActionsController.onScrollChipInvalidated(); + // Delay scroll capture eval a bit to allow the underlying activity + // to set up in the new orientation. + mScreenshotHandler.postDelayed( + () -> requestScrollCapture(requestId, owner), 150); + mViewProxy.updateInsets( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); + // Screenshot animation calculations won't be valid anymore, + // so just end + if (mScreenshotAnimation != null + && mScreenshotAnimation.isRunning()) { + mScreenshotAnimation.end(); + } + } + } + + @Override + public void requestCompatCameraControl(boolean showControl, + boolean transformationApplied, + ICompatCameraControlCallback callback) { + Log.w(TAG, "Unexpected requestCompatCameraControl callback"); + } + }); + }); + } + + private void requestScrollCapture(UUID requestId, UserHandle owner) { + mScrollCaptureExecutor.requestScrollCapture( + mDisplay.getDisplayId(), + mWindow.getDecorView().getWindowToken(), + (response) -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, + 0, response.getPackageName()); + mActionsController.onScrollChipReady(requestId, + () -> onScrollButtonClicked(owner, response)); + return Unit.INSTANCE; + } + ); + } + + private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) { + if (DEBUG_INPUT) { + Log.d(TAG, "scroll chip tapped"); + } + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0, + response.getPackageName()); + Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplay.getDisplayId(), + getFullScreenRect()); + if (newScreenshot == null) { + Log.e(TAG, "Failed to capture current screenshot for scroll transition!"); + return; + } + // delay starting scroll capture to make sure scrim is up before the app moves + mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, + mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner)); + } + + private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) { + mScrollCaptureExecutor.executeBatchScrollCapture(response, + () -> { + final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent( + owner, mContext); + mContext.startActivity(intent); + }, + mViewProxy::restoreNonScrollingUi, + mViewProxy::startLongScreenshotTransition); + } + + private void withWindowAttached(Runnable action) { + View decorView = mWindow.getDecorView(); + if (decorView.isAttachedToWindow()) { + action.run(); + } else { + decorView.getViewTreeObserver().addOnWindowAttachListener( + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + mAttachRequested = false; + decorView.getViewTreeObserver().removeOnWindowAttachListener(this); + action.run(); + } + + @Override + public void onWindowDetached() { + } + }); + + } + } + + @MainThread + private void attachWindow() { + View decorView = mWindow.getDecorView(); + if (decorView.isAttachedToWindow() || mAttachRequested) { + return; + } + if (DEBUG_WINDOW) { + Log.d(TAG, "attachWindow"); + } + mAttachRequested = true; + mWindowManager.addView(decorView, mWindowLayoutParams); + decorView.requestApplyInsets(); + + ViewGroup layout = decorView.requireViewById(android.R.id.content); + layout.setClipChildren(false); + layout.setClipToPadding(false); + } + + @Override + public void removeWindow() { + final View decorView = mWindow.peekDecorView(); + if (decorView != null && decorView.isAttachedToWindow()) { + if (DEBUG_WINDOW) { + Log.d(TAG, "Removing screenshot window"); + } + mWindowManager.removeViewImmediate(decorView); + mDetachRequested = false; + } + if (mAttachRequested && !mDetachRequested) { + mDetachRequested = true; + withWindowAttached(this::removeWindow); + } + + mViewProxy.stopInputListening(); + } + + private void playCameraSoundIfNeeded() { + if (mScreenshotSoundController == null) return; + // the controller is not-null only on the default display controller + mScreenshotSoundController.playScreenshotSoundAsync(); + } + + /** + * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on + * failure). + */ + private void saveScreenshotAndToast(ScreenshotData screenshot, Consumer<Uri> finisher) { + // Play the shutter sound to notify that we've taken a screenshot + playCameraSoundIfNeeded(); + + if (screenshotSaveImageExporter()) { + saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> { + if (result.uri != null) { + mScreenshotHandler.post(() -> Toast.makeText(mContext, + R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show()); + } + }); + } else { + saveScreenshotInWorkerThread( + screenshot.getUserHandle(), + /* onComplete */ finisher, + /* actionsReadyListener */ imageData -> { + if (DEBUG_CALLBACK) { + Log.d(TAG, + "returning URI to finisher (Consumer<URI>): " + imageData.uri); + } + finisher.accept(imageData.uri); + if (imageData.uri == null) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, + mPackageName); + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_save_text); + } else { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); + mScreenshotHandler.post(() -> Toast.makeText(mContext, + R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show()); + } + }, + null); + } + } + + /** + * Starts the animation after taking the screenshot + */ + private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) { + if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { + mScreenshotAnimation.cancel(); + } + + mScreenshotAnimation = + mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash); + if (onAnimationComplete != null) { + mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + onAnimationComplete.run(); + } + }); + } + + // Play the shutter sound to notify that we've taken a screenshot + playCameraSoundIfNeeded(); + + if (DEBUG_ANIM) { + Log.d(TAG, "starting post-screenshot animation"); + } + mScreenshotAnimation.start(); + } + + /** Reset screenshot view and then call onCompleteRunnable */ + private void finishDismiss() { + Log.d(TAG, "finishDismiss"); + mActionsController.endScreenshotSession(); + mScrollCaptureExecutor.close(); + if (mCurrentRequestCallback != null) { + mCurrentRequestCallback.onFinish(); + mCurrentRequestCallback = null; + } + mViewProxy.reset(); + removeWindow(); + mScreenshotHandler.cancelTimeout(); + } + + private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId, + Consumer<Uri> finisher, Consumer<ImageExporter.Result> onResult) { + ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor, + requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), + mDisplay.getDisplayId()); + future.addListener(() -> { + try { + ImageExporter.Result result = future.get(); + Log.d(TAG, "Saved screenshot: " + result); + logScreenshotResultStatus(result.uri, screenshot.getUserHandle()); + onResult.accept(result); + if (DEBUG_CALLBACK) { + Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) " + + "finisher.accept(\"" + result.uri + "\""); + } + finisher.accept(result.uri); + } catch (Exception e) { + Log.d(TAG, "Failed to store screenshot", e); + if (DEBUG_CALLBACK) { + Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)"); + } + finisher.accept(null); + } + }, mMainExecutor); + } + + /** + * Creates a new worker thread and saves the screenshot to the media store. + */ + private void saveScreenshotInWorkerThread( + UserHandle owner, + @NonNull Consumer<Uri> finisher, + @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener, + @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener + quickShareActionsReadyListener) { + SaveImageInBackgroundTask.SaveImageInBackgroundData + data = new SaveImageInBackgroundTask.SaveImageInBackgroundData(); + data.image = mScreenBitmap; + data.finisher = finisher; + data.mActionsReadyListener = actionsReadyListener; + data.mQuickShareActionsReadyListener = quickShareActionsReadyListener; + data.owner = owner; + data.displayId = mDisplay.getDisplayId(); + + if (mSaveInBgTask != null) { + // just log success/failure for the pre-existing screenshot + mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); + } + + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter, + mScreenshotSmartActions, data, + mScreenshotNotificationSmartActionsProvider); + mSaveInBgTask.execute(); + } + + /** + * Logs success/failure of the screenshot saving task, and shows an error if it failed. + */ + private void logScreenshotResultStatus(Uri uri, UserHandle owner) { + if (uri == null) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName); + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_save_text); + } else { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); + if (mUserManager.isManagedProfile(owner.getIdentifier())) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, + mPackageName); + } + } + } + + /** + * Logs success/failure of the screenshot saving task, and shows an error if it failed. + */ + private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) { + logScreenshotResultStatus(imageData.uri, imageData.owner); + } + + private boolean isUserSetupComplete(UserHandle owner) { + return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) + .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } + + /** + * Updates the window focusability. If the window is already showing, then it updates the + * window immediately, otherwise the layout params will be applied when the window is next + * shown. + */ + private void setWindowFocusable(boolean focusable) { + if (DEBUG_WINDOW) { + Log.d(TAG, "setWindowFocusable: " + focusable); + } + int flags = mWindowLayoutParams.flags; + if (focusable) { + mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + } else { + mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + } + if (mWindowLayoutParams.flags == flags) { + if (DEBUG_WINDOW) { + Log.d(TAG, "setWindowFocusable: skipping, already " + focusable); + } + return; + } + final View decorView = mWindow.peekDecorView(); + if (decorView != null && decorView.isAttachedToWindow()) { + mWindowManager.updateViewLayout(decorView, mWindowLayoutParams); + } + } + + private Rect getFullScreenRect() { + DisplayMetrics displayMetrics = new DisplayMetrics(); + mDisplay.getRealMetrics(displayMetrics); + return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels); + } + + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ + private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, + Rect screenBounds) { + int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; + int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom; + + if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 + || bitmap.getHeight() == 0) { + if (DEBUG_UI) { + Log.e(TAG, "Provided bitmap and insets create degenerate region: " + + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets); + } + return false; + } + + float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight; + float boundsAspect = ((float) screenBounds.width()) / screenBounds.height(); + + boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f; + if (DEBUG_UI) { + Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect + + ", bounds: " + boundsAspect); + } + return matchWithinTolerance; + } + + /** Injectable factory to create screenshot controller instances for a specific display. */ + @AssistedFactory + public interface Factory extends InteractiveScreenshotHandler.Factory { + /** + * Creates an instance of the controller for that specific display. + * + * @param display display to capture + */ + LegacyScreenshotController create(Display display); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 54ae225442c9..9bc3bd842664 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -286,7 +286,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE); Intent intent = new Intent(context, SmartActionsReceiver.class) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent) + .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, action.actionIntent) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */); PendingIntent broadcastIntent = PendingIntent.getBroadcast(context, @@ -302,9 +302,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static void addIntentExtras(String screenshotId, Intent intent, String actionType, boolean smartActionsEnabled) { intent - .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType) - .putExtra(ScreenshotController.EXTRA_ID, screenshotId) - .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled); + .putExtra(SmartActionsReceiver.EXTRA_ACTION_TYPE, actionType) + .putExtra(SmartActionsReceiver.EXTRA_ID, screenshotId) + .putExtra(SmartActionsReceiver.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled); } /** @@ -327,8 +327,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN, + .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, quickShare.actionIntent) + .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT_FILLIN, createFillInIntent(uri, imageTime)) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); Bundle extras = quickShare.getExtras(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 0a4635e0849b..653e49ffb08d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -95,28 +95,9 @@ import javax.inject.Provider; /** * Controls the state and flow for screenshots. */ -public class ScreenshotController implements ScreenshotHandler { +public class ScreenshotController implements InteractiveScreenshotHandler { private static final String TAG = logTag(ScreenshotController.class); - public interface TransitionDestination { - /** - * Allows the long screenshot activity to call back with a destination location (the bounds - * on screen of the destination for the transitioning view) and a Runnable to be run once - * the transition animation is complete. - */ - void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd); - } - - // These strings are used for communicating the action invoked to - // ScreenshotNotificationSmartActionsProvider. - public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; - public static final String EXTRA_ID = "android:screenshot_id"; - public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; - public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; - public static final String EXTRA_ACTION_INTENT_FILLIN = - "android:screenshot_action_intent_fillin"; - - // From WizardManagerHelper.java private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; @@ -378,9 +359,7 @@ public class ScreenshotController implements ScreenshotHandler { if (screenshotPrivateProfileAccessibilityAnnouncementFix()) { mAnnouncementResolver.getScreenshotAnnouncement( screenshot.getUserHandle().getIdentifier(), - announcement -> { - mViewProxy.announceForAccessibility(announcement); - }); + mViewProxy::announceForAccessibility); } else { if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { mViewProxy.announceForAccessibility(mContext.getResources().getString( @@ -413,16 +392,19 @@ public class ScreenshotController implements ScreenshotHandler { * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already * being dismissed) */ - void requestDismissal(ScreenshotEvent event) { + @Override + public void requestDismissal(ScreenshotEvent event) { mViewProxy.requestDismissal(event); } - boolean isPendingSharedTransition() { + @Override + public boolean isPendingSharedTransition() { return mActionExecutor.isPendingSharedTransition(); } // Any cleanup needed when the service is being destroyed. - void onDestroy() { + @Override + public void onDestroy() { if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); @@ -603,7 +585,8 @@ public class ScreenshotController implements ScreenshotHandler { layout.setClipToPadding(false); } - void removeWindow() { + @Override + public void removeWindow() { final View decorView = mWindow.peekDecorView(); if (decorView != null && decorView.isAttachedToWindow()) { if (DEBUG_WINDOW) { @@ -854,12 +837,12 @@ public class ScreenshotController implements ScreenshotHandler { /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory - public interface Factory { + public interface Factory extends InteractiveScreenshotHandler.Factory { /** * Creates an instance of the controller for that specific display. * * @param display display to capture */ - ScreenshotController create(Display display); + LegacyScreenshotController create(Display display); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java index f8b22a632000..f90269355b7d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java @@ -17,10 +17,6 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -36,6 +32,15 @@ import javax.inject.Inject; */ public class SmartActionsReceiver extends BroadcastReceiver { private static final String TAG = "SmartActionsReceiver"; + // These strings are used for communicating the action invoked to + // ScreenshotNotificationSmartActionsProvider. + public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; + public static final String EXTRA_ID = "android:screenshot_id"; + public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; + public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; + public static final String EXTRA_ACTION_INTENT_FILLIN = + "android:screenshot_action_intent_fillin"; + private final ScreenshotSmartActions mScreenshotSmartActions; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 07f6e85cfad8..50ea3bbc57e1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -74,7 +74,7 @@ interface ScreenshotHandler { class TakeScreenshotExecutorImpl @Inject constructor( - private val screenshotControllerFactory: ScreenshotController.Factory, + private val interactiveScreenshotHandlerFactory: InteractiveScreenshotHandler.Factory, displayRepository: DisplayRepository, @Application private val mainScope: CoroutineScope, private val screenshotRequestProcessor: ScreenshotRequestProcessor, @@ -83,7 +83,7 @@ constructor( private val headlessScreenshotHandler: HeadlessScreenshotHandler, ) : TakeScreenshotExecutor { private val displays = displayRepository.displays - private var screenshotController: ScreenshotController? = null + private var screenshotController: InteractiveScreenshotHandler? = null private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>() /** @@ -183,7 +183,7 @@ constructor( /** Propagates the close system dialog signal to the ScreenshotController. */ override fun onCloseSystemDialogsReceived() { - if (screenshotController?.isPendingSharedTransition == false) { + if (screenshotController?.isPendingSharedTransition() == false) { screenshotController?.requestDismissal(SCREENSHOT_DISMISSED_OTHER) } } @@ -218,8 +218,9 @@ constructor( } } - private fun getScreenshotController(display: Display): ScreenshotController { - val controller = screenshotController ?: screenshotControllerFactory.create(display) + private fun getScreenshotController(display: Display): InteractiveScreenshotHandler { + val controller = + screenshotController ?: interactiveScreenshotHandlerFactory.create(display) screenshotController = controller return controller } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java index 8feefa4eca0f..9db1f24d2e2e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java @@ -46,13 +46,17 @@ import android.os.Bundle; import android.os.ResultReceiver; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageView; +import android.widget.ListPopupWindow; import android.widget.TextView; import androidx.activity.ComponentActivity; import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; @@ -67,6 +71,7 @@ import com.android.systemui.res.R; import com.android.systemui.screenshot.scroll.CropView; import com.android.systemui.settings.UserTracker; +import java.util.List; import java.util.Set; import javax.inject.Inject; @@ -92,6 +97,7 @@ public class AppClipsActivity extends ComponentActivity { private static final String TAG = AppClipsActivity.class.getSimpleName(); private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0); + private static final int DRAWABLE_END = 2; private final AppClipsViewModel.Factory mViewModelFactory; private final PackageManager mPackageManager; @@ -192,6 +198,7 @@ public class AppClipsActivity extends ComponentActivity { mViewModel.getResultLiveData().observe(this, this::setResultThenFinish); mViewModel.getErrorLiveData().observe(this, this::setErrorThenFinish); mViewModel.getBacklinksLiveData().observe(this, this::setBacklinksData); + mViewModel.mSelectedBacklinksLiveData.observe(this, this::updateBacklinksTextView); if (savedInstanceState == null) { int displayId = getDisplayId(); @@ -305,8 +312,8 @@ public class AppClipsActivity extends ComponentActivity { if (mBacklinksIncludeDataCheckBox.getVisibility() == View.VISIBLE && mBacklinksIncludeDataCheckBox.isChecked() - && mViewModel.getBacklinksLiveData().getValue() != null) { - ClipData backlinksData = mViewModel.getBacklinksLiveData().getValue().getClipData(); + && mViewModel.mSelectedBacklinksLiveData.getValue() != null) { + ClipData backlinksData = mViewModel.mSelectedBacklinksLiveData.getValue().getClipData(); data.putParcelable(EXTRA_CLIP_DATA, backlinksData); DebugLogger.INSTANCE.logcatMessage(this, @@ -330,18 +337,80 @@ public class AppClipsActivity extends ComponentActivity { finish(); } - private void setBacklinksData(InternalBacklinksData backlinksData) { + private void setBacklinksData(List<InternalBacklinksData> backlinksData) { mBacklinksIncludeDataCheckBox.setVisibility(View.VISIBLE); mBacklinksDataTextView.setVisibility( mBacklinksIncludeDataCheckBox.isChecked() ? View.VISIBLE : View.GONE); - mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel()); + // Set up the dropdown when multiple backlinks are available. + if (backlinksData.size() > 1) { + setUpListPopupWindow(backlinksData, mBacklinksDataTextView); + } + } + + private void setUpListPopupWindow(List<InternalBacklinksData> backlinksData, View anchor) { + ListPopupWindow listPopupWindow = new ListPopupWindow(this); + listPopupWindow.setAnchorView(anchor); + listPopupWindow.setOverlapAnchor(true); + listPopupWindow.setBackgroundDrawable( + AppCompatResources.getDrawable(this, R.drawable.backlinks_rounded_rectangle)); + listPopupWindow.setOnItemClickListener((parent, view, position, id) -> { + mViewModel.mSelectedBacklinksLiveData.setValue(backlinksData.get(position)); + listPopupWindow.dismiss(); + }); + + ArrayAdapter<InternalBacklinksData> adapter = new ArrayAdapter<>(this, + R.layout.app_clips_backlinks_drop_down_entry) { + @Override + public View getView(int position, @Nullable View convertView, ViewGroup parent) { + TextView itemView = (TextView) super.getView(position, convertView, parent); + InternalBacklinksData data = backlinksData.get(position); + itemView.setText(data.getClipData().getDescription().getLabel()); + + Drawable icon = data.getAppIcon(); + icon.setBounds(createBacklinksTextViewDrawableBounds()); + itemView.setCompoundDrawablesRelative(/* start= */ icon, /* top= */ null, + /* end= */ null, /* bottom= */ null); + + return itemView; + } + }; + adapter.addAll(backlinksData); + listPopupWindow.setAdapter(adapter); + + mBacklinksDataTextView.setOnClickListener(unused -> listPopupWindow.show()); + } + /** + * Updates the {@link #mBacklinksDataTextView} with the currently selected + * {@link InternalBacklinksData}. The {@link AppClipsViewModel#getBacklinksLiveData()} is + * expected to be already set when this method is called. + */ + private void updateBacklinksTextView(InternalBacklinksData backlinksData) { + mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel()); Drawable appIcon = backlinksData.getAppIcon(); - int size = getResources().getDimensionPixelSize(R.dimen.appclips_backlinks_icon_size); - appIcon.setBounds(/* left= */ 0, /* top= */ 0, /* right= */ size, /* bottom= */ size); + Rect compoundDrawableBounds = createBacklinksTextViewDrawableBounds(); + appIcon.setBounds(compoundDrawableBounds); + + // Try to reuse the dropdown down arrow icon if available, will be null if never set. + Drawable dropDownIcon = mBacklinksDataTextView.getCompoundDrawablesRelative()[DRAWABLE_END]; + if (mViewModel.getBacklinksLiveData().getValue().size() > 1 && dropDownIcon == null) { + // Set up the dropdown down arrow drawable only if it is required. + dropDownIcon = AppCompatResources.getDrawable(this, R.drawable.arrow_pointing_down); + dropDownIcon.setBounds(compoundDrawableBounds); + dropDownIcon.setTint(Utils.getColorAttr(this, + android.R.attr.textColorSecondary).getDefaultColor()); + } + mBacklinksDataTextView.setCompoundDrawablesRelative(/* start= */ appIcon, /* top= */ - null, /* end= */ null, /* bottom= */ null); + null, /* end= */ dropDownIcon, /* bottom= */ null); + } + + private Rect createBacklinksTextViewDrawableBounds() { + int size = getResources().getDimensionPixelSize(R.dimen.appclips_backlinks_icon_size); + Rect bounds = new Rect(); + bounds.set(/* left= */ 0, /* top= */ 0, /* right= */ size, /* bottom= */ size); + return bounds; } private void setError(int errorCode) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java index bd9e295b58f8..3530b3ff0dfd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java @@ -21,12 +21,11 @@ import static android.content.Intent.ACTION_VIEW; import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED; import static android.content.Intent.CATEGORY_LAUNCHER; -import static com.google.common.util.concurrent.Futures.withTimeout; - import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.IActivityTaskManager; +import android.app.TaskInfo; import android.app.WindowConfiguration; import android.app.assist.AssistContent; import android.content.ClipData; @@ -41,7 +40,6 @@ import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.view.Display; @@ -94,7 +92,8 @@ final class AppClipsViewModel extends ViewModel { private final MutableLiveData<Bitmap> mScreenshotLiveData; private final MutableLiveData<Uri> mResultLiveData; private final MutableLiveData<Integer> mErrorLiveData; - private final MutableLiveData<InternalBacklinksData> mBacklinksLiveData; + private final MutableLiveData<List<InternalBacklinksData>> mBacklinksLiveData; + final MutableLiveData<InternalBacklinksData> mSelectedBacklinksLiveData; private AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper, ImageExporter imageExporter, IActivityTaskManager atmService, @@ -112,6 +111,7 @@ final class AppClipsViewModel extends ViewModel { mResultLiveData = new MutableLiveData<>(); mErrorLiveData = new MutableLiveData<>(); mBacklinksLiveData = new MutableLiveData<>(); + mSelectedBacklinksLiveData = new MutableLiveData<>(); } /** @@ -135,10 +135,11 @@ final class AppClipsViewModel extends ViewModel { /** * Triggers the Backlinks flow which: * <ul> - * <li>Evaluates the task to query. - * <li>Requests {@link AssistContent} from that task. - * <li>Transforms the {@link AssistContent} into {@link ClipData} for Backlinks. - * <li>The {@link ClipData} is reported to activity via {@link #getBacklinksLiveData()}. + * <li>Evaluates the tasks to query. + * <li>Requests {@link AssistContent} from all valid tasks. + * <li>Transforms {@link AssistContent} into {@link InternalBacklinksData} for Backlinks. + * <li>The {@link InternalBacklinksData}s are reported to activity via + * {@link #getBacklinksLiveData()}. * </ul> * * @param taskIdsToIgnore id of the tasks to ignore when querying for {@link AssistContent} @@ -146,24 +147,24 @@ final class AppClipsViewModel extends ViewModel { */ void triggerBacklinks(Set<Integer> taskIdsToIgnore, int displayId) { DebugLogger.INSTANCE.logcatMessage(this, () -> "Backlinks triggered"); - mBgExecutor.execute(() -> { - ListenableFuture<InternalBacklinksData> backlinksData = getBacklinksData( - taskIdsToIgnore, displayId); - Futures.addCallback(backlinksData, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable InternalBacklinksData result) { - if (result != null) { - mBacklinksLiveData.setValue(result); - } + ListenableFuture<List<InternalBacklinksData>> backlinksData = getAllAvailableBacklinks( + taskIdsToIgnore, displayId); + Futures.addCallback(backlinksData, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List<InternalBacklinksData> result) { + if (result != null && !result.isEmpty()) { + // Set the list of backlinks before setting the selected backlink as this is + // required when updating the backlink data text view. + mBacklinksLiveData.setValue(result); + mSelectedBacklinksLiveData.setValue(result.get(0)); } + } - @Override - public void onFailure(Throwable t) { - Log.e(TAG, "Error querying for Backlinks data", t); - } - }, mMainExecutor); - - }); + @Override + public void onFailure(Throwable t) { + Log.e(TAG, "Error querying for Backlinks data", t); + } + }, mMainExecutor); } /** Returns a {@link LiveData} that holds the captured screenshot. */ @@ -184,8 +185,11 @@ final class AppClipsViewModel extends ViewModel { return mErrorLiveData; } - /** Returns a {@link LiveData} that holds Backlinks data in {@link InternalBacklinksData}. */ - LiveData<InternalBacklinksData> getBacklinksLiveData() { + /** + * Returns a {@link LiveData} that holds all the available Backlinks data and the currently + * selected index for displaying the Backlinks in the UI. + */ + LiveData<List<InternalBacklinksData>> getBacklinksLiveData() { return mBacklinksLiveData; } @@ -230,26 +234,58 @@ final class AppClipsViewModel extends ViewModel { return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height()); } - private ListenableFuture<InternalBacklinksData> getBacklinksData(Set<Integer> taskIdsToIgnore, - int displayId) { - return getAllRootTaskInfosOnDisplay(displayId) - .stream() - .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore)) - .findFirst() - .map(this::getBacklinksDataForTaskId) - .orElse(Futures.immediateFuture(null)); + private ListenableFuture<List<InternalBacklinksData>> getAllAvailableBacklinks( + Set<Integer> taskIdsToIgnore, int displayId) { + ListenableFuture<List<TaskInfo>> allTasksOnDisplayFuture = getAllTasksOnDisplay(displayId); + + ListenableFuture<List<ListenableFuture<InternalBacklinksData>>> backlinksNestedListFuture = + Futures.transform(allTasksOnDisplayFuture, allTasksOnDisplay -> + allTasksOnDisplay + .stream() + .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore)) + .map(this::getBacklinksDataForTaskInfo) + .toList(), + mBgExecutor); + + return Futures.transformAsync(backlinksNestedListFuture, Futures::allAsList, mBgExecutor); } - private List<RootTaskInfo> getAllRootTaskInfosOnDisplay(int displayId) { - try { - return mAtmService.getAllRootTaskInfosOnDisplay(displayId); - } catch (RemoteException e) { - Log.e(TAG, String.format("Error while querying for tasks on display %d", displayId), e); - return Collections.emptyList(); - } + /** + * Returns all tasks on a given display after querying {@link IActivityTaskManager} from the + * {@link #mBgExecutor}. + */ + private ListenableFuture<List<TaskInfo>> getAllTasksOnDisplay(int displayId) { + SettableFuture<List<TaskInfo>> recentTasksFuture = SettableFuture.create(); + mBgExecutor.execute(() -> { + try { + // Directly call into ActivityTaskManagerService instead of going through WMShell + // because WMShell is only available in the main SysUI process and App Clips runs + // in its own separate process as it deals with bitmaps. + List<TaskInfo> allTasksOnDisplay = mAtmService.getTasks( + /* maxNum= */ Integer.MAX_VALUE, + // PIP tasks are not visible in recents. So _not_ filtering for + // tasks that are only visible in recents. + /* filterOnlyVisibleRecents= */ false, + /* keepIntentExtra= */ false, + displayId) + .stream() + .map(runningTaskInfo -> (TaskInfo) runningTaskInfo) + .toList(); + recentTasksFuture.set(allTasksOnDisplay); + } catch (Exception e) { + Log.e(TAG, String.format("Error getting all tasks on displayId %d", displayId), e); + recentTasksFuture.set(Collections.emptyList()); + } + }); + + return withTimeout(recentTasksFuture); } - private boolean shouldIncludeTask(RootTaskInfo taskInfo, Set<Integer> taskIdsToIgnore) { + /** + * Returns whether the app represented by the provided {@link TaskInfo} should be included for + * querying for {@link AssistContent}. + */ + private boolean shouldIncludeTask(TaskInfo taskInfo, Set<Integer> taskIdsToIgnore) { DebugLogger.INSTANCE.logcatMessage(this, () -> String.format("shouldIncludeTask taskId %d; topActivity %s", taskInfo.taskId, taskInfo.topActivity)); @@ -262,11 +298,14 @@ final class AppClipsViewModel extends ViewModel { && taskInfo.numActivities > 0 && taskInfo.topActivity != null && taskInfo.topActivityInfo != null - && taskInfo.childTaskIds.length > 0 && taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_STANDARD && canAppStartThroughLauncher(taskInfo.topActivity.getPackageName()); } + /** + * Returns whether the app represented by the provided {@code packageName} can be launched + * through the all apps tray by a user. + */ private boolean canAppStartThroughLauncher(String packageName) { // Use Intent.resolveActivity API to check if the intent resolves as that is what Android // uses internally when apps use Context.startActivity. @@ -274,8 +313,12 @@ final class AppClipsViewModel extends ViewModel { != null; } - private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskId( - RootTaskInfo taskInfo) { + /** + * Returns an {@link InternalBacklinksData} that represents the Backlink data internally, which + * is captured by querying the system using {@link TaskInfo#taskId}. + */ + private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskInfo( + TaskInfo taskInfo) { DebugLogger.INSTANCE.logcatMessage(this, () -> String.format("getBacklinksDataForTaskId for taskId %d; topActivity %s", taskInfo.taskId, taskInfo.topActivity)); @@ -284,7 +327,13 @@ final class AppClipsViewModel extends ViewModel { int taskId = taskInfo.taskId; mAssistContentRequester.requestAssistContent(taskId, assistContent -> backlinksData.set(getBacklinksDataFromAssistContent(taskInfo, assistContent))); - return withTimeout(backlinksData, 5L, TimeUnit.SECONDS, newSingleThreadScheduledExecutor()); + return withTimeout(backlinksData); + } + + /** Returns the same {@link ListenableFuture} but with a 5 {@link TimeUnit#SECONDS} timeout. */ + private static <V> ListenableFuture<V> withTimeout(ListenableFuture<V> future) { + return Futures.withTimeout(future, 5L, TimeUnit.SECONDS, + newSingleThreadScheduledExecutor()); } /** @@ -306,7 +355,7 @@ final class AppClipsViewModel extends ViewModel { * @param content the {@link AssistContent} to map into Backlinks {@link ClipData}. * @return {@link InternalBacklinksData} that represents the Backlinks data along with app icon. */ - private InternalBacklinksData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo, + private InternalBacklinksData getBacklinksDataFromAssistContent(TaskInfo taskInfo, @Nullable AssistContent content) { DebugLogger.INSTANCE.logcatMessage(this, () -> String.format("getBacklinksDataFromAssistContent taskId %d; topActivity %s", @@ -365,7 +414,7 @@ final class AppClipsViewModel extends ViewModel { return resolvedComponent.getPackageName().equals(requiredPackageName); } - private String getAppNameOfTask(RootTaskInfo taskInfo) { + private String getAppNameOfTask(TaskInfo taskInfo) { return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index 682f848bfaad..254dde45148c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -16,12 +16,17 @@ package com.android.systemui.screenshot.dagger; +import static com.android.systemui.Flags.screenshotUiControllerRefactor; + import android.app.Service; import android.view.accessibility.AccessibilityManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.screenshot.ImageCapture; import com.android.systemui.screenshot.ImageCaptureImpl; +import com.android.systemui.screenshot.InteractiveScreenshotHandler; +import com.android.systemui.screenshot.LegacyScreenshotController; +import com.android.systemui.screenshot.ScreenshotController; import com.android.systemui.screenshot.ScreenshotPolicy; import com.android.systemui.screenshot.ScreenshotPolicyImpl; import com.android.systemui.screenshot.ScreenshotSoundController; @@ -90,4 +95,15 @@ public abstract class ScreenshotModule { AccessibilityManager accessibilityManager) { return new ScreenshotViewModel(accessibilityManager); } + + @Provides + static InteractiveScreenshotHandler.Factory providesScreenshotController( + LegacyScreenshotController.Factory legacyScreenshotController, + ScreenshotController.Factory screenshotController) { + if (screenshotUiControllerRefactor()) { + return screenshotController; + } else { + return legacyScreenshotController; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java index ebac5bf2debd..08c1fcaced5e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java @@ -16,8 +16,9 @@ package com.android.systemui.screenshot.scroll; +import android.graphics.Rect; + import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.screenshot.ScreenshotController; import java.util.concurrent.atomic.AtomicReference; @@ -30,9 +31,18 @@ import javax.inject.Inject; @SysUISingleton public class LongScreenshotData { private final AtomicReference<ScrollCaptureController.LongScreenshot> mLongScreenshot; - private final AtomicReference<ScreenshotController.TransitionDestination> + private final AtomicReference<TransitionDestination> mTransitionDestinationCallback; + public interface TransitionDestination { + /** + * Allows the long screenshot activity to call back with a destination location (the bounds + * on screen of the destination for the transitioning view) and a Runnable to be run once + * the transition animation is complete. + */ + void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd); + } + @Inject public LongScreenshotData() { mLongScreenshot = new AtomicReference<>(); @@ -63,15 +73,14 @@ public class LongScreenshotData { /** * Set the holder's TransitionDestination callback. */ - public void setTransitionDestinationCallback( - ScreenshotController.TransitionDestination destination) { + public void setTransitionDestinationCallback(TransitionDestination destination) { mTransitionDestinationCallback.set(destination); } /** * Return the current TransitionDestination callback and clear. */ - public ScreenshotController.TransitionDestination takeTransitionDestinationCallback() { + public TransitionDestination takeTransitionDestinationCallback() { return mTransitionDestinationCallback.getAndSet(null); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt index 4869114bc565..89227cfb2b23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import javax.inject.Inject @@ -33,6 +34,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** @@ -54,12 +56,20 @@ constructor( keyguardStatusBarInteractor: KeyguardStatusBarInteractor, batteryController: BatteryController, ) { + + private val showingHeadsUpStatusBar: Flow<Boolean> = + if (NotificationsHeadsUpRefactor.isEnabled) { + headsUpNotificationInteractor.showHeadsUpStatusBar + } else { + flowOf(false) + } + /** True if this view should be visible and false otherwise. */ val isVisible: StateFlow<Boolean> = combine( sceneInteractor.currentScene, keyguardInteractor.isDozing, - headsUpNotificationInteractor.showHeadsUpStatusBar, + showingHeadsUpStatusBar, ) { currentScene, isDozing, showHeadsUpStatusBar -> currentScene == Scenes.Lockscreen && !isDozing && !showHeadsUpStatusBar } diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java index 8ab5bc68fa26..169f865fb1dd 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java @@ -26,8 +26,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; @@ -64,7 +64,7 @@ class ProximitySensorImpl implements ProximitySensor { ThresholdSensor mSecondaryThresholdSensor; private final DelayableExecutor mDelayableExecutor; private final Execution mExecution; - private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>(); + private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); private String mTag = null; @VisibleForTesting protected boolean mPaused; private ThresholdSensorEvent mLastPrimaryEvent; @@ -246,7 +246,7 @@ class ProximitySensorImpl implements ProximitySensor { public void unregister(ThresholdSensor.Listener listener) { mExecution.assertIsMainThread(); mListeners.remove(listener); - if (mListeners.size() == 0) { + if (mListeners.isEmpty()) { unregisterInternal(); } } @@ -296,8 +296,7 @@ class ProximitySensorImpl implements ProximitySensor { } if (mLastEvent != null) { ThresholdSensorEvent lastEvent = mLastEvent; // Listeners can null out mLastEvent. - List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners); - listeners.forEach(proximitySensorListener -> + mListeners.forEach(proximitySensorListener -> proximitySensorListener.onThresholdCrossed(lastEvent)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 6dcea144f2a3..9df653f1550b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -74,7 +74,6 @@ import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticat import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -92,6 +91,7 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -1379,6 +1379,28 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_BP_TALKBACK) + fun no_hint_for_talkback_guidance_after_auth() = runGenericTest { + val hint by collectLastValue(kosmos.promptViewModel.accessibilityHint) + + kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0) + kosmos.promptViewModel.confirmAuthenticated() + + // Touches should fall outside of sensor area + whenever(kosmos.udfpsUtils.getTouchInNativeCoordinates(any(), any(), any())) + .thenReturn(Point(0, 0)) + whenever(kosmos.udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any())) + .thenReturn("Direction") + + kosmos.promptViewModel.onAnnounceAccessibilityHint( + obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER), + true + ) + + assertThat(hint.isNullOrBlank()).isTrue() + } + + @Test @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun descriptionOverriddenByVerticalListContentView() = runGenericTest(description = "test description", contentView = promptContentView) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt index 24e8b1886438..5e07aef7d8e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt @@ -228,7 +228,7 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() { ) val quickSharePendingIntent = quickShareAction.actionIntent.intent.extras!!.getParcelable( - ScreenshotController.EXTRA_ACTION_INTENT, + SmartActionsReceiver.EXTRA_ACTION_INTENT, PendingIntent::class.java ) @@ -266,7 +266,7 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() { assertEquals( immutablePendingIntent, quickShareAction.actionIntent.intent.extras!!.getParcelable( - ScreenshotController.EXTRA_ACTION_INTENT, + SmartActionsReceiver.EXTRA_ACTION_INTENT, PendingIntent::class.java ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java index 471fdc0b8fde..9dc5cfed2bd6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java @@ -16,8 +16,8 @@ package com.android.systemui.screenshot; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID; +import static com.android.systemui.screenshot.SmartActionsReceiver.EXTRA_ACTION_TYPE; +import static com.android.systemui.screenshot.SmartActionsReceiver.EXTRA_ID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -57,7 +57,7 @@ public class SmartActionsReceiverTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mSmartActionsReceiver = new SmartActionsReceiver(mMockScreenshotSmartActions); mIntent = new Intent(mContext, SmartActionsReceiver.class) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, mMockPendingIntent); + .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, mMockPendingIntent); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index 8d3a29ac7278..a2959811cd0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -42,10 +42,10 @@ import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest class TakeScreenshotExecutorTest : SysuiTestCase() { - private val controller = mock<ScreenshotController>() + private val controller = mock<LegacyScreenshotController>() private val notificationsController0 = mock<ScreenshotNotificationsController>() private val notificationsController1 = mock<ScreenshotNotificationsController>() - private val controllerFactory = mock<ScreenshotController.Factory>() + private val controllerFactory = mock<InteractiveScreenshotHandler.Factory>() private val callback = mock<TakeScreenshotService.RequestCallback>() private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>() @@ -287,7 +287,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun onCloseSystemDialogsReceived_controllerHasPendingTransitions() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - whenever(controller.isPendingSharedTransition).thenReturn(true) + whenever(controller.isPendingSharedTransition()).thenReturn(true) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java index 998620563a29..a8d5008c61c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java @@ -17,6 +17,7 @@ package com.android.systemui.screenshot.appclips; import static android.app.Activity.RESULT_OK; +import static android.app.ActivityManager.RunningTaskInfo; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED; @@ -32,7 +33,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.ActivityTaskManager.RootTaskInfo; import android.app.IActivityTaskManager; import android.app.assist.AssistContent; import android.content.ComponentName; @@ -103,7 +103,7 @@ public final class AppClipsActivityTest extends SysuiTestCase { private static final String BACKLINKS_TASK_APP_NAME = "Backlinks app"; private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName"; - private static final RootTaskInfo TASK_THAT_SUPPORTS_BACKLINKS = + private static final RunningTaskInfo TASK_THAT_SUPPORTS_BACKLINKS = createTaskInfoForBacklinksTask(); private static final AssistContent ASSIST_CONTENT_FOR_BACKLINKS_TASK = createAssistContentForBacklinksTask(); @@ -233,6 +233,10 @@ public final class AppClipsActivityTest extends SysuiTestCase { assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME); assertThat(backlinksData.getCompoundDrawablesRelative()[0]).isEqualTo(FAKE_DRAWABLE); + // Verify dropdown icon is not shown and there are no click listeners on text view. + assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNull(); + assertThat(backlinksData.hasOnClickListeners()).isFalse(); + CheckBox backlinksIncludeData = mActivity.findViewById(R.id.backlinks_include_data); assertThat(backlinksIncludeData.getVisibility()).isEqualTo(View.VISIBLE); assertThat(backlinksIncludeData.getText().toString()) @@ -258,20 +262,71 @@ public final class AppClipsActivityTest extends SysuiTestCase { assertThat(backlinksData.getVisibility()).isEqualTo(View.GONE); } + @Test + @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS) + public void appClipsLaunched_backlinks_multipleBacklinksAvailable_defaultShown() + throws RemoteException { + // Set up mocking for multiple backlinks. + ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo(); + + int taskId2 = BACKLINKS_TASK_ID + 2; + String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2; + String appName2 = BACKLINKS_TASK_APP_NAME + 2; + + ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo(); + ActivityInfo activityInfo2 = resolveInfo2.activityInfo; + activityInfo2.name = appName2; + activityInfo2.packageName = package2; + activityInfo2.applicationInfo.packageName = package2; + RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask(); + runningTaskInfo2.taskId = taskId2; + runningTaskInfo2.topActivity = new ComponentName(package2, "backlinksClass"); + runningTaskInfo2.topActivityInfo = resolveInfo2.activityInfo; + runningTaskInfo2.baseIntent = new Intent().setComponent(runningTaskInfo2.topActivity); + + when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false), + mDisplayIdCaptor.capture())) + .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS, runningTaskInfo2)); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(resolveInfo1, + resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2); + when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE); + + // Using same AssistContent data for both tasks. + mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID); + mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, taskId2); + + // Mocking complete, trigger backlinks. + launchActivity(); + waitForIdleSync(); + + // Verify default backlink shown to user and text view has on click listener. + TextView backlinksData = mActivity.findViewById(R.id.backlinks_data); + assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME); + assertThat(backlinksData.hasOnClickListeners()).isTrue(); + + // Verify dropdown icon is not null. + assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNotNull(); + } + private void setUpMocksForBacklinks() throws RemoteException { - when(mAtmService.getAllRootTaskInfosOnDisplay(mDisplayIdCaptor.capture())) + when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false), + mDisplayIdCaptor.capture())) .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS)); - doAnswer(invocation -> { - AssistContentRequester.Callback callback = invocation.getArgument(1); - callback.onAssistContentAvailable(ASSIST_CONTENT_FOR_BACKLINKS_TASK); - return null; - }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID); when(mPackageManager .resolveActivity(any(Intent.class), anyInt())) .thenReturn(createBacklinksTaskResolveInfo()); when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE); } + private void mockForAssistContent(AssistContent expected, int taskId) { + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(expected); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(taskId), any()); + } + private void launchActivity() { launchActivity(createResultReceiver(FAKE_CONSUMER)); } @@ -319,8 +374,8 @@ public final class AppClipsActivityTest extends SysuiTestCase { return resolveInfo; } - private static RootTaskInfo createTaskInfoForBacklinksTask() { - RootTaskInfo taskInfo = new RootTaskInfo(); + private static RunningTaskInfo createTaskInfoForBacklinksTask() { + RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = BACKLINKS_TASK_ID; taskInfo.isVisible = true; taskInfo.isRunning = true; @@ -328,7 +383,6 @@ public final class AppClipsActivityTest extends SysuiTestCase { taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass"); taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo; taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity); - taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1}; taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); return taskInfo; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java index 193d29c1d550..178547e4ca3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.ActivityTaskManager.RootTaskInfo; +import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityTaskManager; import android.app.assist.AssistContent; import android.content.ClipData; @@ -107,7 +107,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { mPackageManagerIntentCaptor = ArgumentCaptor.forClass(Intent.class); // Set up mocking for backlinks. - when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY)) + when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY)) .thenReturn(List.of(createTaskInfoForBacklinksTask())); when(mPackageManager.resolveActivity(mPackageManagerIntentCaptor.capture(), anyInt())) .thenReturn(createBacklinksTaskResolveInfo()); @@ -190,11 +190,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { Uri expectedUri = Uri.parse("https://developers.android.com"); AssistContent contentWithUri = new AssistContent(); contentWithUri.setWebUri(expectedUri); - doAnswer(invocation -> { - AssistContentRequester.Callback callback = invocation.getArgument(1); - callback.onAssistContentAvailable(contentWithUri); - return null; - }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID); mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); waitForIdleSync(); @@ -203,7 +199,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { assertThat(queriedIntent.getData()).isEqualTo(expectedUri); assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW); - InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue(); + InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue(); assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE); ClipData clipData = result.getClipData(); ClipDescription resultDescription = clipData.getDescription(); @@ -211,6 +207,8 @@ public final class AppClipsViewModelTest extends SysuiTestCase { assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_URILIST); assertThat(clipData.getItemCount()).isEqualTo(1); assertThat(clipData.getItemAt(0).getUri()).isEqualTo(expectedUri); + + assertThat(result).isEqualTo(mViewModel.getBacklinksLiveData().getValue().get(0)); } @Test @@ -218,12 +216,8 @@ public final class AppClipsViewModelTest extends SysuiTestCase { Uri expectedUri = Uri.parse("https://developers.android.com"); AssistContent contentWithUri = new AssistContent(); contentWithUri.setWebUri(expectedUri); + mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID); resetPackageManagerMockingForUsingFallbackBacklinks(); - doAnswer(invocation -> { - AssistContentRequester.Callback callback = invocation.getArgument(1); - callback.onAssistContentAvailable(contentWithUri); - return null; - }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); waitForIdleSync(); @@ -236,11 +230,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME); AssistContent contentWithAppProvidedIntent = new AssistContent(); contentWithAppProvidedIntent.setIntent(expectedIntent); - doAnswer(invocation -> { - AssistContentRequester.Callback callback = invocation.getArgument(1); - callback.onAssistContentAvailable(contentWithAppProvidedIntent); - return null; - }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID); mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); waitForIdleSync(); @@ -248,7 +238,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { Intent queriedIntent = mPackageManagerIntentCaptor.getValue(); assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage()); - InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue(); + InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue(); assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE); ClipData clipData = result.getClipData(); ClipDescription resultDescription = clipData.getDescription(); @@ -263,12 +253,8 @@ public final class AppClipsViewModelTest extends SysuiTestCase { Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME); AssistContent contentWithAppProvidedIntent = new AssistContent(); contentWithAppProvidedIntent.setIntent(expectedIntent); + mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID); resetPackageManagerMockingForUsingFallbackBacklinks(); - doAnswer(invocation -> { - AssistContentRequester.Callback callback = invocation.getArgument(1); - callback.onAssistContentAvailable(contentWithAppProvidedIntent); - return null; - }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); waitForIdleSync(); @@ -278,11 +264,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { @Test public void triggerBacklinks_shouldUpdateBacklinks_withMainLauncherIntent() { - doAnswer(invocation -> { - AssistContentRequester.Callback callback = invocation.getArgument(1); - callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT); - return null; - }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID); mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); waitForIdleSync(); @@ -298,15 +280,12 @@ public final class AppClipsViewModelTest extends SysuiTestCase { @Test public void triggerBacklinks_withNonResolvableMainLauncherIntent_noBacklinksAvailable() { reset(mPackageManager); - doAnswer(invocation -> { - AssistContentRequester.Callback callback = invocation.getArgument(1); - callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT); - return null; - }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID); mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); waitForIdleSync(); + assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull(); assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull(); } @@ -314,14 +293,15 @@ public final class AppClipsViewModelTest extends SysuiTestCase { public void triggerBacklinks_nonStandardActivityIgnored_noBacklinkAvailable() throws RemoteException { reset(mAtmService); - RootTaskInfo taskInfo = createTaskInfoForBacklinksTask(); + RunningTaskInfo taskInfo = createTaskInfoForBacklinksTask(); taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME); - when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY)) + when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY)) .thenReturn(List.of(taskInfo)); mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); waitForIdleSync(); + assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull(); assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull(); } @@ -330,9 +310,68 @@ public final class AppClipsViewModelTest extends SysuiTestCase { mViewModel.triggerBacklinks(Set.of(BACKLINKS_TASK_ID), DEFAULT_DISPLAY); waitForIdleSync(); + assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull(); assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull(); } + @Test + public void triggerBacklinks_multipleAppsOnScreen_multipleBacklinksAvailable() + throws RemoteException { + // Set up mocking for multiple backlinks. + reset(mAtmService, mPackageManager); + RunningTaskInfo runningTaskInfo1 = createTaskInfoForBacklinksTask(); + ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo(); + + int taskId2 = BACKLINKS_TASK_ID + 2; + String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2; + String appName2 = BACKLINKS_TASK_APP_NAME + 2; + + ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo(); + ActivityInfo activityInfo2 = resolveInfo2.activityInfo; + activityInfo2.name = appName2; + activityInfo2.packageName = package2; + activityInfo2.applicationInfo.packageName = package2; + RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask(); + runningTaskInfo2.taskId = taskId2; + runningTaskInfo2.topActivity = new ComponentName(package2, "backlinksClass"); + runningTaskInfo2.topActivityInfo = resolveInfo2.activityInfo; + runningTaskInfo2.baseIntent = new Intent().setComponent(runningTaskInfo2.topActivity); + + // For each task, the logic queries PM 3 times, twice for verifying if an app can be + // launched via launcher and once with the data provided in backlink intent. + when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo1, + resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2); + when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE); + when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY)) + .thenReturn(List.of(runningTaskInfo1, runningTaskInfo2)); + + // Using app provided web uri for the first backlink. + Uri expectedUri = Uri.parse("https://developers.android.com"); + AssistContent contentWithUri = new AssistContent(); + contentWithUri.setWebUri(expectedUri); + mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID); + + // Using app provided intent for the second backlink. + Intent expectedIntent = new Intent().setPackage(package2); + AssistContent contentWithAppProvidedIntent = new AssistContent(); + contentWithAppProvidedIntent.setIntent(expectedIntent); + mockForAssistContent(contentWithAppProvidedIntent, taskId2); + + // Set up complete, trigger the backlinks action. + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + // Verify two backlinks are received and the first backlink is set as default selected. + assertThat(mViewModel.mSelectedBacklinksLiveData.getValue().getClipData().getItemAt( + 0).getUri()).isEqualTo(expectedUri); + List<InternalBacklinksData> actualBacklinks = mViewModel.getBacklinksLiveData().getValue(); + assertThat(actualBacklinks).hasSize(2); + assertThat(actualBacklinks.get(0).getClipData().getItemAt(0).getUri()) + .isEqualTo(expectedUri); + assertThat(actualBacklinks.get(1).getClipData().getItemAt(0).getIntent()) + .isEqualTo(expectedIntent); + } + private void resetPackageManagerMockingForUsingFallbackBacklinks() { ResolveInfo backlinksTaskResolveInfo = createBacklinksTaskResolveInfo(); reset(mPackageManager); @@ -350,7 +389,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { } private void verifyMainLauncherBacklinksIntent() { - InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue(); + InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue(); assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE); ClipData clipData = result.getClipData(); @@ -368,6 +407,14 @@ public final class AppClipsViewModelTest extends SysuiTestCase { new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, BACKLINKS_TASK_APP_NAME)); } + private void mockForAssistContent(AssistContent expected, int taskId) { + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(expected); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(taskId), any()); + } + private static ResolveInfo createBacklinksTaskResolveInfo() { ActivityInfo activityInfo = new ActivityInfo(); activityInfo.applicationInfo = new ApplicationInfo(); @@ -379,8 +426,8 @@ public final class AppClipsViewModelTest extends SysuiTestCase { return resolveInfo; } - private static RootTaskInfo createTaskInfoForBacklinksTask() { - RootTaskInfo taskInfo = new RootTaskInfo(); + private static RunningTaskInfo createTaskInfoForBacklinksTask() { + RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = BACKLINKS_TASK_ID; taskInfo.isVisible = true; taskInfo.isRunning = true; @@ -388,7 +435,6 @@ public final class AppClipsViewModelTest extends SysuiTestCase { taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass"); taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo; taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity); - taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1}; taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); return taskInfo; } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt index 5568c6c3c310..34e99d3a9a3c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt @@ -20,24 +20,13 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType -import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout val Kosmos.gridLayoutTypeInteractor by Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) } val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by - Kosmos.Fixture { - mapOf( - Pair(PartitionedGridLayoutType, partitionedGridLayout), - Pair(InfiniteGridLayoutType, infiniteGridLayout) - ) - } + Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) } var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by - Kosmos.Fixture { - mapOf( - Pair(PartitionedGridLayoutType, noopGridConsistencyInteractor), - Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor) - ) - } + Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt index 6625bb54fa76..9481fcac97d6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt @@ -20,7 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor -import com.android.systemui.qs.panels.domain.interactor.partitionedGridLayout +import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor val Kosmos.tileGridViewModel by @@ -29,7 +29,7 @@ val Kosmos.tileGridViewModel by gridLayoutTypeInteractor, gridLayoutMap, currentTilesInteractor, - partitionedGridLayout, + infiniteGridLayout, applicationCoroutineScope, ) } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 3633d0f9dd6f..33cf84220009 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -55,6 +55,7 @@ import android.telephony.Annotation.SrvccState; import android.telephony.BarringInfo; import android.telephony.CallQuality; import android.telephony.CallState; +import android.telephony.CarrierConfigManager; import android.telephony.CellIdentity; import android.telephony.CellInfo; import android.telephony.CellSignalStrength; @@ -426,6 +427,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private boolean[] mSCBMStarted; private boolean[] mCarrierRoamingNtnMode = null; + private boolean[] mCarrierRoamingNtnEligible = null; /** * Per-phone map of precise data connection state. The key of the map is the pair of transport @@ -726,6 +728,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mSCBMReason = copyOf(mSCBMReason, mNumPhones); mSCBMStarted = copyOf(mSCBMStarted, mNumPhones); mCarrierRoamingNtnMode = copyOf(mCarrierRoamingNtnMode, mNumPhones); + mCarrierRoamingNtnEligible = copyOf(mCarrierRoamingNtnEligible, mNumPhones); // ds -> ss switch. if (mNumPhones < oldNumPhones) { cutListToSize(mCellInfo, mNumPhones); @@ -785,6 +788,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN; mSCBMStarted[i] = false; mCarrierRoamingNtnMode[i] = false; + mCarrierRoamingNtnEligible[i] = false; } } } @@ -859,6 +863,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mSCBMReason = new int[numPhones]; mSCBMStarted = new boolean[numPhones]; mCarrierRoamingNtnMode = new boolean[numPhones]; + mCarrierRoamingNtnEligible = new boolean[numPhones]; for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; @@ -903,6 +908,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN; mSCBMStarted[i] = false; mCarrierRoamingNtnMode[i] = false; + mCarrierRoamingNtnEligible[i] = false; } mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -1518,6 +1524,15 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if (events.contains( + TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED)) { + try { + r.callback.onCarrierRoamingNtnEligibleStateChanged( + mCarrierRoamingNtnEligible[r.phoneId]); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } @@ -3536,6 +3551,53 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + /** + * Notify external listeners that device eligibility to connect to carrier roaming + * non-terrestrial network changed. + * + * @param subId subscription ID. + * @param eligible {@code true} when the device is eligible for satellite + * communication if all the following conditions are met: + * <ul> + * <li>Any subscription on the device supports P2P satellite messaging which is defined by + * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li> + * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to + * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li> + * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi, + * and the hysteresis timer defined by {@link CarrierConfigManager + * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li> + * </ul> + */ + public void notifyCarrierRoamingNtnEligibleStateChanged(int subId, boolean eligible) { + if (!checkNotifyPermission("notifyCarrierRoamingNtnEligibleStateChanged")) { + log("notifyCarrierRoamingNtnEligibleStateChanged: caller does not have required " + + "permissions."); + return; + } + + if (VDBG) { + log("notifyCarrierRoamingNtnEligibleStateChanged: " + + "subId=" + subId + " eligible" + eligible); + } + + synchronized (mRecords) { + int phoneId = getPhoneIdFromSubId(subId); + mCarrierRoamingNtnEligible[phoneId] = eligible; + for (Record r : mRecords) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED) + && idMatch(r, subId, phoneId)) { + try { + r.callback.onCarrierRoamingNtnEligibleStateChanged(eligible); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } + } + @NeverCompile // Avoid size overhead of debugging code. @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { @@ -3589,6 +3651,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mECBMStarted=" + mECBMStarted[i]); pw.println("mSCBMReason=" + mSCBMReason[i]); pw.println("mSCBMStarted=" + mSCBMStarted[i]); + pw.println("mCarrierRoamingNtnMode=" + mCarrierRoamingNtnMode[i]); + pw.println("mCarrierRoamingNtnEligible=" + mCarrierRoamingNtnEligible[i]); // We need to obfuscate package names, and primitive arrays' native toString is ugly Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 8df4e7702be8..e46ab8f0f1cb 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -124,6 +124,7 @@ import com.android.server.net.BaseNetworkObserver; import com.android.server.pm.UserManagerInternal; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.AggregatedPowerStatsConfig; +import com.android.server.power.stats.AmbientDisplayPowerStatsProcessor; import com.android.server.power.stats.AudioPowerStatsProcessor; import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsDumpHelperImpl; @@ -142,6 +143,7 @@ import com.android.server.power.stats.PowerStatsExporter; import com.android.server.power.stats.PowerStatsScheduler; import com.android.server.power.stats.PowerStatsStore; import com.android.server.power.stats.PowerStatsUidResolver; +import com.android.server.power.stats.ScreenPowerStatsProcessor; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import com.android.server.power.stats.VideoPowerStatsProcessor; import com.android.server.power.stats.WifiPowerStatsProcessor; @@ -488,6 +490,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setProcessor( new CpuPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies)); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .setProcessor( + new ScreenPowerStatsProcessor(mPowerProfile)); + + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, + BatteryConsumer.POWER_COMPONENT_SCREEN) + .setProcessor(new AmbientDisplayPowerStatsProcessor()); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) .trackDeviceStates( AggregatedPowerStatsConfig.STATE_POWER, @@ -636,6 +652,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryConsumer.POWER_COMPONENT_CPU, Flags.streamlinedBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_SCREEN, + Flags.streamlinedMiscBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_SCREEN, + Flags.streamlinedMiscBatteryStats()); + + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, + Flags.streamlinedMiscBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, + Flags.streamlinedMiscBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, Flags.streamlinedConnectivityBatteryStats()); mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 36a9c80717f2..f813997a8fec 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1259,9 +1259,9 @@ public class InputManagerService extends IInputManager.Stub * @param dragAndDropChannel The input channel associated with the system drag window. * @return true if drag and drop was successfully started, false otherwise. */ - public boolean startDragAndDrop(@NonNull InputChannel fromChannel, + public boolean startDragAndDrop(@NonNull IBinder fromChannelToken, @NonNull InputChannel dragAndDropChannel) { - return mNative.transferTouchGesture(fromChannel.getToken(), dragAndDropChannel.getToken(), + return mNative.transferTouchGesture(fromChannelToken, dragAndDropChannel.getToken(), true /* isDragDrop */); } diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index 7ebf5950de16..ae411338a4dc 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -36,6 +36,7 @@ import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_ import static com.android.server.inputmethod.InputMethodManagerService.computeImeDisplayIdForTarget; import android.accessibilityservice.AccessibilityService; +import android.annotation.AnyThread; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -83,6 +84,7 @@ public final class ImeVisibilityStateComputer { * A map used to track the requested IME target window and its state. The key represents the * token of the window and the value is the corresponding IME window state. */ + @GuardedBy("ImfLock.class") private final WeakHashMap<IBinder, ImeTargetWindowState> mRequestWindowStateMap = new WeakHashMap<>(); @@ -93,6 +95,7 @@ public final class ImeVisibilityStateComputer { * @see InputMethodManager#HIDE_IMPLICIT_ONLY that system will not hide IME when the value is * {@code true}. */ + @GuardedBy("ImfLock.class") boolean mRequestedShowExplicitly; /** @@ -101,23 +104,28 @@ public final class ImeVisibilityStateComputer { * @see InputMethodManager#SHOW_FORCED * @see InputMethodManager#HIDE_NOT_ALWAYS */ + @GuardedBy("ImfLock.class") boolean mShowForced; /** * Set if we last told the input method to show itself. */ + @GuardedBy("ImfLock.class") private boolean mInputShown; /** * Set if we called * {@link com.android.server.wm.ImeTargetVisibilityPolicy#showImeScreenshot(IBinder, int)}. */ + @GuardedBy("ImfLock.class") private boolean mRequestedImeScreenshot; /** The window token of the current visible IME layering target overlay. */ + @GuardedBy("ImfLock.class") private IBinder mCurVisibleImeLayeringOverlay; /** The window token of the current visible IME input target. */ + @GuardedBy("ImfLock.class") private IBinder mCurVisibleImeInputTarget; /** Represent the invalid IME visibility state */ @@ -203,25 +211,32 @@ public final class ImeVisibilityStateComputer { public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken, @WindowManager.LayoutParams.WindowType int windowType, boolean visible, boolean removed) { - mCurVisibleImeLayeringOverlay = - // Ignoring the starting window since it's ok to cover the IME target - // window in temporary without affecting the IME visibility. - (visible && !removed && windowType != TYPE_APPLICATION_STARTING) + // Ignoring the starting window since it's ok to cover the IME target + // window in temporary without affecting the IME visibility. + final var overlay = (visible && !removed && windowType != TYPE_APPLICATION_STARTING) ? overlayWindowToken : null; + synchronized (ImfLock.class) { + mCurVisibleImeLayeringOverlay = overlay; + + } } @Override public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget, boolean visibleRequested, boolean removed) { - if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed) - && mCurVisibleImeLayeringOverlay != null) { - final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE; - final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, - ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */); - mService.onApplyImeVisibilityFromComputer(imeInputTarget, statsToken, - new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason)); + synchronized (ImfLock.class) { + if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested + || removed) + && mCurVisibleImeLayeringOverlay != null) { + final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */); + mService.onApplyImeVisibilityFromComputerLocked(imeInputTarget, statsToken, + new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason)); + } + mCurVisibleImeInputTarget = + (visibleRequested && !removed) ? imeInputTarget : null; } - mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null; } }); } @@ -232,6 +247,7 @@ public final class ImeVisibilityStateComputer { * @param statsToken The token tracking the current IME request. * @return {@code true} when the show request can proceed. */ + @GuardedBy("ImfLock.class") boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int showFlags) { if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) { @@ -258,6 +274,7 @@ public final class ImeVisibilityStateComputer { * @param statsToken The token tracking the current IME request. * @return {@code true} when the hide request can proceed. */ + @GuardedBy("ImfLock.class") boolean canHideIme(@NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int hideFlags) { if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 @@ -279,6 +296,7 @@ public final class ImeVisibilityStateComputer { * Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags} * to {@link InputMethod.ShowFlags}. */ + @GuardedBy("ImfLock.class") @InputMethod.ShowFlags int getShowFlagsForInputMethodServiceOnly() { int flags = 0; @@ -294,6 +312,7 @@ public final class ImeVisibilityStateComputer { * Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags} * to {@link InputMethodManager.ShowFlags}. */ + @GuardedBy("ImfLock.class") @InputMethodManager.ShowFlags int getShowFlags() { int flags = 0; @@ -305,12 +324,14 @@ public final class ImeVisibilityStateComputer { return flags; } + @GuardedBy("ImfLock.class") void clearImeShowFlags() { mRequestedShowExplicitly = false; mShowForced = false; mInputShown = false; } + @GuardedBy("ImfLock.class") int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) { final int displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator); state.setImeDisplayId(displayToShowIme); @@ -328,6 +349,7 @@ public final class ImeVisibilityStateComputer { * visibility state, it could be {@link #STATE_SHOW_IME} or * {@link #STATE_HIDE_IME}. */ + @GuardedBy("ImfLock.class") void requestImeVisibility(IBinder windowToken, boolean showIme) { ImeTargetWindowState state = getOrCreateWindowState(windowToken); if (!mPolicy.mPendingA11yRequestingHideKeyboard) { @@ -343,6 +365,7 @@ public final class ImeVisibilityStateComputer { setWindowStateInner(windowToken, state); } + @GuardedBy("ImfLock.class") ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) { ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); if (state == null) { @@ -351,11 +374,13 @@ public final class ImeVisibilityStateComputer { return state; } + @GuardedBy("ImfLock.class") ImeTargetWindowState getWindowStateOrNull(IBinder windowToken) { ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); return state; } + @GuardedBy("ImfLock.class") void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) { final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); if (state != null && newState.hasEditorFocused() @@ -367,6 +392,7 @@ public final class ImeVisibilityStateComputer { setWindowStateInner(windowToken, newState); } + @GuardedBy("ImfLock.class") private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) { if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken + ", state=" + newState); @@ -391,6 +417,7 @@ public final class ImeVisibilityStateComputer { } } + @GuardedBy("ImfLock.class") ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) { // TODO: Output the request IME visibility state according to the requested window state final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE; @@ -540,6 +567,7 @@ public final class ImeVisibilityStateComputer { return null; } + @GuardedBy("ImfLock.class") @VisibleForTesting ImeVisibilityResult onInteractiveChanged(IBinder windowToken, boolean interactive) { final ImeTargetWindowState state = getWindowStateOrNull(windowToken); @@ -568,6 +596,7 @@ public final class ImeVisibilityStateComputer { return userData.mImeBindingState.mFocusedWindow; } + @GuardedBy("ImfLock.class") IBinder getWindowTokenFrom(ImeTargetWindowState windowState) { for (IBinder windowToken : mRequestWindowStateMap.keySet()) { final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); @@ -578,6 +607,7 @@ public final class ImeVisibilityStateComputer { return null; } + @GuardedBy("ImfLock.class") boolean shouldRestoreImeVisibility(@NonNull ImeTargetWindowState state) { final int softInputMode = state.getSoftInputModeState(); switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { @@ -591,14 +621,17 @@ public final class ImeVisibilityStateComputer { return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state)); } + @GuardedBy("ImfLock.class") boolean isInputShown() { return mInputShown; } + @GuardedBy("ImfLock.class") void setInputShown(boolean inputShown) { mInputShown = inputShown; } + @GuardedBy("ImfLock.class") void dumpDebug(ProtoOutputStream proto, long fieldId) { proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly); proto.write(SHOW_FORCED, mShowForced); @@ -607,6 +640,7 @@ public final class ImeVisibilityStateComputer { proto.write(INPUT_SHOWN, mInputShown); } + @GuardedBy("ImfLock.class") void dump(@NonNull PrintWriter pw, @NonNull String prefix) { final Printer p = new PrintWriterPrinter(pw); p.println(prefix + "mRequestedShowExplicitly=" + mRequestedShowExplicitly @@ -629,12 +663,14 @@ public final class ImeVisibilityStateComputer { * * This prevents the IME from showing when it otherwise may have shown. */ + @GuardedBy("ImfLock.class") private boolean mImeHiddenByDisplayPolicy; /** * Set when the accessibility service requests to hide IME by * {@link AccessibilityService.SoftKeyboardController#setShowMode} */ + @GuardedBy("ImfLock.class") private boolean mA11yRequestingNoSoftKeyboard; /** @@ -643,16 +679,20 @@ public final class ImeVisibilityStateComputer { * {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without * changing the requested IME visible state. */ + @GuardedBy("ImfLock.class") private boolean mPendingA11yRequestingHideKeyboard; + @GuardedBy("ImfLock.class") void setImeHiddenByDisplayPolicy(boolean hideIme) { mImeHiddenByDisplayPolicy = hideIme; } + @GuardedBy("ImfLock.class") boolean isImeHiddenByDisplayPolicy() { return mImeHiddenByDisplayPolicy; } + @GuardedBy("ImfLock.class") void setA11yRequestNoSoftKeyboard(int keyboardShowMode) { mA11yRequestingNoSoftKeyboard = (keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN; @@ -661,11 +701,13 @@ public final class ImeVisibilityStateComputer { } } + @GuardedBy("ImfLock.class") boolean isA11yRequestNoSoftKeyboard() { return mA11yRequestingNoSoftKeyboard; } } + @GuardedBy("ImfLock.class") ImeVisibilityPolicy getImePolicy() { return mPolicy; } @@ -721,63 +763,78 @@ public final class ImeVisibilityStateComputer { /** * Set if the client has asked for the input method to be shown. */ + @GuardedBy("ImfLock.class") private boolean mRequestedImeVisible; /** * A identifier for knowing the requester of {@link InputMethodManager#showSoftInput} or * {@link InputMethodManager#hideSoftInputFromWindow}. */ + @GuardedBy("ImfLock.class") private IBinder mRequestImeToken; /** * The IME target display id for which the latest startInput was called. */ + @GuardedBy("ImfLock.class") private int mImeDisplayId = DEFAULT_DISPLAY; + @AnyThread boolean hasImeFocusChanged() { return mImeFocusChanged; } + @AnyThread boolean hasEditorFocused() { return mHasFocusedEditor; } + @AnyThread boolean isStartInputByGainFocus() { return mIsStartInputByGainFocus; } + @AnyThread int getSoftInputModeState() { return mSoftInputModeState; } + @AnyThread int getWindowFlags() { return mWindowFlags; } + @AnyThread int getToolType() { return mToolType; } + @GuardedBy("ImfLock.class") private void setImeDisplayId(int imeDisplayId) { mImeDisplayId = imeDisplayId; } + @GuardedBy("ImfLock.class") int getImeDisplayId() { return mImeDisplayId; } + @GuardedBy("ImfLock.class") private void setRequestedImeVisible(boolean requestedImeVisible) { mRequestedImeVisible = requestedImeVisible; } + @GuardedBy("ImfLock.class") boolean isRequestedImeVisible() { return mRequestedImeVisible; } + @GuardedBy("ImfLock.class") void setRequestImeToken(IBinder token) { mRequestImeToken = token; } + @GuardedBy("ImfLock.class") IBinder getRequestImeToken() { return mRequestImeToken; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4716e6c24154..9d007c601b8d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1046,10 +1046,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. SecureSettingsWrapper.onUserStarting(userId); mService.mIoHandler.post(() -> { synchronized (ImfLock.class) { - if (mService.mConcurrentMultiUserModeEnabled) { - if (mService.mCurrentUserId != userId && mService.mSystemReady) { - mService.initializeVisibleBackgroundUserLocked(userId); - } + if (mService.mSystemReady) { + mService.onUserReadyLocked(userId); } } }); @@ -1095,7 +1093,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset; return; } - mHandler.removeCallbacks(mUserSwitchHandlerTask); + mIoHandler.removeCallbacks(mUserSwitchHandlerTask); } // Hide soft input before user switch task since switch task may block main handler a while // and delayed the hideCurrentInputLocked(). @@ -1105,7 +1103,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, clientToBeReset); mUserSwitchHandlerTask = task; - mHandler.post(task); + mIoHandler.post(task); } @VisibleForTesting @@ -1386,30 +1384,32 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. UserHandle.ALL, broadcastFilterForAllUsers, null, null, Context.RECEIVER_EXPORTED); - final String defaultImiId = SecureSettingsWrapper.getString( - Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId); - final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); - final var settings = InputMethodSettingsRepository.get(currentUserId); - postInputMethodSettingUpdatedLocked( - !imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId); - updateFromSettingsLocked(true, currentUserId); - InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( - getPackageManagerForUser(mContext, currentUserId), - settings.getEnabledInputMethodList()); - AdditionalSubtypeMapRepository.startWriterThread(); - if (mConcurrentMultiUserModeEnabled) { - for (int userId : mUserManagerInternal.getUserIds()) { - if (userId != mCurrentUserId) { - initializeVisibleBackgroundUserLocked(userId); - } - } + for (int userId : mUserManagerInternal.getUserIds()) { + onUserReadyLocked(userId); } } } } + @GuardedBy("ImfLock.class") + void onUserReadyLocked(@UserIdInt int userId) { + if (!mUserManagerInternal.isUserRunning(userId)) { + return; + } + + final String defaultImiId = SecureSettingsWrapper.getString( + Settings.Secure.DEFAULT_INPUT_METHOD, null, userId); + final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); + final var settings = InputMethodSettingsRepository.get(userId); + postInputMethodSettingUpdatedLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */, + userId); + updateFromSettingsLocked(true, userId); + InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( + getPackageManagerForUser(mContext, userId), settings.getEnabledInputMethodList()); + } + void registerImeRequestedChangedListener() { mWindowManagerInternal.setOnImeRequestedChangedListener( (windowToken, imeVisible, statsToken) -> { @@ -1812,8 +1812,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - @Override - public boolean isInputShownLocked() { + private boolean isInputShownLocked() { return mVisibilityStateComputer.isInputShown(); } @@ -2826,60 +2825,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } - /** - * This initialization logic is used when and only when {@link #mConcurrentMultiUserModeEnabled} - * is set to {@code true}. - * - * <p>There remain several yet-to-be-implemented features. For the canonical and desired - * behaviors always refer to single-user code paths such as - * {@link #updateInputMethodsFromSettingsLocked(boolean, int)}.</p> - * - * <p>Here are examples of missing features.</p> - * <ul> - * <li>Profiles are not supported.</li> - * <li> - * {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated. - * </li> - * <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li> - * <li>and so on.</li> - * </ul> - */ - @GuardedBy("ImfLock.class") - void initializeVisibleBackgroundUserLocked(@UserIdInt int userId) { - final var settings = InputMethodSettingsRepository.get(userId); - - // Until we figure out what makes most sense, we enable all the pre-installed IMEs in - // concurrent multi-user IME mode. - String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); - for (var imi : settings.getMethodList()) { - if (!imi.isSystem()) { - continue; - } - enabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(enabledImeIdsStr, imi.getId()); - } - if (!TextUtils.equals(settings.getEnabledInputMethodsStr(), enabledImeIdsStr)) { - settings.putEnabledInputMethodsStr(enabledImeIdsStr); - } - - // Also update the currently-selected IME. - String id = settings.getSelectedInputMethod(); - if (TextUtils.isEmpty(id)) { - final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( - settings.getEnabledInputMethodList()); - if (imi != null) { - id = imi.getId(); - settings.putSelectedInputMethod(id); - } - } - final var userData = getUserData(userId); - final var bindingController = userData.mBindingController; - bindingController.setSelectedMethodId(id); - - // Also re-initialize controllers. - userData.mSwitchingController.resetCircularListLocked(mContext, settings); - userData.mHardwareKeyboardShortcutController.update(settings); - } - @GuardedBy("ImfLock.class") void updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) { final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); @@ -3058,52 +3003,75 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @GuardedBy("ImfLock.class") + private void sendResultReceiverFailureLocked(@Nullable ResultReceiver resultReceiver) { + final boolean isInputShown = mVisibilityStateComputer.isInputShown(); + resultReceiver.send(isInputShown + ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null); + } + @Override public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput"); - final int uid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getUserId(uid); ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#showSoftInput", mDumper); synchronized (ImfLock.class) { - final int userId = resolveImeUserIdLocked(callingUserId); - if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken, - userId)) { - ImeTracker.forLogging().onFailed( - statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - return false; + final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags, + lastClickToolType, resultReceiver, reason); + // When ZeroJankProxy is enabled, the app has already received "true" as the return + // value, and expect "resultReceiver" to be notified later. See b/327751155. + if (!result && Flags.useZeroJankProxy()) { + sendResultReceiverFailureLocked(resultReceiver); } - final long ident = Binder.clearCallingIdentity(); - final var userData = getUserData(userId); - try { - if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); - if (Flags.refactorInsetsController()) { - boolean wasVisible = isInputShownLocked(); - if (userData.mImeBindingState != null - && userData.mImeBindingState.mFocusedWindowClient != null - && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { - userData.mImeBindingState.mFocusedWindowClient.mClient - .setImeVisibility(true, statsToken); - if (resultReceiver != null) { - resultReceiver.send( - wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN - : InputMethodManager.RESULT_SHOWN, null); - } - return true; + return result; // ignored when ZeroJankProxy is enabled. + } + } + + @GuardedBy("ImfLock.class") + private boolean showSoftInputLocked(IInputMethodClient client, IBinder windowToken, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickToolType, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { + final int uid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(uid); + final int userId = resolveImeUserIdLocked(callingUserId); + if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken, + userId)) { + ImeTracker.forLogging().onFailed( + statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return false; + } + final var userData = getUserData(userId); + final long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); + if (Flags.refactorInsetsController()) { + boolean wasVisible = isInputShownLocked(); + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(true, statsToken); + if (resultReceiver != null) { + resultReceiver.send( + wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : InputMethodManager.RESULT_SHOWN, null); } - return false; - } else { - return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType, - resultReceiver, reason, userId); + return true; } - } finally { - Binder.restoreCallingIdentity(ident); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return false; + } else { + return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType, + resultReceiver, reason, userId); } + } finally { + Binder.restoreCallingIdentity(ident); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -3498,50 +3466,64 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - final int uid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getUserId(uid); ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#hideSoftInput", mDumper); synchronized (ImfLock.class) { - final int userId = resolveImeUserIdLocked(callingUserId); - if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) { - if (isInputShownLocked()) { - ImeTracker.forLogging().onFailed( - statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); - } else { - ImeTracker.forLogging().onCancelled(statsToken, - ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); - } - return false; + final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags, + resultReceiver, reason); + // When ZeroJankProxy is enabled, the app has already received "true" as the return + // value, and expect "resultReceiver" to be notified later. See b/327751155. + if (!result && Flags.useZeroJankProxy()) { + sendResultReceiverFailureLocked(resultReceiver); } - final long ident = Binder.clearCallingIdentity(); - final var userData = getUserData(userId); - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput"); - if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); - if (Flags.refactorInsetsController()) { - if (userData.mImeBindingState != null - && userData.mImeBindingState.mFocusedWindowClient != null - && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { - boolean wasVisible = isInputShownLocked(); - // TODO add windowToken to interface - userData.mImeBindingState.mFocusedWindowClient.mClient - .setImeVisibility(false, statsToken); - if (resultReceiver != null) { - resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN - : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null); - } - return true; + return result; // ignored when ZeroJankProxy is enabled. + } + } + + @GuardedBy("ImfLock.class") + private boolean hideSoftInputLocked(IInputMethodClient client, IBinder windowToken, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, + ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + final int uid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(uid); + final int userId = resolveImeUserIdLocked(callingUserId); + if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) { + if (isInputShownLocked()) { + ImeTracker.forLogging().onFailed( + statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); + } else { + ImeTracker.forLogging().onCancelled(statsToken, + ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); + } + return false; + } + final var userData = getUserData(userId); + final long ident = Binder.clearCallingIdentity(); + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput"); + if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); + if (Flags.refactorInsetsController()) { + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { + boolean wasVisible = isInputShownLocked(); + // TODO add windowToken to interface + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(false, statsToken); + if (resultReceiver != null) { + resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN + : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null); } - return false; - } else { - return InputMethodManagerService.this.hideCurrentInputLocked(windowToken, - statsToken, flags, resultReceiver, reason, userId); + return true; } - } finally { - Binder.restoreCallingIdentity(ident); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return false; + } else { + return InputMethodManagerService.this.hideCurrentInputLocked( + windowToken, statsToken, flags, resultReceiver, reason, userId); } + } finally { + Binder.restoreCallingIdentity(ident); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -4916,14 +4898,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return mVisibilityApplier; } - void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken, - @NonNull ImeVisibilityResult result) { - synchronized (ImfLock.class) { - // TODO(b/305849394): Infer userId from windowToken - final int userId = mCurrentUserId; - mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(), - result.getReason(), userId); - } + @GuardedBy("ImfLock.class") + void onApplyImeVisibilityFromComputerLocked(IBinder windowToken, + @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) { + // TODO(b/305849394): Infer userId from windowToken + final int userId = mCurrentUserId; + mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(), + result.getReason(), userId); } @GuardedBy("ImfLock.class") diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java index 770e12d8e49a..fdb2e6faf91b 100644 --- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java @@ -86,8 +86,6 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback { interface Callback extends IInputMethodManagerImpl.Callback { @GuardedBy("ImfLock.class") ClientState getClientStateLocked(IInputMethodClient client); - @GuardedBy("ImfLock.class") - boolean isInputShownLocked(); } private final Callback mInner; @@ -178,19 +176,8 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback { @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - offload( - () -> { - if (!mInner.showSoftInput( - client, - windowToken, - statsToken, - flags, - lastClickToolType, - resultReceiver, - reason)) { - sendResultReceiverFailure(resultReceiver); - } - }); + offload(() -> mInner.showSoftInput( + client, windowToken, statsToken, flags, lastClickToolType, resultReceiver, reason)); return true; } @@ -198,30 +185,11 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback { public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - offload( - () -> { - if (!mInner.hideSoftInput( - client, windowToken, statsToken, flags, resultReceiver, reason)) { - sendResultReceiverFailure(resultReceiver); - } - }); + offload(() -> mInner.hideSoftInput( + client, windowToken, statsToken, flags, resultReceiver, reason)); return true; } - private void sendResultReceiverFailure(@Nullable ResultReceiver resultReceiver) { - if (resultReceiver == null) { - return; - } - final boolean isInputShown; - synchronized (ImfLock.class) { - isInputShown = mInner.isInputShownLocked(); - } - resultReceiver.send(isInputShown - ? InputMethodManager.RESULT_UNCHANGED_SHOWN - : InputMethodManager.RESULT_UNCHANGED_HIDDEN, - null); - } - @Override @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) public void hideSoftInputFromServerForTest() { diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index b12a917eede9..95d8bb953065 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -2811,8 +2811,9 @@ public class ZenModeHelper { private final class H extends Handler { private static final int MSG_DISPATCH = 1; private static final int MSG_METRICS = 2; - private static final int MSG_RINGER_AUDIO = 5; private static final int MSG_APPLY_EFFECTS = 6; + private static final int MSG_AUDIO_APPLIED_TO_RINGER = 7; + private static final int MSG_AUDIO_NOT_APPLIED_TO_RINGER = 8; private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000; @@ -2831,8 +2832,13 @@ public class ZenModeHelper { } private void postUpdateRingerAndAudio(boolean shouldApplyToRinger) { - removeMessages(MSG_RINGER_AUDIO); - sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger)); + if (shouldApplyToRinger) { + removeMessages(MSG_AUDIO_APPLIED_TO_RINGER); + sendEmptyMessage(MSG_AUDIO_APPLIED_TO_RINGER); + } else { + removeMessages(MSG_AUDIO_NOT_APPLIED_TO_RINGER); + sendEmptyMessage(MSG_AUDIO_NOT_APPLIED_TO_RINGER); + } } private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) { @@ -2849,9 +2855,11 @@ public class ZenModeHelper { case MSG_METRICS: mMetrics.emit(); break; - case MSG_RINGER_AUDIO: - boolean shouldApplyToRinger = (boolean) msg.obj; - updateRingerAndAudio(shouldApplyToRinger); + case MSG_AUDIO_APPLIED_TO_RINGER: + updateRingerAndAudio(/* shouldApplyToRinger= */ true); + break; + case MSG_AUDIO_NOT_APPLIED_TO_RINGER: + updateRingerAndAudio(/* shouldApplyToRinger= */ false); break; case MSG_APPLY_EFFECTS: @ConfigChangeOrigin int origin = msg.arg1; diff --git a/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java new file mode 100644 index 000000000000..a42929f6c508 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.power.stats; + +import android.os.BatteryConsumer; +import android.os.PersistableBundle; + +import com.android.internal.os.PowerStats; + +public class AmbientDisplayPowerStatsProcessor extends PowerStatsProcessor { + private final PowerStatsLayout mStatsLayout; + private final PowerStats.Descriptor mDescriptor; + private final long[] mTmpDeviceStats; + private PowerStats.Descriptor mScreenPowerStatsDescriptor; + private ScreenPowerStatsLayout mScreenPowerStatsLayout; + private long[] mTmpScreenStats; + + public AmbientDisplayPowerStatsProcessor() { + mStatsLayout = new PowerStatsLayout(); + mStatsLayout.addDeviceSectionPowerEstimate(); + PersistableBundle extras = new PersistableBundle(); + mStatsLayout.toExtras(extras); + mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, + mStatsLayout.getDeviceStatsArrayLength(), null, 0, 0, extras); + mTmpDeviceStats = new long[mDescriptor.statsArrayLength]; + } + + @Override + void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) { + stats.setPowerStatsDescriptor(mDescriptor); + + PowerComponentAggregatedPowerStats screenStats = + stats.getAggregatedPowerStats().getPowerComponentStats( + BatteryConsumer.POWER_COMPONENT_SCREEN); + if (screenStats == null) { + return; + } + + if (mScreenPowerStatsDescriptor == null) { + mScreenPowerStatsDescriptor = screenStats.getPowerStatsDescriptor(); + if (mScreenPowerStatsDescriptor == null) { + return; + } + + mScreenPowerStatsLayout = new ScreenPowerStatsLayout(mScreenPowerStatsDescriptor); + mTmpScreenStats = new long[mScreenPowerStatsDescriptor.statsArrayLength]; + } + + MultiStateStats.States[] deviceStateConfig = screenStats.getConfig().getDeviceStateConfig(); + + // Ambient display power estimates have already been calculated by the screen power stats + // processor. All that remains to be done is copy the estimates over. + MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig, + states -> { + screenStats.getDeviceStats(mTmpScreenStats, states); + double power = + mScreenPowerStatsLayout.getScreenDozePowerEstimate(mTmpScreenStats); + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, power); + stats.setDeviceStats(states, mTmpDeviceStats); + }); + } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 4052a64aabba..c4b37c6939b7 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -296,6 +296,7 @@ public class BatteryStatsImpl extends BatteryStats { private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>(); private int[] mCpuPowerBracketMap; private final CpuPowerStatsCollector mCpuPowerStatsCollector; + private final ScreenPowerStatsCollector mScreenPowerStatsCollector; private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector; private final WifiPowerStatsCollector mWifiPowerStatsCollector; private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector; @@ -303,6 +304,54 @@ public class BatteryStatsImpl extends BatteryStats { private final GnssPowerStatsCollector mGnssPowerStatsCollector; private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector; private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray(); + private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever = + new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() { + + @Override + public long getScreenOnTimeMs(int display) { + synchronized (BatteryStatsImpl.this) { + return getDisplayScreenOnTime(display, + mClock.elapsedRealtime() * 1000) / 1000; + } + } + + @Override + public long getBrightnessLevelTimeMs(int display, int brightnessLevel) { + synchronized (BatteryStatsImpl.this) { + return getDisplayScreenBrightnessTime(display, brightnessLevel, + mClock.elapsedRealtime() * 1000) / 1000; + } + } + + @Override + public long getScreenDozeTimeMs(int display) { + synchronized (BatteryStatsImpl.this) { + return getDisplayScreenDozeTime(display, + mClock.elapsedRealtime() * 1000) / 1000; + } + } + + @Override + public void retrieveTopActivityTimes(Callback callback) { + synchronized (BatteryStatsImpl.this) { + long elapsedTimeUs = mClock.elapsedRealtime() * 1000; + for (int i = mUidStats.size() - 1; i >= 0; i--) { + Uid uid = mUidStats.valueAt(i); + long topStateTime = uid.getProcessStateTime(Uid.PROCESS_STATE_TOP, + elapsedTimeUs, STATS_SINCE_CHARGED) / 1000; + Timer timer = uid.getForegroundActivityTimer(); + if (timer == null) { + callback.onUidTopActivityTime(uid.mUid, topStateTime); + } else { + long topActivityTime = timer.getTotalTimeLocked(elapsedTimeUs, + STATS_SINCE_CHARGED) / 1000; + callback.onUidTopActivityTime(uid.mUid, Math.min(topStateTime, + topActivityTime)); + } + } + } + } + }; private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever = new WifiPowerStatsCollector.WifiStatsRetriever() { @Override @@ -1966,8 +2015,9 @@ public class BatteryStatsImpl extends BatteryStats { } private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector, - MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector, - BluetoothPowerStatsCollector.Injector, EnergyConsumerPowerStatsCollector.Injector { + ScreenPowerStatsCollector.Injector, MobileRadioPowerStatsCollector.Injector, + WifiPowerStatsCollector.Injector, BluetoothPowerStatsCollector.Injector, + EnergyConsumerPowerStatsCollector.Injector { private PackageManager mPackageManager; private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; private NetworkStatsManager mNetworkStatsManager; @@ -2039,6 +2089,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() { + return mScreenUsageTimeRetriever; + } + + @Override + public int getDisplayCount() { + return BatteryStatsImpl.this.getDisplayCount(); + } + + @Override public Supplier<NetworkStats> getMobileNetworkStatsSupplier() { return () -> readMobileNetworkStatsLocked(mNetworkStatsManager); } @@ -5736,13 +5796,17 @@ public class BatteryStatsImpl extends BatteryStats { maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs); if (shouldScheduleSync) { - final int numDisplays = mPerDisplayBatteryStats.length; - final int[] displayStates = new int[numDisplays]; - for (int i = 0; i < numDisplays; i++) { - displayStates[i] = mPerDisplayBatteryStats[i].screenState; + if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) { + mScreenPowerStatsCollector.schedule(); + } else { + final int numDisplays = mPerDisplayBatteryStats.length; + final int[] displayStates = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + displayStates[i] = mPerDisplayBatteryStats[i].screenState; + } + mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag, + batteryRunning, batteryScreenOffRunning, state, displayStates); } - mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag, - batteryRunning, batteryScreenOffRunning, state, displayStates); } } @@ -11290,6 +11354,9 @@ public class BatteryStatsImpl extends BatteryStats { mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); + mScreenPowerStatsCollector = new ScreenPowerStatsCollector(mPowerStatsCollectorInjector); + mScreenPowerStatsCollector.addConsumer(this::recordPowerStats); + mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector( mPowerStatsCollectorInjector, this::onMobileRadioPowerStatsRetrieved); mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats); @@ -14750,6 +14817,10 @@ public class BatteryStatsImpl extends BatteryStats { mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)); mCpuPowerStatsCollector.schedule(); + mScreenPowerStatsCollector.setEnabled( + mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)); + mScreenPowerStatsCollector.schedule(); + mMobileRadioPowerStatsCollector.setEnabled( mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)); mMobileRadioPowerStatsCollector.schedule(); @@ -14786,6 +14857,8 @@ public class BatteryStatsImpl extends BatteryStats { switch (powerComponent) { case BatteryConsumer.POWER_COMPONENT_CPU: return mCpuPowerStatsCollector; + case BatteryConsumer.POWER_COMPONENT_SCREEN: + return mScreenPowerStatsCollector; case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO: return mMobileRadioPowerStatsCollector; case BatteryConsumer.POWER_COMPONENT_WIFI: @@ -16329,6 +16402,7 @@ public class BatteryStatsImpl extends BatteryStats { */ public void schedulePowerStatsSampleCollection() { mCpuPowerStatsCollector.forceSchedule(); + mScreenPowerStatsCollector.forceSchedule(); mMobileRadioPowerStatsCollector.forceSchedule(); mWifiPowerStatsCollector.forceSchedule(); mBluetoothPowerStatsCollector.forceSchedule(); @@ -16351,6 +16425,7 @@ public class BatteryStatsImpl extends BatteryStats { */ public void dumpStatsSample(PrintWriter pw) { mCpuPowerStatsCollector.collectAndDump(pw); + mScreenPowerStatsCollector.collectAndDump(pw); mMobileRadioPowerStatsCollector.collectAndDump(pw); mWifiPowerStatsCollector.collectAndDump(pw); mBluetoothPowerStatsCollector.collectAndDump(pw); diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index ac6896696de6..a5e4cf5a0e33 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -110,8 +110,13 @@ public class BatteryUsageStatsProvider { if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) { mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile)); } - mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) { + mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); + } + if (!mPowerStatsExporterEnabled.get( + BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) { + mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); + } mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_ANY)) { mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile)); diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 8384b2b8db82..6820197fa0f2 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -163,15 +163,14 @@ class PowerComponentAggregatedPowerStats { } } - void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) { + void setDeviceStats(int[] states, long[] values) { if (mDeviceStats == null) { createDeviceStats(0); } mDeviceStats.setStats(states, values); } - void setUidStats(int uid, @AggregatedPowerStatsConfig.TrackedState int[] states, - long[] values) { + void setUidStats(int uid, int[] states, long[] values) { UidStats uidStats = getUidStats(uid); uidStats.stats.setStats(states, values); } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java index dfc8daa15c37..7d7b3c2fa3c5 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java @@ -220,8 +220,7 @@ public abstract class PowerStatsProcessor { } @Nullable - public DeviceStateEstimation getDeviceStateEstimate( - @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + public DeviceStateEstimation getDeviceStateEstimate(int[] stateValues) { String label = concatLabels(mConfig.getDeviceStateConfig(), stateValues); for (int i = 0; i < deviceStateEstimations.size(); i++) { DeviceStateEstimation deviceStateEstimation = this.deviceStateEstimations.get(i); @@ -233,8 +232,7 @@ public abstract class PowerStatsProcessor { } public CombinedDeviceStateEstimate getCombinedDeviceStateEstimate( - MultiStateStats.States[] deviceStates, - @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + MultiStateStats.States[] deviceStates, int[] stateValues) { String label = concatLabels(deviceStates, stateValues); for (int i = 0; i < combinedDeviceStateEstimations.size(); i++) { CombinedDeviceStateEstimate cdse = combinedDeviceStateEstimations.get(i); @@ -275,12 +273,10 @@ public abstract class PowerStatsProcessor { protected static class DeviceStateEstimation { public final String id; - @AggregatedPowerStatsConfig.TrackedState public final int[] stateValues; public Object intermediates; - public DeviceStateEstimation(MultiStateStats.States[] config, - @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + public DeviceStateEstimation(MultiStateStats.States[] config, int[] stateValues) { id = concatLabels(config, stateValues); this.stateValues = stateValues; } @@ -288,11 +284,12 @@ public abstract class PowerStatsProcessor { protected static class CombinedDeviceStateEstimate { public final String id; + public final int[] stateValues; public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>(); public Object intermediates; - public CombinedDeviceStateEstimate(MultiStateStats.States[] config, - @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + public CombinedDeviceStateEstimate(MultiStateStats.States[] config, int[] stateValues) { + this.stateValues = Arrays.copyOf(stateValues, stateValues.length); id = concatLabels(config, stateValues); } } @@ -310,19 +307,16 @@ public abstract class PowerStatsProcessor { } protected static class UidStateProportionalEstimate { - @AggregatedPowerStatsConfig.TrackedState public final int[] stateValues; public Object intermediates; - protected UidStateProportionalEstimate( - @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + protected UidStateProportionalEstimate(int[] stateValues) { this.stateValues = stateValues; } } @NonNull - private static String concatLabels(MultiStateStats.States[] config, - @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + private static String concatLabels(MultiStateStats.States[] config, int[] stateValues) { List<String> labels = new ArrayList<>(); for (int state = 0; state < config.length; state++) { if (config[state] != null && config[state].isTracked()) { @@ -334,7 +328,6 @@ public abstract class PowerStatsProcessor { return labels.toString(); } - @AggregatedPowerStatsConfig.TrackedState private static int[][] getAllTrackedStateCombinations(MultiStateStats.States[] states) { List<int[]> combinations = new ArrayList<>(); MultiStateStats.States.forEachTrackedStateCombination(states, stateValues -> { diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java new file mode 100644 index 000000000000..291f28940424 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.PersistableBundle; +import android.util.Slog; +import android.util.SparseLongArray; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import java.util.Arrays; +import java.util.function.IntSupplier; + +public class ScreenPowerStatsCollector extends PowerStatsCollector { + private static final String TAG = "ScreenPowerStatsCollector"; + + interface ScreenUsageTimeRetriever { + interface Callback { + void onUidTopActivityTime(int uid, long topActivityTimeMs); + } + + void retrieveTopActivityTimes(Callback callback); + + long getScreenOnTimeMs(int display); + long getBrightnessLevelTimeMs(int display, int brightnessLevel); + long getScreenDozeTimeMs(int display); + } + + interface Injector { + Handler getHandler(); + Clock getClock(); + PowerStatsUidResolver getUidResolver(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); + ConsumedEnergyRetriever getConsumedEnergyRetriever(); + IntSupplier getVoltageSupplier(); + ScreenUsageTimeRetriever getScreenUsageTimeRetriever(); + int getDisplayCount(); + } + + private static final long ENERGY_UNSPECIFIED = -1; + + private final Injector mInjector; + private boolean mIsInitialized; + private ScreenPowerStatsLayout mLayout; + private int mDisplayCount; + private PowerStats mPowerStats; + private ConsumedEnergyRetriever mConsumedEnergyRetriever; + private IntSupplier mVoltageSupplier; + private ScreenUsageTimeRetriever mScreenUsageTimeRetriever; + private int[] mEnergyConsumerIds = new int[0]; + private long[] mLastConsumedEnergyUws; + private int mLastVoltageMv; + private boolean mFirstSample = true; + private long[] mLastScreenOnTime; + private long[][] mLastBrightnessLevelTime; + private long[] mLastDozeTime; + private final SparseLongArray mLastTopActivityTime = new SparseLongArray(); + private long mLastCollectionTime; + + ScreenPowerStatsCollector(Injector injector) { + super(injector.getHandler(), + injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_SCREEN)), + injector.getUidResolver(), injector.getClock()); + mInjector = injector; + } + + private boolean ensureInitialized() { + if (mIsInitialized) { + return true; + } + + if (!isEnabled()) { + return false; + } + + mDisplayCount = mInjector.getDisplayCount(); + mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); + mVoltageSupplier = mInjector.getVoltageSupplier(); + mScreenUsageTimeRetriever = mInjector.getScreenUsageTimeRetriever(); + mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds( + EnergyConsumerType.DISPLAY); + mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length]; + Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); + + mLayout = new ScreenPowerStatsLayout(); + mLayout.addDeviceScreenUsageDurationSection(mInjector.getDisplayCount()); + mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length); + mLayout.addDeviceSectionUsageDuration(); + mLayout.addDeviceSectionPowerEstimate(); + mLayout.addUidTopActivitiyDuration(); + mLayout.addUidSectionPowerEstimate(); + + PersistableBundle extras = new PersistableBundle(); + mLayout.toExtras(extras); + PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor( + BatteryConsumer.POWER_COMPONENT_SCREEN, mLayout.getDeviceStatsArrayLength(), + null, 0, mLayout.getUidStatsArrayLength(), + extras); + + mLastScreenOnTime = new long[mDisplayCount]; + mLastBrightnessLevelTime = new long[mDisplayCount][BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS]; + mLastDozeTime = new long[mDisplayCount]; + + mPowerStats = new PowerStats(powerStatsDescriptor); + + mIsInitialized = true; + return true; + } + + @Override + protected PowerStats collectStats() { + if (!ensureInitialized()) { + return null; + } + + if (mEnergyConsumerIds.length != 0) { + collectEnergyConsumers(); + } + + for (int display = 0; display < mDisplayCount; display++) { + long screenOnTimeMs = mScreenUsageTimeRetriever.getScreenOnTimeMs(display); + if (!mFirstSample) { + mLayout.setScreenOnDuration(mPowerStats.stats, display, + screenOnTimeMs - mLastScreenOnTime[display]); + } + mLastScreenOnTime[display] = screenOnTimeMs; + + for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) { + long brightnessLevelTimeMs = + mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(display, level); + if (!mFirstSample) { + mLayout.setBrightnessLevelDuration(mPowerStats.stats, display, level, + brightnessLevelTimeMs - mLastBrightnessLevelTime[display][level]); + } + mLastBrightnessLevelTime[display][level] = brightnessLevelTimeMs; + } + long screenDozeTimeMs = mScreenUsageTimeRetriever.getScreenDozeTimeMs(display); + if (!mFirstSample) { + mLayout.setScreenDozeDuration(mPowerStats.stats, display, + screenDozeTimeMs - mLastDozeTime[display]); + } + mLastDozeTime[display] = screenDozeTimeMs; + } + + mPowerStats.uidStats.clear(); + + mScreenUsageTimeRetriever.retrieveTopActivityTimes((uid, topActivityTimeMs) -> { + long topActivityDuration = topActivityTimeMs - mLastTopActivityTime.get(uid); + if (topActivityDuration == 0) { + return; + } + mLastTopActivityTime.put(uid, topActivityTimeMs); + + int mappedUid = mUidResolver.mapUid(uid); + long[] uidStats = mPowerStats.uidStats.get(mappedUid); + if (uidStats == null) { + uidStats = new long[mLayout.getUidStatsArrayLength()]; + mPowerStats.uidStats.put(mappedUid, uidStats); + } + + mLayout.setUidTopActivityDuration(uidStats, + mLayout.getUidTopActivityDuration(uidStats) + topActivityDuration); + }); + + long elapsedRealtime = mClock.elapsedRealtime(); + mPowerStats.durationMs = elapsedRealtime - mLastCollectionTime; + mLastCollectionTime = elapsedRealtime; + + mFirstSample = false; + + return mPowerStats; + } + + private void collectEnergyConsumers() { + int voltageMv = mVoltageSupplier.getAsInt(); + if (voltageMv <= 0) { + Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv + + " mV) when querying energy consumers"); + return; + } + + int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; + mLastVoltageMv = voltageMv; + + long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds); + if (energyUws == null) { + return; + } + + for (int i = energyUws.length - 1; i >= 0; i--) { + long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED + ? energyUws[i] - mLastConsumedEnergyUws[i] : 0; + if (energyDelta < 0) { + // Likely, restart of powerstats HAL + energyDelta = 0; + } + mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage)); + mLastConsumedEnergyUws[i] = energyUws[i]; + } + } + + @Override + protected void onUidRemoved(int uid) { + mLastTopActivityTime.delete(uid); + } +} diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java new file mode 100644 index 000000000000..f134aa81057d --- /dev/null +++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.annotation.NonNull; +import android.os.BatteryStats; +import android.os.PersistableBundle; + +import com.android.internal.os.PowerStats; + +/** + * Captures the positions and lengths of sections of the stats array, such as time-in-state, + * power usage estimates etc. + */ +public class ScreenPowerStatsLayout extends PowerStatsLayout { + private static final String EXTRA_DEVICE_SCREEN_COUNT = "dsc"; + private static final String EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION = "dsd"; + private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbd"; + private static final String EXTRA_DEVICE_DOZE_DURATION_POSITION = "ddd"; + private static final String EXTRA_DEVICE_DOZE_POWER_POSITION = "ddp"; + private static final String EXTRA_UID_FOREGROUND_DURATION = "uf"; + + private int mDisplayCount; + private int mDeviceScreenOnDurationPosition; + private int[] mDeviceBrightnessDurationPositions; + private int mDeviceScreenDozeDurationPosition; + private int mDeviceScreenDozePowerPosition; + private int mUidTopActivityTimePosition; + + ScreenPowerStatsLayout() { + } + + ScreenPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) { + super(descriptor); + } + + void addDeviceScreenUsageDurationSection(int displayCount) { + mDisplayCount = displayCount; + mDeviceScreenOnDurationPosition = addDeviceSection(displayCount, "on"); + mDeviceBrightnessDurationPositions = new int[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS]; + for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) { + mDeviceBrightnessDurationPositions[level] = + addDeviceSection(displayCount, BatteryStats.SCREEN_BRIGHTNESS_NAMES[level]); + } + mDeviceScreenDozeDurationPosition = addDeviceSection(displayCount, "doze"); + } + + @Override + public void addDeviceSectionPowerEstimate() { + super.addDeviceSectionPowerEstimate(); + // Used by AmbientDisplayPowerStatsProcessor + mDeviceScreenDozePowerPosition = addDeviceSection(1, "doze-power", FLAG_HIDDEN); + } + + public int getDisplayCount() { + return mDisplayCount; + } + + /** + * Stores screen-on time for the specified display. + */ + public void setScreenOnDuration(long[] stats, int display, long durationMs) { + stats[mDeviceScreenOnDurationPosition + display] = durationMs; + } + + /** + * Returns screen-on time for the specified display. + */ + public long getScreenOnDuration(long[] stats, int display) { + return stats[mDeviceScreenOnDurationPosition + display]; + } + + /** + * Stores time at the specified brightness level for the specified display. + */ + public void setBrightnessLevelDuration(long[] stats, int display, int brightnessLevel, + long durationMs) { + stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display] = durationMs; + } + + /** + * Returns time at the specified brightness level for the specified display. + */ + public long getBrightnessLevelDuration(long[] stats, int display, int brightnessLevel) { + return stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display]; + } + + /** + * Stores time in the doze (ambient) state for the specified display. + */ + public void setScreenDozeDuration(long[] stats, int display, long durationMs) { + stats[mDeviceScreenDozeDurationPosition + display] = durationMs; + } + + /** + * Retrieves time in the doze (ambient) state for the specified display. + */ + public long getScreenDozeDuration(long[] stats, int display) { + return stats[mDeviceScreenDozeDurationPosition + display]; + } + + /** + * Stores estimated power in the doze (ambient) state. + */ + public void setScreenDozePowerEstimate(long[] stats, double power) { + stats[mDeviceScreenDozePowerPosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Retrieves estimated power in the doze (ambient) state. + */ + public double getScreenDozePowerEstimate(long[] stats) { + return stats[mDeviceScreenDozePowerPosition] / MILLI_TO_NANO_MULTIPLIER; + } + + void addUidTopActivitiyDuration() { + mUidTopActivityTimePosition = addUidSection(1, "top"); + } + + /** + * Stores time the UID spent in the TOP state. + */ + public void setUidTopActivityDuration(long[] stats, long durationMs) { + stats[mUidTopActivityTimePosition] = durationMs; + } + + /** + * Returns time the UID spent in the TOP state. + */ + public long getUidTopActivityDuration(long[] stats) { + return stats[mUidTopActivityTimePosition]; + } + + @Override + public void toExtras(PersistableBundle extras) { + super.toExtras(extras); + extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount); + extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition); + extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS, + mDeviceBrightnessDurationPositions); + extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition); + extras.putInt(EXTRA_DEVICE_DOZE_POWER_POSITION, mDeviceScreenDozePowerPosition); + extras.putInt(EXTRA_UID_FOREGROUND_DURATION, mUidTopActivityTimePosition); + } + + @Override + public void fromExtras(PersistableBundle extras) { + super.fromExtras(extras); + mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1); + mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION); + mDeviceBrightnessDurationPositions = extras.getIntArray( + EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS); + mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION); + mDeviceScreenDozePowerPosition = extras.getInt(EXTRA_DEVICE_DOZE_POWER_POSITION); + mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION); + } +} diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java new file mode 100644 index 000000000000..e203e4a4175b --- /dev/null +++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static android.os.BatteryConsumer.PROCESS_STATE_ANY; + +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import android.os.BatteryStats; +import android.util.Slog; + +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; + +import java.util.ArrayList; +import java.util.List; + +public class ScreenPowerStatsProcessor extends PowerStatsProcessor { + private static final String TAG = "ScreenPowerStatsProcessor"; + private final int mDisplayCount; + private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators; + private final UsageBasedPowerEstimator[] mScreenDozePowerEstimators; + private final UsageBasedPowerEstimator[][] mScreenBrightnessLevelPowerEstimators; + private PowerStats.Descriptor mLastUsedDescriptor; + private ScreenPowerStatsLayout mStatsLayout; + private PowerEstimationPlan mPlan; + private long[] mTmpDeviceStatsArray; + private long[] mTmpUidStatsArray; + + private static class Intermediates { + public double power; + } + + public ScreenPowerStatsProcessor(PowerProfile powerProfile) { + mDisplayCount = powerProfile.getNumDisplays(); + mScreenOnPowerEstimators = new UsageBasedPowerEstimator[mDisplayCount]; + mScreenDozePowerEstimators = new UsageBasedPowerEstimator[mDisplayCount]; + mScreenBrightnessLevelPowerEstimators = new UsageBasedPowerEstimator[mDisplayCount][]; + for (int display = 0; display < mDisplayCount; display++) { + mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display)); + + double averagePowerFullBrightness = powerProfile.getAveragePowerForOrdinal( + POWER_GROUP_DISPLAY_SCREEN_FULL, display); + mScreenBrightnessLevelPowerEstimators[display] = + new UsageBasedPowerEstimator[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS]; + for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) { + // For example, if the number of bins is 3, the corresponding averages + // are calculated as 0.5 * full, 1.5 * full, 2.5 * full + final double binPowerMah = averagePowerFullBrightness * (bin + 0.5) + / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + mScreenBrightnessLevelPowerEstimators[display][bin] = + new UsageBasedPowerEstimator(binPowerMah); + } + + mScreenDozePowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, display)); + } + } + + private boolean unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) { + if (descriptor == null) { + return false; + } + + if (descriptor.equals(mLastUsedDescriptor)) { + return true; + } + + mLastUsedDescriptor = descriptor; + mStatsLayout = new ScreenPowerStatsLayout(descriptor); + if (mStatsLayout.getDisplayCount() != mDisplayCount) { + Slog.e(TAG, "Incompatible number of displays: " + mStatsLayout.getDisplayCount() + + ", expected: " + mDisplayCount); + return false; + } + + mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; + mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; + return true; + } + + @Override + void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) { + if (!unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor())) { + return; + } + + if (mPlan == null) { + mPlan = new PowerEstimationPlan(stats.getConfig()); + } + + computeDevicePowerEstimates(stats); + combineDeviceStateEstimates(); + + List<Integer> uids = new ArrayList<>(); + stats.collectUids(uids); + + if (!uids.isEmpty()) { + computeUidPowerEstimates(stats, uids); + } + } + + private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) { + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i); + if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) { + continue; + } + + if (estimation.stateValues[STATE_SCREEN] == SCREEN_STATE_ON) { + double power; + if (mStatsLayout.getEnergyConsumerCount() > 0) { + power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0)); + } else { + power = 0; + for (int display = 0; display < mStatsLayout.getDisplayCount(); display++) { + power += computeDisplayPower(mTmpDeviceStatsArray, display); + } + } + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power); + Intermediates intermediates = new Intermediates(); + intermediates.power = power; + estimation.intermediates = intermediates; + } else { + double power = 0; + if (mStatsLayout.getEnergyConsumerCount() > 0) { + power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0)); + } else { + for (int display = 0; display < mStatsLayout.getDisplayCount(); display++) { + power += mScreenDozePowerEstimators[display].calculatePower( + mStatsLayout.getScreenDozeDuration(mTmpDeviceStatsArray, display)); + } + } + mStatsLayout.setScreenDozePowerEstimate(mTmpDeviceStatsArray, power); + } + + stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray); + } + } + + private double computeDisplayPower(long[] stats, int display) { + double power = mScreenOnPowerEstimators[display] + .calculatePower(mStatsLayout.getScreenOnDuration(stats, display)); + for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) { + power += mScreenBrightnessLevelPowerEstimators[display][bin] + .calculatePower(mStatsLayout.getBrightnessLevelDuration(stats, display, bin)); + } + return power; + } + + /** + * Combine power estimates before distributing them proportionally to UIDs. + */ + private void combineDeviceStateEstimates() { + for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) { + CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i); + List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations; + double power = 0; + for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) { + DeviceStateEstimation dse = deviceStateEstimations.get(j); + Intermediates intermediates = (Intermediates) dse.intermediates; + if (intermediates != null) { + power += intermediates.power; + } + } + if (power != 0) { + Intermediates cdseIntermediates = new Intermediates(); + cdseIntermediates.power = power; + cdse.intermediates = cdseIntermediates; + } + } + } + + private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, + List<Integer> uids) { + int[] uidStateValues = new int[stats.getConfig().getUidStateConfig().length]; + uidStateValues[STATE_SCREEN] = SCREEN_STATE_ON; + uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_ANY; + + for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) { + UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i); + Intermediates intermediates = + (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates; + int[] deviceStateValues = uidStateEstimate.combinedDeviceStateEstimate + .stateValues; + if (deviceStateValues[STATE_SCREEN] != SCREEN_STATE_ON + || intermediates == null) { + continue; + } + + uidStateValues[STATE_POWER] = deviceStateValues[STATE_POWER]; + + long totalTopActivityDuration = 0; + for (int j = uids.size() - 1; j >= 0; j--) { + int uid = uids.get(j); + if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) { + totalTopActivityDuration += + mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray); + } + } + + if (totalTopActivityDuration == 0) { + return; + } + + for (int j = uids.size() - 1; j >= 0; j--) { + int uid = uids.get(j); + if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) { + long duration = mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray); + double power = intermediates.power * duration / totalTopActivityDuration; + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray); + } + } + } + } +} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index bea16dc3d59a..7d70ea13f12c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8403,7 +8403,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ @ActivityInfo.SizeChangesSupportMode private int supportsSizeChanges() { - if (mLetterboxUiController.shouldOverrideForceNonResizeApp()) { + if (mAppCompatController.getAppCompatResizeOverrides() + .shouldOverrideForceNonResizeApp()) { return SIZE_CHANGES_UNSUPPORTED_OVERRIDE; } @@ -8411,7 +8412,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return SIZE_CHANGES_SUPPORTED_METADATA; } - if (mLetterboxUiController.shouldOverrideForceResizeApp()) { + if (mAppCompatController.getAppCompatResizeOverrides() + .shouldOverrideForceResizeApp()) { return SIZE_CHANGES_SUPPORTED_OVERRIDE; } @@ -10494,7 +10496,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAppCompatController.getAppCompatOrientationOverrides() .shouldIgnoreOrientationRequestLoop()); proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP, - mLetterboxUiController.shouldOverrideForceResizeApp()); + mAppCompatController.getAppCompatResizeOverrides().shouldOverrideForceResizeApp()); proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS, mAppCompatController.getAppCompatAspectRatioOverrides() .shouldEnableUserAspectRatioSettings()); diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 998d65d84b3d..54223b609449 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -87,6 +87,11 @@ class AppCompatController { return mAppCompatOverrides.getAppCompatAspectRatioOverrides(); } + @NonNull + AppCompatResizeOverrides getAppCompatResizeOverrides() { + return mAppCompatOverrides.getAppCompatResizeOverrides(); + } + @Nullable AppCompatCameraPolicy getAppCompatCameraPolicy() { if (mActivityRecord.mDisplayContent != null) { diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java index f1ee23b60038..445001178d26 100644 --- a/services/core/java/com/android/server/wm/AppCompatOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java @@ -16,12 +16,6 @@ package com.android.server.wm; -import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; -import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; - -import static com.android.server.wm.AppCompatUtils.isChangeEnabled; - import android.annotation.NonNull; import com.android.server.wm.utils.OptPropFactory; @@ -32,10 +26,6 @@ import com.android.server.wm.utils.OptPropFactory; public class AppCompatOverrides { @NonNull - private final ActivityRecord mActivityRecord; - @NonNull - private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp; - @NonNull private final AppCompatOrientationOverrides mAppCompatOrientationOverrides; @NonNull private final AppCompatCameraOverrides mAppCompatCameraOverrides; @@ -43,26 +33,24 @@ public class AppCompatOverrides { private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides; @NonNull private final AppCompatFocusOverrides mAppCompatFocusOverrides; + @NonNull + private final AppCompatResizeOverrides mAppCompatResizeOverrides; AppCompatOverrides(@NonNull ActivityRecord activityRecord, @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder) { - mActivityRecord = activityRecord; - - mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord, + mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord, appCompatConfiguration, optPropBuilder); - mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord, + mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord, appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides); // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability. mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord, appCompatConfiguration, optPropBuilder, activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture, activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier); - mAppCompatFocusOverrides = new AppCompatFocusOverrides(mActivityRecord, + mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration, optPropBuilder); - - mAllowForceResizeOverrideOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder); } @NonNull @@ -85,35 +73,8 @@ public class AppCompatOverrides { return mAppCompatFocusOverrides; } - /** - * Whether we should apply the force resize per-app override. When this override is applied it - * forces the packages it is applied to to be resizable. It won't change whether the app can be - * put into multi-windowing mode, but allow the app to resize without going into size-compat - * mode when the window container resizes, such as display size change or screen rotation. - * - * <p>This method returns {@code true} when the following conditions are met: - * <ul> - * <li>Opt-out component property isn't enabled - * <li>Per-app override is enabled - * </ul> - */ - boolean shouldOverrideForceResizeApp() { - return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( - isChangeEnabled(mActivityRecord, FORCE_RESIZE_APP)); - } - - /** - * Whether we should apply the force non resize per-app override. When this override is applied - * it forces the packages it is applied to to be non-resizable. - * - * <p>This method returns {@code true} when the following conditions are met: - * <ul> - * <li>Opt-out component property isn't enabled - * <li>Per-app override is enabled - * </ul> - */ - boolean shouldOverrideForceNonResizeApp() { - return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( - isChangeEnabled(mActivityRecord, FORCE_NON_RESIZE_APP)); + @NonNull + AppCompatResizeOverrides getAppCompatResizeOverrides() { + return mAppCompatResizeOverrides; } } diff --git a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java new file mode 100644 index 000000000000..60c18254eca7 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; +import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; + +import static com.android.server.wm.AppCompatUtils.isChangeEnabled; + +import android.annotation.NonNull; + +import com.android.server.wm.utils.OptPropFactory; + +/** + * Encapsulate app compat logic about resizability. + */ +class AppCompatResizeOverrides { + + @NonNull + private final ActivityRecord mActivityRecord; + + @NonNull + private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp; + + AppCompatResizeOverrides(@NonNull ActivityRecord activityRecord, + @NonNull OptPropFactory optPropBuilder) { + mActivityRecord = activityRecord; + mAllowForceResizeOverrideOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + } + + /** + * Whether we should apply the force resize per-app override. When this override is applied it + * forces the packages it is applied to to be resizable. It won't change whether the app can be + * put into multi-windowing mode, but allow the app to resize without going into size-compat + * mode when the window container resizes, such as display size change or screen rotation. + * + * <p>This method returns {@code true} when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Per-app override is enabled + * </ul> + */ + boolean shouldOverrideForceResizeApp() { + return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( + isChangeEnabled(mActivityRecord, FORCE_RESIZE_APP)); + } + + /** + * Whether we should apply the force non resize per-app override. When this override is applied + * it forces the packages it is applied to to be non-resizable. + * + * <p>This method returns {@code true} when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Per-app override is enabled + * </ul> + */ + boolean shouldOverrideForceNonResizeApp() { + return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( + isChangeEnabled(mActivityRecord, FORCE_NON_RESIZE_APP)); + } +} diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index cbb210f514a0..9996bbcb6597 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -27,7 +27,6 @@ import static com.android.server.wm.AppCompatUtils.computeAspectRatio; import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity; import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 5d4198fead34..444097a3ea05 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -116,54 +116,6 @@ final class LetterboxUiController { } } - /** - * Whether we should apply the force resize per-app override. When this override is applied it - * forces the packages it is applied to to be resizable. It won't change whether the app can be - * put into multi-windowing mode, but allow the app to resize without going into size-compat - * mode when the window container resizes, such as display size change or screen rotation. - * - * <p>This method returns {@code true} when the following conditions are met: - * <ul> - * <li>Opt-out component property isn't enabled - * <li>Per-app override is enabled - * </ul> - */ - boolean shouldOverrideForceResizeApp() { - return getAppCompatOverrides().shouldOverrideForceResizeApp(); - } - - /** - * Whether we should apply the force non resize per-app override. When this override is applied - * it forces the packages it is applied to to be non-resizable. - * - * <p>This method returns {@code true} when the following conditions are met: - * <ul> - * <li>Opt-out component property isn't enabled - * <li>Per-app override is enabled - * </ul> - */ - boolean shouldOverrideForceNonResizeApp() { - return getAppCompatOverrides().shouldOverrideForceNonResizeApp(); - } - - /** - * Whether should fix display orientation to landscape natural orientation when a task is - * fullscreen and the display is ignoring orientation requests. - * - * <p>This treatment is enabled when the following conditions are met: - * <ul> - * <li>Opt-out component property isn't enabled - * <li>Opt-in per-app override is enabled - * <li>Task is in fullscreen. - * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled - * <li>Natural orientation of the display is landscape. - * </ul> - */ - boolean shouldUseDisplayLandscapeNaturalOrientation() { - return getAppCompatOverrides().getAppCompatOrientationOverrides() - .shouldUseDisplayLandscapeNaturalOrientation(); - } - boolean hasWallpaperBackgroundForLetterbox() { return mShowWallpaperForLetterboxBackground; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f9772f4c7c5c..ab1e96952c53 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1132,6 +1132,9 @@ class Task extends TaskFragment { final Task oldParentTask = oldParent.asTask(); if (oldParentTask != null) { forAllActivities(oldParentTask::cleanUpActivityReferences); + + // Update the task description of the previous parent as well + oldParentTask.updateTaskDescription(); } if (newParent == null || !newParent.inPinnedWindowingMode()) { @@ -1163,6 +1166,9 @@ class Task extends TaskFragment { } catch (RemoteException e) { } } + + // Update the ancestor tasks' task description after reparenting + updateTaskDescription(); } // First time we are adding the task to the system. @@ -3353,7 +3359,7 @@ class Task extends TaskFragment { //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child // order changes. - final Task topTask = top != null ? top.getTask() : this; + final Task topTask = top != null && top.getTask() != null ? top.getTask() : this; info.resizeMode = topTask.mResizeMode; info.topActivityType = topTask.getActivityType(); info.displayCutoutInsets = topTask.getDisplayCutoutInsets(); @@ -6138,9 +6144,8 @@ class Task extends TaskFragment { @Override void onChildPositionChanged(WindowContainer child) { - dispatchTaskInfoChangedIfNeeded(false /* force */); - if (!mChildren.contains(child)) { + dispatchTaskInfoChangedIfNeeded(false /* force */); return; } if (child.asTask() != null) { @@ -6152,6 +6157,10 @@ class Task extends TaskFragment { // Send for TaskFragmentParentInfo#hasDirectActivity change. sendTaskFragmentParentInfoChangedIfNeeded(); } + + // Update the ancestor tasks' task description after any children have reparented + updateTaskDescription(); + dispatchTaskInfoChangedIfNeeded(false /* force */); } void reparent(TaskDisplayArea newParent, boolean onTop) { diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 8ae1cf0e0709..88e8064edc0e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -380,7 +380,7 @@ public abstract class WindowManagerInternal { InputChannel source) { return state.register(display) .thenApply(unused -> - service.startDragAndDrop(source, state.getInputChannel())); + service.startDragAndDrop(source.getToken(), state.getInputChannel())); } /** diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java index 337d5c1faf94..faa198ca41eb 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java @@ -49,6 +49,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.internal.annotations.GuardedBy; import com.android.server.wm.ImeTargetChangeListener; import com.android.server.wm.WindowManagerInternal; @@ -93,33 +94,37 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showImplicit() { - initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), - InputMethodManager.SHOW_IMPLICIT); - mComputer.requestImeVisibility(mWindowToken, res); - - final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); - assertThat(state).isNotNull(); - assertThat(state.hasEditorFocused()).isTrue(); - assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); - assertThat(state.isRequestedImeVisible()).isTrue(); - - assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + synchronized (ImfLock.class) { + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEditorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isTrue(); + + assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + } } @Test public void testRequestImeVisibility_showExplicit() { - initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); - mComputer.requestImeVisibility(mWindowToken, res); - - final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); - assertThat(state).isNotNull(); - assertThat(state.hasEditorFocused()).isTrue(); - assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); - assertThat(state.isRequestedImeVisible()).isTrue(); - - assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + synchronized (ImfLock.class) { + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEditorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isTrue(); + + assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + } } /** @@ -128,12 +133,14 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes */ @Test public void testRequestImeVisibility_showExplicit_thenShowImplicit() { - initImeTargetWindowState(mWindowToken); - mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); - assertThat(mComputer.mRequestedShowExplicitly).isTrue(); - - mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); - assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + synchronized (ImfLock.class) { + initImeTargetWindowState(mWindowToken); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); + assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + + mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + } } /** @@ -142,162 +149,183 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes */ @Test public void testRequestImeVisibility_showForced_thenShowExplicit() { - initImeTargetWindowState(mWindowToken); - mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED); - assertThat(mComputer.mShowForced).isTrue(); - - mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); - assertThat(mComputer.mShowForced).isTrue(); + synchronized (ImfLock.class) { + initImeTargetWindowState(mWindowToken); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED); + assertThat(mComputer.mShowForced).isTrue(); + + mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); + assertThat(mComputer.mShowForced).isTrue(); + } } @Test public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() { - // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy - mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN); - - initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), - InputMethodManager.SHOW_IMPLICIT); - mComputer.requestImeVisibility(mWindowToken, res); - - final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); - assertThat(state).isNotNull(); - assertThat(state.hasEditorFocused()).isTrue(); - assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); - assertThat(state.isRequestedImeVisible()).isFalse(); - - assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + synchronized (ImfLock.class) { + // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy + mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN); + + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEditorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isFalse(); + + assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + } } @Test public void testRequestImeVisibility_showImplicit_imeHiddenPolicy() { - // Precondition: set IME hidden display policy before calling showSoftInput - mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); - - initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), - InputMethodManager.SHOW_IMPLICIT); - mComputer.requestImeVisibility(mWindowToken, res); - - final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); - assertThat(state).isNotNull(); - assertThat(state.hasEditorFocused()).isTrue(); - assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); - assertThat(state.isRequestedImeVisible()).isFalse(); - - assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + synchronized (ImfLock.class) { + // Precondition: set IME hidden display policy before calling showSoftInput + mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); + + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEditorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isFalse(); + + assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + } } @Test public void testRequestImeVisibility_hideNotAlways() { - // Precondition: ensure IME has shown before hiding request. - mComputer.setInputShown(true); - - initImeTargetWindowState(mWindowToken); - assertThat(mComputer.canHideIme(ImeTracker.Token.empty(), - InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); - mComputer.requestImeVisibility(mWindowToken, false); - - final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); - assertThat(state).isNotNull(); - assertThat(state.hasEditorFocused()).isTrue(); - assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); - assertThat(state.isRequestedImeVisible()).isFalse(); + synchronized (ImfLock.class) { + // Precondition: ensure IME has shown before hiding request. + mComputer.setInputShown(true); + + initImeTargetWindowState(mWindowToken); + assertThat(mComputer.canHideIme(ImeTracker.Token.empty(), + InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); + mComputer.requestImeVisibility(mWindowToken, false); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEditorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isFalse(); + } } @Test public void testComputeImeDisplayId() { - final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken); - - mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL; - mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY); - assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); - assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY); - - mComputer.computeImeDisplayId(state, 10 /* displayId */); - assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); - assertThat(state.getImeDisplayId()).isEqualTo(10); - - mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE; - mComputer.computeImeDisplayId(state, 10 /* displayId */); - assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue(); - assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY); - - mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY; - mComputer.computeImeDisplayId(state, 10 /* displayId */); - assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); - assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID); + synchronized (ImfLock.class) { + final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken); + + mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL; + mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); + assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY); + + mComputer.computeImeDisplayId(state, 10 /* displayId */); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); + assertThat(state.getImeDisplayId()).isEqualTo(10); + + mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE; + mComputer.computeImeDisplayId(state, 10 /* displayId */); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue(); + assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY); + + mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY; + mComputer.computeImeDisplayId(state, 10 /* displayId */); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); + assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID); + } } @Test public void testComputeState_lastImeRequestedVisible_preserved_When_StateUnChanged() { - // Assume the last IME targeted window has requested IME visible - final IBinder lastImeTargetWindowToken = new Binder(); - mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken; - mComputer.requestImeVisibility(lastImeTargetWindowToken, true); - final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull( - lastImeTargetWindowToken); - assertThat(lastState.isRequestedImeVisible()).isTrue(); - - // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME - // visibility state will be preserved to the current window state. - final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(mWindowToken); - mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */); - assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo( - lastState.isRequestedImeVisible()); + synchronized (ImfLock.class) { + // Assume the last IME targeted window has requested IME visible + final IBinder lastImeTargetWindowToken = new Binder(); + mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken; + mComputer.requestImeVisibility(lastImeTargetWindowToken, true); + final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull( + lastImeTargetWindowToken); + assertThat(lastState.isRequestedImeVisible()).isTrue(); + + // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME + // visibility state will be preserved to the current window state. + final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState( + mWindowToken); + mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */); + assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo( + lastState.isRequestedImeVisible()); + } } @Test public void testOnInteractiveChanged() { - mComputer.getOrCreateWindowState(mWindowToken); - // Precondition: ensure IME has shown before hiding request. - mComputer.requestImeVisibility(mWindowToken, true); - mComputer.setInputShown(true); - - // No need any visibility change When initially shows IME on the device was interactive. - ImeVisibilityStateComputer.ImeVisibilityResult result = mComputer.onInteractiveChanged( - mWindowToken, true /* interactive */); - assertThat(result).isNull(); - - // Show the IME screenshot to capture the last IME visible state when the device inactive. - result = mComputer.onInteractiveChanged(mWindowToken, false /* interactive */); - assertThat(result).isNotNull(); - assertThat(result.getState()).isEqualTo(STATE_SHOW_IME_SNAPSHOT); - assertThat(result.getReason()).isEqualTo(SHOW_IME_SCREENSHOT_FROM_IMMS); - - // Remove the IME screenshot when the device became interactive again. - result = mComputer.onInteractiveChanged(mWindowToken, true /* interactive */); - assertThat(result).isNotNull(); - assertThat(result.getState()).isEqualTo(STATE_REMOVE_IME_SNAPSHOT); - assertThat(result.getReason()).isEqualTo(REMOVE_IME_SCREENSHOT_FROM_IMMS); + synchronized (ImfLock.class) { + mComputer.getOrCreateWindowState(mWindowToken); + // Precondition: ensure IME has shown before hiding request. + mComputer.requestImeVisibility(mWindowToken, true); + mComputer.setInputShown(true); + + // No need any visibility change When initially shows IME on the device was interactive. + ImeVisibilityStateComputer.ImeVisibilityResult result = mComputer.onInteractiveChanged( + mWindowToken, true /* interactive */); + assertThat(result).isNull(); + + // Show the IME screenshot to capture the last IME visible state when the device + // inactive. + result = mComputer.onInteractiveChanged(mWindowToken, false /* interactive */); + assertThat(result).isNotNull(); + assertThat(result.getState()).isEqualTo(STATE_SHOW_IME_SNAPSHOT); + assertThat(result.getReason()).isEqualTo(SHOW_IME_SCREENSHOT_FROM_IMMS); + + // Remove the IME screenshot when the device became interactive again. + result = mComputer.onInteractiveChanged(mWindowToken, true /* interactive */); + assertThat(result).isNotNull(); + assertThat(result.getState()).isEqualTo(STATE_REMOVE_IME_SNAPSHOT); + assertThat(result.getReason()).isEqualTo(REMOVE_IME_SCREENSHOT_FROM_IMMS); + } } @Test public void testOnApplyImeVisibilityFromComputer() { - final IBinder testImeTargetOverlay = new Binder(); - final IBinder testImeInputTarget = new Binder(); - - // Simulate a test IME input target was visible. - mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false); - - // Simulate a test IME layering target overlay fully occluded the IME input target. - mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay, - TYPE_APPLICATION_OVERLAY, true, false); - mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false); - final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class); - final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass( - ImeVisibilityResult.class); - verify(mInputMethodManagerService).onApplyImeVisibilityFromComputer(targetCaptor.capture(), - notNull() /* statsToken */, resultCaptor.capture()); - final IBinder imeInputTarget = targetCaptor.getValue(); - final ImeVisibilityResult result = resultCaptor.getValue(); - - // Verify the computer will callback hiding IME state to IMMS. - assertThat(imeInputTarget).isEqualTo(testImeInputTarget); - assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT); - assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE); + synchronized (ImfLock.class) { + final IBinder testImeTargetOverlay = new Binder(); + final IBinder testImeInputTarget = new Binder(); + + // Simulate a test IME input target was visible. + mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false); + + // Simulate a test IME layering target overlay fully occluded the IME input target. + mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay, + TYPE_APPLICATION_OVERLAY, true, false); + mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false); + final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class); + final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass( + ImeVisibilityResult.class); + synchronized (ImfLock.class) { + verify(mInputMethodManagerService).onApplyImeVisibilityFromComputerLocked( + targetCaptor.capture(), notNull() /* statsToken */, resultCaptor.capture()); + } + final IBinder imeInputTarget = targetCaptor.getValue(); + final ImeVisibilityResult result = resultCaptor.getValue(); + + // Verify the computer will callback hiding IME state to IMMS. + assertThat(imeInputTarget).isEqualTo(testImeInputTarget); + assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT); + assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE); + } } + @GuardedBy("ImfLock.class") private ImeTargetWindowState initImeTargetWindowState(IBinder windowToken) { final ImeTargetWindowState state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNCHANGED, 0, true, true, true); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java new file mode 100644 index 000000000000..8d2849bdfe73 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.Handler; +import android.platform.test.ravenwood.RavenwoodRule; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; +import com.android.server.power.stats.ScreenPowerStatsCollector.Injector; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.IntSupplier; + +public class AmbientDisplayPowerStatsProcessorTest { + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setNumDisplays(2) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 0, 180.0) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 1, 360.0); + + private static final double PRECISION = 0.1; + private static final int VOLTAGE_MV = 3500; + + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + @Mock + private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever; + + private final Injector mInjector = new Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return new PowerStatsUidResolver(); + } + + @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> VOLTAGE_MV; + } + + @Override + public int getDisplayCount() { + return 2; + } + + @Override + public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() { + return mScreenUsageTimeRetriever; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void processPowerStats() { + PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats(); + + assertPowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 16.2); + assertPowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 5.4); + assertPowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 0); + assertPowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 0); + } + + private PowerComponentAggregatedPowerStats collectAndAggregatePowerStats() { + ScreenPowerStatsProcessor screenPowerStatsProcessor = + new ScreenPowerStatsProcessor(mStatsRule.getPowerProfile()); + AmbientDisplayPowerStatsProcessor ambientDisplayPowerStatsProcessor = + new AmbientDisplayPowerStatsProcessor(); + + AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN) + .setProcessor(screenPowerStatsProcessor); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, + BatteryConsumer.POWER_COMPONENT_SCREEN) + .setProcessor(ambientDisplayPowerStatsProcessor); + + AggregatedPowerStats stats = new AggregatedPowerStats(config); + + stats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0); + stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_OTHER, 0); + + ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector); + collector.setEnabled(true); + + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY)) + .thenReturn(new int[0]); + + stats.addPowerStats(collector.collectStats(), 1000); + + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0)) + .thenReturn(60_000L); + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1)) + .thenReturn(120_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0)) + .thenReturn(180_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1)) + .thenReturn(240_000L); + stats.setDeviceState(STATE_POWER, POWER_STATE_BATTERY, 101_000); + stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 401_000); + + // Slightly larger than 600_000 total screen time, to simulate a sight race + // between state changes and power stats collection + stats.addPowerStats(collector.collectStats(), 612_000); + + stats.finish(612_000); + + return stats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY); + } + + private void assertPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats, + int powerState, int screenState, double expectedPowerEstimate) { + PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor(); + PowerStatsLayout layout = new PowerStatsLayout(descriptor); + long[] stats = new long[descriptor.statsArrayLength]; + aggregatedStats.getDeviceStats(stats, new int[]{powerState, screenState}); + assertThat(layout.getDevicePowerEstimate(stats)).isWithin(PRECISION) + .of(expectedPowerEstimate); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java new file mode 100644 index 000000000000..817fdcb10577 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Handler; +import android.platform.test.ravenwood.RavenwoodRule; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; +import com.android.server.power.stats.ScreenPowerStatsCollector.Injector; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.IntSupplier; + +public class ScreenPowerStatsCollectorTest { + private static final int APP_UID1 = 42; + private static final int APP_UID2 = 24; + private static final int ISOLATED_UID = 99123; + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, 1000); + + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + @Mock + private PowerStatsUidResolver mPowerStatsUidResolver; + @Mock + private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever; + + private final Injector mInjector = new Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> 3500; + } + + @Override + public int getDisplayCount() { + return 2; + } + + @Override + public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() { + return mScreenUsageTimeRetriever; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> { + int uid = invocation.getArgument(0); + if (uid == ISOLATED_UID) { + return APP_UID2; + } else { + return uid; + } + }); + } + + @Test + public void collectStats() { + ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector); + collector.setEnabled(true); + + // Establish a baseline + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY)) + .thenReturn(new int[]{77}); + when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77})) + .thenReturn(new long[]{10_000}); + + doAnswer(inv -> { + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback = + inv.getArgument(0); + callback.onUidTopActivityTime(APP_UID1, 1000); + callback.onUidTopActivityTime(APP_UID2, 2000); + return null; + }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any( + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class)); + + collector.collectStats(); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77})) + .thenReturn(new long[]{45_000}); + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0)) + .thenReturn(60_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_DARK)) + .thenReturn(10_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_MEDIUM)) + .thenReturn(20_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_BRIGHT)) + .thenReturn(30_000L); + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1)) + .thenReturn(120_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0)) + .thenReturn(180_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1)) + .thenReturn(240_000L); + doAnswer(inv -> { + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback = + inv.getArgument(0); + callback.onUidTopActivityTime(APP_UID1, 3000); + callback.onUidTopActivityTime(APP_UID2, 5000); + callback.onUidTopActivityTime(ISOLATED_UID, 7000); + return null; + }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any( + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class)); + + + PowerStats powerStats = collector.collectStats(); + + ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(); + layout.fromExtras(powerStats.descriptor.extras); + + // (45000 - 10000) / 3500 + assertThat(layout.getConsumedEnergy(powerStats.stats, 0)) + .isEqualTo(10_000); + + assertThat(layout.getScreenOnDuration(powerStats.stats, 0)) + .isEqualTo(60_000); + assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0, + BatteryStats.SCREEN_BRIGHTNESS_DARK)) + .isEqualTo(10_000); + assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0, + BatteryStats.SCREEN_BRIGHTNESS_MEDIUM)) + .isEqualTo(20_000); + assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0, + BatteryStats.SCREEN_BRIGHTNESS_BRIGHT)) + .isEqualTo(30_000); + assertThat(layout.getScreenOnDuration(powerStats.stats, 1)) + .isEqualTo(120_000); + assertThat(layout.getScreenDozeDuration(powerStats.stats, 0)) + .isEqualTo(180_000); + assertThat(layout.getScreenDozeDuration(powerStats.stats, 1)) + .isEqualTo(240_000); + + assertThat(powerStats.uidStats.size()).isEqualTo(2); + // 3000 - 1000 + assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID1))) + .isEqualTo(2000); + // (5000 - 2000) + 7000 + assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID2))) + .isEqualTo(10000); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java new file mode 100644 index 000000000000..9fde61a51e75 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static android.os.BatteryConsumer.PROCESS_STATE_ANY; + +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.Process; +import android.platform.test.ravenwood.RavenwoodRule; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; +import com.android.server.power.stats.ScreenPowerStatsCollector.Injector; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.IntSupplier; + +public class ScreenPowerStatsProcessorTest { + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setNumDisplays(2) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 0, 180.0) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 1, 360.0) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 0, 480.0) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 1, 720.0) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 4800.0) + .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 1, 7200.0) + .initMeasuredEnergyStatsLocked(); + + private static final double PRECISION = 0.1; + private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42; + private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101; + private static final int VOLTAGE_MV = 3500; + + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + @Mock + private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever; + + private final Injector mInjector = new Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return new PowerStatsUidResolver(); + } + + @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> VOLTAGE_MV; + } + + @Override + public int getDisplayCount() { + return 2; + } + + @Override + public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() { + return mScreenUsageTimeRetriever; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void processPowerStats_powerProfile() { + PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats(false); + + assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 195.5, 0); + assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0, 0.6); + assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 97.8, 0); + assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0, 0); + + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_ON, 78.2); + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0); + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_ON, 39.1); + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0); + + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_ON, 117.3); + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0); + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_ON, 58.7); + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0); + } + + @Test + public void processPowerStats_energyConsumer() { + PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats(true); + + assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 261.9, 0); + assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0, 7.2); + assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 130.9, 0); + assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0, 0); + + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_ON, 104.8); + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0); + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_ON, 52.4); + assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0); + + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_ON, 157.1); + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0); + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_ON, 78.6); + assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0); + } + + private PowerComponentAggregatedPowerStats collectAndAggregatePowerStats( + boolean energyConsumer) { + ScreenPowerStatsProcessor processor = + new ScreenPowerStatsProcessor(mStatsRule.getPowerProfile()); + + PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); + + ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector); + collector.setEnabled(true); + + if (energyConsumer) { + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY)) + .thenReturn(new int[]{77}); + when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77})) + .thenReturn(new long[]{10_000}); + } else { + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY)) + .thenReturn(new int[0]); + } + + doAnswer(inv -> { + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback = + inv.getArgument(0); + callback.onUidTopActivityTime(APP_UID1, 1000); + callback.onUidTopActivityTime(APP_UID2, 2000); + return null; + }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any( + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class)); + + aggregatedStats.addPowerStats(collector.collectStats(), 1000); + + if (energyConsumer) { + // 400 mAh represented as microWattSeconds + long energyUws = 400L * 3600 * VOLTAGE_MV; + when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77})) + .thenReturn(new long[]{10_000 + energyUws}); + } + + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0)) + .thenReturn(60_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_DARK)) + .thenReturn(10_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_MEDIUM)) + .thenReturn(20_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_BRIGHT)) + .thenReturn(30_000L); + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1)) + .thenReturn(120_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0)) + .thenReturn(180_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1)) + .thenReturn(240_000L); + doAnswer(inv -> { + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback = + inv.getArgument(0); + callback.onUidTopActivityTime(APP_UID1, 3000); + callback.onUidTopActivityTime(APP_UID2, 5000); + return null; + }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any( + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class)); + + aggregatedStats.setState(STATE_POWER, POWER_STATE_BATTERY, 201_000); + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 601_000); + + // Slightly larger than 600_000 total screen time, to simulate a sight race + // between state changes and power stats collection + aggregatedStats.addPowerStats(collector.collectStats(), 612_000); + + aggregatedStats.getConfig().getProcessor().finish(aggregatedStats, 180_000); + return aggregatedStats; + } + + private static PowerComponentAggregatedPowerStats createAggregatedPowerStats( + ScreenPowerStatsProcessor processor) { + AggregatedPowerStatsConfig.PowerComponent config = + new AggregatedPowerStatsConfig.PowerComponent( + BatteryConsumer.POWER_COMPONENT_SCREEN) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN) + .setProcessor(processor); + + PowerComponentAggregatedPowerStats aggregatedStats = + new PowerComponentAggregatedPowerStats( + new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config); + + aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0); + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0); + + return aggregatedStats; + } + + private void assertDevicePowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats, + int powerState, int screenState, double expectedScreenPowerEstimate, + double expectedDozePowerEstimate) { + PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor(); + ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor); + long[] stats = new long[descriptor.statsArrayLength]; + aggregatedStats.getDeviceStats(stats, new int[]{powerState, screenState}); + assertThat(layout.getDevicePowerEstimate(stats)).isWithin(PRECISION) + .of(expectedScreenPowerEstimate); + assertThat(layout.getScreenDozePowerEstimate(stats)).isWithin(PRECISION) + .of(expectedDozePowerEstimate); + } + + private void assertUidPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats, int uid, + int powerState, int screenState, double expectedScreenPowerEstimate) { + PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor(); + ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor); + long[] stats = new long[descriptor.uidStatsArrayLength]; + aggregatedStats.getUidStats(stats, uid, + new int[]{powerState, screenState, PROCESS_STATE_ANY}); + assertThat(layout.getUidPowerEstimate(stats)).isWithin(PRECISION) + .of(expectedScreenPowerEstimate); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index b07940ad8de3..d7bae457c70a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -1044,6 +1044,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class); mZenModeHelper.mAudioManager = mAudioManager; setupZenConfig(); + mTestableLooper.processAllMessages(); + reset(mAudioManager); // Turn manual zen mode on mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, @@ -1063,6 +1065,44 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + public void testSetConfig_updatesAudioForSequentialChangesToZenMode() { + AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class); + mZenModeHelper.mAudioManager = mAudioManager; + setupZenConfig(); + mTestableLooper.processAllMessages(); + reset(mAudioManager); + + // Turn manual zen mode on + mZenModeHelper.setManualZenMode( + ZEN_MODE_IMPORTANT_INTERRUPTIONS, + null, + UPDATE_ORIGIN_APP, + null, + "test", + CUSTOM_PKG_UID); + mZenModeHelper.setManualZenMode( + ZEN_MODE_IMPORTANT_INTERRUPTIONS, + null, + UPDATE_ORIGIN_APP, + null, + "test", + CUSTOM_PKG_UID); + + // audio manager shouldn't do anything until the handler processes its messages + verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal(); + + // now process the looper's messages + mTestableLooper.processAllMessages(); + + // Expect calls to audio manager + verify(mAudioManager, times(2)).updateRingerModeAffectedStreamsInternal(); + verify(mAudioManager, times(1)).setRingerModeInternal(anyInt(), anyString()); + + // called during applyZenToRingerMode(), which should be true since zen changed + verify(mAudioManager, atLeastOnce()).getRingerModeInternal(); + } + + @Test public void testParcelConfig() { mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 1f4a469bdd5a..bea691783a8a 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -1603,7 +1603,7 @@ public class VibratorManagerServiceTest { @Test @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_vendorEffectsWithPermission_successful() throws Exception { - // Deny permission to vibrate with vendor effects + // Grant permission to vibrate with vendor effects grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS); mockVibrators(1); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); @@ -1767,6 +1767,9 @@ public class VibratorManagerServiceTest { }) public void vibrate_withIntensitySettingsAndAdaptiveHaptics_appliesSettingsToVendorEffects() throws Exception { + // Grant permission to vibrate with vendor effects + grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); @@ -1789,7 +1792,7 @@ public class VibratorManagerServiceTest { assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1); VibrationEffect.VendorEffect scaled = fakeVibrator.getAllVendorEffects().get(0); - assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_STRONG); + assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_LIGHT); assertThat(scaled.getLinearScale()).isEqualTo(0.4f); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java index f5c2e1924180..220248cdb2c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -440,6 +440,7 @@ class AppCompatActivityRobot { spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides()); spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy()); spyOn(activity.mAppCompatController.getAppCompatFocusOverrides()); + spyOn(activity.mAppCompatController.getAppCompatResizeOverrides()); spyOn(activity.mLetterboxUiController); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java new file mode 100644 index 000000000000..8fc1a77bd5e3 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; +import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import android.compat.testing.PlatformCompatChangeRule; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; + +import libcore.junit.util.compat.CoreCompatChangeRule; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Test class for {@link AppCompatResizeOverrides}. + * <p> + * Build/Install/Run: + * atest WmTests:AppCompatResizeOverridesTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppCompatResizeOverridesTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP}) + public void testShouldOverrideForceResizeApp_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceResizeApp(/* expected */ true); + }); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP}) + public void testShouldOverrideForceResizeApp_propertyTrue_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceResizeApp(/* expected */ true); + }); + } + + @Test + @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP}) + public void testShouldOverrideForceResizeApp_propertyTrue_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceResizeApp(/* expected */ false); + }); + } + + @Test + @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP}) + public void testShouldOverrideForceResizeApp_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceResizeApp(/* expected */ false); + }); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP}) + public void testShouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceResizeApp(/* expected */ false); + }); + } + + @Test + @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP}) + public void testShouldOverrideForceResizeApp_propertyFalse_noOverride_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceResizeApp(/* expected */ false); + }); + } + + + @Test + @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP}) + public void testShouldOverrideForceNonResizeApp_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceNonResizeApp(/* expected */ true); + }); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP}) + public void testShouldOverrideForceNonResizeApp_propertyTrue_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceNonResizeApp(/* expected */ true); + }); + } + + @Test + @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP}) + public void testShouldOverrideForceNonResizeApp_propertyTrue_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceNonResizeApp(/* expected */ false); + }); + } + + @Test + @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP}) + public void testShouldOverrideForceNonResizeApp_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceNonResizeApp(/* expected */ false); + }); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP}) + public void testShouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceNonResizeApp(/* expected */ false); + }); + } + + @Test + @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP}) + public void testShouldOverrideForceNonResizeApp_propertyFalse_noOverride_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + robot.activity().createActivityWithComponent(); + robot.checkShouldOverrideForceNonResizeApp(/* expected */ false); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<ResizeOverridesRobotTest> consumer) { + spyOn(mWm.mAppCompatConfiguration); + final ResizeOverridesRobotTest robot = new ResizeOverridesRobotTest(mWm, mAtm, mSupervisor); + consumer.accept(robot); + } + + private static class ResizeOverridesRobotTest extends AppCompatRobotBase { + + ResizeOverridesRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor) { + super(wm, atm, supervisor); + } + + + void checkShouldOverrideForceResizeApp(boolean expected) { + Assert.assertEquals(expected, activity().top().mAppCompatController + .getAppCompatResizeOverrides().shouldOverrideForceResizeApp()); + } + + void checkShouldOverrideForceNonResizeApp(boolean expected) { + Assert.assertEquals(expected, activity().top().mAppCompatController + .getAppCompatResizeOverrides().shouldOverrideForceNonResizeApp()); + } + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 8cdb574a967b..e5650068c8fb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -152,8 +152,8 @@ public class DragDropControllerTests extends WindowTestsBase { // Use a new TestIWindow so we don't collect events for other windows final WindowState window = createWindow( null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow()); - window.mInputChannel = new InputChannel(); - window.mInputChannelToken = window.mInputChannel.getToken(); + InputChannel channel = new InputChannel(); + window.openInputChannel(channel); window.mHasSurface = true; mWm.mWindowMap.put(window.mClient.asBinder(), window); mWm.mInputToWindowMap.put(window.mInputChannelToken, window); @@ -178,7 +178,7 @@ public class DragDropControllerTests extends WindowTestsBase { TEST_PID, TEST_UID); mWindow = createDropTargetWindow("Drag test window", 0); doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0); - when(mWm.mInputManager.startDragAndDrop(any(InputChannel.class), + when(mWm.mInputManager.startDragAndDrop(any(IBinder.class), any(InputChannel.class))).thenReturn(true); mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); @@ -707,8 +707,7 @@ public class DragDropControllerTests extends WindowTestsBase { .setFormat(PixelFormat.TRANSLUCENT) .build(); - assertTrue(mWm.mInputManager.startDragAndDrop(new InputChannel(), - new InputChannel())); + assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new InputChannel())); mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, 0, 0, data); assertNotNull(mToken); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 26a4411f57c2..e2c0f6c20c01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -16,10 +16,7 @@ package com.android.server.wm; -import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; -import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; @@ -32,15 +29,12 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.Property; import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; @@ -58,9 +52,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.window.flags.Flags; -import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; -import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -306,122 +297,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - @EnableCompatChanges({FORCE_RESIZE_APP}) - public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() { - mController = new LetterboxUiController(mWm, mActivity); - - assertTrue(mController.shouldOverrideForceResizeApp()); - } - - @Test - @EnableCompatChanges({FORCE_RESIZE_APP}) - public void testshouldOverrideForceResizeApp_propertyTrue_overrideEnabled_returnsTrue() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true); - mController = new LetterboxUiController(mWm, mActivity); - - assertTrue(mController.shouldOverrideForceResizeApp()); - } - - @Test - @DisableCompatChanges({FORCE_RESIZE_APP}) - public void testshouldOverrideForceResizeApp_propertyTrue_overrideDisabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceResizeApp()); - } - - @Test - @DisableCompatChanges({FORCE_RESIZE_APP}) - public void testshouldOverrideForceResizeApp_overrideDisabled_returnsFalse() { - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceResizeApp()); - } - - @Test - @EnableCompatChanges({FORCE_RESIZE_APP}) - public void testshouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false); - - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceResizeApp()); - } - - @Test - @DisableCompatChanges({FORCE_RESIZE_APP}) - public void testshouldOverrideForceResizeApp_propertyFalse_noOverride_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceResizeApp()); - } - - @Test - @EnableCompatChanges({FORCE_NON_RESIZE_APP}) - public void testshouldOverrideForceNonResizeApp_overrideEnabled_returnsTrue() { - mController = new LetterboxUiController(mWm, mActivity); - - assertTrue(mController.shouldOverrideForceNonResizeApp()); - } - - @Test - @EnableCompatChanges({FORCE_NON_RESIZE_APP}) - public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideEnabled_returnsTrue() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true); - mController = new LetterboxUiController(mWm, mActivity); - - assertTrue(mController.shouldOverrideForceNonResizeApp()); - } - - @Test - @DisableCompatChanges({FORCE_NON_RESIZE_APP}) - public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideDisabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceNonResizeApp()); - } - - @Test - @DisableCompatChanges({FORCE_NON_RESIZE_APP}) - public void testshouldOverrideForceNonResizeApp_overrideDisabled_returnsFalse() { - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceNonResizeApp()); - } - - @Test - @EnableCompatChanges({FORCE_NON_RESIZE_APP}) - public void testshouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false); - - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceNonResizeApp()); - } - - @Test - @DisableCompatChanges({FORCE_NON_RESIZE_APP}) - public void testshouldOverrideForceNonResizeApp_propertyFalse_noOverride_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mController.shouldOverrideForceNonResizeApp()); - } - - @Test public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() { doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration) .isCameraCompatTreatmentEnabled(); @@ -550,14 +425,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { verify(mAppCompatConfiguration).getIsEducationEnabled(); } - private void mockThatProperty(String propertyName, boolean value) throws Exception { - Property property = new Property(propertyName, /* value */ value, /* packageName */ "", - /* className */ ""); - PackageManager pm = mWm.mContext.getPackageManager(); - spyOn(pm); - doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); - } - private ActivityRecord setUpActivityWithComponent() { mDisplayContent = new TestDisplayContent .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index a1ac02a21e35..a232ff0dfcb6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -46,6 +46,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.google.common.truth.Truth.assertThat; @@ -78,6 +79,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; @@ -2031,6 +2033,47 @@ public class TaskTests extends WindowTestsBase { task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode); } + @Test + public void testUpdateTaskDescriptionOnReparent() { + final Task rootTask1 = createTask(mDisplayContent); + final Task rootTask2 = createTask(mDisplayContent); + final Task childTask = createTaskInRootTask(rootTask1, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, childTask); + final String testLabel = "test_task_description_label"; + final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(testLabel); + activity.setTaskDescription(td); + + // Ensure the td is set for the original root task + assertEquals(testLabel, rootTask1.getTaskDescription().getLabel()); + assertNull(rootTask2.getTaskDescription().getLabel()); + + childTask.reparent(rootTask2, POSITION_TOP, false /* moveParents */, "reparent"); + + // Ensure the td is set for the new root task + assertEquals(testLabel, rootTask2.getTaskDescription().getLabel()); + } + + @Test + public void testUpdateTaskDescriptionOnReorder() { + final Task task = createTask(mDisplayContent); + final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task); + final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task); + final ActivityManager.TaskDescription td1 = new ActivityManager.TaskDescription(); + td1.setBackgroundColor(Color.RED); + activity1.setTaskDescription(td1); + final ActivityManager.TaskDescription td2 = new ActivityManager.TaskDescription(); + td2.setBackgroundColor(Color.BLUE); + activity2.setTaskDescription(td2); + + // Ensure the td is set for the original root task + assertEquals(Color.BLUE, task.getTaskDescription().getBackgroundColor()); + + task.positionChildAt(POSITION_TOP, activity1, false /* includeParents */); + + // Ensure the td is set for the original root task + assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor()); + } + private Task getTestTask() { return new TaskBuilder(mSupervisor).setCreateActivity(true).build(); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 61698dbebe7c..0468f48379cb 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9992,6 +9992,51 @@ public class CarrierConfigManager { @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool"; + /** @hide */ + @IntDef({ + CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC, + CARRIER_ROAMING_NTN_CONNECT_MANUAL, + }) + public @interface CARRIER_ROAMING_NTN_CONNECT_TYPE {} + + /** + * Device can connect to carrier roaming non-terrestrial network automatically. + * @hide + */ + public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0; + /** + * Device can connect to carrier roaming non-terrestrial network only if user manually triggers + * satellite connection. + * @hide + */ + public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1; + /** + * Indicates carrier roaming non-terrestrial network connect type that the device can use to + * perform satellite communication. + * If this key is set to CARRIER_ROAMING_NTN_CONNECT_MANUAL then connect button will be + * displayed to user when the device is eligible to use carrier roaming + * non-terrestrial network. + * @hide + */ + public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT = + "carrier_roaming_ntn_connect_type_int"; + + /** + * The carrier roaming non-terrestrial network hysteresis time in seconds. + * + * If the device supports P2P satellite messaging which is defined by + * {@link CarrierConfigManager#KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE} + * and the device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi, + * then hysteresis timer defined by this key will start. + * After the timer is expired, device is marked as eligible for satellite communication. + * + * The default value is 180 seconds. + * + * @hide + */ + public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT = + "carrier_supported_satellite_notification_hysteresis_sec_int"; + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. @@ -11150,6 +11195,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT, (int) TimeUnit.SECONDS.toMillis(30)); sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false); + sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0); + sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false); |