Merge "Remove ZeroJankProxy.Callback#isInputShownLocked()" into main
diff --git a/Android.bp b/Android.bp
index cf73451..6fa6650 100644
--- a/Android.bp
+++ b/Android.bp
@@ -636,7 +636,6 @@
"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/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 44edf29..475984e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -345,7 +345,10 @@
@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 @@
}
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 @@
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/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index fbeab84..d454716 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -266,9 +266,9 @@
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 4281da1..5ac0c50 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1689,6 +1689,10 @@
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 b8b84d9..c360e64 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -653,6 +653,27 @@
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 @@
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 @@
* {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 @@
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 6160fdb..10f03c1 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -1091,6 +1091,34 @@
}
/**
+ * 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 @@
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 792c223..f177e14 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -82,4 +82,5 @@
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 04332cd..e500a37a 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -121,4 +121,5 @@
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 92b1c04..1d360cc 100644
--- a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
@@ -18,7 +18,6 @@
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.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 438c5ae..f3803d2 100644
--- a/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
+++ b/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
@@ -20,8 +20,8 @@
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 407c6c3..a9e781c 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -19,8 +19,8 @@
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 c9a6d22..4ecd2da 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.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 c8015d4..dfde0bc 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.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 cb04a74..65d8e2b 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.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 4366e02c..7799185 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.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 86e95832..ad3a16b 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 @@
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 20421d1..b60d614 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 @@
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 61a3a11..dbd6c2b 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.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 2986d61..6c23ea3 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.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 8f8488f..ec5e205 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.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 af36dbb..edeea6d 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 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 9aef2ca..85f5d69 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.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 f7f9569..ac69a0f 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.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 255020a..64e4d3b 100644
--- a/core/tests/coretests/src/android/database/CursorWindowTest.java
+++ b/core/tests/coretests/src/android/database/CursorWindowTest.java
@@ -23,8 +23,8 @@
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 e25fdf9..c00c171 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 e2d2bae..470d4a9 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.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 9bad6d2..efa9b7a 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.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 09c380a..e20a806 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.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 82bd588..a4dedc5 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.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 2b663bd..dbe7a9a 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.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 c4695d9..bd9c4b8 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 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.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 24d27c4..832ebe5 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.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 a9d1482..ced1846 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.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 fa824b1..1690741 100644
--- a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
+++ b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
@@ -23,8 +23,8 @@
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 ce0bf30..938147c 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 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 37fe22f..242a273 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 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 2b37b52..d310b76 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 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 3b8f715..29001ae 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 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 2b15d73..7677c3c 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 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 aec54e1..07353f8 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 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 ba5e74a..7f91d03 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 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/view/CompositionSamplingListenerTest.java b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
index 729a555..e74027e 100644
--- a/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
+++ b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
@@ -21,8 +21,8 @@
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 7872810..0fdc239 100644
--- a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
+++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
@@ -23,8 +23,8 @@
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 0d1dde3..2c66330 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.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 77dd8bd..7778ba1 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.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 1682135..668487d 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.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 61e05da..c3bd065 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.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 75749c7..1144ee1 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.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 bad0485..d0f9a38 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.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 b5b2d0c..8ac9292 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.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 4349021..3992aa1 100644
--- a/core/tests/coretests/src/android/view/RoundedCornerTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -23,8 +23,8 @@
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 ec665ad..c26d945 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.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 5f258949..bee5dc4 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.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 dc43204..726ee85 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.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 71bdce4..5a5510c 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 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 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 65dd34f..9ab14af 100644
--- a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
+++ b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
@@ -21,16 +21,16 @@
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 218047c..9144cd6 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.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 62291d4..18364ad 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.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 54524b2..f5c71f8 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.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 c25a2deb..d4181d3 100644
--- a/core/tests/coretests/src/android/view/ViewInvalidateTest.java
+++ b/core/tests/coretests/src/android/view/ViewInvalidateTest.java
@@ -31,9 +31,9 @@
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 e1b403f..c5e0ebd 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.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 46e3a4c..1c26da7 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 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 e4cfc53..4635e9b 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 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 20a8768..07fe12f 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 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 8225afc..2ed016c 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.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 11eb567..d92da6e 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 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 011866d..ec46426 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 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 31f8029..de6f1d2 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.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 4f0b44b..bd1f7e1 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.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 14c077c..61e6738 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.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 4eccbe5..a466caf 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 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 a0e9947..43cff8d 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.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 a978e3b..7b9ea55 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 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/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 5097ed8..19a109e 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.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 @@
/** 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 @@
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 @@
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 @@
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 e8226051..f9259e7 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 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 @@
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 f32683d..d289ef2 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 @@
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 247cc42..4299841 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.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 @@
}
}
- /** 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 @@
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)
+
+ if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d", taskId, displayId)
+ updateActiveTasksListeners(displayId)
+ }
+ }
+
+ /** 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)
}
}
-
- 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) }
- }
- 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
+ /** 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)
+ }
+ }
+
+ /** 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 (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
- )
- }
- return added
- }
+ 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 }
- /**
- * 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
- }
- }
- 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 }
-
- /**
- * 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) }
+
+ /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */
+ fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
+ 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)
+ }
+ }
}
- /** Get a list of freeform tasks, ordered from top-bottom (top at index 0). */
- fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
- ArrayList(displayData[displayId]?.freeformTasksInZOrder ?: emptyList())
-
/**
- * 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 @@
}
}
- /** 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 @@
}
/**
- * 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 @@
}
/** 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 @@
}
}
- /**
- * 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 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 {
- return joinToString(separator = ", ", prefix = "[", postfix = "]")
-}
+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 2ef045d..9d3b2e5 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 @@
/** 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 @@
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 @@
}
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 @@
.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 @@
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 @@
// 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 c5ed1be..a011ff5 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 @@
}
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 @@
}
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 @@
"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 @@
}
@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 e4aa115..d03a561 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.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.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 @@
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 @@
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 @@
}
}
+ 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 4531967..229d972 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 @@
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 @@
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 @@
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/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d7ee563..2531ff1 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_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 @@
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 1076eca..d1ab3e9 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 @@
}
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/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 18b08bf..0a5672d 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 @@
}
@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 @@
}
@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 @@
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 @@
}
@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 @@
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 @@
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
@@ -329,6 +363,7 @@
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+
repo.updateVisibleFreeformTasks(INVALID_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
@@ -337,65 +372,73 @@
}
@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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
@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/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index ba84287..c1e43c9 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -19,3 +19,13 @@
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 ab8f97a..c71ef83 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.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.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
/**
@@ -60,6 +62,18 @@
((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 462db34..e4f27aa 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -601,6 +601,13 @@
}
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 11c104e..b4e513c 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 @@
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 @@
}
}
+/** 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/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 069f2f5..18ca0f7 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.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 @@
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 @@
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 @@
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
- mediaHost: MediaHost,
+ qqsMediaHost: MediaHost,
+ qsMediaHost: MediaHost,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
@@ -210,7 +218,7 @@
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
+ mediaHost = qqsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
)
@@ -223,7 +231,7 @@
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 56b3679..42db96e 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.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 @@
@RunWith(AndroidJUnit4::class)
class GridConsistencyInteractorTest : SysuiTestCase() {
+ data object NoopGridLayoutType : GridLayoutType
+
private val kosmos =
testKosmos().apply {
defaultLargeTilesRepository =
@@ -54,6 +56,11 @@
TileSpec.create("largeD"),
)
}
+ gridConsistencyInteractorsMap =
+ mapOf(
+ Pair(NoopGridLayoutType, noopGridConsistencyInteractor),
+ Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
+ )
}
private val underTest = with(kosmos) { gridConsistencyInteractor }
@@ -71,7 +78,7 @@
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 55b7454..0000000
--- 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/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 3904ee1..b1cba2f 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 @@
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 b387855..830ef3b 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 @@
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 78f4b4b..072d322 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.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 @@
@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 @@
@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 b1942fe..323f39b 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 @@
/** 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 6c84edd..0000000
--- 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 3e48245..0000000
--- 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
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
rename to packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt
index 37c9552..26405f0 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 0000000..a2583e6
--- /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 54ae225..9bc3bd8 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 @@
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 @@
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 @@
}
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 0a4635e..653e49f 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 @@
/**
* 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 @@
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 @@
* 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 @@
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 @@
/** 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 f8b22a6..f902693 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 @@
*/
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 07f6e85..50ea3bb 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 @@
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 @@
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 @@
/** 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 @@
}
}
- 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/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 682f848..254dde4 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 @@
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 ebac5bf..08c1fca 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 @@
@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 @@
/**
* 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 4869114..89227cf 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.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.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 @@
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 8ab5bc6..169f865 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.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 @@
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 @@
public void unregister(ThresholdSensor.Listener listener) {
mExecution.assertIsMainThread();
mListeners.remove(listener);
- if (mListeners.size() == 0) {
+ if (mListeners.isEmpty()) {
unregisterInternal();
}
}
@@ -296,8 +296,7 @@
}
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 6dcea14..9df653f 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.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.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 @@
}
@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 24e8b18..5e07aef 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 @@
)
val quickSharePendingIntent =
quickShareAction.actionIntent.intent.extras!!.getParcelable(
- ScreenshotController.EXTRA_ACTION_INTENT,
+ SmartActionsReceiver.EXTRA_ACTION_INTENT,
PendingIntent::class.java
)
@@ -266,7 +266,7 @@
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 471fdc0..9dc5cfe 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 @@
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 8d3a29a..a295981 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 @@
@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 @@
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/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 5568c6c..34e99d3 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.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 6625bb5..9481fca 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.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 @@
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 3633d0f..33cf842 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.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 @@
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 @@
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 @@
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
mSCBMStarted[i] = false;
mCarrierRoamingNtnMode[i] = false;
+ mCarrierRoamingNtnEligible[i] = false;
}
}
}
@@ -859,6 +863,7 @@
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 @@
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
mSCBMStarted[i] = false;
mCarrierRoamingNtnMode[i] = false;
+ mCarrierRoamingNtnEligible[i] = false;
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1518,6 +1524,15 @@
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 @@
}
}
+ /**
+ * 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 @@
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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f7faee1..9d007c6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1093,7 +1093,7 @@
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().
@@ -1103,7 +1103,7 @@
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
mUserSwitchHandlerTask = task;
- mHandler.post(task);
+ mIoHandler.post(task);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b12a917..95d8bb9 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 @@
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 @@
}
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 @@
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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index bea16dc..7d70ea1 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 @@
*/
@ActivityInfo.SizeChangesSupportMode
private int supportsSizeChanges() {
- if (mLetterboxUiController.shouldOverrideForceNonResizeApp()) {
+ if (mAppCompatController.getAppCompatResizeOverrides()
+ .shouldOverrideForceNonResizeApp()) {
return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
}
@@ -8411,7 +8412,8 @@
return SIZE_CHANGES_SUPPORTED_METADATA;
}
- if (mLetterboxUiController.shouldOverrideForceResizeApp()) {
+ if (mAppCompatController.getAppCompatResizeOverrides()
+ .shouldOverrideForceResizeApp()) {
return SIZE_CHANGES_SUPPORTED_OVERRIDE;
}
@@ -10494,7 +10496,7 @@
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 998d65d..54223b6 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 @@
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 f1ee23b..4450011 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 @@
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 @@
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 @@
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 0000000..60c1825
--- /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 cbb210f..9996bbc 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.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 5d4198f..444097a 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 @@
}
}
- /**
- * 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 f9772f4..ab1e969 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 @@
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 @@
} 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 @@
//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 @@
@Override
void onChildPositionChanged(WindowContainer child) {
- dispatchTaskInfoChangedIfNeeded(false /* force */);
-
if (!mChildren.contains(child)) {
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
return;
}
if (child.asTask() != null) {
@@ -6152,6 +6157,10 @@
// 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/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index b07940a..d7bae45 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 @@
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 @@
}
@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 1f4a469..bea6917 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 @@
@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 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 @@
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 f5c2e19..220248c 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 @@
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 0000000..8fc1a77
--- /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/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 26a4411..e2c0f6c2 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.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 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 @@
}
@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 @@
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 a1ac02a..a232ff0 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.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.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 @@
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 61698db..0468f48 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9992,6 +9992,51 @@
@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 @@
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);