Merge "Fix ModifierShortcutManagerTests on AOSP build." into main
diff --git a/OWNERS b/OWNERS
index bde7ab2..096da29 100644
--- a/OWNERS
+++ b/OWNERS
@@ -14,6 +14,7 @@
nandana@google.com #{LAST_RESORT_SUGGESTION}
narayan@google.com #{LAST_RESORT_SUGGESTION}
ogunwale@google.com #{LAST_RESORT_SUGGESTION}
+omakoto@google.com #{LAST_RESORT_SUGGESTION}
roosa@google.com #{LAST_RESORT_SUGGESTION}
smoreland@google.com #{LAST_RESORT_SUGGESTION}
yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
index 2643bae..d7b1c9a2 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -43,6 +43,7 @@
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -196,6 +197,7 @@
*/
@Test
@Parameters(method = "getParams")
+ @Ignore("b/351034205")
public void time(Config config) throws Exception {
reset();
setup(config);
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
index 4f285ff..8916a3c 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -43,6 +43,7 @@
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -201,6 +202,7 @@
@Test
@Parameters(method = "getParams")
+ @Ignore("b/351034205")
public void throughput(Config config) throws Exception {
setup(config);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 9a178e5..18ee6f2 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -165,6 +165,7 @@
import com.android.server.SystemServiceManager;
import com.android.server.SystemTimeZone;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -3763,8 +3764,10 @@
}
mNextAlarmClockForUser.put(userId, alarmClock);
if (mStartUserBeforeScheduledAlarms) {
- mUserWakeupStore.addUserWakeup(userId, convertToElapsed(
- mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC));
+ if (shouldAddWakeupForUser(userId)) {
+ mUserWakeupStore.addUserWakeup(userId, convertToElapsed(
+ mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC));
+ }
}
} else {
if (DEBUG_ALARM_CLOCK) {
@@ -3784,6 +3787,23 @@
}
/**
+ * Checks whether the user is of type that needs to be started before the alarm.
+ */
+ @VisibleForTesting
+ boolean shouldAddWakeupForUser(@UserIdInt int userId) {
+ final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+ if (umInternal.getUserInfo(userId) == null || umInternal.getUserInfo(userId).isGuest()) {
+ // Guest user should not be started in the background.
+ return false;
+ } else {
+ // SYSTEM user is always running, so no need to schedule wakeup for it.
+ // Profiles are excluded from the wakeup list because users can explicitly stop them and
+ // so starting them in the background would go against the user's intent.
+ return userId != UserHandle.USER_SYSTEM && umInternal.getUserInfo(userId).isFull();
+ }
+ }
+
+ /**
* Updates NEXT_ALARM_FORMATTED and sends NEXT_ALARM_CLOCK_CHANGED_INTENT for all users
* for which alarm clocks have changed since the last call to this.
*
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
index 93904a7..9fe197d 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.os.Environment;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -119,13 +118,10 @@
* @param alarmTime time when alarm is expected to trigger.
*/
public void addUserWakeup(int userId, long alarmTime) {
- // SYSTEM user is always running, so no need to schedule wakeup for it.
- if (userId != UserHandle.USER_SYSTEM) {
- synchronized (mUserWakeupLock) {
- mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
- }
- updateUserListFile();
+ synchronized (mUserWakeupLock) {
+ mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
}
+ updateUserListFile();
}
/**
diff --git a/core/api/current.txt b/core/api/current.txt
index c5a70df..354e26b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43729,6 +43729,7 @@
field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT = "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
@@ -43911,6 +43912,7 @@
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_screen_off_inactivity_timeout_duration_sec_int";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e148b5c..df707d1 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -321,7 +321,7 @@
package android.net.wifi {
public final class WifiMigration {
- method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration") public static void migrateLegacyKeystoreToWifiBlobstore();
+ method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static void migrateLegacyKeystoreToWifiBlobstore();
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fd0262e..bed1b43 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3465,6 +3465,7 @@
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName, int);
method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
@@ -3489,8 +3490,10 @@
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName, int);
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int, int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 07e4f22..2f80b30 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7066,9 +7066,6 @@
&& level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
return;
}
- if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- PropertyInvalidatedCache.onTrimMemory();
- }
final ArrayList<ComponentCallbacks2> callbacks =
collectComponentCallbacks(true /* includeUiContexts */);
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index b03011f..a07f620 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -32,7 +32,7 @@
public boolean topActivityEligibleForLetterboxEducation;
/**
- * Whether the letterbox education is enabled
+ * Whether the letterbox education is enabled.
*/
public boolean isLetterboxEducationEnabled;
@@ -73,26 +73,26 @@
public boolean isFromLetterboxDoubleTap;
/**
- * If {@link isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position or
- * {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise.
+ * If {@link #isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position
+ * or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
public int topActivityLetterboxVerticalPosition;
/**
- * If {@link isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position or
- * {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise.
+ * If {@link #isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position
+ * or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
public int topActivityLetterboxHorizontalPosition;
/**
- * If {@link isLetterboxDoubleTapEnabled} it contains the current width of the letterboxed
- * activity or {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise.
+ * If {@link #isLetterboxDoubleTapEnabled} it contains the current width of the letterboxed
+ * activity or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
public int topActivityLetterboxWidth;
/**
- * If {@link isLetterboxDoubleTapEnabled} it contains the current height of the letterboxed
- * activity or {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise.
+ * If {@link #isLetterboxDoubleTapEnabled} it contains the current height of the letterboxed
+ * activity or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
public int topActivityLetterboxHeight;
@@ -133,7 +133,7 @@
};
/**
- * @return {@value true} if the task has some compat ui.
+ * @return {@code true} if the task has some compat ui.
*/
public boolean hasCompatUI() {
return topActivityInSizeCompat || topActivityEligibleForLetterboxEducation
@@ -142,14 +142,14 @@
}
/**
- * @return {@value true} if top activity is pillarboxed.
+ * @return {@code true} if top activity is pillarboxed.
*/
public boolean isTopActivityPillarboxed() {
return topActivityLetterboxWidth < topActivityLetterboxHeight;
}
/**
- * @return {@code true} if the app compat parameters that are important for task organizers
+ * @return {@code true} if the app compat parameters that are important for task organizers
* are equal.
*/
public boolean equalsForTaskOrganizer(@Nullable AppCompatTaskInfo that) {
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 27f9f54..22804a2 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1617,18 +1617,4 @@
Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
}
}
-
- /**
- * Trim memory by clearing all the caches.
- * @hide
- */
- public static void onTrimMemory() {
- ArrayList<PropertyInvalidatedCache> activeCaches;
- synchronized (sGlobalLock) {
- activeCaches = getActiveCaches();
- }
- for (int i = 0; i < activeCaches.size(); i++) {
- activeCaches.get(i).clear();
- }
- }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a7070b9..965e3c4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11964,6 +11964,34 @@
}
/**
+ * Adds a user restriction on {@code targetUser}, specified by the {@code key}.
+ *
+ * <p>Called by a system service only, meaning that the caller's UID must be equal to
+ * {@link Process#SYSTEM_UID}.
+ *
+ * @param systemEntity The service entity that adds the restriction. A user restriction set by
+ * a service entity can only be cleared by the same entity. This can be
+ * just the calling package name, or any string of the caller's choice
+ * can be used.
+ * @param key The key of the restriction.
+ * @param targetUser The user to add the restriction on.
+ * @throws SecurityException if the caller is not a system service
+ *
+ * @hide
+ */
+ public void addUserRestriction(@NonNull String systemEntity,
+ @NonNull @UserManager.UserRestrictionKey String key, @UserIdInt int targetUser) {
+ if (mService != null) {
+ try {
+ mService.setUserRestrictionForUser(
+ systemEntity, key, /* enable= */ true, targetUser);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by a profile owner, device owner or a holder of any permission that is associated with
* a user restriction to set a user restriction specified by the provided {@code key} globally
* on all users. To clear the restriction use {@link #clearUserRestriction}.
@@ -11971,7 +11999,7 @@
* <p>For a given user, a restriction will be set if it was applied globally or locally by any
* admin.
*
- * <p> The calling device admin must be a profile owner, device owner or or a holder of any
+ * <p> The calling device admin must be a profile owner, device owner or a holder of any
* permission that is associated with a user restriction; if it is not, a security
* exception will be thrown.
*
@@ -12072,6 +12100,34 @@
}
/**
+ * Clears a user restriction from {@code targetUser}, specified by the {@code key}.
+ *
+ * <p>Called by a system service only, meaning that the caller's UID must be equal to
+ * {@link Process#SYSTEM_UID}.
+ *
+ * @param systemEntity The system entity that clears the restriction. A user restriction
+ * set by a system entity can only be cleared by the same entity. This
+ * can be just the calling package name, or any string of the caller's
+ * choice can be used.
+ * @param key The key of the restriction.
+ * @param targetUser The user to clear the restriction from.
+ * @throws SecurityException if the caller is not a system service
+ *
+ * @hide
+ */
+ public void clearUserRestriction(@NonNull String systemEntity,
+ @NonNull @UserManager.UserRestrictionKey String key, @UserIdInt int targetUser) {
+ if (mService != null) {
+ try {
+ mService.setUserRestrictionForUser(
+ systemEntity, key, /* enable= */ false, targetUser);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by an admin to get user restrictions set by themselves with
* {@link #addUserRestriction(ComponentName, String)}.
* <p>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 381f996..c393a9e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -255,6 +255,7 @@
ComponentName getRestrictionsProvider(int userHandle);
void setUserRestriction(in ComponentName who, in String callerPackage, in String key, boolean enable, boolean parent);
+ void setUserRestrictionForUser(in String systemEntity, in String key, boolean enable, int targetUser);
void setUserRestrictionGlobally(in String callerPackage, in String key);
Bundle getUserRestrictions(in ComponentName who, in String callerPackage, boolean parent);
Bundle getUserRestrictionsGlobally(in String callerPackage);
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 0653839..a56bc02 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -112,6 +112,24 @@
void removeActivityPolicyExemption(in ComponentName exemption);
/**
+ * Specifies a policy for this virtual device on the given display.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void setDevicePolicyForDisplay(int displayId, int policyType, int devicePolicy);
+
+ /**
+ * Adds an exemption to the default activity launch policy on the given display.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void addActivityPolicyExemptionForDisplay(int displayId, in ComponentName exemption);
+
+ /**
+ * Removes an exemption to the default activity launch policy on the given display.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void removeActivityPolicyExemptionForDisplay(int displayId, in ComponentName exemption);
+
+ /**
* Notifies that an audio session being started.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d3fcfc6..d8899b2 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -17,7 +17,11 @@
package android.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -282,6 +286,16 @@
void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ switch (policyType) {
+ case POLICY_TYPE_RECENTS:
+ case POLICY_TYPE_CLIPBOARD:
+ case POLICY_TYPE_ACTIVITY:
+ case POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR:
+ break;
+ default:
+ throw new IllegalArgumentException("Device policy " + policyType
+ + " cannot be changed at runtime. ");
+ }
try {
mVirtualDevice.setDevicePolicy(policyType, devicePolicy);
} catch (RemoteException e) {
@@ -305,6 +319,42 @@
}
}
+ void setDevicePolicyForDisplay(int displayId,
+ @VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ switch (policyType) {
+ case POLICY_TYPE_RECENTS:
+ case POLICY_TYPE_ACTIVITY:
+ break;
+ default:
+ throw new IllegalArgumentException("Device policy " + policyType
+ + " cannot be changed for a specific display. ");
+ }
+
+ try {
+ mVirtualDevice.setDevicePolicyForDisplay(displayId, policyType, devicePolicy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void addActivityPolicyExemptionForDisplay(int displayId, @NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.addActivityPolicyExemptionForDisplay(displayId, componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void removeActivityPolicyExemptionForDisplay(int displayId,
+ @NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.removeActivityPolicyExemptionForDisplay(displayId, componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 296ca33..42da7e9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -739,6 +739,7 @@
*
* @param policyType the type of policy, i.e. which behavior to specify a policy for.
* @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+ * @throws IllegalArgumentException if the policy cannot be changed at runtime.
*
* @see VirtualDeviceParams#POLICY_TYPE_RECENTS
* @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
@@ -797,6 +798,81 @@
}
/**
+ * Specifies a policy for this virtual device to be applied on the given virtual display.
+ * <p>
+ * Any policy specified for a particular display takes precedence over the policy specified
+ * for the device itself.
+ * </p>
+ *
+ * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+ * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+ * @param displayId the ID of the display, for which to apply the policy.
+ * @throws IllegalArgumentException if the specified policy cannot be changed per
+ * display or if the specified display does not belong to the virtual device.
+ *
+ * @see #setDevicePolicy(int, int)
+ * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+ * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setDevicePolicy(
+ @VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy,
+ int displayId) {
+ mVirtualDeviceInternal.setDevicePolicyForDisplay(displayId, policyType, devicePolicy);
+ }
+
+ /**
+ * Specifies a component name to be exempt from the given display's activity launch policy.
+ *
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
+ * then the specified component will be blocked from launching.
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
+ * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
+ * specified component will be allowed to launch.</p>
+ *
+ * <p>Note that changing the activity launch policy will clear current set of exempt
+ * components.</p>
+ * <p>Any change to the exemptions will only be applied for new activity launches.</p>
+ *
+ * @see #removeActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemption(
+ @NonNull ComponentName componentName, int displayId) {
+ mVirtualDeviceInternal.addActivityPolicyExemptionForDisplay(
+ displayId, Objects.requireNonNull(componentName));
+ }
+
+ /**
+ * Makes the specified component name adhere to the given display's activity launch policy.
+ *
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
+ * then the specified component will be allowed to launch.
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
+ * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
+ * specified component will be blocked from launching.</p>
+ *
+ * <p>Note that changing the activity launch policy will clear current set of exempt
+ * components.</p>
+ *
+ * @see #addActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemption(
+ @NonNull ComponentName componentName, int displayId) {
+ mVirtualDeviceInternal.removeActivityPolicyExemptionForDisplay(
+ displayId, Objects.requireNonNull(componentName));
+ }
+
+ /**
* Creates a virtual dpad.
*
* @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index f7f842f..c1fc51d 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -159,7 +159,8 @@
* @hide
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
- POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA})
+ POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA,
+ POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -177,6 +178,17 @@
public @interface DynamicPolicyType {}
/**
+ * Policy types that can be dynamically changed for a specific display.
+ *
+ * @see VirtualDeviceManager.VirtualDevice#setDevicePolicyForDisplay
+ * @hide
+ */
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface DynamicDisplayPolicyType {}
+
+ /**
* Tells the sensor framework how to handle sensor requests from contexts associated with this
* virtual device, namely the sensors returned by
* {@link android.hardware.SensorManager#getSensorList}:
@@ -229,6 +241,8 @@
* @see VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption
* @see VirtualDeviceManager.VirtualDevice#removeActivityPolicyExemption
*/
+ // TODO(b/333443509): Update the documentation of custom policy and link to the new policy
+ // POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR
@FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
public static final int POLICY_TYPE_ACTIVITY = 3;
@@ -276,6 +290,7 @@
* experience on the virtual device.
* </ul>
*/
+ // TODO(b/333443509): Link to POLICY_TYPE_ACTIVITY
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
public static final int POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR = 6;
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index b29b52d..91586b6 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -81,13 +81,6 @@
}
flag {
- name: "stream_permissions"
- namespace: "virtual_devices"
- description: "Enable streaming permission dialogs to Virtual Devices"
- bug: "291737919"
-}
-
-flag {
name: "persistent_device_id_api"
is_exported: true
namespace: "virtual_devices"
@@ -96,13 +89,6 @@
}
flag {
- name: "express_metrics"
- namespace: "virtual_devices"
- description: "Enable express metrics in VDM"
- bug: "307297730"
-}
-
-flag {
name: "interactive_screen_mirror"
is_exported: true
namespace: "virtual_devices"
@@ -119,17 +105,6 @@
}
flag {
- name: "intercept_intents_before_applying_policy"
- is_exported: true
- namespace: "virtual_devices"
- description: "Apply intent interception before applying activity policy"
- bug: "333444131"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "impulse_velocity_strategy_for_touch_navigation"
is_exported: true
namespace: "virtual_devices"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index a1ae9da..c3c3f0e 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -47,14 +47,6 @@
flag {
namespace: "virtual_devices"
- name: "metrics_collection"
- description: "Enable collection of VDM-related metrics"
- bug: "324842215"
- is_fixed_read_only: true
-}
-
-flag {
- namespace: "virtual_devices"
name: "activity_control_api"
description: "Enable APIs for fine grained activity policy, fallback and callbacks"
bug: "333443509"
@@ -92,27 +84,6 @@
flag {
namespace: "virtual_devices"
- name: "virtual_display_multi_window_mode_support"
- description: "Add support for WINDOWING_MODE_MULTI_WINDOW to virtual displays by default"
- is_fixed_read_only: true
- bug: "341151395"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "virtual_devices"
- name: "intent_interception_action_matching_fix"
- description: "Do not match intents without actions if the filter has actions"
- bug: "343805037"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "virtual_devices"
name: "virtual_display_rotation_api"
description: "API for on-demand rotation of virtual displays"
bug: "291748430"
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index e0dc568..914aa51 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -34,17 +34,22 @@
import android.util.LruCache;
import android.util.Pair;
import android.util.Printer;
+import com.android.internal.util.RingBuffer;
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
+
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
+import java.util.Locale;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;
@@ -185,7 +190,7 @@
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
mPool = pool;
- mRecentOperations = new OperationLog(mPool);
+ mRecentOperations = new OperationLog();
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
@@ -305,6 +310,16 @@
}
}
+ /** Record the start of a transaction for logging and debugging. */
+ void recordBeginTransaction(String mode) {
+ mRecentOperations.beginTransaction(mode);
+ }
+
+ /** Record the end of a transaction for logging and debugging. */
+ void recordEndTransaction(boolean successful) {
+ mRecentOperations.endTransaction(successful);
+ }
+
private void setPageSize() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getDefaultPageSize();
@@ -1335,6 +1350,7 @@
}
printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
+ printer.println(" totalLongOperations: " + mRecentOperations.getTotalLongOperations());
mRecentOperations.dump(printer);
@@ -1593,51 +1609,84 @@
}
}
- private static final class OperationLog {
+ /**
+ * This class implements a leaky bucket strategy to rate-limit operations. A client
+ * accumulates one credit every <n> milliseconds; a credit allows the client execute an
+ * operation (which then deducts the credit). Credits accumulate up to a maximum amount after
+ * which they no longer accumulate. The strategy allows a client to execute an operation
+ * every <n> milliseconds, or to execute a burst, after a period of no operations.
+ */
+ private static class RateLimiter {
+ // When the bucket was created, in ms.
+ private final long mCreationUptimeMs;
+ // The time required to accumulate a single credit.
+ private final long mMsPerCredit;
+ // The maximum number of credits the process can accumulate.
+ private final int mMaxCredits;
+ // Total credits consumed so far.
+ private long mSpent = 0;
+
+ RateLimiter(long msPerCredit, int maxCredits) {
+ mMsPerCredit = msPerCredit;
+ mMaxCredits = maxCredits;
+ mCreationUptimeMs = SystemClock.uptimeMillis() - (mMsPerCredit * mMaxCredits);
+ }
+
+ /** Return true if there is a credit available (and consume that credit). */
+ boolean tryAcquire() {
+ final long now = SystemClock.uptimeMillis();
+ long credits = (now - mCreationUptimeMs) / mMsPerCredit;
+
+ long available = credits - mSpent;
+ if (available > mMaxCredits) {
+ mSpent += available - mMaxCredits;
+ available = credits - mSpent;
+ }
+ if (available > 0) {
+ mSpent++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ private final class OperationLog {
private static final int MAX_RECENT_OPERATIONS = 20;
private static final int COOKIE_GENERATION_SHIFT = 8;
private static final int COOKIE_INDEX_MASK = 0xff;
+ // Operations over 2s are long. Save the last ten.
+ private static final long LONG_OPERATION_THRESHOLD_MS = 2_000;
+ private static final int MAX_LONG_OPERATIONS = 10;
+
private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
- private int mIndex;
- private int mGeneration;
- private final SQLiteConnectionPool mPool;
+ private int mIndex = -1;
+ private int mGeneration = 0;
+ private final Operation mTransaction = new Operation();
private long mResultLong = Long.MIN_VALUE;
private String mResultString;
- OperationLog(SQLiteConnectionPool pool) {
- mPool = pool;
- }
+ private final RingBuffer<Operation> mLongOperations =
+ new RingBuffer<>(()->{return new Operation();},
+ (n) ->{return new Operation[n];},
+ MAX_LONG_OPERATIONS);
+ private int mTotalLongOperations = 0;
+
+ // Limit log messages to one every 5 minutes, except that a burst may be 10 messages long.
+ private final RateLimiter mLongLimiter = new RateLimiter(300_000, 10);
public int beginOperation(String kind, String sql, Object[] bindArgs) {
mResultLong = Long.MIN_VALUE;
mResultString = null;
synchronized (mOperations) {
- final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
- Operation operation = mOperations[index];
- if (operation == null) {
- operation = new Operation();
- mOperations[index] = operation;
- } else {
- operation.mFinished = false;
- operation.mException = null;
- if (operation.mBindArgs != null) {
- operation.mBindArgs.clear();
- }
- }
- operation.mStartWallTime = System.currentTimeMillis();
- operation.mStartTime = SystemClock.uptimeMillis();
+ Operation operation = newOperationLocked();
operation.mKind = kind;
operation.mSql = sql;
- operation.mPath = mPool.getPath();
- operation.mResultLong = Long.MIN_VALUE;
- operation.mResultString = null;
if (bindArgs != null) {
if (operation.mBindArgs == null) {
operation.mBindArgs = new ArrayList<Object>();
- } else {
- operation.mBindArgs.clear();
}
for (int i = 0; i < bindArgs.length; i++) {
final Object arg = bindArgs[i];
@@ -1649,16 +1698,45 @@
}
}
}
- operation.mCookie = newOperationCookieLocked(index);
- if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
+ operation.mTraced = Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE);
+ if (operation.mTraced) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- mIndex = index;
return operation.mCookie;
}
}
+ public void beginTransaction(String kind) {
+ synchronized (mOperations) {
+ Operation operation = newOperationLocked();
+ operation.mKind = kind;
+ mTransaction.copyFrom(operation);
+
+ if (operation.mTraced) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
+ operation.mCookie);
+ }
+ }
+ }
+
+ /**
+ * Fetch a new operation from the ring buffer. The operation is properly initialized.
+ * This advances mIndex to point to the next element.
+ */
+ private Operation newOperationLocked() {
+ final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
+ Operation operation = mOperations[index];
+ if (operation == null) {
+ mOperations[index] = new Operation();
+ operation = mOperations[index];
+ }
+ operation.start();
+ operation.mCookie = newOperationCookieLocked(index);
+ mIndex = index;
+ return operation;
+ }
+
public void failOperation(int cookie, Exception ex) {
synchronized (mOperations) {
final Operation operation = getOperationLocked(cookie);
@@ -1682,6 +1760,20 @@
}
}
+ public boolean endTransaction(boolean success) {
+ synchronized (mOperations) {
+ mTransaction.mResultLong = success ? 1 : 0;
+ final long execTime = finishOperationLocked(mTransaction);
+ final Operation operation = getOperationLocked(mTransaction.mCookie);
+ if (operation != null) {
+ operation.copyFrom(mTransaction);
+ }
+ mTransaction.setEmpty();
+ return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES
+ && SQLiteDebug.shouldLogSlowQuery(execTime);
+ }
+ }
+
public void logOperation(int cookie, String detail) {
synchronized (mOperations) {
logOperationLocked(cookie, detail);
@@ -1699,13 +1791,11 @@
private boolean endOperationDeferLogLocked(int cookie) {
final Operation operation = getOperationLocked(cookie);
if (operation != null) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
+ if (operation.mTraced) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- operation.mEndTime = SystemClock.uptimeMillis();
- operation.mFinished = true;
- final long execTime = operation.mEndTime - operation.mStartTime;
+ final long execTime = finishOperationLocked(operation);
mPool.onStatementExecuted(execTime);
return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
execTime);
@@ -1730,10 +1820,25 @@
return generation << COOKIE_GENERATION_SHIFT | index;
}
+ /** Close out the operation and return the elapsed time. */
+ private long finishOperationLocked(Operation operation) {
+ operation.mEndTime = SystemClock.uptimeMillis();
+ operation.mFinished = true;
+ final long elapsed = operation.mEndTime - operation.mStartTime;
+ if (elapsed > LONG_OPERATION_THRESHOLD_MS) {
+ mLongOperations.getNextSlot().copyFrom(operation);
+ mTotalLongOperations++;
+ if (mLongLimiter.tryAcquire()) {
+ Log.i(TAG, "Long db operation: " + mConfiguration.label);
+ }
+ }
+ return elapsed;
+ }
+
private Operation getOperationLocked(int cookie) {
final int index = cookie & COOKIE_INDEX_MASK;
final Operation operation = mOperations[index];
- return operation.mCookie == cookie ? operation : null;
+ return (operation != null && operation.mCookie == cookie) ? operation : null;
}
public String describeCurrentOperation() {
@@ -1748,48 +1853,87 @@
}
}
- public void dump(Printer printer) {
+ /**
+ * Dump an Operation if it is not in the recent operations list. Return 1 if the
+ * operation was dumped and 0 if not.
+ */
+ private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) {
+ if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) {
+ return 0;
+ }
+ pw.println(op.describe(counter));
+ return 1;
+ }
+
+ private void dumpRecentLocked(Printer printer) {
synchronized (mOperations) {
printer.println(" Most recently executed operations:");
int index = mIndex;
- Operation operation = mOperations[index];
- if (operation != null) {
- // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
- // and is relatively expensive to create during preloading. This method is only
- // used when dumping a connection, which is a rare (mainly error) case.
- SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- int n = 0;
- do {
- StringBuilder msg = new StringBuilder();
- msg.append(" ").append(n).append(": [");
- String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
- msg.append(formattedStartTime);
- msg.append("] ");
- operation.describe(msg, false); // Never dump bingargs in a bugreport
- printer.println(msg.toString());
-
- if (index > 0) {
- index -= 1;
- } else {
- index = MAX_RECENT_OPERATIONS - 1;
- }
- n += 1;
- operation = mOperations[index];
- } while (operation != null && n < MAX_RECENT_OPERATIONS);
- } else {
+ if (index == 0) {
printer.println(" <none>");
+ return;
}
+
+ // Operations are dumped in order of most recent first.
+ int counter = 0;
+ int n = 0;
+ Operation operation = mOperations[index];
+ do {
+ printer.println(operation.describe(counter));
+
+ if (index > 0) {
+ index -= 1;
+ } else {
+ index = MAX_RECENT_OPERATIONS - 1;
+ }
+ n++;
+ counter++;
+ operation = mOperations[index];
+ } while (operation != null && n < MAX_RECENT_OPERATIONS);
+ counter += dumpIfNotRecentLocked(printer, mTransaction, counter);
+ }
+ }
+
+ private void dumpLongLocked(Printer printer) {
+ printer.println(" Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:");
+ if (mLongOperations.isEmpty()) {
+ printer.println(" <none>");
+ return;
+ }
+ Operation[] longOps = mLongOperations.toArray();
+ for (int i = 0; i < longOps.length; i++) {
+ if (longOps[i] != null) {
+ printer.println(longOps[i].describe(i));
+ }
+ }
+ }
+
+ public long getTotalLongOperations() {
+ return mTotalLongOperations;
+ }
+
+ public void dump(Printer printer) {
+ synchronized (mOperations) {
+ dumpRecentLocked(printer);
+ dumpLongLocked(printer);
}
}
}
- private static final class Operation {
+ private final class Operation {
// Trim all SQL statements to 256 characters inside the trace marker.
// This limit gives plenty of context while leaving space for other
// entries in the trace buffer (and ensures atrace doesn't truncate the
// marker for us, potentially losing metadata in the process).
private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
+ // The reserved start time that indicates the Operation is empty.
+ private static final long EMPTY_OPERATION = -1;
+
+ // The formatter for the timestamp.
+ private static final DateTimeFormatter sDateTime =
+ DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US);
+
public long mStartWallTime; // in System.currentTimeMillis()
public long mStartTime; // in SystemClock.uptimeMillis();
public long mEndTime; // in SystemClock.uptimeMillis();
@@ -1799,16 +1943,61 @@
public boolean mFinished;
public Exception mException;
public int mCookie;
- public String mPath;
public long mResultLong; // MIN_VALUE means "value not set".
public String mResultString;
+ public boolean mTraced;
+
+ /** Reset the object to begin a new operation. */
+ void start() {
+ mStartWallTime = System.currentTimeMillis();
+ mStartTime = SystemClock.uptimeMillis();
+ mEndTime = Long.MIN_VALUE;
+ mKind = null;
+ mSql = null;
+ if (mBindArgs != null) mBindArgs.clear();
+ mFinished = false;
+ mException = null;
+ mCookie = -1;
+ mResultLong = Long.MIN_VALUE;
+ mResultString = null;
+ mTraced = false;
+ }
+
+ /**
+ * Initialize from the source object. This is meant to clone the object for use in a
+ * transaction operation. To that end, the local bind args are set to null.
+ */
+ void copyFrom(Operation r) {
+ mStartWallTime = r.mStartWallTime;
+ mStartTime = r.mStartTime;
+ mEndTime = r.mEndTime;
+ mKind = r.mKind;
+ mSql = r.mSql;
+ mBindArgs = null;
+ mFinished = r.mFinished;
+ mException = r.mException;
+ mCookie = r.mCookie;
+ mResultLong = r.mResultLong;
+ mResultString = r.mResultString;
+ mTraced = r.mTraced;
+ }
+
+ /** Mark the operation empty. */
+ void setEmpty() {
+ mStartWallTime = EMPTY_OPERATION;
+ }
+
+ /** Return true if the operation is empty. */
+ boolean isEmpty() {
+ return mStartWallTime == EMPTY_OPERATION;
+ }
public void describe(StringBuilder msg, boolean allowDetailedLog) {
msg.append(mKind);
if (mFinished) {
msg.append(" took ").append(mEndTime - mStartTime).append("ms");
} else {
- msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
+ msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime)
.append("ms ago");
}
msg.append(" - ").append(getStatus());
@@ -1837,7 +2026,7 @@
}
msg.append("]");
}
- msg.append(", path=").append(mPath);
+ msg.append(", path=").append(mPool.getPath());
if (mException != null) {
msg.append(", exception=\"").append(mException.getMessage()).append("\"");
}
@@ -1849,6 +2038,21 @@
}
}
+ /**
+ * Convert a wall-clock time in milliseconds to logcat format.
+ */
+ private String timeString(long millis) {
+ return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis));
+ }
+
+ public String describe(int n) {
+ final StringBuilder msg = new StringBuilder();
+ final String start = timeString(mStartWallTime);
+ msg.append(" ").append(n).append(": [").append(start).append("] ");
+ describe(msg, false); // Never dump bindargs in a bugreport
+ return msg.toString();
+ }
+
private String getStatus() {
if (!mFinished) {
return "running";
@@ -1862,7 +2066,6 @@
return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
return methodName;
}
-
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index ad335b6..15d7d66 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1175,7 +1175,7 @@
+ ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
+ ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
+ ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
- printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
+ printer.println(" IsReadOnlyDatabase: " + mConfiguration.isReadOnlyDatabase());
if (isCompatibilityWalEnabled) {
printer.println(" Compatibility WAL enabled: wal_syncmode="
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 7d9f02d..3b14d9d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -312,6 +312,15 @@
cancellationSignal);
}
+ private String modeString(int transactionMode) {
+ switch (transactionMode) {
+ case TRANSACTION_MODE_IMMEDIATE: return "TRANSACTION-IMMEDIATE";
+ case TRANSACTION_MODE_EXCLUSIVE: return "TRANSACTION-EXCLUSIVE";
+ case TRANSACTION_MODE_DEFERRED: return "TRANSACTION-DEFERRED";
+ default: return "TRANSACTION";
+ }
+ }
+
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
@@ -321,6 +330,7 @@
if (mTransactionStack == null) {
acquireConnection(null, connectionFlags, cancellationSignal); // might throw
+ mConnection.recordBeginTransaction(modeString(transactionMode));
}
try {
// Set up the transaction such that we can back out safely
@@ -465,6 +475,7 @@
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
} finally {
+ mConnection.recordEndTransaction(successful);
releaseConnection(); // might throw
}
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 42f5fc8..9007b62 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -240,18 +240,19 @@
*
* @param logoDescription The logo description text that will be shown on the prompt.
* @return This builder.
- * @throws IllegalArgumentException If logo description is null or exceeds certain character
- * limit.
+ * @throws IllegalArgumentException If logo description is null.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
- if (logoDescription == null
- || logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalArgumentException(
- "Logo description passed in can not be null or exceed "
- + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + " character number.");
+ if (logoDescription == null || logoDescription.isEmpty()) {
+ throw new IllegalArgumentException("Logo description passed in can not be null");
+ }
+ if (logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
+ Log.w(TAG,
+ "Logo description passed in exceeds" + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER
+ + " character number and may be truncated.");
}
mPromptInfo.setLogoDescription(logoDescription);
return this;
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index c091062..109b0a8 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -60,7 +60,8 @@
@IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
BRIGHTNESS_MAX_REASON_NONE,
BRIGHTNESS_MAX_REASON_THERMAL,
- BRIGHTNESS_MAX_REASON_POWER_IC
+ BRIGHTNESS_MAX_REASON_POWER_IC,
+ BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessMaxReason {}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index bec1c9e..d85e41d 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -25,6 +25,7 @@
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import android.Manifest;
@@ -326,6 +327,15 @@
}
/**
+ * Returns true if the feature flag for touchpad visualizer is enabled.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadVisualizerFeatureFlagEnabled() {
+ return touchpadVisualizer();
+ }
+
+ /**
* Returns true if the touchpad should allow tap dragging.
*
* The returned value only applies to gesture-compatible touchpads.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index b4ad050..6f1d63d8 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -85,3 +85,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "modifier_shortcut_dump"
+ namespace: "input"
+ description: "Dump keyboard shortcuts in dumpsys window"
+ bug: "351963350"
+}
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index dd484f6..e039953 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -124,8 +124,8 @@
static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background";
static final String XML_ATTR_TIME_IN_FOREGROUND_SERVICE = "time_in_foreground_service";
- // We need about 700 bytes per UID
- private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 5_000 * 700;
+ // Max window size. CursorWindow uses only as much memory as needed.
+ private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 20_000_000; // bytes
private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 475984e..f3ef9e1 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -1024,6 +1024,7 @@
new Creator<Composed>() {
@Override
public Composed createFromParcel(Parcel in) {
+ in.readInt(); // Skip the parcel type token
return new Composed(in);
}
@@ -1298,6 +1299,7 @@
new Creator<VendorEffect>() {
@Override
public VendorEffect createFromParcel(Parcel in) {
+ in.readInt(); // Skip the parcel type token
return new VendorEffect(in);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ee6f43..5703f69 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5912,6 +5912,14 @@
public static final String SHOW_KEY_PRESSES = "show_key_presses";
/**
+ * Show touchpad input visualization on screen.
+ * 0 = no
+ * 1 = yes
+ * @hide
+ */
+ public static final String TOUCHPAD_VISUALIZER = "touchpad_visualizer";
+
+ /**
* Show rotary input dispatched to focused windows on the screen.
* 0 = no
* 1 = yes
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cf329d3..a7641c0 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4898,5 +4898,4 @@
public static void notifyShutdown() {
nativeNotifyShutdown();
}
-
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 14978ed..85d4ec0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -478,6 +478,12 @@
*/
int TRANSIT_SLEEP = 12;
/**
+ * An Activity was going to be visible from back navigation.
+ * @hide
+ */
+ int TRANSIT_PREPARE_BACK_NAVIGATION = 13;
+
+ /**
* The first slot for custom transition types. Callers (like Shell) can make use of custom
* transition types for dealing with special cases. These types are effectively ignored by
* Core and will just be passed along as part of TransitionInfo objects. An example is
@@ -505,6 +511,7 @@
TRANSIT_PIP,
TRANSIT_WAKE,
TRANSIT_SLEEP,
+ TRANSIT_PREPARE_BACK_NAVIGATION,
TRANSIT_FIRST_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
@@ -1918,6 +1925,7 @@
case TRANSIT_PIP: return "PIP";
case TRANSIT_WAKE: return "WAKE";
case TRANSIT_SLEEP: return "SLEEP";
+ case TRANSIT_PREPARE_BACK_NAVIGATION: return "PREDICTIVE_BACK";
case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
default:
if (type > TRANSIT_FIRST_CUSTOM) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 2af935d..09306c7 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -28,7 +28,6 @@
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.view.WindowInsets;
import dalvik.system.CloseGuard;
@@ -882,13 +881,12 @@
}
/**
- * @return the edges to which outsets can be applied to
+ * @return if a window animation has outsets applied to it.
*
* @hide
*/
- @WindowInsets.Side.InsetsSide
- public int getExtensionEdges() {
- return 0x0;
+ public boolean hasExtension() {
+ return false;
}
/**
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index bbdc9d0..5aaa994 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -21,7 +21,6 @@
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
-import android.view.WindowInsets;
import java.util.ArrayList;
import java.util.List;
@@ -541,12 +540,12 @@
/** @hide */
@Override
- @WindowInsets.Side.InsetsSide
- public int getExtensionEdges() {
- int edge = 0x0;
+ public boolean hasExtension() {
for (Animation animation : mAnimations) {
- edge |= animation.getExtensionEdges();
+ if (animation.hasExtension()) {
+ return true;
+ }
}
- return edge;
+ return false;
}
}
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
index ed047c7..210eb8a 100644
--- a/core/java/android/view/animation/ExtendAnimation.java
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -20,7 +20,6 @@
import android.content.res.TypedArray;
import android.graphics.Insets;
import android.util.AttributeSet;
-import android.view.WindowInsets;
/**
* An animation that controls the outset of an object.
@@ -51,8 +50,6 @@
private float mToRightValue;
private float mToBottomValue;
- private int mExtensionEdges = 0x0;
-
/**
* Constructor used when an ExtendAnimation is loaded from a resource.
*
@@ -154,22 +151,9 @@
/** @hide */
@Override
- @WindowInsets.Side.InsetsSide
- public int getExtensionEdges() {
- mExtensionEdges = 0x0;
- if (mFromLeftValue > 0 || mToLeftValue > 0) {
- mExtensionEdges |= WindowInsets.Side.LEFT;
- }
- if (mFromRightValue > 0 || mToRightValue > 0) {
- mExtensionEdges |= WindowInsets.Side.RIGHT;
- }
- if (mFromTopValue > 0 || mToTopValue > 0) {
- mExtensionEdges |= WindowInsets.Side.TOP;
- }
- if (mFromBottomValue > 0 || mToBottomValue > 0) {
- mExtensionEdges |= WindowInsets.Side.BOTTOM;
- }
- return mExtensionEdges;
+ public boolean hasExtension() {
+ return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
+ || mFromInsets.bottom < 0;
}
@Override
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index 95cae226..abef7cf 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -94,8 +94,15 @@
* @hide
*/
public boolean isRelayoutFixEnabled() {
+ AutofillManager autofillManager = getAutofillManager();
+ if (autofillManager == null) {
+ if (Helper.sDebug) {
+ Log.d(TAG, "isRelayoutFixEnabled() : getAutofillManager() == null");
+ }
+ return false;
+ }
if (mRelayoutFix == null) {
- mRelayoutFix = getAutofillManager().isRelayoutFixEnabled();
+ mRelayoutFix = autofillManager.isRelayoutFixEnabled();
}
return mRelayoutFix;
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d40b72c..2ac5873 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2949,7 +2949,8 @@
* @param callback Consumer callback that provides {@code true} if view belongs to allowed
* delegate package declared in
* {@link #prepareStylusHandwritingDelegation(View, String)} and handwriting
- * session can start.
+ * session can start. Note: The caller should hold a reference to the callback.
+ * The framework only holds a weak reference.
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
*/
@@ -2979,7 +2980,8 @@
* @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
* @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
* @param executor The executor to run the callback on.
- * @param callback {@code true>} would be received if delegation was accepted.
+ * @param callback {@code true} would be received if delegation was accepted. The caller should
+ * hold a reference to the callback. The framework only holds a weak reference.
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
*/
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9099db8..ac899f4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -31,6 +31,7 @@
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import android.R;
import android.annotation.CallSuper;
@@ -2738,6 +2739,8 @@
InputMethodManager imm = getInputMethodManager();
if (imm != null) imm.restartInput(this);
+
+ ensureEditorFocusedNotifiedToHandwritingInitiator();
}
private void setInputTypeFromEditor() {
@@ -7843,6 +7846,20 @@
if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
createEditorIfNeeded();
mEditor.mInputType = type;
+ ensureEditorFocusedNotifiedToHandwritingInitiator();
+ }
+
+ private void ensureEditorFocusedNotifiedToHandwritingInitiator() {
+ if (!initiationWithoutInputConnection() || isHandwritingDelegate()) {
+ return;
+ }
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null) {
+ return;
+ }
+ if (isFocused() && hasWindowFocus() && onCheckIsTextEditor()) {
+ viewRoot.getHandwritingInitiator().onEditorFocused(this);
+ }
}
@Override
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index 9cd2a71..62a7283 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -23,7 +23,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.WindowConfiguration;
-import android.companion.virtualdevice.flags.Flags;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -67,9 +66,7 @@
public DisplayWindowPolicyController() {
synchronized (mSupportedWindowingModes) {
mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- if (Flags.virtualDisplayMultiWindowModeSupport()) {
- mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
- }
+ mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
}
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 91ac4ff..2d5b02a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -192,6 +192,13 @@
}
flag {
+ name: "enable_desktop_windowing_transitions"
+ namespace: "lse_desktop_experience"
+ description: "Enables desktop windowing transition & motion polish changes"
+ bug: "356570693"
+}
+
+flag {
name: "enable_compat_ui_visibility_status"
namespace: "lse_desktop_experience"
description: "Enables the tracking of the status for compat ui elements."
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index e5a9b6a..80a0102 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -201,20 +201,20 @@
}
flag {
- name: "enforce_shell_thread_model"
+ name: "custom_animations_behind_translucent"
namespace: "windowing_frontend"
- description: "Crash the shell process if someone calls in from the wrong thread"
- bug: "351189446"
- is_fixed_read_only: true
+ description: "A change can use its own layer parameters to animate behind a translucent activity"
+ bug: "327332488"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
- name: "custom_animations_behind_translucent"
+ name: "migrate_predictive_back_transition"
namespace: "windowing_frontend"
- description: "A change can use its own layer parameters to animate behind a translucent activity"
- bug: "327332488"
+ description: "Create transition when visibility change from predictive back"
+ bug: "347168362"
+ is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4c18bbf..b8c2a5f 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -135,14 +135,3 @@
purpose: PURPOSE_BUGFIX
}
}
-
-flag {
- namespace: "windowing_sdk"
- name: "per_user_display_window_settings"
- description: "Whether to store display window settings per user to avoid conflicts"
- bug: "346668297"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6258f5c..ca4d1b6 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2960,10 +2960,10 @@
}
private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
- return shouldShowTabs()
- && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
- UserHandle.of(UserHandle.myUserId())).getCount() > 0
- || shouldShowStickyContentPreviewWhenEmpty())
+ ResolverListAdapter adapter = mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ UserHandle.of(UserHandle.myUserId()));
+ boolean isEmpty = adapter == null || adapter.getCount() == 0;
+ return shouldShowTabs() && (!isEmpty || shouldShowStickyContentPreviewWhenEmpty())
&& shouldShowContentPreview();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index e29f256..1d43f6f 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -51,8 +51,7 @@
void showWirelessChargingAnimation(int batteryLevel);
- void setImeWindowStatus(int displayId, in IBinder token, int vis, int backDisposition,
- boolean showImeSwitcher);
+ void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher);
void setWindowState(int display, int window, int state);
void showRecentApps(boolean triggeredFromAltTab);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index fc60f06..ff08dd2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -61,8 +61,7 @@
void setIconVisibility(String slot, boolean visible);
@UnsupportedAppUsage
void removeIcon(String slot);
- void setImeWindowStatus(int displayId, in IBinder token, int vis, int backDisposition,
- boolean showImeSwitcher);
+ void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher);
void expandSettingsPanel(String subPanel);
// ---- Methods below are for use by the status bar policy services ----
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 4f827cd..7240aff 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -16,7 +16,6 @@
package com.android.internal.statusbar;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -35,7 +34,6 @@
public final int mImeBackDisposition; // switch[4]
public final boolean mShowImeSwitcher; // switch[5]
public final int mDisabledFlags2; // switch[6]
- public final IBinder mImeToken;
public final boolean mNavbarColorManagedByIme;
public final int mBehavior;
public final int mRequestedVisibleTypes;
@@ -45,7 +43,7 @@
public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
- int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
+ int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2,
boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) {
mIcons = new ArrayMap<>(icons);
@@ -56,7 +54,6 @@
mImeBackDisposition = imeBackDisposition;
mShowImeSwitcher = showImeSwitcher;
mDisabledFlags2 = disabledFlags2;
- mImeToken = imeToken;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
mRequestedVisibleTypes = requestedVisibleTypes;
@@ -80,7 +77,6 @@
dest.writeInt(mImeBackDisposition);
dest.writeBoolean(mShowImeSwitcher);
dest.writeInt(mDisabledFlags2);
- dest.writeStrongBinder(mImeToken);
dest.writeBoolean(mNavbarColorManagedByIme);
dest.writeInt(mBehavior);
dest.writeInt(mRequestedVisibleTypes);
@@ -106,7 +102,6 @@
final int imeBackDisposition = source.readInt();
final boolean showImeSwitcher = source.readBoolean();
final int disabledFlags2 = source.readInt();
- final IBinder imeToken = source.readStrongBinder();
final boolean navbarColorManagedByIme = source.readBoolean();
final int behavior = source.readInt();
final int requestedVisibleTypes = source.readInt();
@@ -116,7 +111,7 @@
source.readParcelableArray(null, LetterboxDetails.class);
return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
- disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
+ disabledFlags2, navbarColorManagedByIme, behavior,
requestedVisibleTypes, packageName, transientBarTypes,
letterboxDetails);
}
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 77de272..0fd1391 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -31,6 +31,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.VectorDrawable;
+import android.text.NoCopySpan;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.BackgroundColorSpan;
@@ -188,6 +189,10 @@
Object[] spans = ss.getSpans(0, ss.length(), Object.class);
SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
for (Object span : spans) {
+ if (span instanceof NoCopySpan) {
+ // These spans can contain external references and should not be copied.
+ continue;
+ }
Object resultSpan = span;
if (resultSpan instanceof CharacterStyle) {
resultSpan = ((CharacterStyle) span).getUnderlying();
@@ -254,6 +259,10 @@
Object[] spans = ss.getSpans(0, ss.length(), Object.class);
SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
for (Object span : spans) {
+ if (span instanceof NoCopySpan) {
+ // These spans can contain external references and should not be copied.
+ continue;
+ }
Object resultSpan = span;
if (resultSpan instanceof CharacterStyle) {
resultSpan = ((CharacterStyle) span).getUnderlying();
@@ -300,6 +309,10 @@
Object[] spans = ss.getSpans(0, ss.length(), Object.class);
SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
for (Object span : spans) {
+ if (span instanceof NoCopySpan) {
+ // These spans can contain external references and should not be copied.
+ continue;
+ }
Object resultSpan = span;
int spanStart = ss.getSpanStart(span);
int spanEnd = ss.getSpanEnd(span);
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index e5ced25..e795e809 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -69,6 +69,7 @@
// 0 = no, 1 = yes
optional SettingProto window_orientation_listener_log = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto show_key_presses = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto touchpad_visualizer = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional DevOptions developer_options = 7;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f3dac23..a00cc8b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6107,9 +6107,8 @@
android:description="@string/permdesc_deliverCompanionMessages"
android:protectionLevel="normal" />
- <!-- @hide @FlaggedApi("android.companion.flags.companion_transport_apis")
- Allows an application to send and receive messages via CDM transports.
- -->
+ <!-- Allows an application to send and receive messages via CDM transports.
+ @hide -->
<permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
android:protectionLevel="signature" />
diff --git a/core/res/res/drawable/ic_zen_mode_icon_ball_sports.xml b/core/res/res/drawable/ic_zen_mode_icon_ball_sports.xml
new file mode 100644
index 0000000..fdf0bcf
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_ball_sports.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M368,956L298,916L418,708L350,668L290,772L220,732L426,376Q388,337 369,287Q350,237 350,184Q350,148 359,112.5Q368,77 388,44L388,44L456,84L456,84Q442,107 436,131.5Q430,156 430,182Q430,235 456,281.5Q482,328 530,356L620,408Q682,444 711,511.5Q740,579 740,638Q740,676 730,712Q720,748 702,780L702,780L632,740L632,740Q646,716 652,691Q658,666 658,640Q658,608 649,578Q640,548 620,522L368,956ZM640,360Q607,360 583.5,336.5Q560,313 560,280Q560,247 583.5,223.5Q607,200 640,200Q673,200 696.5,223.5Q720,247 720,280Q720,313 696.5,336.5Q673,360 640,360ZM540,160Q514,160 497,142Q480,124 480,100Q480,74 498,57Q516,40 540,40Q566,40 583,58Q600,76 600,100Q600,126 582,143Q564,160 540,160Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_beach.xml b/core/res/res/drawable/ic_zen_mode_icon_beach.xml
new file mode 100644
index 0000000..a42d9fd
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_beach.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M784,840L530,586L586,530L840,784L784,840ZM238,812Q178,752 149,677Q120,602 120,524Q120,446 149,372Q178,298 238,238Q298,178 372.5,148.5Q447,119 525,119Q603,119 677.5,148.5Q752,178 812,238L238,812ZM246,690L300,636Q284,615 269.5,593Q255,571 243,549Q231,527 222,505Q213,483 206,462Q195,521 204.5,580Q214,639 246,690ZM358,580L580,356Q537,323 493.5,302.5Q450,282 412,274.5Q374,267 343.5,272Q313,277 296,294Q279,312 274,342.5Q269,373 276.5,411.5Q284,450 304.5,493Q325,536 358,580ZM636,300L692,246Q639,214 580,204Q521,194 462,206Q484,213 506,222Q528,231 550,242.5Q572,254 593.5,268.5Q615,283 636,300Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_camping.xml b/core/res/res/drawable/ic_zen_mode_icon_camping.xml
new file mode 100644
index 0000000..1797ebd
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_camping.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M80,880L80,694L430,222L360,128L424,80L480,155L536,80L600,128L530,222L880,694L880,880L80,880ZM480,289L160,720L160,800L280,800L480,520L680,800L800,800L800,720L480,289ZM378,800L582,800L480,658L378,800ZM480,520L680,800L680,800L680,800L480,520L280,800L280,800L280,800L480,520Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_gaming.xml b/core/res/res/drawable/ic_zen_mode_icon_gaming.xml
new file mode 100644
index 0000000..8bea9ae
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_gaming.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M182,760Q131,760 103,724.5Q75,689 82,638L124,338Q133,278 177.5,239Q222,200 282,200L678,200Q738,200 782.5,239Q827,278 836,338L878,638Q885,689 857,724.5Q829,760 778,760Q757,760 739,752.5Q721,745 706,730L616,640L344,640L254,730Q239,745 221,752.5Q203,760 182,760ZM198,674L312,560L648,560L762,674Q764,676 778,680Q789,680 795.5,673.5Q802,667 800,656L756,348Q752,319 730,299.5Q708,280 678,280L282,280Q252,280 230,299.5Q208,319 204,348L160,656Q158,667 164.5,673.5Q171,680 182,680Q184,680 198,674ZM680,520Q697,520 708.5,508.5Q720,497 720,480Q720,463 708.5,451.5Q697,440 680,440Q663,440 651.5,451.5Q640,463 640,480Q640,497 651.5,508.5Q663,520 680,520ZM600,400Q617,400 628.5,388.5Q640,377 640,360Q640,343 628.5,331.5Q617,320 600,320Q583,320 571.5,331.5Q560,343 560,360Q560,377 571.5,388.5Q583,400 600,400ZM310,520L370,520L370,450L440,450L440,390L370,390L370,320L310,320L310,390L240,390L240,450L310,450L310,520ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_golf.xml b/core/res/res/drawable/ic_zen_mode_icon_golf.xml
new file mode 100644
index 0000000..d899c5b
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_golf.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M780,840Q755,840 737.5,822.5Q720,805 720,780Q720,755 737.5,737.5Q755,720 780,720Q805,720 822.5,737.5Q840,755 840,780Q840,805 822.5,822.5Q805,840 780,840ZM400,880Q300,880 230,856.5Q160,833 160,800Q160,777 193,759Q226,741 280,730L280,800L360,800L360,80L680,236L440,360L440,722Q526,727 583,748.5Q640,770 640,800Q640,833 570,856.5Q500,880 400,880Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_gym.xml b/core/res/res/drawable/ic_zen_mode_icon_gym.xml
new file mode 100644
index 0000000..585e564
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_gym.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M536,876L480,820L622,678L282,338L140,480L84,424L140,366L84,310L168,226L112,168L168,112L226,168L310,84L366,140L424,84L480,140L338,282L678,622L820,480L876,536L820,594L876,650L792,734L848,792L792,848L734,792L650,876L594,820L536,876Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_hiking.xml b/core/res/res/drawable/ic_zen_mode_icon_hiking.xml
new file mode 100644
index 0000000..d2b8c85
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_hiking.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M280,920L403,298Q409,269 430,254.5Q451,240 474,240Q497,240 516.5,250Q536,260 548,280L588,344Q606,373 634.5,396.5Q663,420 700,431L700,360L760,360L760,920L700,920L700,514Q652,503 611,479Q570,455 540,420L516,540L600,620L600,920L520,920L520,680L436,600L364,920L280,920ZM297,525L212,509Q196,506 187,492.5Q178,479 181,462L211,305Q217,273 245,254.5Q273,236 305,242L351,251L297,525ZM540,220Q507,220 483.5,196.5Q460,173 460,140Q460,107 483.5,83.5Q507,60 540,60Q573,60 596.5,83.5Q620,107 620,140Q620,173 596.5,196.5Q573,220 540,220Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_martial_arts.xml b/core/res/res/drawable/ic_zen_mode_icon_martial_arts.xml
new file mode 100644
index 0000000..7cabd86
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_martial_arts.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M400,880L380,520L253,447L239,499L320,640L251,680L152,510L200,338L430,206L320,96L376,40L560,223L416,306L464,348L792,80L840,136L500,480L480,880L400,880ZM200,280Q167,280 143.5,256.5Q120,233 120,200Q120,167 143.5,143.5Q167,120 200,120Q233,120 256.5,143.5Q280,167 280,200Q280,233 256.5,256.5Q233,280 200,280Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_swimming.xml b/core/res/res/drawable/ic_zen_mode_icon_swimming.xml
new file mode 100644
index 0000000..465a02a
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_swimming.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M80,840L80,760Q118,760 137,740Q156,720 212,720Q268,720 289,740Q310,760 346,760Q382,760 403,740Q424,720 480,720Q536,720 557,740Q578,760 614,760Q650,760 671,740Q692,720 748,720Q804,720 823,740Q842,760 880,760L880,840Q821,840 802.5,820Q784,800 748,800Q712,800 691,820Q670,840 614,840Q558,840 537,820Q516,800 480,800Q444,800 423,820Q402,840 346,840Q290,840 269,820Q248,800 212,800Q176,800 157.5,820Q139,840 80,840ZM80,660L80,580Q118,580 137,560Q156,540 212,540Q268,540 289.5,560Q311,580 346,580Q382,580 403,560Q424,540 480,540Q536,540 557,560Q578,580 614,580Q650,580 671,560Q692,540 748,540Q804,540 823,560Q842,580 880,580L880,660Q821,660 802.5,640Q784,620 748,620Q712,620 692.5,640Q673,660 614,660Q557,660 536.5,640Q516,620 480,620Q442,620 423.5,640Q405,660 346,660Q287,660 267.5,640Q248,620 212,620Q176,620 157.5,640Q139,660 80,660ZM276,456L409,323L369,283Q336,250 299,235Q262,220 208,220L208,120Q283,120 332,136.5Q381,153 428,200L684,456Q667,467 651,473.5Q635,480 614,480Q578,480 557,460Q536,440 480,440Q424,440 403,460Q382,480 346,480Q325,480 309,473.5Q293,467 276,456ZM668,120Q710,120 739,149.5Q768,179 768,220Q768,262 739,291Q710,320 668,320Q626,320 597,291Q568,262 568,220Q568,179 597,149.5Q626,120 668,120Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_work.xml b/core/res/res/drawable/ic_zen_mode_icon_work.xml
new file mode 100644
index 0000000..7820458
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_work.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M160,840Q127,840 103.5,816.5Q80,793 80,760L80,320Q80,287 103.5,263.5Q127,240 160,240L320,240L320,160Q320,127 343.5,103.5Q367,80 400,80L560,80Q593,80 616.5,103.5Q640,127 640,160L640,240L800,240Q833,240 856.5,263.5Q880,287 880,320L880,760Q880,793 856.5,816.5Q833,840 800,840L160,840ZM160,760L800,760Q800,760 800,760Q800,760 800,760L800,320Q800,320 800,320Q800,320 800,320L160,320Q160,320 160,320Q160,320 160,320L160,760Q160,760 160,760Q160,760 160,760ZM400,240L560,240L560,160Q560,160 560,160Q560,160 560,160L400,160Q400,160 400,160Q400,160 400,160L400,240ZM160,760Q160,760 160,760Q160,760 160,760L160,320Q160,320 160,320Q160,320 160,320L160,320Q160,320 160,320Q160,320 160,320L160,760Q160,760 160,760Q160,760 160,760Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_workshop.xml b/core/res/res/drawable/ic_zen_mode_icon_workshop.xml
new file mode 100644
index 0000000..844c7b7
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_workshop.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M756,840L537,621L621,537L840,756L756,840ZM204,840L120,756L396,480L328,412L300,440L249,389L249,471L221,499L100,378L128,350L210,350L160,300L302,158Q322,138 345,129Q368,120 392,120Q416,120 439,129Q462,138 482,158L390,250L440,300L412,328L480,396L570,306Q566,295 563.5,283Q561,271 561,259Q561,200 601.5,159.5Q642,119 701,119Q716,119 729.5,122Q743,125 757,131L658,230L730,302L829,203Q836,217 838.5,230.5Q841,244 841,259Q841,318 800.5,358.5Q760,399 701,399Q689,399 677,397Q665,395 654,390L204,840Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_type_managed.xml b/core/res/res/drawable/ic_zen_mode_type_managed.xml
index 5e224eb..46fb435 100644
--- a/core/res/res/drawable/ic_zen_mode_type_managed.xml
+++ b/core/res/res/drawable/ic_zen_mode_type_managed.xml
@@ -21,5 +21,5 @@
android:viewportWidth="960">
<path
android:fillColor="@android:color/white"
- android:pathData="M234,684Q285,645 348,622.5Q411,600 480,600Q549,600 612,622.5Q675,645 726,684Q761,643 780.5,591Q800,539 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,539 179.5,591Q199,643 234,684ZM480,520Q421,520 380.5,479.5Q340,439 340,380Q340,321 380.5,280.5Q421,240 480,240Q539,240 579.5,280.5Q620,321 620,380Q620,439 579.5,479.5Q539,520 480,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q533,800 580,784.5Q627,769 666,740Q627,711 580,695.5Q533,680 480,680Q427,680 380,695.5Q333,711 294,740Q333,769 380,784.5Q427,800 480,800ZM480,440Q506,440 523,423Q540,406 540,380Q540,354 523,337Q506,320 480,320Q454,320 437,337Q420,354 420,380Q420,406 437,423Q454,440 480,440ZM480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380ZM480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Z" />
+ android:pathData="M680,600Q638,600 609,571Q580,542 580,500Q580,458 609,429Q638,400 680,400Q722,400 751,429Q780,458 780,500Q780,542 751,571Q722,600 680,600ZM480,800L480,744Q480,720 492.5,699.5Q505,679 528,670Q564,655 602.5,647.5Q641,640 680,640Q719,640 757.5,647.5Q796,655 832,670Q855,679 867.5,699.5Q880,720 880,744L880,800L480,800ZM400,480Q334,480 287,433Q240,386 240,320Q240,254 287,207Q334,160 400,160Q466,160 513,207Q560,254 560,320Q560,386 513,433Q466,480 400,480ZM400,320Q400,320 400,320Q400,320 400,320Q400,320 400,320Q400,320 400,320Q400,320 400,320Q400,320 400,320Q400,320 400,320Q400,320 400,320ZM80,800L80,688Q80,654 97,625.5Q114,597 144,582Q204,552 268.5,536Q333,520 400,520Q435,520 470,526Q505,532 540,540Q523,557 506,574Q489,591 472,608Q454,603 436,601.5Q418,600 400,600Q342,600 286.5,614Q231,628 180,654Q170,659 165,668Q160,677 160,688L160,720L400,720L400,800L80,800ZM400,720Q400,720 400,720Q400,720 400,720Q400,720 400,720Q400,720 400,720L400,720L400,720Q400,720 400,720Q400,720 400,720Q400,720 400,720Q400,720 400,720ZM400,400Q433,400 456.5,376.5Q480,353 480,320Q480,287 456.5,263.5Q433,240 400,240Q367,240 343.5,263.5Q320,287 320,320Q320,353 343.5,376.5Q367,400 400,400Z" />
</vector>
\ No newline at end of file
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 85d34e2..4d2085bb 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -548,4 +548,7 @@
<item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item>
<item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
</style>
+
+ <!-- Device default theme for the Input Method Switcher dialog. Override to make it dark. -->
+ <style name="Theme.DeviceDefault.InputMethodSwitcherDialog" parent="Theme.DeviceDefault.Dialog.Alert"/>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e94db2d..9104379 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6496,10 +6496,14 @@
<string name="screen_not_shared_sensitive_content">App content hidden from screen share for security</string>
<!-- Satellite related messages -->
- <!-- Notification title when satellite service is connected. -->
+ <!-- Notification title when satellite service is auto connected. -->
<string name="satellite_notification_title">Auto connected to satellite</string>
- <!-- Notification summary when satellite service is connected. [CHAR LIMIT=NONE] -->
+ <!-- Notification summary when satellite service is auto connected. [CHAR LIMIT=NONE] -->
<string name="satellite_notification_summary">You can send and receive messages without a mobile or Wi-Fi network</string>
+ <!-- Notification title when satellite service can be manually enabled. -->
+ <string name="satellite_notification_manual_title">Use satellite messaging?</string>
+ <!-- Notification summary when satellite service can be manually enabled. [CHAR LIMIT=NONE] -->
+ <string name="satellite_notification_manual_summary">Send and receive messages without a mobile or Wi-Fi network</string>
<!-- Invoke "What to expect" dialog of messaging application -->
<string name="satellite_notification_open_message">Open Messages</string>
<!-- Invoke Satellite setting activity of Settings -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8734b44..b158e0f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5499,6 +5499,8 @@
<!-- System notification for satellite service -->
<java-symbol type="string" name="satellite_notification_title" />
<java-symbol type="string" name="satellite_notification_summary" />
+ <java-symbol type="string" name="satellite_notification_manual_title" />
+ <java-symbol type="string" name="satellite_notification_manual_summary" />
<java-symbol type="string" name="satellite_notification_open_message" />
<java-symbol type="string" name="satellite_notification_how_it_works" />
<java-symbol type="drawable" name="ic_satellite_alt_24px" />
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index cd6abdd..b5ee130 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -375,20 +375,4 @@
PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
assertEquals(n1, "cache_key.bluetooth.get_state");
}
-
- @Test
- public void testOnTrimMemory() {
- TestCache cache = new TestCache(MODULE, "trimMemoryTest");
- // The cache is not active until it has been invalidated once.
- cache.invalidateCache();
- // Populate the cache with six entries.
- for (int i = 0; i < 6; i++) {
- cache.query(i);
- }
- // The maximum number of entries in TestCache is 4, so even though six entries were
- // created, only four are retained.
- assertEquals(4, cache.size());
- PropertyInvalidatedCache.onTrimMemory();
- assertEquals(0, cache.size());
- }
}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index bd9c4b8..519f23b 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -28,6 +28,7 @@
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Printer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -43,10 +44,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -400,4 +404,138 @@
}
assertFalse(allowed);
}
+
+ /** Dumpsys information about a single database. */
+
+ /**
+ * Collect and parse dumpsys output. This is not a full parser. It is only enough to support
+ * the unit tests.
+ */
+ private static class Dumpsys {
+ // Regular expressions for parsing the output. Reportedly, regular expressions are
+ // expensive, so these are created only if a dumpsys object is created.
+ private static final Object sLock = new Object();
+ static Pattern mPool;
+ static Pattern mConnection;
+ static Pattern mEntry;
+ static Pattern mSingleWord;
+ static Pattern mNone;
+
+ // The raw strings read from dumpsys. Once loaded, this list never changes.
+ final ArrayList<String> mRaw = new ArrayList<>();
+
+ // Parsed dumpsys. This contains only the bits that are being tested.
+ static class Connection {
+ ArrayList<String> mRecent = new ArrayList<>();
+ ArrayList<String> mLong = new ArrayList<>();
+ }
+ static class Database {
+ String mPath;
+ ArrayList<Connection> mConnection = new ArrayList<>();
+ }
+ ArrayList<Database> mDatabase;
+ ArrayList<String> mConcurrent;
+
+ Dumpsys() {
+ SQLiteDebug.dump(
+ new Printer() { public void println(String x) { mRaw.add(x); } },
+ new String[0]);
+ parse();
+ }
+
+ /** Parse the raw text. Return true if no errors were detected. */
+ boolean parse() {
+ initialize();
+
+ // Reset the parsed information. This method may be called repeatedly.
+ mDatabase = new ArrayList<>();
+ mConcurrent = new ArrayList<>();
+
+ Database current = null;
+ Connection connection = null;
+ Matcher matcher;
+ for (int i = 0; i < mRaw.size(); i++) {
+ final String line = mRaw.get(i);
+ matcher = mPool.matcher(line);
+ if (matcher.lookingAt()) {
+ current = new Database();
+ mDatabase.add(current);
+ current.mPath = matcher.group(1);
+ continue;
+ }
+ matcher = mConnection.matcher(line);
+ if (matcher.lookingAt()) {
+ connection = new Connection();
+ current.mConnection.add(connection);
+ continue;
+ }
+
+ if (line.contains("Most recently executed operations")) {
+ i += readTable(connection.mRecent, i, mEntry);
+ continue;
+ }
+
+ if (line.contains("Operations exceeding 2000ms")) {
+ i += readTable(connection.mLong, i, mEntry);
+ continue;
+ }
+ if (line.contains("Concurrently opened database files")) {
+ i += readTable(mConcurrent, i, mSingleWord);
+ continue;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Read a series of lines following a header. Return the number of lines read. The input
+ * line number is the number of the header.
+ */
+ private int readTable(List<String> s, int header, Pattern p) {
+ // Special case: if the first line is "<none>" then there are no more lines to the
+ // table.
+ if (lookingAt(header+1, mNone)) return 1;
+
+ int i;
+ for (i = header + 1; i < mRaw.size() && lookingAt(i, p); i++) {
+ s.add(mRaw.get(i).trim());
+ }
+ return i - header;
+ }
+
+ /** Return true if the n'th raw line matches the pattern. */
+ boolean lookingAt(int n, Pattern p) {
+ return p.matcher(mRaw.get(n)).lookingAt();
+ }
+
+ /** Compile the regular expressions the first time. */
+ private static void initialize() {
+ synchronized (sLock) {
+ if (mPool != null) return;
+ mPool = Pattern.compile("Connection pool for (\\S+):");
+ mConnection = Pattern.compile("\\s+Connection #(\\d+):");
+ mEntry = Pattern.compile("\\s+(\\d+): ");
+ mSingleWord = Pattern.compile(" (\\S+)$");
+ mNone = Pattern.compile("\\s+<none>$");
+ }
+ }
+ }
+
+ @Test
+ public void testDumpsys() throws Exception {
+ Dumpsys dumpsys = new Dumpsys();
+
+ assertEquals(1, dumpsys.mDatabase.size());
+ // Note: cannot test mConcurrent because that attribute is not hermitic with respect to
+ // the tests.
+
+ Dumpsys.Database db = dumpsys.mDatabase.get(0);
+
+ // Work with normalized paths.
+ String wantPath = mDatabaseFile.toPath().toRealPath().toString();
+ String realPath = new File(db.mPath).toPath().toRealPath().toString();
+ assertEquals(wantPath, realPath);
+
+ assertEquals(1, db.mConnection.size());
+ }
}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
index 5464ea3..d6c0e99 100644
--- a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -16,7 +16,6 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.BiometricPrompt.MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER;
import static android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.MAX_DESCRIPTION_CHARACTER_NUMBER;
import static android.hardware.biometrics.PromptVerticalListContentView.MAX_EACH_ITEM_CHARACTER_NUMBER;
import static android.hardware.biometrics.PromptVerticalListContentView.MAX_ITEM_NUMBER;
@@ -117,19 +116,7 @@
() -> new BiometricPrompt.Builder(mContext).setLogoDescription(null)
);
- assertThat(e).hasMessageThat().contains(
- "Logo description passed in can not be null or exceed");
- }
-
- @Test
- public void testLogoDescription_charLimit() {
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> new BiometricPrompt.Builder(mContext).setLogoDescription(
- generateRandomString(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + 1))
- );
-
- assertThat(e).hasMessageThat().contains(
- "Logo description passed in can not be null or exceed");
+ assertThat(e).hasMessageThat().isEqualTo("Logo description passed in can not be null");
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index af2a2bb..c733bae 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -21,7 +21,6 @@
import static com.google.common.truth.Truth.assertThat;
import android.graphics.Rect;
-import android.os.Binder;
import android.os.Parcel;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -63,7 +62,6 @@
0x10 /* imeBackDisposition */,
false /* showImeSwitcher */,
0x20 /* disabledFlags2 */,
- new Binder() /* imeToken */,
true /* navbarColorManagedByIme */,
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
WindowInsets.Type.defaultVisible(),
@@ -85,7 +83,6 @@
assertThat(copy.mImeBackDisposition).isEqualTo(original.mImeBackDisposition);
assertThat(copy.mShowImeSwitcher).isEqualTo(original.mShowImeSwitcher);
assertThat(copy.mDisabledFlags2).isEqualTo(original.mDisabledFlags2);
- assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
assertThat(copy.mRequestedVisibleTypes).isEqualTo(original.mRequestedVisibleTypes);
diff --git a/core/tests/resourceflaggingtests/Android.bp b/core/tests/resourceflaggingtests/Android.bp
index e8bb710..dd86094 100644
--- a/core/tests/resourceflaggingtests/Android.bp
+++ b/core/tests/resourceflaggingtests/Android.bp
@@ -34,12 +34,24 @@
}
genrule {
+ name: "resource-flagging-test-app-resources-compile2",
+ tools: ["aapt2"],
+ srcs: [
+ "flagged_resources_res/values/bools2.xml",
+ ],
+ out: ["values_bools2.arsc.flat"],
+ cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
+ "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+}
+
+genrule {
name: "resource-flagging-test-app-apk",
tools: ["aapt2"],
// The first input file in the list must be the manifest
srcs: [
"TestAppAndroidManifest.xml",
":resource-flagging-test-app-resources-compile",
+ ":resource-flagging-test-app-resources-compile2",
],
out: ["resapp.apk"],
cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml b/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
index f4defd9..8d01465 100644
--- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
+++ b/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
@@ -5,4 +5,6 @@
<bool name="res2">false</bool>
<bool name="res2" android:featureFlag="test.package.trueFlag">true</bool>
+
+ <bool name="res3">false</bool>
</resources>
\ No newline at end of file
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml b/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml
new file mode 100644
index 0000000..e7563aa
--- /dev/null
+++ b/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <bool name="res3" android:featureFlag="test.package.trueFlag">true</bool>
+</resources>
\ No newline at end of file
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index a0cbe3c..ad8542e 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -63,6 +63,11 @@
assertThat(getBoolean("res2")).isTrue();
}
+ @Test
+ public void testFlagEnabledDifferentCompilationUnit() {
+ assertThat(getBoolean("res3")).isTrue();
+ }
+
private boolean getBoolean(String name) {
int resId = mResources.getIdentifier(name, "bool", "com.android.intenal.flaggedresources");
assertThat(resId).isNotEqualTo(0);
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index 098ade4..bd3d944 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -1071,6 +1071,27 @@
.isHapticFeedbackCandidate());
}
+ @Test
+ public void testParcelingComposed() {
+ Parcel p = Parcel.obtain();
+ VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+ effect.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ VibrationEffect parceledEffect = VibrationEffect.Composed.CREATOR.createFromParcel(p);
+ assertThat(parceledEffect).isEqualTo(effect);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testParcelingVendorEffect() {
+ Parcel p = Parcel.obtain();
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+ effect.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ VibrationEffect parceledEffect = VibrationEffect.VendorEffect.CREATOR.createFromParcel(p);
+ assertThat(parceledEffect).isEqualTo(effect);
+ }
+
private void assertArrayEq(long[] expected, long[] actual) {
assertTrue(
String.format("Expected pattern %s, but was %s",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 409cde3..e555176 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1334,13 +1334,24 @@
if (shouldContainerBeExpanded(container)) {
// Make sure that the existing container is expanded.
mPresenter.expandTaskFragment(wct, container);
- } else {
- // Put activity into a new expanded container.
- final TaskFragmentContainer newContainer =
- new TaskFragmentContainer.Builder(this, getTaskId(activity), activity)
- .setPendingAppearedActivity(activity).build();
- mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
+ return;
}
+
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
+ if (splitContainer instanceof SplitPinContainer
+ && !container.isPinned() && container.getRunningActivityCount() == 1) {
+ // This is already the expected state when the pinned container is shown with an
+ // expanded activity in a standalone container on the side. Moving the activity into
+ // another new expanded container again is not necessary and could result in
+ // recursively creating new TaskFragmentContainers if the activity somehow relaunched.
+ return;
+ }
+
+ // Put activity into a new expanded container.
+ final TaskFragmentContainer newContainer =
+ new TaskFragmentContainer.Builder(this, getTaskId(activity), activity)
+ .setPendingAppearedActivity(activity).build();
+ mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
}
/**
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_new_window.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_new_window.xml
new file mode 100644
index 0000000..c154059
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_new_window.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M15 16V14H13V12.5H15V10.5H16.5V12.5H18.5V14H16.5V16H15ZM3.5 17C3.09722 17 2.74306 16.8542 2.4375 16.5625C2.14583 16.2569 2 15.9028 2 15.5V4.5C2 4.08333 2.14583 3.72917 2.4375 3.4375C2.74306 3.14583 3.09722 3 3.5 3H14.5C14.9167 3 15.2708 3.14583 15.5625 3.4375C15.8542 3.72917 16 4.08333 16 4.5V9H14.5V7H3.5V15.5H13.625V17H3.5ZM3.5 5.5H14.5V4.5H3.5V5.5ZM3.5 5.5V4.5V5.5Z"
+ android:fillColor="#1C1C14"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 49d9029..eea3de8 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -125,7 +125,7 @@
<LinearLayout
android:id="@+id/more_actions_pill"
android:layout_width="match_parent"
- android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
+ android:layout_height="wrap_content"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="vertical"
@@ -139,6 +139,14 @@
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <Button
+ android:id="@+id/new_window_button"
+ android:contentDescription="@string/new_window_text"
+ android:text="@string/new_window_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_new_window"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton" />
</LinearLayout>
<LinearLayout
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1eb2458..e6807ac 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -497,7 +497,7 @@
<!-- The maximum height of the handle menu in desktop mode. Four pills (52dp each) plus 2dp
spacing between them plus 4dp top padding. -->
- <dimen name="desktop_mode_handle_menu_height">218dp</dimen>
+ <dimen name="desktop_mode_handle_menu_height">270dp</dimen>
<!-- The elevation set on the handle menu pills. -->
<dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
@@ -508,8 +508,11 @@
<!-- The height of the handle menu's "Windowing" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
- <!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen>
+ <!-- The maximum height of the handle menu's "New Window" button in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_new_window_height">52dp</dimen>
+
+ <!-- The maximum height of the handle menu's "Screenshot" button in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_screenshot_height">52dp</dimen>
<!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 8669af3..0a8166f 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -282,6 +282,8 @@
<string name="screenshot_text">Screenshot</string>
<!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] -->
<string name="open_in_browser_text">Open in browser</string>
+ <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
+ <string name="new_window_text">New Window</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 1304969..6d63971 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -26,105 +26,112 @@
*
* The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
* value and the developer option override state (if applicable).
- **/
+ */
enum class DesktopModeFlags(
// Function called to obtain aconfig flag value.
private val flagFunction: () -> Boolean,
// Whether the flag state should be affected by developer option.
private val shouldOverrideByDevOption: Boolean
) {
- // All desktop mode related flags will be added here
- DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
- CASCADING_WINDOWS(Flags::enableCascadingWindows, true),
- WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true),
- MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
- THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
- QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
- APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
- TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
- SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
- DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
- ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true);
-
- /**
- * Determines state of flag based on the actual flag and desktop mode developer option overrides.
- */
- fun isEnabled(context: Context): Boolean =
- if (!Flags.showDesktopWindowingDevOption() ||
- !shouldOverrideByDevOption ||
- context.contentResolver == null) {
- flagFunction()
- } else {
- val shouldToggleBeEnabledByDefault = DesktopModeStatus.shouldDevOptionBeEnabledByDefault()
- when (getToggleOverride(context)) {
- ToggleOverride.OVERRIDE_UNSET -> flagFunction()
- // When toggle override matches its default state, don't override flags. This helps users
- // reset their feature overrides.
- ToggleOverride.OVERRIDE_OFF ->
- if (shouldToggleBeEnabledByDefault) false else flagFunction()
- ToggleOverride.OVERRIDE_ON -> if (shouldToggleBeEnabledByDefault) flagFunction() else true
- }
- }
-
- private fun getToggleOverride(context: Context): ToggleOverride {
- val override =
- cachedToggleOverride
- ?: run {
- val override = getToggleOverrideFromSystem(context)
- // Cache toggle override the first time we encounter context. Override does not change
- // with context, as context is just used to fetch Settings.Global
- cachedToggleOverride = override
- Log.d(TAG, "Toggle override initialized to: $override")
- override
- }
-
- return override
- }
-
- private fun getToggleOverrideFromSystem(context: Context): ToggleOverride =
- convertToToggleOverrideWithFallback(
- Settings.Global.getInt(
- context.contentResolver,
- Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
- ToggleOverride.OVERRIDE_UNSET.setting),
- ToggleOverride.OVERRIDE_UNSET)
-
- /**
- * Override state of desktop mode developer option toggle.
- *
- * @property setting The integer value that is associated with the developer option toggle
- * override
- */
- enum class ToggleOverride(val setting: Int) {
- /** No override is set. */
- OVERRIDE_UNSET(-1),
- /** Override to off. */
- OVERRIDE_OFF(0),
- /** Override to on. */
- OVERRIDE_ON(1)
- }
-
- companion object {
- private const val TAG = "DesktopModeFlags"
+ // All desktop mode related flags will be added here
+ DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
+ CASCADING_WINDOWS(Flags::enableCascadingWindows, true),
+ WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true),
+ MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
+ THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
+ QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
+ APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
+ TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
+ SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
+ DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
+ ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
+ BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true),
+ EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
+ TASKBAR_RUNNING_APPS(Flags::enableDesktopWindowingTaskbarRunningApps, true);
/**
- * Local cache for toggle override, which is initialized once on its first access. It needs to
- * be refreshed only on reboots as overridden state is expected to take effect on reboots.
+ * Determines state of flag based on the actual flag and desktop mode developer option
+ * overrides.
*/
- private var cachedToggleOverride: ToggleOverride? = null
+ fun isEnabled(context: Context): Boolean =
+ if (!Flags.showDesktopWindowingDevOption() ||
+ !shouldOverrideByDevOption ||
+ context.contentResolver == null) {
+ flagFunction()
+ } else {
+ val shouldToggleBeEnabledByDefault =
+ DesktopModeStatus.shouldDevOptionBeEnabledByDefault()
+ when (getToggleOverride(context)) {
+ ToggleOverride.OVERRIDE_UNSET -> flagFunction()
+ // When toggle override matches its default state, don't override flags. This helps
+ // users reset their feature overrides.
+ ToggleOverride.OVERRIDE_OFF ->
+ if (shouldToggleBeEnabledByDefault) false else flagFunction()
+ ToggleOverride.OVERRIDE_ON ->
+ if (shouldToggleBeEnabledByDefault) flagFunction() else true
+ }
+ }
- private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
+ private fun getToggleOverride(context: Context): ToggleOverride {
+ val override =
+ cachedToggleOverride
+ ?: run {
+ val override = getToggleOverrideFromSystem(context)
+ // Cache toggle override the first time we encounter context. Override does not
+ // change with context, as context is just used to fetch Settings.Global
+ cachedToggleOverride = override
+ Log.d(TAG, "Toggle override initialized to: $override")
+ override
+ }
- @JvmStatic
- fun convertToToggleOverrideWithFallback(
- overrideInt: Int,
- fallbackOverride: ToggleOverride
- ): ToggleOverride {
- return settingToToggleOverrideMap[overrideInt]
- ?: run {
- Log.w(TAG, "Unknown toggleOverride int $overrideInt")
- fallbackOverride
- }
+ return override
}
- }
+
+ private fun getToggleOverrideFromSystem(context: Context): ToggleOverride =
+ convertToToggleOverrideWithFallback(
+ Settings.Global.getInt(
+ context.contentResolver,
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+ ToggleOverride.OVERRIDE_UNSET.setting),
+ ToggleOverride.OVERRIDE_UNSET)
+
+ /**
+ * Override state of desktop mode developer option toggle.
+ *
+ * @property setting The integer value that is associated with the developer option toggle
+ * override
+ */
+ enum class ToggleOverride(val setting: Int) {
+ /** No override is set. */
+ OVERRIDE_UNSET(-1),
+ /** Override to off. */
+ OVERRIDE_OFF(0),
+ /** Override to on. */
+ OVERRIDE_ON(1)
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeFlags"
+
+ /**
+ * Local cache for toggle override, which is initialized once on its first access. It needs
+ * to be refreshed only on reboots as overridden state is expected to take effect on
+ * reboots.
+ */
+ private var cachedToggleOverride: ToggleOverride? = null
+
+ private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
+
+ @JvmStatic
+ fun convertToToggleOverrideWithFallback(
+ overrideInt: Int,
+ fallbackOverride: ToggleOverride
+ ): ToggleOverride {
+ return settingToToggleOverrideMap[overrideInt]
+ ?: run {
+ Log.w(TAG, "Unknown toggleOverride int $overrideInt")
+ fallbackOverride
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 86e0f14..8d30db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -146,11 +146,6 @@
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
- if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
- && mAnimation.getExtensionEdges() != 0) {
- t.setEdgeExtensionEffect(mLeash, mAnimation.getExtensionEdges());
- }
-
mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -170,7 +165,7 @@
if (!cropRect.intersect(mWholeAnimationBounds)) {
// Hide the surface when it is outside of the animation area.
t.setAlpha(mLeash, 0);
- } else if (mAnimation.getExtensionEdges() != 0) {
+ } else if (mAnimation.hasExtension()) {
// Allow the surface to be shown in its original bounds in case we want to use edge
// extensions.
cropRect.union(mContentBounds);
@@ -185,7 +180,6 @@
@CallSuper
void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
onAnimationUpdate(t, mAnimation.getDuration());
- t.setEdgeExtensionEffect(mLeash, /* edge */ 0);
}
final long getDurationHint() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d2cef4b..5696a54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -144,10 +144,8 @@
// ending states.
prepareForJumpCut(info, startTransaction);
} else {
- if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
- addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
- postStartTransactionCallbacks, adapters);
- }
+ addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+ postStartTransactionCallbacks, adapters);
addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
@@ -343,7 +341,7 @@
@NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
final Animation animation = adapter.mAnimation;
- if (animation.getExtensionEdges() == 0) {
+ if (!animation.hasExtension()) {
continue;
}
if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index a9fdea3..f14f419 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,7 +16,12 @@
package com.android.wm.shell.back;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
+import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -30,11 +35,13 @@
import android.content.Context;
import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -48,6 +55,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
import android.window.BackEvent;
@@ -57,6 +65,9 @@
import android.window.IBackAnimationFinishedCallback;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
@@ -66,12 +77,14 @@
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -101,6 +114,7 @@
/** Tracks if an uninterruptible animation is in progress */
private boolean mPostCommitAnimationInProgress = false;
+ private boolean mRealCallbackInvoked = false;
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
@@ -123,6 +137,8 @@
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
private final WindowManager mWindowManager;
+ private final Transitions mTransitions;
+ private final BackTransitionHandler mBackTransitionHandler;
@VisibleForTesting
final Rect mTouchableArea = new Rect();
@@ -190,7 +206,8 @@
Context context,
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
- ShellCommandHandler shellCommandHandler) {
+ ShellCommandHandler shellCommandHandler,
+ Transitions transitions) {
this(
shellInit,
shellController,
@@ -201,7 +218,8 @@
context.getContentResolver(),
backAnimationBackground,
shellBackAnimationRegistry,
- shellCommandHandler);
+ shellCommandHandler,
+ transitions);
}
@VisibleForTesting
@@ -215,7 +233,8 @@
ContentResolver contentResolver,
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
- ShellCommandHandler shellCommandHandler) {
+ ShellCommandHandler shellCommandHandler,
+ Transitions transitions) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -230,6 +249,9 @@
mLatencyTracker = LatencyTracker.getInstance(mContext);
mShellCommandHandler = shellCommandHandler;
mWindowManager = context.getSystemService(WindowManager.class);
+ mTransitions = transitions;
+ mBackTransitionHandler = new BackTransitionHandler();
+ mTransitions.addHandler(mBackTransitionHandler);
updateTouchableArea();
}
@@ -730,7 +752,7 @@
mBackAnimationFinishedCallback = null;
}
- if (mBackNavigationInfo != null) {
+ if (mBackNavigationInfo != null && !mRealCallbackInvoked) {
final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
if (touchTracker.getTriggerBack()) {
dispatchOnBackInvoked(callback);
@@ -738,6 +760,7 @@
tryDispatchOnBackCancelled(callback);
}
}
+ mRealCallbackInvoked = false;
finishBackNavigation(touchTracker.getTriggerBack());
}
@@ -815,14 +838,38 @@
// The next callback should be {@link #onBackAnimationFinished}.
if (mCurrentTracker.getTriggerBack()) {
- // notify gesture finished
- mBackNavigationInfo.onBackGestureFinished(true);
+ if (migratePredictiveBackTransition()) {
+ // notify core gesture is commit
+ if (shouldTriggerCloseTransition()) {
+ mBackTransitionHandler.mCloseTransitionRequested = true;
+ final IOnBackInvokedCallback callback =
+ mBackNavigationInfo.getOnBackInvokedCallback();
+ // invoked client side onBackInvoked
+ dispatchOnBackInvoked(callback);
+ mRealCallbackInvoked = true;
+ }
+ } else {
+ // notify gesture finished
+ mBackNavigationInfo.onBackGestureFinished(true);
+ }
+
+ // start post animation
dispatchOnBackInvoked(mActiveCallback);
} else {
tryDispatchOnBackCancelled(mActiveCallback);
}
}
+ // Close window won't create any transition
+ private boolean shouldTriggerCloseTransition() {
+ if (mBackNavigationInfo == null) {
+ return false;
+ }
+ int type = mBackNavigationInfo.getType();
+ return type == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ || type == BackNavigationInfo.TYPE_CROSS_TASK
+ || type == BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+ }
/**
* Called when the post commit animation is completed or timeout.
* This will trigger the real {@link IOnBackInvokedCallback} behavior.
@@ -857,6 +904,7 @@
"mCurrentBackGestureInfo was null when back animation finished");
}
resetTouchTracker();
+ mBackTransitionHandler.onAnimationFinished();
}
/**
@@ -1016,11 +1064,13 @@
endLatencyTracking();
if (!validateAnimationTargets(apps)) {
Log.e(TAG, "Invalid animation targets!");
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
return;
}
mBackAnimationFinishedCallback = finishedCallback;
mApps = apps;
startSystemAnimation();
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
// Dispatch the first progress after animation start for
// smoothing the initial animation, instead of waiting for next
@@ -1041,6 +1091,7 @@
public void onAnimationCancelled() {
mShellExecutor.execute(
() -> {
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
if (!mShellBackAnimationRegistry.cancel(
mBackNavigationInfo != null
? mBackNavigationInfo.getType()
@@ -1073,4 +1124,249 @@
mQueuedTracker.dump(pw, prefix + " ");
}
+ class BackTransitionHandler implements Transitions.TransitionHandler {
+
+ Runnable mOnAnimationFinishCallback;
+ boolean mCloseTransitionRequested;
+ boolean mOpeningRunning;
+ SurfaceControl.Transaction mFinishOpenTransaction;
+ Transitions.TransitionFinishCallback mFinishOpenTransitionCallback;
+ QueuedTransition mQueuedTransition = null;
+ void onAnimationFinished() {
+ if (!mCloseTransitionRequested) {
+ applyFinishOpenTransition();
+ }
+ if (mOnAnimationFinishCallback != null) {
+ mOnAnimationFinishCallback.run();
+ mOnAnimationFinishCallback = null;
+ }
+ }
+
+ void consumeQueuedTransitionIfNeeded() {
+ if (mQueuedTransition != null) {
+ mQueuedTransition.consume();
+ mQueuedTransition = null;
+ }
+ }
+
+ private void applyFinishOpenTransition() {
+ if (mFinishOpenTransaction != null) {
+ mFinishOpenTransaction.apply();
+ mFinishOpenTransaction = null;
+ }
+ if (mFinishOpenTransitionCallback != null) {
+ mFinishOpenTransitionCallback.onTransitionFinished(null);
+ mFinishOpenTransitionCallback = null;
+ }
+ mOpeningRunning = false;
+ }
+
+ private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ applyFinishOpenTransition();
+ st.apply();
+ ft.apply();
+ finishCallback.onTransitionFinished(null);
+ mCloseTransitionRequested = false;
+ }
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
+ // need to post to ShellExecutor when called.
+ if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
+ && !isGestureBackTransition(info)) {
+ return false;
+ }
+ if (mApps == null || mApps.length == 0) {
+ if (mBackNavigationInfo != null && mShellBackAnimationRegistry
+ .isWaitingAnimation(mBackNavigationInfo.getType())) {
+ // Waiting for animation? Queue update to wait for animation start.
+ consumeQueuedTransitionIfNeeded();
+ mQueuedTransition = new QueuedTransition(info, st, ft, finishCallback);
+ } else {
+ // animation was done, consume directly
+ applyAndFinish(st, ft, finishCallback);
+ }
+ return true;
+ }
+
+ if (handlePrepareTransition(info, st, ft, finishCallback)) {
+ return true;
+ }
+ return handleCloseTransition(info, st, ft, finishCallback);
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (!isGestureBackTransition(info)) {
+ if (mOpeningRunning) {
+ applyFinishOpenTransition();
+ }
+ if (mQueuedTransition != null) {
+ consumeQueuedTransitionIfNeeded();
+ }
+ return;
+ }
+ // Handle the commit transition if this handler is running the open transition.
+ finishCallback.onTransitionFinished(null);
+ if (mCloseTransitionRequested) {
+ if (mApps == null || mApps.length == 0) {
+ if (mQueuedTransition == null) {
+ // animation was done
+ applyFinishOpenTransition();
+ mCloseTransitionRequested = false;
+ } // else, let queued transition to play
+ } else {
+ // we are animating, wait until animation finish
+ mOnAnimationFinishCallback = () -> {
+ applyFinishOpenTransition();
+ mCloseTransitionRequested = false;
+ };
+ }
+ }
+ }
+
+ /**
+ * Check whether this transition is prepare for predictive back animation, which could
+ * happen when core make an activity become visible.
+ */
+ private boolean handlePrepareTransition(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ return false;
+ }
+
+ SurfaceControl openingLeash = null;
+ for (int i = mApps.length - 1; i >= 0; --i) {
+ if (mApps[i].mode == MODE_OPENING) {
+ openingLeash = mApps[i].leash;
+ }
+ }
+ if (openingLeash != null) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (TransitionUtil.isOpeningMode(c.getMode())) {
+ final Point offset = c.getEndRelOffset();
+ st.setPosition(c.getLeash(), offset.x, offset.y);
+ st.reparent(c.getLeash(), openingLeash);
+ }
+ }
+ }
+ st.apply();
+ mFinishOpenTransaction = ft;
+ mFinishOpenTransitionCallback = finishCallback;
+ mOpeningRunning = true;
+ return true;
+ }
+
+ private boolean isGestureBackTransition(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+ && (TransitionUtil.isOpeningMode(c.getMode())
+ || TransitionUtil.isClosingMode(c.getMode()))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * Check whether this transition is triggered from back gesture commitment.
+ * Reparent the transition targets to animation leashes, so the animation won't be broken.
+ */
+ private boolean handleCloseTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ SurfaceControl openingLeash = null;
+ SurfaceControl closingLeash = null;
+ for (int i = mApps.length - 1; i >= 0; --i) {
+ if (mApps[i].mode == MODE_OPENING) {
+ openingLeash = mApps[i].leash;
+ }
+ if (mApps[i].mode == MODE_CLOSING) {
+ closingLeash = mApps[i].leash;
+ }
+ }
+ if (openingLeash != null && closingLeash != null) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (TransitionUtil.isOpeningMode(c.getMode())) {
+ final Point offset = c.getEndRelOffset();
+ st.setPosition(c.getLeash(), offset.x, offset.y);
+ st.reparent(c.getLeash(), openingLeash);
+ } else if (TransitionUtil.isClosingMode(c.getMode())) {
+ st.reparent(c.getLeash(), closingLeash);
+ }
+ }
+ }
+ st.apply();
+ // mApps must exists
+ mOnAnimationFinishCallback = () -> {
+ ft.apply();
+ finishCallback.onTransitionFinished(null);
+ mCloseTransitionRequested = false;
+ };
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(
+ @NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ if (request.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ return new WindowContainerTransaction();
+ }
+ if (TransitionUtil.isClosingType(request.getType()) && mCloseTransitionRequested) {
+ return new WindowContainerTransaction();
+ }
+ return null;
+ }
+
+ class QueuedTransition {
+ final TransitionInfo mInfo;
+ final SurfaceControl.Transaction mSt;
+ final SurfaceControl.Transaction mFt;
+ final Transitions.TransitionFinishCallback mFinishCallback;
+ QueuedTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ mInfo = info;
+ mSt = st;
+ mFt = ft;
+ mFinishCallback = finishCallback;
+ }
+
+ void consume() {
+ // not animating, consume transition directly
+ if (mApps == null || mApps.length == 0) {
+ applyAndFinish(mSt, mFt, mFinishCallback);
+ return;
+ }
+ // we are animating
+ if (handlePrepareTransition(mInfo, mSt, mFt, mFinishCallback)) {
+ // handle merge transition if any
+ if (mCloseTransitionRequested) {
+ mOnAnimationFinishCallback = () -> {
+ applyFinishOpenTransition();
+ mCloseTransitionRequested = false;
+ };
+ }
+ }
+ handleCloseTransition(mInfo, mSt, mFt, mFinishCallback);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
index a6be640..4cd2fd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
@@ -22,7 +22,6 @@
import android.content.pm.PackageManager
import android.os.UserHandle
import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
-import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
@@ -41,7 +40,6 @@
/**
* Returns whether a specific component desires to be launched in multiple instances.
*/
- @VisibleForTesting
fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean {
if (componentName == null || componentName.packageName == null) {
// TODO(b/262864589): Handle empty component case
@@ -60,7 +58,7 @@
if (!supportsMultiInstanceProperty) {
// If not checking the multi-instance properties, then return early
- return false;
+ return false
}
// Check the activity property first
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index bc6ed1f..2e1789a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
@@ -29,13 +28,8 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
-import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.view.Display;
-import android.view.DisplayInfo;
import androidx.annotation.Nullable;
@@ -86,7 +80,7 @@
private final float mFixedRatio;
/** Allows split ratios to calculated dynamically instead of using {@link #mFixedRatio}. */
private final boolean mAllowFlexibleSplitRatios;
- private boolean mIsHorizontalDivision;
+ private final boolean mIsHorizontalDivision;
/** The first target which is still splitting the screen */
private final SnapTarget mFirstSplitTarget;
@@ -98,27 +92,6 @@
private final SnapTarget mDismissEndTarget;
private final SnapTarget mMiddleTarget;
- public static DividerSnapAlgorithm create(Context ctx, Rect insets) {
- DisplayInfo displayInfo = new DisplayInfo();
- ctx.getSystemService(DisplayManager.class).getDisplay(
- Display.DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
- int dividerWindowWidth = ctx.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- int dividerInsets = ctx.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
- return new DividerSnapAlgorithm(ctx.getResources(),
- displayInfo.logicalWidth, displayInfo.logicalHeight,
- dividerWindowWidth - 2 * dividerInsets,
- ctx.getApplicationContext().getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_PORTRAIT,
- insets);
- }
-
- public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
- boolean isHorizontalDivision, Rect insets) {
- this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
- DOCKED_INVALID, false /* minimized */, true /* resizable */);
- }
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isHorizontalDivision, Rect insets, int dockSide) {
@@ -160,29 +133,11 @@
}
/**
- * @return whether it's feasible to enable split screen in the current configuration, i.e. when
- * snapping in the middle both tasks are larger than the minimal task size.
- */
- public boolean isSplitScreenFeasible() {
- int statusBarSize = mInsets.top;
- int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
- int size = mIsHorizontalDivision
- ? mDisplayHeight
- : mDisplayWidth;
- int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
- return availableSpace / 2 >= mMinimalSizeResizableTask;
- }
-
- public SnapTarget calculateSnapTarget(int position, float velocity) {
- return calculateSnapTarget(position, velocity, true /* hardDismiss */);
- }
-
- /**
* @param position the top/left position of the divider
* @param velocity current dragging velocity
- * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
+ * @param hardToDismiss if set, make it a bit harder to get reach the dismiss targets
*/
- public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
+ public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardToDismiss) {
if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
return mDismissStartTarget;
}
@@ -190,7 +145,7 @@
return mDismissEndTarget;
}
if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
- return snap(position, hardDismiss);
+ return snap(position, hardToDismiss);
}
if (velocity < 0) {
return mFirstSplitTarget;
@@ -236,19 +191,6 @@
return 0f;
}
- public SnapTarget getClosestDismissTarget(int position) {
- if (position < mFirstSplitTarget.position) {
- return mDismissStartTarget;
- } else if (position > mLastSplitTarget.position) {
- return mDismissEndTarget;
- } else if (position - mDismissStartTarget.position
- < mDismissEndTarget.position - position) {
- return mDismissStartTarget;
- } else {
- return mDismissEndTarget;
- }
- }
-
public SnapTarget getFirstSplitTarget() {
return mFirstSplitTarget;
}
@@ -411,22 +353,6 @@
return mMiddleTarget;
}
- public SnapTarget getNextTarget(SnapTarget snapTarget) {
- int index = mTargets.indexOf(snapTarget);
- if (index != -1 && index < mTargets.size() - 1) {
- return mTargets.get(index + 1);
- }
- return snapTarget;
- }
-
- public SnapTarget getPreviousTarget(SnapTarget snapTarget) {
- int index = mTargets.indexOf(snapTarget);
- if (index != -1 && index > 0) {
- return mTargets.get(index - 1);
- }
- return snapTarget;
- }
-
/**
* @return whether or not there are more than 1 split targets that do not include the two
* dismiss targets, used in deciding to display the middle target for accessibility
@@ -451,26 +377,6 @@
}
/**
- * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
- * if {@param increment} is negative and moves right otherwise.
- */
- public SnapTarget cycleNonDismissTarget(SnapTarget snapTarget, int increment) {
- int index = mTargets.indexOf(snapTarget);
- if (index != -1) {
- SnapTarget newTarget = mTargets.get((index + mTargets.size() + increment)
- % mTargets.size());
- if (newTarget == mDismissStartTarget) {
- return mLastSplitTarget;
- } else if (newTarget == mDismissEndTarget) {
- return mFirstSplitTarget;
- } else {
- return newTarget;
- }
- }
- return snapTarget;
- }
-
- /**
* Represents a snap target for the divider.
*/
public static class SnapTarget {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2dc6382..717a414 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -375,7 +375,8 @@
@ShellBackgroundThread Handler backgroundHandler,
BackAnimationBackground backAnimationBackground,
Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
- ShellCommandHandler shellCommandHandler) {
+ ShellCommandHandler shellCommandHandler,
+ Transitions transitions) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -387,7 +388,8 @@
context,
backAnimationBackground,
animations,
- shellCommandHandler));
+ shellCommandHandler,
+ transitions));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index da1af0d..9cfbde4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -226,7 +226,8 @@
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor,
- AppToWebGenericLinksParser genericLinksParser) {
+ AppToWebGenericLinksParser genericLinksParser,
+ MultiInstanceHelper multiInstanceHelper) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -246,7 +247,8 @@
desktopTasksController,
rootTaskDisplayAreaOrganizer,
interactionJankMonitor,
- genericLinksParser);
+ genericLinksParser,
+ multiInstanceHelper);
}
return new CaptionWindowDecorViewModel(
context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index 1a6ca0e..eca3c1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -63,8 +63,7 @@
pw.println("Error: task id should be an integer")
return false
}
-
- return controller.moveToDesktop(taskId, transitionSource = UNKNOWN)
+ return controller.moveTaskToDesktop(taskId, transitionSource = UNKNOWN)
}
private fun runMoveToNextDisplay(args: Array<String>, pw: PrintWriter): Boolean {
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 5f838d3..b71cd41 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
@@ -54,7 +54,6 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -99,6 +98,7 @@
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import com.android.wm.shell.windowdecor.extension.isFullscreen
+import com.android.wm.shell.windowdecor.extension.isMultiWindow
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
@@ -256,81 +256,82 @@
/** Returns true if any tasks are visible in Desktop Mode. */
fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
- /** Enter desktop by using the focused task in given `displayId` */
+ /** Moves focused task to desktop mode for given [displayId]. */
fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) {
- val allFocusedTasks =
- shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo ->
- taskInfo.isFocused &&
- (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
- taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
- taskInfo.activityType != ACTIVITY_TYPE_HOME
- }
- if (allFocusedTasks.isNotEmpty()) {
- when (allFocusedTasks.size) {
- 2 -> {
- // Split-screen case where there are two focused tasks, then we find the child
- // task to move to desktop.
- val splitFocusedTask =
- if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) {
- allFocusedTasks[1]
- } else {
- allFocusedTasks[0]
- }
- moveToDesktop(splitFocusedTask, transitionSource = transitionSource)
- }
- 1 -> {
- // Fullscreen case where we move the current focused task.
- moveToDesktop(allFocusedTasks[0].taskId, transitionSource = transitionSource)
- }
- else -> logW("Cannot enter desktop, expected < 3 focused tasks, found %d",
- allFocusedTasks.size)
- }
+ val allFocusedTasks = getAllFocusedTasks(displayId)
+ when (allFocusedTasks.size) {
+ 0 -> return
+ // Full screen case
+ 1 -> moveRunningTaskToDesktop(
+ allFocusedTasks.single(), transitionSource = transitionSource)
+ // Split-screen case where there are two focused tasks, then we find the child
+ // task to move to desktop.
+ 2 -> moveRunningTaskToDesktop(
+ getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]),
+ transitionSource = transitionSource)
+ else -> logW(
+ "DesktopTasksController: Cannot enter desktop, expected less " +
+ "than 3 focused tasks but found %d", allFocusedTasks.size)
}
}
- /** Move a task with given `taskId` to desktop */
- fun moveToDesktop(
+ /**
+ * Returns all focused tasks in full screen or split screen mode in [displayId] when
+ * it is not the home activity.
+ */
+ private fun getAllFocusedTasks(displayId: Int): List<RunningTaskInfo> =
+ shellTaskOrganizer.getRunningTasks(displayId).filter {
+ it.isFocused &&
+ (it.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ it.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+ it.activityType != ACTIVITY_TYPE_HOME
+ }
+
+ /** Returns child task from two focused tasks in split screen mode. */
+ private fun getSplitFocusedTask(task1: RunningTaskInfo, task2: RunningTaskInfo) =
+ if (task1.taskId == task2.parentTaskId) task2 else task1
+
+ /** Moves task to desktop mode if task is running, else launches it in desktop mode. */
+ fun moveTaskToDesktop(
taskId: Int,
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
): Boolean {
- shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
- moveToDesktop(it, wct, transitionSource)
+ val runningTask = shellTaskOrganizer.getRunningTaskInfo(taskId)
+ if (runningTask == null) {
+ return moveBackgroundTaskToDesktop(taskId, wct, transitionSource)
}
- ?: moveToDesktopFromNonRunningTask(taskId, wct, transitionSource)
+ moveRunningTaskToDesktop(runningTask, wct, transitionSource)
return true
}
- private fun moveToDesktopFromNonRunningTask(
- taskId: Int,
- wct: WindowContainerTransaction,
- transitionSource: DesktopModeTransitionSource,
+ private fun moveBackgroundTaskToDesktop(
+ taskId: Int,
+ wct: WindowContainerTransaction,
+ transitionSource: DesktopModeTransitionSource,
): Boolean {
- recentTasksController?.findTaskInBackground(taskId)?.let {
- logV("moveToDesktopFromNonRunningTask with taskId=%d, displayId=%d", taskId)
- // TODO(342378842): Instead of using default display, support multiple displays
- val taskToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId)
- addMoveToDesktopChangesNonRunningTask(wct, taskId)
- // TODO(343149901): Add DPI changes for task launch
- val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
- addPendingMinimizeTransition(transition, taskToMinimize)
- return true
+ if (recentTasksController?.findTaskInBackground(taskId) == null) {
+ logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
+ return false
}
- ?: return false
+ logV("moveBackgroundTaskToDesktop with taskId=%d, displayId=%d", taskId)
+ // TODO(342378842): Instead of using default display, support multiple displays
+ val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
+ DEFAULT_DISPLAY, wct, taskId)
+ wct.startTask(
+ taskId,
+ ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ }.toBundle(),
+ )
+ // TODO(343149901): Add DPI changes for task launch
+ val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
+ addPendingMinimizeTransition(transition, taskToMinimize)
+ return true
}
- private fun addMoveToDesktopChangesNonRunningTask(
- wct: WindowContainerTransaction,
- taskId: Int
- ) {
- val options = ActivityOptions.makeBasic()
- options.launchWindowingMode = WINDOWING_MODE_FREEFORM
- wct.startTask(taskId, options.toBundle())
- }
-
- /** Move a task to desktop */
- fun moveToDesktop(
+ /** Moves a running task to desktop. */
+ fun moveRunningTaskToDesktop(
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
@@ -340,7 +341,7 @@
logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId)
return
}
- logV("moveToDesktop taskId=%d", task.taskId)
+ logV("moveRunningTaskToDesktop taskId=%d", task.taskId)
exitSplitIfApplicable(wct, task)
// Bring other apps to front first
val taskToMinimize =
@@ -818,8 +819,12 @@
// Check if we should skip handling this transition
var reason = ""
val triggerTask = request.triggerTask
+ var shouldHandleMidRecentsFreeformLaunch =
+ recentsAnimationRunning && isFreeformRelaunch(triggerTask, request)
val shouldHandleRequest =
when {
+ // Handle freeform relaunch during recents animation
+ shouldHandleMidRecentsFreeformLaunch -> true
recentsAnimationRunning -> {
reason = "recents animation is running"
false
@@ -859,6 +864,8 @@
val result =
triggerTask?.let { task ->
when {
+ // Check if freeform task launch during recents should be handled
+ shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
// Check if the closing task needs to be handled
TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
// Check if the top task shouldn't be allowed to enter desktop mode
@@ -892,6 +899,12 @@
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
+ /** Returns whether an existing desktop task is being relaunched in freeform or not. */
+ private fun isFreeformRelaunch(triggerTask: RunningTaskInfo?, request: TransitionRequestInfo) =
+ (triggerTask != null && triggerTask.windowingMode == WINDOWING_MODE_FREEFORM
+ && TransitionUtil.isOpeningType(request.type)
+ && taskRepository.isActiveTask(triggerTask.taskId))
+
private fun isIncompatibleTask(task: TaskInfo) =
DesktopModeFlags.MODALS_POLICY.isEnabled(context)
&& isTopActivityExemptFromDesktopWindowing(context, task)
@@ -902,6 +915,75 @@
request.triggerTask != null
}
+ fun openNewWindow(
+ taskInfo: RunningTaskInfo
+ ) {
+ // TODO(b/337915660): Add a transition handler for these; animations
+ // need updates in some cases.
+ val newTaskWindowingMode = when {
+ taskInfo.isFreeform -> {
+ WINDOWING_MODE_FREEFORM
+ }
+ taskInfo.isFullscreen || taskInfo.isMultiWindow -> {
+ WINDOWING_MODE_MULTI_WINDOW
+ }
+ else -> {
+ error("Invalid windowing mode: ${taskInfo.windowingMode}")
+ }
+ }
+
+ val baseActivity = taskInfo.baseActivity ?: return
+ val fillIn: Intent = context.packageManager
+ .getLaunchIntentForPackage(
+ baseActivity.packageName
+ ) ?: return
+ fillIn
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ val options =
+ ActivityOptions.makeBasic().apply {
+ launchWindowingMode = newTaskWindowingMode
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ }
+ val launchIntent = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ fillIn,
+ PendingIntent.FLAG_IMMUTABLE
+ )
+ when (newTaskWindowingMode) {
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ val splitPosition = splitScreenController.determineNewInstancePosition(taskInfo)
+ splitScreenController.startIntent(
+ launchIntent, context.userId, fillIn, splitPosition,
+ options.toBundle(), null /* hideTaskToken */
+ )
+ }
+ WINDOWING_MODE_FREEFORM -> {
+ // TODO(b/336289597): This currently does not respect the desktop window limit.
+ val wct = WindowContainerTransaction()
+ wct.sendPendingIntent(launchIntent, fillIn, options.toBundle())
+ transitions.startTransition(TRANSIT_OPEN, wct, null)
+ }
+ }
+ }
+
+ /**
+ * Handles the case where a freeform task is launched from recents.
+ *
+ * This is a special case where we want to launch the task in fullscreen instead of freeform.
+ */
+ private fun handleMidRecentsFreeformTaskLaunch(
+ task: RunningTaskInfo
+ ): WindowContainerTransaction? {
+ logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch")
+ val wct = WindowContainerTransaction()
+ addMoveToFullscreenChanges(wct, task)
+ wct.reorder(task.token, true)
+ return wct
+ }
+
private fun handleFreeformTaskLaunch(
task: RunningTaskInfo,
transition: IBinder
@@ -982,7 +1064,7 @@
}
taskRepository.addClosingTask(task.displayId, task.taskId)
// If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
- if (Flags.enableDesktopWindowingBackNavigation() &&
+ if (DesktopModeFlags.BACK_NAVIGATION.isEnabled(context) &&
taskRepository.isVisibleTask(task.taskId)) {
wct.removeTask(task.token)
}
@@ -1488,7 +1570,8 @@
}
override fun hideStashedDesktopApps(displayId: Int) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: hideStashedDesktopApps is deprecated")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: hideStashedDesktopApps is deprecated")
}
override fun getVisibleTaskCount(displayId: Int): Int {
@@ -1519,8 +1602,8 @@
}
override fun moveToDesktop(taskId: Int, transitionSource: DesktopModeTransitionSource) {
- executeRemoteCallWithTaskPermission(controller, "moveToDesktop") { c ->
- c.moveToDesktop(taskId, transitionSource = transitionSource)
+ executeRemoteCallWithTaskPermission(controller, "moveTaskToDesktop") { c ->
+ c.moveTaskToDesktop(taskId, transitionSource = transitionSource)
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 9e79eddb0..5221a45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -29,10 +29,10 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
-import com.android.internal.protolog.ProtoLog
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -220,16 +220,18 @@
startCancelAnimation()
} else if (
state.draggedTaskChange != null &&
- (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
+ (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
cancelState == CancelState.CANCEL_SPLIT_RIGHT)
- ) {
+ ) {
// We have a valid dragged task, but the animation will be handled by
// SplitScreenController; request the transition here.
- @SplitPosition val splitPosition = if (cancelState == CancelState.CANCEL_SPLIT_LEFT) {
- SPLIT_POSITION_TOP_OR_LEFT
- } else {
- SPLIT_POSITION_BOTTOM_OR_RIGHT
- }
+ @SplitPosition
+ val splitPosition =
+ if (cancelState == CancelState.CANCEL_SPLIT_LEFT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
val wct = WindowContainerTransaction()
restoreWindowOrder(wct, state)
state.startTransitionFinishTransaction?.apply()
@@ -252,20 +254,20 @@
wct: WindowContainerTransaction
) {
val state = requireTransitionState()
- val taskInfo = state.draggedTaskChange?.taskInfo
- ?: error("Expected non-null taskInfo")
+ val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
val taskBounds = Rect(taskInfo.configuration.windowConfiguration.bounds)
val taskScale = state.dragAnimator.scale
val scaledWidth = taskBounds.width() * taskScale
val scaledHeight = taskBounds.height() * taskScale
val dragPosition = PointF(state.dragAnimator.position)
state.dragAnimator.cancelAnimator()
- val animatedTaskBounds = Rect(
- dragPosition.x.toInt(),
- dragPosition.y.toInt(),
- (dragPosition.x + scaledWidth).toInt(),
- (dragPosition.y + scaledHeight).toInt()
- )
+ val animatedTaskBounds =
+ Rect(
+ dragPosition.x.toInt(),
+ dragPosition.y.toInt(),
+ (dragPosition.x + scaledWidth).toInt(),
+ (dragPosition.y + scaledHeight).toInt()
+ )
requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
}
@@ -286,12 +288,7 @@
}
wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
wct.setDensityDpi(taskInfo.token, context.resources.displayMetrics.densityDpi)
- splitScreenController.requestEnterSplitSelect(
- taskInfo,
- wct,
- splitPosition,
- taskBounds
- )
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds)
}
override fun startAnimation(
@@ -390,7 +387,7 @@
// occurred.
if (
change.taskInfo?.taskId == state.draggedTaskId &&
- state.cancelState != CancelState.STANDARD_CANCEL
+ state.cancelState != CancelState.STANDARD_CANCEL
) {
// We need access to the dragged task's change in both non-cancel and split
// cancel cases.
@@ -398,8 +395,8 @@
}
if (
change.taskInfo?.taskId == state.draggedTaskId &&
- state.cancelState == CancelState.NO_CANCEL
- ) {
+ state.cancelState == CancelState.NO_CANCEL
+ ) {
taskDisplayAreaOrganizer.reparentToDisplayArea(
change.endDisplayId,
change.leash,
@@ -432,19 +429,20 @@
startCancelDragToDesktopTransition()
} else if (
state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
- state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
- ){
+ state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
+ ) {
// Cancel-early case for split-cancel. The state was flagged already as a cancel for
// requesting split select. Similar to the above, this can happen due to quick fling
// gestures. We can simply request split here without needing to calculate animated
// task bounds as the task has not shrunk at all.
- val splitPosition = if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) {
- SPLIT_POSITION_TOP_OR_LEFT
- } else {
- SPLIT_POSITION_BOTTOM_OR_RIGHT
- }
- val taskInfo = state.draggedTaskChange?.taskInfo
- ?: error("Expected non-null task info.")
+ val splitPosition =
+ if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
+ val taskInfo =
+ state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.")
val wct = WindowContainerTransaction()
restoreWindowOrder(wct)
state.startTransitionFinishTransaction?.apply()
@@ -463,8 +461,10 @@
) {
val state = requireTransitionState()
// We don't want to merge the split select animation if that's what we requested.
- if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
- state.cancelState == CancelState.CANCEL_SPLIT_RIGHT) {
+ if (
+ state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
+ state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
+ ) {
clearState()
return
}
@@ -574,7 +574,8 @@
startTransitionFinishCb.onTransitionFinished(null /* null */)
clearState()
interactionJankMonitor.end(
- CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+ )
}
}
)
@@ -673,9 +674,7 @@
val wct = WindowContainerTransaction()
restoreWindowOrder(wct, state)
state.cancelTransitionToken =
- transitions.startTransition(
- TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this
- )
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this)
}
private fun restoreWindowOrder(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 54f908b..da7e03f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -19,7 +19,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.pm.PackageManager.FEATURE_PC;
-import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
@@ -365,7 +364,7 @@
private boolean shouldEnableRunningTasksForDesktopMode() {
return mPcFeatureEnabled
|| (DesktopModeStatus.canEnterDesktopMode(mContext)
- && enableDesktopWindowingTaskbarRunningApps());
+ && DesktopModeFlags.TASKBAR_RUNNING_APPS.isEnabled(mContext));
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 234b4d0..ad3f4f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -19,12 +19,14 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
@@ -775,6 +777,20 @@
// Don't consider order-only & non-leaf changes as changing apps.
if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
hasChangingApp = true;
+ // Check if the changing app is moving to top and fullscreen. This handles
+ // the case where we moved from desktop to recents and launching a desktop
+ // task in fullscreen.
+ if ((change.getFlags() & FLAG_MOVED_TO_TOP) != 0
+ && taskInfo != null
+ && taskInfo.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN) {
+ if (openingTasks == null) {
+ openingTasks = new ArrayList<>();
+ openingTaskIsLeafs = new IntArray();
+ }
+ openingTasks.add(change);
+ openingTaskIsLeafs.add(1);
+ }
} else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
&& !isRecentsTask ) {
// Unless it is a 3p launcher. This means that the 3p launcher was already
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index b857556..c4af148 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -431,6 +432,20 @@
mStageCoordinator.clearSplitPairedInRecents(reason);
}
+ /**
+ * Determines which split position a new instance of a task should take.
+ * @param callingTask The task requesting a new instance.
+ * @return the split position of the new instance
+ */
+ public int determineNewInstancePosition(@NonNull ActivityManager.RunningTaskInfo callingTask) {
+ if (callingTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ || getSplitPosition(callingTask.taskId) == SPLIT_POSITION_TOP_OR_LEFT) {
+ return SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ } else {
+ return SPLIT_POSITION_TOP_OR_LEFT;
+ }
+ }
+
public void enterSplitScreen(int taskId, boolean leftOrTop) {
enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index d8c8c60..7784784 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -502,8 +502,7 @@
backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
backgroundColorForTransition);
- if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader() && !isTask
- && a.getExtensionEdges() != 0) {
+ if (!isTask && a.hasExtension()) {
if (!TransitionUtil.isOpeningType(mode)) {
// Can screenshot now (before startTransaction is applied)
edgeExtendWindow(change, a, startTransaction, finishTransaction);
@@ -513,8 +512,6 @@
postStartTransactionCallbacks
.add(t -> edgeExtendWindow(change, a, t, finishTransaction));
}
- } else if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
- finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
}
final Rect clipRect = TransitionUtil.isClosingType(mode)
@@ -1011,10 +1008,6 @@
Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
tmpTransformation.clear();
anim.getTransformation(time, tmpTransformation);
- if (anim.getExtensionEdges() != 0
- && com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
- t.setEdgeExtensionEffect(leash, anim.getExtensionEdges());
- }
if (position != null) {
tmpTransformation.getMatrix().postTranslate(position.x, position.y);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 874cca5..c850ff8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -38,9 +38,9 @@
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.window.flags.Flags.enforceShellThreadModel;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
+import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -822,7 +822,8 @@
}
// The change has already animated by back gesture, don't need to play transition
// animation on it.
- if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ if (!migratePredictiveBackTransition()
+ && change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
info.getChanges().remove(i);
}
}
@@ -936,9 +937,7 @@
}
private void onMerged(@NonNull IBinder playingToken, @NonNull IBinder mergedToken) {
- if (enforceShellThreadModel()) {
- mMainExecutor.assertCurrentThread();
- }
+ mMainExecutor.assertCurrentThread();
ActiveTransition playing = mKnownTransitions.get(playingToken);
if (playing == null) {
@@ -1087,9 +1086,7 @@
}
private void onFinish(IBinder token, @Nullable WindowContainerTransaction wct) {
- if (enforceShellThreadModel()) {
- mMainExecutor.assertCurrentThread();
- }
+ mMainExecutor.assertCurrentThread();
final ActiveTransition active = mKnownTransitions.get(token);
if (active == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index de514f6..9de0651 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -279,7 +279,7 @@
final Resources res = mResult.mRootView.getResources();
mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
- new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+ new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(mContext, res),
getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 7c8f205..5a24198 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -30,7 +30,6 @@
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_OUTSIDE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
@@ -93,6 +92,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
@@ -117,6 +117,8 @@
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Supplier;
@@ -145,6 +147,7 @@
private final DesktopTasksController mDesktopTasksController;
private final InputManager mInputManager;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private final MultiInstanceHelper mMultiInstanceHelper;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -204,7 +207,8 @@
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor,
- AppToWebGenericLinksParser genericLinksParser
+ AppToWebGenericLinksParser genericLinksParser,
+ MultiInstanceHelper multiInstanceHelper
) {
this(
context,
@@ -223,6 +227,7 @@
transitions,
desktopTasksController,
genericLinksParser,
+ multiInstanceHelper,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
@@ -249,6 +254,7 @@
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
AppToWebGenericLinksParser genericLinksParser,
+ MultiInstanceHelper multiInstanceHelper,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -268,6 +274,7 @@
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopTasksController = desktopTasksController.get();
+ mMultiInstanceHelper = multiInstanceHelper;
mShellCommandHandler = shellCommandHandler;
mWindowManager = windowManager;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
@@ -412,13 +419,13 @@
mWindowDecorByTaskId.remove(taskInfo.taskId);
}
- private void onMaximizeOrRestore(int taskId, String tag) {
+ private void onMaximizeOrRestore(int taskId, String source) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
return;
}
mInteractionJankMonitor.begin(
- decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, tag);
+ decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
@@ -435,10 +442,14 @@
decoration.closeMaximizeMenu();
}
- private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) {
+ private void onOpenInBrowser(int taskId, @NonNull Uri uri) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
openInBrowser(uri);
- decor.closeHandleMenu();
- decor.closeMaximizeMenu();
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
}
private void openInBrowser(Uri uri) {
@@ -448,6 +459,57 @@
mContext.startActivity(intent);
}
+ private void onToDesktop(int taskId, DesktopModeTransitionSource source) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
+ // App sometimes draws before the insets from WindowDecoration#relayout have
+ // been added, so they must be added here
+ decoration.addCaptionInset(wct);
+ mDesktopTasksController.moveTaskToDesktop(taskId, wct, source);
+ decoration.closeHandleMenu();
+ }
+
+ private void onToFullscreen(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ if (isTaskInSplitScreen(taskId)) {
+ mSplitScreenController.moveTaskToFullscreen(taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE);
+ } else {
+ mDesktopTasksController.moveToFullscreen(taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
+ }
+ }
+
+ private void onToSplitScreen(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ // When the app enters split-select, the handle will no longer be visible, meaning
+ // we shouldn't receive input for it any longer.
+ decoration.disposeStatusBarInputLayer();
+ mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
+ }
+
+ private void onNewWindow(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ mDesktopTasksController.openNewWindow(decoration.mTaskInfo);
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -505,37 +567,6 @@
moveTaskToFront(decoration.mTaskInfo);
decoration.createHandleMenu(mSplitScreenController);
}
- } else if (id == R.id.desktop_button) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // App sometimes draws before the insets from WindowDecoration#relayout have
- // been added, so they must be added here
- mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
- CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
- mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- mDesktopTasksController.moveToDesktop(mTaskId, wct,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
- decoration.closeHandleMenu();
- } else if (id == R.id.fullscreen_button) {
- decoration.closeHandleMenu();
- if (isTaskInSplitScreen(mTaskId)) {
- mSplitScreenController.moveTaskToFullscreen(mTaskId,
- SplitScreenController.EXIT_REASON_DESKTOP_MODE);
- } else {
- mDesktopTasksController.moveToFullscreen(mTaskId,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
- }
- } else if (id == R.id.split_screen_button) {
- decoration.closeHandleMenu();
- // When the app enters split-select, the handle will no longer be visible, meaning
- // we shouldn't receive input for it any longer.
- decoration.disposeStatusBarInputLayer();
- mDesktopTasksController.requestSplit(decoration.mTaskInfo);
- } else if (id == R.id.open_in_browser_button) {
- // TODO(b/346441962): let the decoration handle the click gesture and only call back
- // to the ViewModel via #setOpenInBrowserClickListener
- decoration.onOpenInBrowserClick();
- } else if (id == R.id.collapse_menu_button) {
- decoration.closeHandleMenu();
} else if (id == R.id.maximize_window) {
// TODO(b/346441962): move click detection logic into the decor's
// {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report
@@ -550,16 +581,6 @@
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- if (e.getActionMasked() == ACTION_OUTSIDE) {
- if (id == R.id.handle_menu) {
- // Close handle menu on outside touch if menu is directly touchable; if not,
- // it will be handled by handleEventOutsideCaption.
- if (decoration.mTaskInfo.isFreeform()
- || Flags.enableAdditionalWindowsAboveStatusBar()) {
- decoration.closeHandleMenu();
- }
- }
- }
if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
mTouchscreenInUse = e.getActionMasked() != ACTION_UP
&& e.getActionMasked() != ACTION_CANCEL;
@@ -1161,7 +1182,8 @@
mMainChoreographer,
mSyncQueue,
mRootTaskDisplayAreaOrganizer,
- mGenericLinksParser);
+ mGenericLinksParser,
+ mMultiInstanceHelper);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final DragPositioningCallback dragPositioningCallback;
@@ -1181,14 +1203,36 @@
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
- windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore);
- windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> {
- onSnapResize(taskId, true /* isLeft */);
+ windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
+ onMaximizeOrRestore(taskInfo.taskId, "maximize_menu");
+ return Unit.INSTANCE;
});
- windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
- onSnapResize(taskId, false /* isLeft */);
+ windowDecoration.setOnLeftSnapClickListener(() -> {
+ onSnapResize(taskInfo.taskId, true /* isLeft */);
+ return Unit.INSTANCE;
});
- windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser);
+ windowDecoration.setOnRightSnapClickListener(() -> {
+ onSnapResize(taskInfo.taskId, false /* isLeft */);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
+ onToDesktop(taskInfo.taskId, desktopModeTransitionSource);
+ });
+ windowDecoration.setOnToFullscreenClickListener(() -> {
+ onToFullscreen(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOnToSplitScreenClickListener(() -> {
+ onToSplitScreen(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOpenInBrowserClickListener((uri) -> {
+ onOpenInBrowser(taskInfo.taskId, uri);
+ });
+ windowDecoration.setOnNewWindowClickListener(() -> {
+ onNewWindow(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index e82990f..24fb971 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -26,6 +26,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -74,20 +75,23 @@
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder;
import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -114,9 +118,13 @@
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
- private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener;
- private OnTaskActionClickListener mOnLeftSnapClickListener;
- private OnTaskActionClickListener mOnRightSnapClickListener;
+ private Function0<Unit> mOnMaximizeOrRestoreClickListener;
+ private Function0<Unit> mOnLeftSnapClickListener;
+ private Function0<Unit> mOnRightSnapClickListener;
+ private Consumer<DesktopModeTransitionSource> mOnToDesktopClickListener;
+ private Function0<Unit> mOnToFullscreenClickListener;
+ private Function0<Unit> mOnToSplitscreenClickListener;
+ private Function0<Unit> mOnNewWindowClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -139,7 +147,7 @@
private CharSequence mAppName;
private CapturedLink mCapturedLink;
private Uri mGenericLink;
- private OpenInBrowserClickListener mOpenInBrowserClickListener;
+ private Consumer<Uri> mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
@@ -157,6 +165,7 @@
// to cancel the close.
private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
+ private final MultiInstanceHelper mMultiInstanceHelper;
DesktopModeWindowDecoration(
Context context,
@@ -171,13 +180,15 @@
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- AppToWebGenericLinksParser genericLinksParser) {
+ AppToWebGenericLinksParser genericLinksParser,
+ MultiInstanceHelper multiInstanceHelper) {
this (context, userContext, displayController, splitScreenController, taskOrganizer,
taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new,
SurfaceControl.Transaction::new, WindowContainerTransaction::new,
SurfaceControl::new, new SurfaceControlViewHostFactory() {},
- DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE);
+ DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE,
+ multiInstanceHelper);
}
DesktopModeWindowDecoration(
@@ -200,7 +211,8 @@
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
MaximizeMenuFactory maximizeMenuFactory,
- HandleMenuFactory handleMenuFactory) {
+ HandleMenuFactory handleMenuFactory,
+ MultiInstanceHelper multiInstanceHelper) {
super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -214,6 +226,7 @@
mGenericLinksParser = genericLinksParser;
mMaximizeMenuFactory = maximizeMenuFactory;
mHandleMenuFactory = handleMenuFactory;
+ mMultiInstanceHelper = multiInstanceHelper;
}
/**
@@ -222,24 +235,42 @@
* TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of
* having the ViewModel deal with parsing motion events.
*/
- void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) {
+ void setOnMaximizeOrRestoreClickListener(Function0<Unit> listener) {
mOnMaximizeOrRestoreClickListener = listener;
}
- /**
- * Register a listener to be called back when one of the tasks snap-left action is triggered.
- */
- void setOnLeftSnapClickListener(OnTaskActionClickListener listener) {
+ /** Registers a listener to be called when the decoration's snap-left action is triggered.*/
+ void setOnLeftSnapClickListener(Function0<Unit> listener) {
mOnLeftSnapClickListener = listener;
}
- /**
- * Register a listener to be called back when one of the tasks' snap-right action is triggered.
- */
- void setOnRightSnapClickListener(OnTaskActionClickListener listener) {
+ /** Registers a listener to be called when the decoration's snap-right action is triggered. */
+ void setOnRightSnapClickListener(Function0<Unit> listener) {
mOnRightSnapClickListener = listener;
}
+ /** Registers a listener to be called when the decoration's to-desktop action is triggered. */
+ void setOnToDesktopClickListener(Consumer<DesktopModeTransitionSource> listener) {
+ mOnToDesktopClickListener = listener;
+ }
+
+ /**
+ * Registers a listener to be called when the decoration's to-fullscreen action is triggered.
+ */
+ void setOnToFullscreenClickListener(Function0<Unit> listener) {
+ mOnToFullscreenClickListener = listener;
+ }
+
+ /** Registers a listener to be called when the decoration's to-split action is triggered. */
+ void setOnToSplitScreenClickListener(Function0<Unit> listener) {
+ mOnToSplitscreenClickListener = listener;
+ }
+
+ /** Registers a listener to be called when the decoration's new window action is triggered. */
+ void setOnNewWindowClickListener(Function0<Unit> listener) {
+ mOnNewWindowClickListener = listener;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
@@ -264,7 +295,7 @@
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
- void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) {
+ void setOpenInBrowserClickListener(Consumer<Uri> listener) {
mOpenInBrowserClickListener = listener;
}
@@ -439,14 +470,6 @@
}
}
- void onOpenInBrowserClick() {
- if (mOpenInBrowserClickListener == null || mHandleMenu == null) {
- return;
- }
- mOpenInBrowserClickListener.onClick(this, mHandleMenu.getOpenInBrowserLink());
- onCapturedLinkExpired();
- }
-
@Nullable
private Uri getBrowserLink() {
// If the captured link is available and has not expired, return the captured link.
@@ -492,8 +515,9 @@
final Resources res = mResult.mRootView.getResources();
if (mDragResizeListener.setGeometry(
new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
- new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
- getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
+ new Size(mResult.mWidth, mResult.mHeight),
+ getResizeEdgeHandleSize(mContext, res), getFineResizeCornerSize(res),
+ getLargeResizeCornerSize(res)), touchSlop)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
updateExclusionRegion();
}
@@ -958,20 +982,41 @@
mHandleMenu = mHandleMenuFactory.create(
this,
mRelayoutParams.mLayoutResId,
- mOnCaptionButtonClickListener,
- mOnCaptionTouchListener,
mAppIconBitmap,
mAppName,
- mDisplayController,
splitScreenController,
DesktopModeStatus.canEnterDesktopMode(mContext),
+ Flags.enableDesktopWindowingMultiInstanceFeatures()
+ && mMultiInstanceHelper
+ .supportsMultiInstanceSplit(mTaskInfo.baseActivity),
getBrowserLink(),
mResult.mCaptionWidth,
mResult.mCaptionHeight,
mResult.mCaptionX
);
mWindowDecorViewHolder.onHandleMenuOpened();
- mHandleMenu.show();
+ mHandleMenu.show(
+ /* onToDesktopClickListener= */ () -> {
+ mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
+ return Unit.INSTANCE;
+ },
+ /* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
+ /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
+ /* onNewWindowClickListener= */ mOnNewWindowClickListener,
+ /* openInBrowserClickListener= */ (uri) -> {
+ mOpenInBrowserClickListener.accept(uri);
+ onCapturedLinkExpired();
+ return Unit.INSTANCE;
+ },
+ /* onCloseMenuClickListener= */ () -> {
+ closeHandleMenu();
+ return Unit.INSTANCE;
+ },
+ /* onOutsideTouchListener= */ () -> {
+ closeHandleMenu();
+ return Unit.INSTANCE;
+ }
+ );
}
private void updateGenericLink() {
@@ -1273,7 +1318,8 @@
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- AppToWebGenericLinksParser genericLinksParser) {
+ AppToWebGenericLinksParser genericLinksParser,
+ MultiInstanceHelper multiInstanceHelper) {
return new DesktopModeWindowDecoration(
context,
userContext,
@@ -1287,7 +1333,8 @@
choreographer,
syncQueue,
rootTaskDisplayAreaOrganizer,
- genericLinksParser);
+ genericLinksParser,
+ multiInstanceHelper);
}
}
@@ -1308,14 +1355,6 @@
}
}
-
- /** Listener for the handle menu's "Open in browser" button */
- interface OpenInBrowserClickListener {
-
- /** Inform the implementing class that the "Open in browser" button has been clicked */
- void onClick(DesktopModeWindowDecoration decoration, Uri uri);
- }
-
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 32df8b3..1729548 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -81,6 +81,7 @@
private final InputChannel mInputChannel;
private final TaskResizeInputEventReceiver mInputEventReceiver;
+ private final Context mContext;
private final SurfaceControl mInputSinkSurface;
private final IBinder mSinkClientToken;
private final InputChannel mSinkInputChannel;
@@ -97,6 +98,7 @@
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
DisplayController displayController) {
+ mContext = context;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
mDecorationSurface = decorationSurface;
@@ -180,7 +182,7 @@
mTouchRegion.setEmpty();
// Apply the geometry to the touch region.
- geometry.union(mTouchRegion);
+ geometry.union(mContext, mTouchRegion);
mInputEventReceiver.setGeometry(geometry);
mInputEventReceiver.setTouchRegion(mTouchRegion);
@@ -354,7 +356,7 @@
*/
@NonNull Region getCornersRegion() {
Region region = new Region();
- mDragResizeWindowGeometry.union(region);
+ mDragResizeWindowGeometry.union(mContext, region);
return region;
}
@@ -395,7 +397,7 @@
// Touch events are tracked in four corners. Other events are tracked in resize edges.
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mShouldHandleEvents = mDragResizeWindowGeometry.shouldHandleEvent(e,
+ mShouldHandleEvents = mDragResizeWindowGeometry.shouldHandleEvent(mContext, e,
new Point() /* offset */);
if (mShouldHandleEvents) {
// Save the id of the pointer for this drag interaction; we will use the
@@ -405,8 +407,9 @@
float y = e.getY(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
- final int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(
- isEventFromTouchscreen(e), isEdgeResizePermitted(e), x, y);
+ final int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(mContext,
+ isEventFromTouchscreen(e), isEdgeResizePermitted(mContext, e), x,
+ y);
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
"%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
@@ -496,7 +499,7 @@
// Since we are handling cursor, we know that this is not a touchscreen event, and
// that edge resizing should always be allowed.
@DragPositioningCallback.CtrlType int ctrlType =
- mDragResizeWindowGeometry.calculateCtrlType(/* isTouchscreen= */
+ mDragResizeWindowGeometry.calculateCtrlType(mContext, /* isTouchscreen= */
false, /* isEdgeResizePermitted= */ true, x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
@@ -537,7 +540,7 @@
}
private boolean shouldHandleEvent(MotionEvent e, Point offset) {
- return mDragResizeWindowGeometry.shouldHandleEvent(e, offset);
+ return mDragResizeWindowGeometry.shouldHandleEvent(mContext, e, offset);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index ba5f079..014d61d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -18,7 +18,7 @@
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
-import static com.android.window.flags.Flags.enableWindowingEdgeDragResize;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.EDGE_DRAG_RESIZE;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
@@ -26,6 +26,7 @@
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
import android.annotation.NonNull;
+import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
@@ -69,8 +70,8 @@
/**
* Returns the resource value to use for the resize handle on the edge of the window.
*/
- static int getResizeEdgeHandleSize(@NonNull Resources res) {
- return enableWindowingEdgeDragResize()
+ static int getResizeEdgeHandleSize(@NonNull Context context, @NonNull Resources res) {
+ return EDGE_DRAG_RESIZE.isEnabled(context)
? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
: res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
}
@@ -103,11 +104,11 @@
* Returns the union of all regions that can be touched for drag resizing; the corners window
* and window edges.
*/
- void union(@NonNull Region region) {
+ void union(@NonNull Context context, @NonNull Region region) {
// Apply the edge resize regions.
mTaskEdges.union(region);
- if (enableWindowingEdgeDragResize()) {
+ if (EDGE_DRAG_RESIZE.isEnabled(context)) {
// Apply the corners as well for the larger corners, to ensure we capture all possible
// touches.
mLargeTaskCorners.union(region);
@@ -120,11 +121,12 @@
/**
* Returns if this MotionEvent should be handled, based on its source and position.
*/
- boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
+ boolean shouldHandleEvent(@NonNull Context context, @NonNull MotionEvent e,
+ @NonNull Point offset) {
final float x = e.getX(0) + offset.x;
final float y = e.getY(0) + offset.y;
- if (enableWindowingEdgeDragResize()) {
+ if (EDGE_DRAG_RESIZE.isEnabled(context)) {
// First check if touch falls within a corner.
// Large corner bounds are used for course input like touch, otherwise fine bounds.
boolean result = isEventFromTouchscreen(e)
@@ -132,7 +134,7 @@
: isInCornerBounds(mFineTaskCorners, x, y);
// Check if touch falls within the edge resize handle. Limit edge resizing to stylus and
// mouse input.
- if (!result && isEdgeResizePermitted(e)) {
+ if (!result && isEdgeResizePermitted(context, e)) {
result = isInEdgeResizeBounds(x, y);
}
return result;
@@ -148,8 +150,8 @@
return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
}
- static boolean isEdgeResizePermitted(@NonNull MotionEvent e) {
- if (enableWindowingEdgeDragResize()) {
+ static boolean isEdgeResizePermitted(@NonNull Context context, @NonNull MotionEvent e) {
+ if (EDGE_DRAG_RESIZE.isEnabled(context)) {
return e.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
|| e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
} else {
@@ -174,8 +176,9 @@
* resize region.
*/
@DragPositioningCallback.CtrlType
- int calculateCtrlType(boolean isTouchscreen, boolean isEdgeResizePermitted, float x, float y) {
- if (enableWindowingEdgeDragResize()) {
+ int calculateCtrlType(@NonNull Context context, boolean isTouchscreen,
+ boolean isEdgeResizePermitted, float x, float y) {
+ if (EDGE_DRAG_RESIZE.isEnabled(context)) {
// First check if touch falls within a corner.
// Large corner bounds are used for course input like touch, otherwise fine bounds.
int ctrlType = isTouchscreen
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 7a81d4c..b348d65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -17,7 +17,8 @@
import android.annotation.ColorInt
import android.annotation.DimenRes
-import android.app.ActivityManager
+import android.annotation.SuppressLint
+import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
@@ -28,7 +29,9 @@
import android.graphics.PointF
import android.graphics.Rect
import android.net.Uri
+import android.view.LayoutInflater
import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_OUTSIDE
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
@@ -42,7 +45,6 @@
import androidx.core.view.isGone
import com.android.window.flags.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -63,21 +65,18 @@
class HandleMenu(
private val parentDecor: DesktopModeWindowDecoration,
private val layoutResId: Int,
- private val onClickListener: View.OnClickListener?,
- private val onTouchListener: View.OnTouchListener?,
private val appIconBitmap: Bitmap?,
private val appName: CharSequence?,
- private val displayController: DisplayController,
private val splitScreenController: SplitScreenController,
private val shouldShowWindowingPill: Boolean,
- val openInBrowserLink: Uri?,
+ private val shouldShowNewWindowButton: Boolean,
+ private val openInBrowserLink: Uri?,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int
) {
private val context: Context = parentDecor.mDecorWindowContext
- private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
- private val decorThemeUtil = DecorThemeUtil(context)
+ private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo
private val isViewAboveStatusBar: Boolean
get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
@@ -93,10 +92,9 @@
private val marginMenuStart = loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_margin_start)
- private var handleMenuAnimator: HandleMenuAnimator? = null
-
@VisibleForTesting
var handleMenuViewContainer: AdditionalViewContainer? = null
+ private var handleMenuView: HandleMenuView? = null
// Position of the handle menu used for laying out the handle view.
@VisibleForTesting
@@ -115,157 +113,88 @@
updateHandleMenuPillPositions(captionX)
}
- fun show() {
+ fun show(
+ onToDesktopClickListener: () -> Unit,
+ onToFullscreenClickListener: () -> Unit,
+ onToSplitScreenClickListener: () -> Unit,
+ onNewWindowClickListener: () -> Unit,
+ openInBrowserClickListener: (Uri) -> Unit,
+ onCloseMenuClickListener: () -> Unit,
+ onOutsideTouchListener: () -> Unit,
+ ) {
val ssg = SurfaceSyncGroup(TAG)
val t = SurfaceControl.Transaction()
- createHandleMenuViewContainer(t, ssg)
+ createHandleMenu(
+ t = t,
+ ssg = ssg,
+ onToDesktopClickListener = onToDesktopClickListener,
+ onToFullscreenClickListener = onToFullscreenClickListener,
+ onToSplitScreenClickListener = onToSplitScreenClickListener,
+ onNewWindowClickListener = onNewWindowClickListener,
+ openInBrowserClickListener = openInBrowserClickListener,
+ onCloseMenuClickListener = onCloseMenuClickListener,
+ onOutsideTouchListener = onOutsideTouchListener,
+ )
ssg.addTransaction(t)
ssg.markSyncReady()
- setupHandleMenu()
- animateHandleMenu()
+
+ handleMenuView?.animateOpenMenu()
}
- private fun createHandleMenuViewContainer(
+ private fun createHandleMenu(
t: SurfaceControl.Transaction,
- ssg: SurfaceSyncGroup
+ ssg: SurfaceSyncGroup,
+ onToDesktopClickListener: () -> Unit,
+ onToFullscreenClickListener: () -> Unit,
+ onToSplitScreenClickListener: () -> Unit,
+ onNewWindowClickListener: () -> Unit,
+ openInBrowserClickListener: (Uri) -> Unit,
+ onCloseMenuClickListener: () -> Unit,
+ onOutsideTouchListener: () -> Unit
) {
+ val handleMenuView = HandleMenuView(
+ context = context,
+ menuWidth = menuWidth,
+ captionHeight = captionHeight,
+ shouldShowWindowingPill = shouldShowWindowingPill,
+ shouldShowBrowserPill = shouldShowBrowserPill,
+ shouldShowNewWindowButton = shouldShowNewWindowButton
+ ).apply {
+ bind(taskInfo, appIconBitmap, appName)
+ this.onToDesktopClickListener = onToDesktopClickListener
+ this.onToFullscreenClickListener = onToFullscreenClickListener
+ this.onToSplitScreenClickListener = onToSplitScreenClickListener
+ this.onNewWindowClickListener = onNewWindowClickListener
+ this.onOpenInBrowserClickListener = {
+ openInBrowserClickListener.invoke(openInBrowserLink!!)
+ }
+ this.onCloseMenuClickListener = onCloseMenuClickListener
+ this.onOutsideTouchListener = onOutsideTouchListener
+ }
+
val x = handleMenuPosition.x.toInt()
val y = handleMenuPosition.y.toInt()
handleMenuViewContainer =
if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) {
AdditionalSystemViewContainer(
context = context,
- layoutId = R.layout.desktop_mode_window_decor_handle_menu,
taskId = taskInfo.taskId,
x = x,
y = y,
width = menuWidth,
height = menuHeight,
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+ view = handleMenuView.rootView
)
} else {
parentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
- t, ssg, x, y, menuWidth, menuHeight
+ handleMenuView.rootView, "Handle Menu", t, ssg, x, y, menuWidth, menuHeight
)
}
- handleMenuViewContainer?.view?.let { view ->
- handleMenuAnimator =
- HandleMenuAnimator(view, menuWidth, captionHeight.toFloat())
- }
- }
- /**
- * Animates the appearance of the handle menu and its three pills.
- */
- private fun animateHandleMenu() {
- when {
- taskInfo.isFullscreen || taskInfo.isMultiWindow -> {
- handleMenuAnimator?.animateCaptionHandleExpandToOpen()
- }
- else -> {
- handleMenuAnimator?.animateOpen()
- }
- }
- }
-
- /**
- * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
- * pill.
- */
- private fun setupHandleMenu() {
- val handleMenu = handleMenuViewContainer?.view ?: return
- handleMenu.setOnTouchListener(onTouchListener)
-
- val style = calculateMenuStyle()
- setupAppInfoPill(handleMenu, style)
- if (shouldShowWindowingPill) {
- setupWindowingPill(handleMenu, style)
- }
- setupMoreActionsPill(handleMenu, style)
- setupOpenInBrowserPill(handleMenu, style)
- }
-
- /**
- * Set up interactive elements of handle menu's app info pill.
- */
- private fun setupAppInfoPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.app_info_pill).apply {
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- pill.requireViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
- .let { collapseBtn ->
- collapseBtn.imageTintList = ColorStateList.valueOf(style.textColor)
- collapseBtn.setOnClickListener(onClickListener)
- collapseBtn.taskInfo = taskInfo
- }
- pill.requireViewById<ImageView>(R.id.application_icon).let { appIcon ->
- appIcon.setImageBitmap(appIconBitmap)
- }
- pill.requireViewById<TextView>(R.id.application_name).let { appNameView ->
- appNameView.text = appName
- appNameView.setTextColor(style.textColor)
- }
- }
-
- /**
- * Set up interactive elements and color of handle menu's windowing pill.
- */
- private fun setupWindowingPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.windowing_pill).apply {
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- val fullscreenBtn = pill.requireViewById<ImageButton>(R.id.fullscreen_button)
- val splitscreenBtn = pill.requireViewById<ImageButton>(R.id.split_screen_button)
- val floatingBtn = pill.requireViewById<ImageButton>(R.id.floating_button)
- // TODO: Remove once implemented.
- floatingBtn.visibility = View.GONE
- val desktopBtn = handleMenu.requireViewById<ImageButton>(R.id.desktop_button)
-
- fullscreenBtn.setOnClickListener(onClickListener)
- splitscreenBtn.setOnClickListener(onClickListener)
- floatingBtn.setOnClickListener(onClickListener)
- desktopBtn.setOnClickListener(onClickListener)
-
- fullscreenBtn.isSelected = taskInfo.isFullscreen
- fullscreenBtn.imageTintList = style.windowingButtonColor
- splitscreenBtn.isSelected = taskInfo.isMultiWindow
- splitscreenBtn.imageTintList = style.windowingButtonColor
- floatingBtn.isSelected = taskInfo.isPinned
- floatingBtn.imageTintList = style.windowingButtonColor
- desktopBtn.isSelected = taskInfo.isFreeform
- desktopBtn.imageTintList = style.windowingButtonColor
- }
-
- /**
- * Set up interactive elements & height of handle menu's more actions pill
- */
- private fun setupMoreActionsPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.more_actions_pill).apply {
- isGone = !SHOULD_SHOW_MORE_ACTIONS_PILL
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
- pill.requireViewById<Button>(R.id.screenshot_button).let { screenshotBtn ->
- screenshotBtn.setTextColor(style.textColor)
- screenshotBtn.compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
- }
-
- private fun setupOpenInBrowserPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.open_in_browser_pill).apply {
- isGone = !shouldShowBrowserPill
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- pill.requireViewById<Button>(R.id.open_in_browser_button).let { browserButton ->
- browserButton.setOnClickListener(onClickListener)
- browserButton.setTextColor(style.textColor)
- browserButton.compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
+ this.handleMenuView = handleMenuView
}
/**
@@ -359,16 +288,8 @@
fun checkMotionEvent(ev: MotionEvent) {
// If the menu view is above status bar, we can let the views handle input directly.
if (isViewAboveStatusBar) return
- val handleMenu = handleMenuViewContainer?.view ?: return
- val collapse = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
val inputPoint = translateInputToLocalSpace(ev)
- val inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y)
- val action = ev.actionMasked
- collapse.isHovered = inputInCollapseButton && action != MotionEvent.ACTION_UP
- collapse.isPressed = inputInCollapseButton && action == MotionEvent.ACTION_DOWN
- if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
- collapse.performClick()
- }
+ handleMenuView?.checkMotionEvent(ev, inputPoint)
}
// Translate the input point from display coordinates to the same space as the handle menu.
@@ -439,9 +360,17 @@
R.dimen.desktop_mode_handle_menu_windowing_pill_height)
menuHeight -= pillTopMargin
}
- if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ if (!SHOULD_SHOW_SCREENSHOT_BUTTON) {
menuHeight -= loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_more_actions_pill_height)
+ R.dimen.desktop_mode_handle_menu_screenshot_height
+ )
+ }
+ if (!shouldShowNewWindowButton) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_new_window_height
+ )
+ }
+ if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton) {
menuHeight -= pillTopMargin
}
if (!shouldShowBrowserPill) {
@@ -460,48 +389,244 @@
}
fun close() {
- val after = {
+ handleMenuView?.animateCloseMenu {
handleMenuViewContainer?.releaseView()
handleMenuViewContainer = null
}
- if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
- handleMenuAnimator?.animateCollapseIntoHandleClose(after)
- } else {
- handleMenuAnimator?.animateClose(after)
- }
}
- private fun calculateMenuStyle(): MenuStyle {
- val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
- return MenuStyle(
- backgroundColor = colorScheme.surfaceBright.toArgb(),
- textColor = colorScheme.onSurface.toArgb(),
- windowingButtonColor = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_pressed),
- intArrayOf(android.R.attr.state_focused),
- intArrayOf(android.R.attr.state_selected),
- intArrayOf(),
+ /** The view within the Handle Menu, with options to change the windowing mode and more. */
+ @SuppressLint("ClickableViewAccessibility")
+ class HandleMenuView(
+ context: Context,
+ menuWidth: Int,
+ captionHeight: Int,
+ private val shouldShowWindowingPill: Boolean,
+ private val shouldShowBrowserPill: Boolean,
+ private val shouldShowNewWindowButton: Boolean
+ ) {
+ val rootView = LayoutInflater.from(context)
+ .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
+
+ // App Info Pill.
+ private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill)
+ private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>(
+ R.id.collapse_menu_button)
+ private val appIconView = appInfoPill.requireViewById<ImageView>(R.id.application_icon)
+ private val appNameView = appInfoPill.requireViewById<TextView>(R.id.application_name)
+
+ // Windowing Pill.
+ private val windowingPill = rootView.requireViewById<View>(R.id.windowing_pill)
+ private val fullscreenBtn = windowingPill.requireViewById<ImageButton>(
+ R.id.fullscreen_button)
+ private val splitscreenBtn = windowingPill.requireViewById<ImageButton>(
+ R.id.split_screen_button)
+ private val floatingBtn = windowingPill.requireViewById<ImageButton>(R.id.floating_button)
+ private val desktopBtn = windowingPill.requireViewById<ImageButton>(R.id.desktop_button)
+
+ // More Actions Pill.
+ private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill)
+ private val screenshotBtn = moreActionsPill.requireViewById<Button>(R.id.screenshot_button)
+ private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
+
+ // Open in Browser Pill.
+ private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
+ private val browserBtn = openInBrowserPill.requireViewById<Button>(
+ R.id.open_in_browser_button)
+
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
+
+ private lateinit var taskInfo: RunningTaskInfo
+ private lateinit var style: MenuStyle
+
+ var onToDesktopClickListener: (() -> Unit)? = null
+ var onToFullscreenClickListener: (() -> Unit)? = null
+ var onToSplitScreenClickListener: (() -> Unit)? = null
+ var onNewWindowClickListener: (() -> Unit)? = null
+ var onOpenInBrowserClickListener: (() -> Unit)? = null
+ var onCloseMenuClickListener: (() -> Unit)? = null
+ var onOutsideTouchListener: (() -> Unit)? = null
+
+ init {
+ fullscreenBtn.setOnClickListener { onToFullscreenClickListener?.invoke() }
+ splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
+ desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
+ browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+ collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
+ newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
+
+ rootView.setOnTouchListener { _, event ->
+ if (event.actionMasked == ACTION_OUTSIDE) {
+ onOutsideTouchListener?.invoke()
+ return@setOnTouchListener false
+ }
+ return@setOnTouchListener true
+ }
+ }
+
+ /** Binds the menu views to the new data. */
+ fun bind(taskInfo: RunningTaskInfo, appIconBitmap: Bitmap?, appName: CharSequence?) {
+ this.taskInfo = taskInfo
+ this.style = calculateMenuStyle(taskInfo)
+
+ bindAppInfoPill(style, appIconBitmap, appName)
+ if (shouldShowWindowingPill) {
+ bindWindowingPill(style)
+ }
+ bindMoreActionsPill(style)
+ bindOpenInBrowserPill(style)
+ }
+
+ /** Animates the menu opening. */
+ fun animateOpenMenu() {
+ if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
+ animator.animateCaptionHandleExpandToOpen()
+ } else {
+ animator.animateOpen()
+ }
+ }
+
+ /** Animates the menu closing. */
+ fun animateCloseMenu(onAnimFinish: () -> Unit) {
+ if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
+ animator.animateCollapseIntoHandleClose(onAnimFinish)
+ } else {
+ animator.animateClose(onAnimFinish)
+ }
+ }
+
+ /**
+ * Checks whether a motion event falls inside this menu, and invokes a click of the
+ * collapse button if needed.
+ * Note: should only be called when regular click detection doesn't work because input is
+ * detected through the status bar layer with a global input monitor.
+ */
+ fun checkMotionEvent(ev: MotionEvent, inputPointLocal: PointF) {
+ val inputInCollapseButton = pointInView(
+ collapseMenuButton,
+ inputPointLocal.x,
+ inputPointLocal.y
+ )
+ val action = ev.actionMasked
+ collapseMenuButton.isHovered = inputInCollapseButton
+ && action != MotionEvent.ACTION_UP
+ collapseMenuButton.isPressed = inputInCollapseButton
+ && action == MotionEvent.ACTION_DOWN
+ if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
+ collapseMenuButton.performClick()
+ }
+ }
+
+ private fun pointInView(v: View?, x: Float, y: Float): Boolean {
+ return v != null && v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ }
+
+ private fun calculateMenuStyle(taskInfo: RunningTaskInfo): MenuStyle {
+ val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
+ return MenuStyle(
+ backgroundColor = colorScheme.surfaceBright.toArgb(),
+ textColor = colorScheme.onSurface.toArgb(),
+ windowingButtonColor = ColorStateList(
+ arrayOf(
+ intArrayOf(android.R.attr.state_pressed),
+ intArrayOf(android.R.attr.state_focused),
+ intArrayOf(android.R.attr.state_selected),
+ intArrayOf(),
+ ),
+ intArrayOf(
+ colorScheme.onSurface.toArgb(),
+ colorScheme.onSurface.toArgb(),
+ colorScheme.primary.toArgb(),
+ colorScheme.onSurface.toArgb(),
+ )
),
- intArrayOf(
- colorScheme.onSurface.toArgb(),
- colorScheme.onSurface.toArgb(),
- colorScheme.primary.toArgb(),
- colorScheme.onSurface.toArgb(),
+ )
+ }
+
+ private fun bindAppInfoPill(
+ style: MenuStyle,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?
+ ) {
+ appInfoPill.background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+
+ collapseMenuButton.apply {
+ imageTintList = ColorStateList.valueOf(style.textColor)
+ this.taskInfo = this@HandleMenuView.taskInfo
+ }
+ appIconView.setImageBitmap(appIconBitmap)
+ appNameView.apply {
+ text = appName
+ setTextColor(style.textColor)
+ }
+ }
+
+ private fun bindWindowingPill(style: MenuStyle) {
+ windowingPill.background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+
+ // TODO: Remove once implemented.
+ floatingBtn.visibility = View.GONE
+
+ fullscreenBtn.isSelected = taskInfo.isFullscreen
+ fullscreenBtn.imageTintList = style.windowingButtonColor
+ splitscreenBtn.isSelected = taskInfo.isMultiWindow
+ splitscreenBtn.imageTintList = style.windowingButtonColor
+ floatingBtn.isSelected = taskInfo.isPinned
+ floatingBtn.imageTintList = style.windowingButtonColor
+ desktopBtn.isSelected = taskInfo.isFreeform
+ desktopBtn.imageTintList = style.windowingButtonColor
+ }
+
+ private fun bindMoreActionsPill(style: MenuStyle) {
+ moreActionsPill.apply {
+ isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
+ }
+ screenshotBtn.apply {
+ isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
+ background.colorFilter =
+ BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY
)
- ),
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ newWindowBtn.apply {
+ isGone = !shouldShowNewWindowButton
+ background.colorFilter =
+ BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ }
+
+ private fun bindOpenInBrowserPill(style: MenuStyle) {
+ openInBrowserPill.apply {
+ isGone = !shouldShowBrowserPill
+ background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+ }
+
+ browserBtn.apply {
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ }
+
+ private data class MenuStyle(
+ @ColorInt val backgroundColor: Int,
+ @ColorInt val textColor: Int,
+ val windowingButtonColor: ColorStateList,
)
}
- private data class MenuStyle(
- @ColorInt val backgroundColor: Int,
- @ColorInt val textColor: Int,
- val windowingButtonColor: ColorStateList,
- )
-
companion object {
private const val TAG = "HandleMenu"
- private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false
+ private const val SHOULD_SHOW_SCREENSHOT_BUTTON = false
}
}
@@ -510,13 +635,11 @@
fun create(
parentDecor: DesktopModeWindowDecoration,
layoutResId: Int,
- onClickListener: View.OnClickListener?,
- onTouchListener: View.OnTouchListener?,
appIconBitmap: Bitmap?,
appName: CharSequence?,
- displayController: DisplayController,
splitScreenController: SplitScreenController,
shouldShowWindowingPill: Boolean,
+ shouldShowNewWindowButton: Boolean,
openInBrowserLink: Uri?,
captionWidth: Int,
captionHeight: Int,
@@ -529,13 +652,11 @@
override fun create(
parentDecor: DesktopModeWindowDecoration,
layoutResId: Int,
- onClickListener: View.OnClickListener?,
- onTouchListener: View.OnTouchListener?,
appIconBitmap: Bitmap?,
appName: CharSequence?,
- displayController: DisplayController,
splitScreenController: SplitScreenController,
shouldShowWindowingPill: Boolean,
+ shouldShowNewWindowButton: Boolean,
openInBrowserLink: Uri?,
captionWidth: Int,
captionHeight: Int,
@@ -544,13 +665,11 @@
return HandleMenu(
parentDecor,
layoutResId,
- onClickListener,
- onTouchListener,
appIconBitmap,
appName,
- displayController,
splitScreenController,
shouldShowWindowingPill,
+ shouldShowNewWindowButton,
openInBrowserLink,
captionWidth,
captionHeight,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index aa2ce0f..013f506 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -66,7 +66,6 @@
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_12
import com.android.wm.shell.windowdecor.common.OPACITY_40
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
import com.android.wm.shell.windowdecor.common.withAlpha
import java.util.function.Supplier
@@ -102,15 +101,15 @@
/** Creates and shows the maximize window. */
fun show(
- onMaximizeClickListener: OnTaskActionClickListener,
- onLeftSnapClickListener: OnTaskActionClickListener,
- onRightSnapClickListener: OnTaskActionClickListener,
+ onMaximizeOrRestoreClickListener: () -> Unit,
+ onLeftSnapClickListener: () -> Unit,
+ onRightSnapClickListener: () -> Unit,
onHoverListener: (Boolean) -> Unit,
onOutsideTouchListener: () -> Unit,
) {
if (maximizeMenu != null) return
createMaximizeMenu(
- onMaximizeClickListener = onMaximizeClickListener,
+ onMaximizeClickListener = onMaximizeOrRestoreClickListener,
onLeftSnapClickListener = onLeftSnapClickListener,
onRightSnapClickListener = onRightSnapClickListener,
onHoverListener = onHoverListener,
@@ -129,9 +128,9 @@
/** Create a maximize menu that is attached to the display area. */
private fun createMaximizeMenu(
- onMaximizeClickListener: OnTaskActionClickListener,
- onLeftSnapClickListener: OnTaskActionClickListener,
- onRightSnapClickListener: OnTaskActionClickListener,
+ onMaximizeClickListener: () -> Unit,
+ onLeftSnapClickListener: () -> Unit,
+ onRightSnapClickListener: () -> Unit,
onHoverListener: (Boolean) -> Unit,
onOutsideTouchListener: () -> Unit
) {
@@ -165,17 +164,10 @@
menuHeight = menuHeight,
menuPadding = menuPadding,
).also { menuView ->
- val taskId = taskInfo.taskId
menuView.bind(taskInfo)
- menuView.onMaximizeClickListener = {
- onMaximizeClickListener.onClick(taskId, "maximize_menu_option")
- }
- menuView.onLeftSnapClickListener = {
- onLeftSnapClickListener.onClick(taskId, "left_snap_option")
- }
- menuView.onRightSnapClickListener = {
- onRightSnapClickListener.onClick(taskId, "right_snap_option")
- }
+ menuView.onMaximizeClickListener = onMaximizeClickListener
+ menuView.onLeftSnapClickListener = onLeftSnapClickListener
+ menuView.onRightSnapClickListener = onRightSnapClickListener
menuView.onMenuHoverListener = onHoverListener
menuView.onOutsideTouchListener = onOutsideTouchListener
viewHost.setView(menuView.rootView, lp)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 6828560..4cab6e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -613,7 +613,7 @@
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
*
- * @param layoutId layout to make the window from
+ * @param v View to attach to the window
* @param t the transaction to apply
* @param xPos x position of new window
* @param yPos y position of new window
@@ -621,9 +621,9 @@
* @param height height of new window
* @return the {@link AdditionalViewHostViewContainer} that was added.
*/
- AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix,
- SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos,
- int width, int height) {
+ AdditionalViewHostViewContainer addWindow(@NonNull View v, @NonNull String namePrefix,
+ @NonNull SurfaceControl.Transaction t, @NonNull SurfaceSyncGroup ssg,
+ int xPos, int yPos, int width, int height) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -631,8 +631,6 @@
.setParent(mDecorationContainerSurface)
.setCallsite("WindowDecoration.addWindow")
.build();
- View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
-
t.setPosition(windowSurfaceControl, xPos, yPos)
.setWindowCrop(windowSurfaceControl, width, height)
.show(windowSurfaceControl);
@@ -653,6 +651,25 @@
}
/**
+ * Create a window associated with this WindowDecoration.
+ * Note that subclass must dispose of this when the task is hidden/closed.
+ *
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
+ * @return the {@link AdditionalViewHostViewContainer} that was added.
+ */
+ AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix,
+ SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos,
+ int width, int height) {
+ final View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+ return addWindow(v, namePrefix, t, ssg, xPos, yPos, width, height);
+ }
+
+ /**
* Adds caption inset source to a WCT
*/
public void addCaptionInset(WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index f1370bb..cadd80e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.additionalviewcontainer
+import android.annotation.LayoutRes
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
@@ -29,24 +30,52 @@
* for view containers that should be above the status bar layer.
*/
class AdditionalSystemViewContainer(
- private val context: Context,
+ context: Context,
taskId: Int,
x: Int,
y: Int,
width: Int,
height: Int,
flags: Int,
- layoutId: Int? = null
-) : AdditionalViewContainer() {
override val view: View
+) : AdditionalViewContainer() {
+
+ constructor(
+ context: Context,
+ taskId: Int,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ flags: Int,
+ @LayoutRes layoutId: Int
+ ) : this(
+ context = context,
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
+ )
+
+ constructor(
+ context: Context, taskId: Int, x: Int, y: Int, width: Int, height: Int, flags: Int
+ ) : this(
+ context = context,
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = View(context)
+ )
+
val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
init {
- if (layoutId != null) {
- view = LayoutInflater.from(context).inflate(layoutId, null)
- } else {
- view = View(context)
- }
val lp = WindowManager.LayoutParams(
width, height, x, y,
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 57e469d..56fad95 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -68,6 +68,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
+import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -114,6 +115,8 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
+ private Transitions mTransitions;
+ @Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private BackAnimationController mController;
@@ -156,7 +159,8 @@
mContentResolver,
mAnimationBackground,
mShellBackAnimationRegistry,
- mShellCommandHandler);
+ mShellCommandHandler,
+ mTransitions);
mShellInit.init();
mShellExecutor.flushAll();
mTouchableRegion = new Rect(0, 0, 100, 100);
@@ -316,7 +320,8 @@
mContentResolver,
mAnimationBackground,
mShellBackAnimationRegistry,
- mShellCommandHandler);
+ mShellCommandHandler,
+ mTransitions);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 95d9017..566735d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -277,7 +277,7 @@
}
@Test
- fun instantiate_canNotEnterDesktopMode_doNotAddInitCallback() {
+ fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
clearInvocations(shellInit)
@@ -752,36 +752,36 @@
val task = setUpFullscreenTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
}
@Test
- fun moveToDesktop_tdaFreeform_windowingModeSetToUndefined() {
+ fun moveRunningTaskToDesktop_tdaFreeform_windowingModeSetToUndefined() {
val task = setUpFullscreenTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
}
@Test
- fun moveToDesktop_nonExistentTask_doesNothing() {
- controller.moveToDesktop(999, transitionSource = UNKNOWN)
+ fun moveTaskToDesktop_nonExistentTask_doesNothing() {
+ controller.moveTaskToDesktop(999, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
+ fun moveTaskToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
- controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+ controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
with(getLatestEnterDesktopWct()) {
assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
@@ -790,12 +790,12 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
- controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+ controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
with(getLatestEnterDesktopWct()) {
// Add desktop wallpaper activity
@@ -807,7 +807,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
@@ -815,7 +815,7 @@
numActivities = 1
}
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -823,7 +823,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
@@ -831,13 +831,13 @@
numActivities = 1
}
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveToDesktop_systemUIActivity_doesNothing() {
+ fun moveRunningTaskToDesktop_systemUIActivity_doesNothing() {
val task = setUpFullscreenTask()
// Set task as systemUI package
@@ -846,15 +846,15 @@
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
task.baseActivity = baseComponent
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
- fun moveToDesktop_deviceSupported_taskIsMovedToDesktop() {
+ fun moveRunningTaskToDesktop_deviceSupported_taskIsMovedToDesktop() {
val task = setUpFullscreenTask()
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -862,13 +862,13 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
+ fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveToDesktop(fullscreenTask, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
with(getLatestEnterDesktopWct()) {
// Operations should include home task, freeform task
@@ -881,12 +881,12 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveToDesktop(fullscreenTask, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
with(getLatestEnterDesktopWct()) {
// Operations should include wallpaper intent, freeform task, fullscreen task
@@ -900,7 +900,7 @@
}
@Test
- fun moveToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
+ fun moveRunningTaskToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
setUpHomeTask(displayId = DEFAULT_DISPLAY)
val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
@@ -910,7 +910,7 @@
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
- controller.moveToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN)
with(getLatestEnterDesktopWct()) {
// Check that hierarchy operations do not include tasks from second display
@@ -921,9 +921,9 @@
}
@Test
- fun moveToDesktop_splitTaskExitsSplit() {
+ fun moveRunningTaskToDesktop_splitTaskExitsSplit() {
val task = setUpSplitScreenTask()
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
verify(splitScreenController)
@@ -931,9 +931,9 @@
}
@Test
- fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
+ fun moveRunningTaskToDesktop_fullscreenTaskDoesNotExitSplit() {
val task = setUpFullscreenTask()
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
verify(splitScreenController, never())
@@ -942,12 +942,12 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
+ fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
- controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home
@@ -960,12 +960,12 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
- controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
+ controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper
@@ -1566,6 +1566,21 @@
}
@Test
+ fun handleRequest_recentsAnimationRunning_relaunchActiveTask_taskBecomesUndefined() {
+ // Set up a visible freeform task
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onAnimationStateChanged(true)
+
+ // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+ assertThat(result?.changes?.get(freeformTask.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index e264160..5e6d01b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -16,19 +16,22 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.WindowingMode
import android.content.ComponentName
import android.content.Context
+import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
+import android.net.Uri
import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -37,6 +40,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
import android.testing.TestableLooper.RunWithLooper
import android.util.SparseArray
import android.view.Choreographer
@@ -68,8 +72,10 @@
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -81,13 +87,17 @@
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
+import java.util.Optional
+import java.util.function.Consumer
+import java.util.function.Supplier
+import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentCaptor.forClass
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -96,13 +106,13 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
import org.mockito.kotlin.eq
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
-import java.util.Optional
-import java.util.function.Supplier
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -145,22 +155,34 @@
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
private val bgExecutor = TestShellExecutor()
+ @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
+ private lateinit var spyContext: TestableContext
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
}
private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
+ private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
@Before
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
+
+ spyContext = spy(mContext)
+ doNothing().`when`(spyContext).startActivity(any())
shellInit = ShellInit(mockShellExecutor)
windowDecorByTaskIdSpy.clear()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
- mContext,
+ spyContext,
mockShellExecutor,
mockMainHandler,
mockMainChoreographer,
@@ -176,6 +198,7 @@
mockTransitions,
Optional.of(mockDesktopTasksController),
mockGenericLinksParser,
+ mockMultiInstanceHelper,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
@@ -201,6 +224,11 @@
desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
}
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
@Test
fun testDeleteCaptionOnChangeTransitionWhenNecessary() {
val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
@@ -296,7 +324,7 @@
whenever(view.id).thenReturn(R.id.back_button)
val inputManager = mock(InputManager::class.java)
- mContext.addMockSystemService(InputManager::class.java, inputManager)
+ spyContext.addMockSystemService(InputManager::class.java, inputManager)
val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java)
desktopModeWindowDecorViewModel
@@ -334,24 +362,16 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
- isTopActivityTransparent = true
- isTopActivityStyleFloating = true
- numActivities = 1
- }
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- setUpMockDecorationsForTasks(task)
-
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
}
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@@ -463,96 +483,70 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_desktopModeUnsupportedOnDevice_decorNotCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate default enforce device restrictions system property
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- onTaskOpening(task)
- assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate enforce device restrictions system property overridden to false
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ // Simulate enforce device restrictions system property overridden to false
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- setUpMockDecorationsForTasks(task)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ setUpMockDecorationsForTasks(task)
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate default enforce device restrictions system property
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- setUpMockDecorationsForTasks(task)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ setUpMockDecorationsForTasks(task)
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
fun testOnDecorMaximizedOrRestored_togglesTaskSize() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
- return@let captor.value
- }
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+ )
- maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+ maxOrRestoreListenerCaptor.value.invoke()
verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo)
}
@Test
fun testOnDecorMaximizedOrRestored_closesMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
- return@let captor.value
- }
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+ )
- maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+ maxOrRestoreListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
@@ -560,30 +554,28 @@
@Test
fun testOnDecorSnappedLeft_snapResizes() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnLeftSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onLeftSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.LEFT)
}
@Test
fun testOnDecorSnappedLeft_closeMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnLeftSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onLeftSnapClickListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
@@ -591,35 +583,234 @@
@Test
fun testOnDecorSnappedRight_snapResizes() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnRightSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onRightSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.RIGHT)
}
@Test
fun testOnDecorSnappedRight_closeMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnRightSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onRightSnapClickListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
}
+ @Test
+ fun testDecor_onClickToDesktop_movesToDesktopWithSource() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(mockDesktopTasksController).moveTaskToDesktop(
+ eq(decor.mTaskInfo.taskId),
+ any(),
+ eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToDesktop_addsCaptionInsets() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(decor).addCaptionInset(any())
+ }
+
+ @Test
+ fun testDecor_onClickToDesktop_closesHandleMenu() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_closesHandleMenu() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_isFreeform_movesToFullscreen() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController).moveToFullscreen(
+ decor.mTaskInfo.taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_isSplit_movesToFullscreen() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(mockSplitScreenController).moveTaskToFullscreen(
+ decor.mTaskInfo.taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_closesHandleMenu() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_requestsSplit() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController).requestSplit(decor.mTaskInfo, leftOrTop = false)
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(decor).disposeStatusBarInputLayer()
+ }
+
+ @Test
+ fun testDecor_onClickToOpenBrowser_closeMenus() {
+ val openInBrowserListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<Uri>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onOpenInBrowserClickListener = openInBrowserListenerCaptor
+ )
+
+ openInBrowserListenerCaptor.value.accept(Uri.EMPTY)
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToOpenBrowser_opensBrowser() {
+ doNothing().whenever(spyContext).startActivity(any())
+ val uri = Uri.parse("https://www.google.com")
+ val openInBrowserListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<Uri>>
+ createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onOpenInBrowserClickListener = openInBrowserListenerCaptor
+ )
+
+ openInBrowserListenerCaptor.value.accept(uri)
+
+ verify(spyContext).startActivity(argThat { intent ->
+ intent.data == uri
+ && ((intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != 0)
+ && intent.categories.contains(Intent.CATEGORY_LAUNCHER)
+ && intent.action == Intent.ACTION_MAIN
+ })
+ }
+
+ private fun createOpenTaskDecoration(
+ @WindowingMode windowingMode: Int,
+ onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onRightSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onToDesktopClickListenerCaptor: ArgumentCaptor<Consumer<DesktopModeTransitionSource>> =
+ forClass(Consumer::class.java) as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>,
+ onToFullscreenClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onToSplitScreenClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onOpenInBrowserClickListener: ArgumentCaptor<Consumer<Uri>> =
+ forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Uri>>,
+ ): DesktopModeWindowDecoration {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
+ onTaskOpening(decor.mTaskInfo)
+ verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture())
+ verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture())
+ verify(decor).setOnRightSnapClickListener(onRightSnapClickListenerCaptor.capture())
+ verify(decor).setOnToDesktopClickListener(onToDesktopClickListenerCaptor.capture())
+ verify(decor).setOnToFullscreenClickListener(onToFullscreenClickListenerCaptor.capture())
+ verify(decor).setOnToSplitScreenClickListener(onToSplitScreenClickListenerCaptor.capture())
+ verify(decor).setOpenInBrowserClickListener(onOpenInBrowserClickListener.capture())
+ return decor
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
@@ -640,7 +831,7 @@
private fun createTask(
displayId: Int = DEFAULT_DISPLAY,
- @WindowConfiguration.WindowingMode windowingMode: Int,
+ @WindowingMode windowingMode: Int,
activityType: Int = ACTIVITY_TYPE_STANDARD,
focused: Boolean = true,
activityInfo: ActivityInfo = ActivityInfo(),
@@ -659,11 +850,16 @@
private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
val decoration = mock(DesktopModeWindowDecoration::class.java)
whenever(
- mockDesktopModeWindowDecorFactory.create(any(), any(), any(), any(), any(), eq(task),
- any(), any(), any(), any(), any(), any(), any())
+ mockDesktopModeWindowDecorFactory.create(
+ any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+ any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
+ if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId))
+ .thenReturn(true)
+ }
return decoration
}
@@ -684,7 +880,7 @@
)
}
- private fun RunningTaskInfo.setWindowingMode(@WindowConfiguration.WindowingMode mode: Int) {
+ private fun RunningTaskInfo.setWindowingMode(@WindowingMode mode: Int) {
configuration.windowConfiguration.windowingMode = mode
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 04b1eed..596adfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -89,14 +89,15 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import org.junit.After;
@@ -110,6 +111,7 @@
import org.mockito.Mock;
import org.mockito.quality.Strictness;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -167,13 +169,14 @@
@Mock
private Handler mMockHandler;
@Mock
- private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
+ private Consumer<Uri> mMockOpenInBrowserClickListener;
@Mock
private AppToWebGenericLinksParser mMockGenericLinksParser;
@Mock
private HandleMenu mMockHandleMenu;
@Mock
private HandleMenuFactory mMockHandleMenuFactory;
+ private MultiInstanceHelper mMockMultiInstanceHelper;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -217,8 +220,9 @@
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
- doReturn(mMockHandleMenu).when(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
- any(), any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt(), anyInt());
+ when(mMockHandleMenuFactory.create(any(), anyInt(), any(), any(),
+ any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt()))
+ .thenReturn(mMockHandleMenu);
}
@After
@@ -739,9 +743,21 @@
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
+
// Simulate menu opening and clicking open in browser button
decor.createHandleMenu(mMockSplitScreenController);
- decor.onOpenInBrowserClick();
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any()
+ );
+ openInBrowserCaptor.getValue().invoke(TEST_URI1);
// Verify handle menu's browser link not set to captured link since link not valid after
// open in browser clicked
@@ -755,10 +771,22 @@
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
decor.createHandleMenu(mMockSplitScreenController);
- decor.onOpenInBrowserClick();
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any()
+ );
- verify(mMockOpenInBrowserClickListener).onClick(any(), any());
+ openInBrowserCaptor.getValue().invoke(TEST_URI1);
+
+ verify(mMockOpenInBrowserClickListener).accept(TEST_URI1);
}
@Test
@@ -773,13 +801,37 @@
verifyHandleMenuCreated(TEST_URI2);
}
+ @Test
+ public void handleMenu_onCloseMenuClick_closesMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ true /* relayout */);
+ final ArgumentCaptor<Function0<Unit>> closeClickListener =
+ ArgumentCaptor.forClass(Function0.class);
+ decoration.createHandleMenu(mMockSplitScreenController);
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ closeClickListener.capture(),
+ any()
+ );
+
+ closeClickListener.getValue().invoke();
+
+ verify(mMockHandleMenu).close();
+ assertFalse(decoration.isHandleMenuActive());
+ }
+
private void verifyHandleMenuCreated(@Nullable Uri uri) {
- verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(), any(), any(), any(),
- any(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
+ verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
+ any(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
}
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
- final OnTaskActionClickListener l = (taskId, tag) -> {};
+ final Function0<Unit> l = () -> Unit.INSTANCE;
decoration.setOnMaximizeOrRestoreClickListener(l);
decoration.setOnLeftSnapClickListener(l);
decoration.setOnRightSnapClickListener(l);
@@ -811,32 +863,47 @@
taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
final String genericLinkString = genericLink == null ? null : genericLink.toString();
doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
// Relayout to set captured link
- decor.relayout(taskInfo);
- return decor;
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(), true /* relayout */);
}
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
- return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory());
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(),
+ false /* relayout */);
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, boolean relayout) {
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(), relayout);
}
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
MaximizeMenuFactory maximizeMenuFactory) {
+ return createWindowDecoration(taskInfo, maximizeMenuFactory, false /* relayout */);
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ MaximizeMenuFactory maximizeMenuFactory,
+ boolean relayout) {
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mContext, mMockDisplayController, mMockSplitScreenController,
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor,
mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
mMockGenericLinksParser, SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory);
+ mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory,
+ mMockMultiInstanceHelper);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
windowDecor.mDecorWindowContext = mContext;
+ if (relayout) {
+ windowDecor.relayout(taskInfo);
+ }
return windowDecor;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 6a94cd8..77337a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -25,6 +25,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.annotation.NonNull;
+import android.content.Context;
import android.graphics.Point;
import android.graphics.Region;
import android.platform.test.annotations.DisableFlags;
@@ -36,6 +37,7 @@
import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
+import com.android.wm.shell.ShellTestCase;
import com.google.common.testing.EqualsTester;
@@ -51,7 +53,7 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class DragResizeWindowGeometryTests {
+public class DragResizeWindowGeometryTests extends ShellTestCase {
private static final Size TASK_SIZE = new Size(500, 1000);
private static final int TASK_CORNER_RADIUS = 10;
private static final int EDGE_RESIZE_THICKNESS = 15;
@@ -107,7 +109,7 @@
@Test
public void testRegionUnionContainsEdges() {
Region region = new Region();
- GEOMETRY.union(region);
+ GEOMETRY.union(mContext, region);
assertThat(region.isComplex()).isTrue();
// Region excludes task area. Note that coordinates start from top left.
assertThat(region.contains(TASK_SIZE.getWidth() / 2, TASK_SIZE.getHeight() / 2)).isFalse();
@@ -147,10 +149,10 @@
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
Region region = new Region();
- GEOMETRY.union(region);
+ GEOMETRY.union(mContext, region);
final int cornerRadius = LARGE_CORNER_SIZE / 2;
- new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+ new TestPoints(mContext, TASK_SIZE, cornerRadius).validateRegion(region);
}
/**
@@ -161,10 +163,10 @@
@DisableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
Region region = new Region();
- GEOMETRY.union(region);
+ GEOMETRY.union(mContext, region);
final int cornerRadius = FINE_CORNER_SIZE / 2;
- new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+ new TestPoints(mContext, TASK_SIZE, cornerRadius).validateRegion(region);
}
@Test
@@ -186,16 +188,16 @@
}
private void validateCtrlTypeForEdges(boolean isTouchscreen, boolean isEdgeResizePermitted) {
- assertThat(GEOMETRY.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
LEFT_EDGE_POINT.x, LEFT_EDGE_POINT.y)).isEqualTo(
isEdgeResizePermitted ? CTRL_TYPE_LEFT : CTRL_TYPE_UNDEFINED);
- assertThat(GEOMETRY.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
TOP_EDGE_POINT.x, TOP_EDGE_POINT.y)).isEqualTo(
isEdgeResizePermitted ? CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
- assertThat(GEOMETRY.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
RIGHT_EDGE_POINT.x, RIGHT_EDGE_POINT.y)).isEqualTo(
isEdgeResizePermitted ? CTRL_TYPE_RIGHT : CTRL_TYPE_UNDEFINED);
- assertThat(GEOMETRY.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
BOTTOM_EDGE_POINT.x, BOTTOM_EDGE_POINT.y)).isEqualTo(
isEdgeResizePermitted ? CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED);
}
@@ -203,8 +205,9 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testCalculateControlType_edgeDragResizeEnabled_corners() {
- final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
- final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+ final TestPoints fineTestPoints = new TestPoints(mContext, TASK_SIZE, FINE_CORNER_SIZE / 2);
+ final TestPoints largeCornerTestPoints =
+ new TestPoints(mContext, TASK_SIZE, LARGE_CORNER_SIZE / 2);
// When the flag is enabled, points within fine corners should pass regardless of touch or
// not. Points outside fine corners should not pass when using a course input (non-touch).
@@ -241,8 +244,10 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testCalculateControlType_edgeDragResizeDisabled_corners() {
- final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
- final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+ final TestPoints fineTestPoints =
+ new TestPoints(mContext, TASK_SIZE, FINE_CORNER_SIZE / 2);
+ final TestPoints largeCornerTestPoints =
+ new TestPoints(mContext, TASK_SIZE, LARGE_CORNER_SIZE / 2);
// When the flag is disabled, points within fine corners should pass only from touchscreen.
// Edge resize permitted (indicating the event is from a cursor/stylus) should have no
@@ -284,6 +289,7 @@
* <p>Creates points that are both just within the bounds of each corner, and just outside.
*/
private static final class TestPoints {
+ private final Context mContext;
private final Point mTopLeftPoint;
private final Point mTopLeftPointOutside;
private final Point mTopRightPoint;
@@ -293,7 +299,8 @@
private final Point mBottomRightPoint;
private final Point mBottomRightPointOutside;
- TestPoints(@NonNull Size taskSize, int cornerRadius) {
+ TestPoints(@NonNull Context context, @NonNull Size taskSize, int cornerRadius) {
+ mContext = context;
// Point just inside corner square is included.
mTopLeftPoint = new Point(-cornerRadius + 1, -cornerRadius + 1);
// Point just outside corner square is excluded.
@@ -340,17 +347,17 @@
public void validateCtrlTypeForInnerPoints(@NonNull DragResizeWindowGeometry geometry,
boolean isTouchscreen, boolean isEdgeResizePermitted,
boolean expectedWithinGeometry) {
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mTopLeftPoint.x, mTopLeftPoint.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mTopRightPoint.x, mTopRightPoint.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mBottomLeftPoint.x, mBottomLeftPoint.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
: CTRL_TYPE_UNDEFINED);
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mBottomRightPoint.x, mBottomRightPoint.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
: CTRL_TYPE_UNDEFINED);
@@ -363,17 +370,17 @@
public void validateCtrlTypeForOutsidePoints(@NonNull DragResizeWindowGeometry geometry,
boolean isTouchscreen, boolean isEdgeResizePermitted,
boolean expectedWithinGeometry) {
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mTopLeftPointOutside.x, mTopLeftPointOutside.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mTopRightPointOutside.x, mTopRightPointOutside.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mBottomLeftPointOutside.x, mBottomLeftPointOutside.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
: CTRL_TYPE_UNDEFINED);
- assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted,
+ assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
mBottomRightPointOutside.x, mBottomRightPointOutside.y)).isEqualTo(
expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
: CTRL_TYPE_UNDEFINED);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index ed43aa3..a1c7947 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -57,6 +57,7 @@
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
/**
@@ -109,6 +110,9 @@
whenever(mockDesktopWindowDecoration.addWindow(
anyInt(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
).thenReturn(mockAdditionalViewHostViewContainer)
+ whenever(mockDesktopWindowDecoration.addWindow(
+ any<View>(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockAdditionalViewHostViewContainer)
whenever(mockAdditionalViewHostViewContainer.view).thenReturn(menuView)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
@@ -226,13 +230,13 @@
}
else -> error("Invalid windowing mode")
}
- val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
- onClickListener, onTouchListener, appIcon, appName, displayController,
- splitScreenController, shouldShowWindowingPill = true,
- null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
- captionX = captionX
+ val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId, appIcon, appName,
+ splitScreenController, shouldShowWindowingPill = true,
+ shouldShowNewWindowButton = true,
+ null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ captionX = captionX
)
- handleMenu.show()
+ handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock())
return handleMenu
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index a75aeaf..d6e19a6 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -104,7 +104,6 @@
scrollable(Screen.MultipleCredentialsScreenFlatten.route) {
MultiCredentialsFlattenScreen(
credentialSelectorUiState = (remember { uiState } as MultipleEntry),
- columnState = it.columnState,
flowEngine = flowEngine,
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 25bc381..e58de64 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -19,10 +19,11 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Icon
import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.LockOpen
@@ -62,7 +63,7 @@
WearButtonText(
text = label,
textAlign = textAlign,
- maxLines = if (secondaryLabel != null) 1 else 2
+ maxLines = 2
)
},
secondaryLabel,
@@ -88,7 +89,13 @@
) {
val labelParam: (@Composable RowScope.() -> Unit) =
{
- text()
+ var horizontalArrangement = Arrangement.Start
+ if (icon == null) {
+ horizontalArrangement = Arrangement.Center
+ }
+ Row(horizontalArrangement = horizontalArrangement, modifier = modifier.fillMaxWidth()) {
+ text()
+ }
}
val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
@@ -97,6 +104,7 @@
Row {
WearSecondaryLabel(
text = secondaryLabel,
+ color = WearMaterialTheme.colors.onSurfaceVariant
)
if (isAuthenticationEntryLocked != null) {
@@ -178,6 +186,7 @@
WearButtonText(
text = stringResource(R.string.dialog_continue_button),
textAlign = TextAlign.Center,
+ color = WearMaterialTheme.colors.surface,
)
},
colors = ChipDefaults.primaryChipColors(),
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
index 0afef5e..a82360b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
@@ -16,9 +16,11 @@
package com.android.credentialmanager.ui.components
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
@@ -36,26 +38,30 @@
icon: Drawable?,
title: String,
) {
- Column(
- modifier = Modifier,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- if (icon != null) {
- Icon(
- bitmap = icon.toBitmap().asImageBitmap(),
- modifier = Modifier.size(24.dp),
- // Decorative purpose only.
- contentDescription = null,
- tint = Color.Unspecified,
+ Row {
+ Spacer(Modifier.weight(0.073f)) // 7.3% side margin
+ Column(
+ modifier = Modifier.weight(0.854f).fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (icon != null) {
+ Icon(
+ bitmap = icon.toBitmap().asImageBitmap(),
+ modifier = Modifier.size(24.dp),
+ // Decorative purpose only.
+ contentDescription = null,
+ tint = Color.Unspecified,
+ )
+ }
+ Spacer(modifier = Modifier.size(8.dp))
+
+ WearTitleText(
+ text = title,
)
+
+ Spacer(modifier = Modifier.size(8.dp))
}
- Spacer(modifier = Modifier.size(8.dp))
-
- WearTitleText(
- text = title,
- )
-
- Spacer(modifier = Modifier.size(8.dp))
+ Spacer(Modifier.weight(0.073f)) // 7.3% side margin
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
index 282fea0..a7b13ad 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.common.ui.components
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Text
@@ -43,7 +44,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = WearMaterialTheme.colors.onSurfaceVariant,
+ color = WearMaterialTheme.colors.onSurface,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
@@ -60,7 +61,7 @@
Text(
modifier = modifier.padding(start = 8.dp, end = 8.dp).wrapContentSize(),
text = text,
- color = WearMaterialTheme.colors.onSurfaceVariant,
+ color = WearMaterialTheme.colors.onSurface,
style = WearMaterialTheme.typography.caption1,
overflow = TextOverflow.Ellipsis,
textAlign = textAlign,
@@ -91,12 +92,13 @@
@Composable
fun WearSecondaryLabel(
text: String,
- modifier: Modifier = Modifier,
+ color: Color = WearMaterialTheme.colors.onSurface,
+ modifier: Modifier = Modifier
) {
Text(
- modifier = modifier.wrapContentSize(),
+ modifier = modifier.fillMaxSize(),
text = text,
- color = WearMaterialTheme.colors.onSurfaceVariant,
+ color = color,
style = WearMaterialTheme.typography.caption1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Start,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index 36e9792..2af5be8 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -15,14 +15,18 @@
*/
package com.android.credentialmanager.ui.screens.multiple
-import com.android.credentialmanager.ui.components.CredentialsScreenChip
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import com.android.credentialmanager.ui.components.CredentialsScreenChip
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.Alignment
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
@@ -32,13 +36,13 @@
import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
-import com.google.android.horologist.compose.layout.ScalingLazyColumnState
+import com.google.android.horologist.compose.layout.rememberColumnState
+import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
/**
* Screen that shows multiple credentials to select from, grouped by accounts
*
* @param credentialSelectorUiState The app bar view model.
- * @param columnState ScalingLazyColumn configuration to be be applied
* @param modifier styling for composable
* @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@@ -46,28 +50,45 @@
@Composable
fun MultiCredentialsFlattenScreen(
credentialSelectorUiState: MultipleEntry,
- columnState: ScalingLazyColumnState,
flowEngine: FlowEngine,
) {
val selectEntry = flowEngine.getEntrySelector()
- ScalingLazyColumn(
- columnState = columnState,
- modifier = Modifier.fillMaxSize(),
- ) {
+ Row {
+ Spacer(Modifier.weight(0.052f)) // 5.2% side margin
+ ScalingLazyColumn(
+ columnState = rememberColumnState(
+ ScalingLazyColumnDefaults.belowTimeText(horizontalAlignment = Alignment.Start),
+ ),
+ modifier = Modifier.weight(0.896f).fillMaxSize(), // 5.2% side margin
+ ) {
+
item {
- // make this credential specific if all credentials are same
- WearButtonText(
- text = stringResource(R.string.sign_in_options_title),
- textAlign = TextAlign.Start,
- )
+ Row {
+ Spacer(Modifier.weight(0.073f)) // 7.3% side margin
+ WearButtonText(
+ text = stringResource(R.string.sign_in_options_title),
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(0.854f).fillMaxSize(),
+ )
+ Spacer(Modifier.weight(0.073f)) // 7.3% side margin
+ }
}
credentialSelectorUiState.accounts.forEach { userNameEntries ->
item {
- WearSecondaryLabel(
- text = userNameEntries.userName,
- modifier = Modifier.padding(top = 12.dp, bottom = 4.dp)
- )
+ Row {
+ Spacer(Modifier.weight(0.0624f)) // 6.24% side margin
+ WearSecondaryLabel(
+ text = userNameEntries.userName,
+ modifier = Modifier.padding(
+ top = 12.dp,
+ bottom = 4.dp,
+ start = 0.dp,
+ end = 0.dp
+ ).fillMaxWidth(0.87f)
+ )
+ Spacer(Modifier.weight(0.0624f)) // 6.24% side margin
+ }
}
userNameEntries.sortedCredentialEntryList.forEach { credential: CredentialEntryInfo ->
@@ -78,7 +99,7 @@
secondaryLabel =
credential.credentialTypeDisplayName.ifEmpty {
credential.providerDisplayName
- },
+ },
icon = credential.icon,
textAlign = TextAlign.Start
)
@@ -87,14 +108,25 @@
}
}
}
- item {
- WearSecondaryLabel(
- text = stringResource(R.string.provider_list_title),
- modifier = Modifier.padding(top = 12.dp, bottom = 4.dp)
- )
- }
- credentialSelectorUiState.actionEntryList.forEach { actionEntry ->
+
+ if (credentialSelectorUiState.actionEntryList.isNotEmpty()) {
item {
+ Row {
+ Spacer(Modifier.weight(0.0624f)) // 6.24% side margin
+ WearSecondaryLabel(
+ text = stringResource(R.string.provider_list_title),
+ modifier = Modifier.padding(
+ top = 12.dp,
+ bottom = 4.dp,
+ start = 0.dp,
+ end = 0.dp
+ ).fillMaxWidth(0.87f)
+ )
+ Spacer(Modifier.weight(0.0624f)) // 6.24% side margin
+ }
+ }
+ credentialSelectorUiState.actionEntryList.forEach { actionEntry ->
+ item {
CredentialsScreenChip(
label = actionEntry.title,
onClick = { selectEntry(actionEntry, false) },
@@ -102,7 +134,10 @@
icon = actionEntry.icon,
)
CredentialsScreenChipSpacer()
+ }
}
}
}
+ Spacer(Modifier.weight(0.052f)) // 5.2% side margin
+ }
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index ce2bad0..38307b0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -16,10 +16,12 @@
package com.android.credentialmanager.ui.screens.multiple
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.Row
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.foundation.layout.fillMaxSize
import com.android.credentialmanager.R
import androidx.compose.ui.res.stringResource
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -53,29 +55,31 @@
flowEngine: FlowEngine,
) {
val selectEntry = flowEngine.getEntrySelector()
- ScalingLazyColumn(
- columnState = columnState,
- modifier = Modifier.fillMaxSize(),
- ) {
- // flatten all credentials into one
- val credentials = credentialSelectorUiState.sortedEntries
- item {
- var title = stringResource(R.string.choose_sign_in_title)
- if (credentials.isNotEmpty()) {
- if (credentials.all { it.credentialType == CredentialType.PASSKEY }) {
- title = stringResource(R.string.choose_passkey_title)
- } else if (credentials.all { it.credentialType == CredentialType.PASSWORD }) {
- title = stringResource(R.string.choose_password_title)
+ Row {
+ Spacer(Modifier.weight(0.052f)) // 5.2% side margin
+ ScalingLazyColumn(
+ columnState = columnState,
+ modifier = Modifier.weight(0.896f).fillMaxSize(),
+ ) {
+ // flatten all credentials into one
+ val credentials = credentialSelectorUiState.sortedEntries
+ item {
+ var title = stringResource(R.string.choose_sign_in_title)
+ if (credentials.isNotEmpty()) {
+ if (credentials.all { it.credentialType == CredentialType.PASSKEY }) {
+ title = stringResource(R.string.choose_passkey_title)
+ } else if (credentials.all { it.credentialType == CredentialType.PASSWORD }) {
+ title = stringResource(R.string.choose_password_title)
+ }
}
+
+ SignInHeader(
+ icon = credentialSelectorUiState.icon,
+ title = title,
+ )
}
- SignInHeader(
- icon = credentialSelectorUiState.icon,
- title = title,
- )
- }
-
- credentials.forEach { credential: CredentialEntryInfo ->
+ credentials.forEach { credential: CredentialEntryInfo ->
item {
CredentialsScreenChip(
label = credential.userName,
@@ -85,29 +89,32 @@
credential.providerDisplayName
},
icon = credential.icon,
+ textAlign = TextAlign.Start
)
CredentialsScreenChipSpacer()
}
}
- credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
- item {
- LockedProviderChip(authenticationEntryInfo) {
- selectEntry(authenticationEntryInfo, false)
+ credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
+ item {
+ LockedProviderChip(authenticationEntryInfo) {
+ selectEntry(authenticationEntryInfo, false)
+ }
+ CredentialsScreenChipSpacer()
}
- CredentialsScreenChipSpacer()
+ }
+ item {
+ Spacer(modifier = Modifier.size(8.dp))
+ }
+
+ item {
+ SignInOptionsChip { flowEngine.openSecondaryScreen() }
+ }
+ item {
+ DismissChip { flowEngine.cancel() }
+ BottomSpacer()
}
}
- item {
- Spacer(modifier = Modifier.size(8.dp))
+ Spacer(Modifier.weight(0.052f)) // 5.2% side margin
}
-
- item {
- SignInOptionsChip { flowEngine.openSecondaryScreen() }
- }
- item {
- DismissChip { flowEngine.cancel() }
- BottomSpacer()
- }
- }
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
index e94dd68..17dd962 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
@@ -18,6 +18,8 @@
package com.android.credentialmanager.ui.screens.single
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -31,15 +33,18 @@
headerContent: @Composable () -> Unit,
accountContent: @Composable () -> Unit,
columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
content: ScalingLazyListScope.() -> Unit,
) {
- ScalingLazyColumn(
- columnState = columnState,
- modifier = modifier.fillMaxSize(),
- ) {
- item { headerContent() }
- item { accountContent() }
- content()
+ Row {
+ Spacer(Modifier.weight(0.052f)) // 5.2% side margin
+ ScalingLazyColumn(
+ columnState = columnState,
+ modifier = Modifier.weight(0.896f).fillMaxSize(),
+ ) {
+ item { headerContent() }
+ item { accountContent() }
+ content()
+ }
+ Spacer(Modifier.weight(0.052f)) // 5.2% side margin
}
}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index 03608a4..ce243b0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -19,11 +19,8 @@
package com.android.credentialmanager.ui.screens.single.passkey
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.R
@@ -75,7 +72,6 @@
}
},
columnState = columnState,
- modifier = Modifier.padding(horizontal = 10.dp)
) {
item {
val selectEntry = flowEngine.getEntrySelector()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 818723b..5bc4796 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -19,11 +19,8 @@
package com.android.credentialmanager.ui.screens.single.password
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
import com.android.credentialmanager.ui.components.PasswordRow
@@ -67,7 +64,6 @@
)
},
columnState = columnState,
- modifier = Modifier.padding(horizontal = 10.dp)
) {
item {
Column {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
index 34d6e97..fd0fc8c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -17,10 +17,7 @@
package com.android.credentialmanager.ui.screens.single.signInWithProvider
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.AccountRow
@@ -47,7 +44,6 @@
fun SignInWithProviderScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
flowEngine: FlowEngine,
) {
SingleAccountScreen(
@@ -72,7 +68,6 @@
}
},
columnState = columnState,
- modifier = modifier.padding(horizontal = 10.dp)
) {
item {
val selectEntry = flowEngine.getEntrySelector()
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
index 6e53012..062e9b8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
@@ -36,7 +36,7 @@
* Enable Edge to Edge and handle overlaps using insets. It should be called before
* setContentView.
*/
- static void enable(@NonNull ComponentActivity activity) {
+ public static void enable(@NonNull ComponentActivity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
return;
}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 83a986b1..754d9423 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -9,16 +9,6 @@
}
flag {
- name: "enable_cached_bluetooth_device_dedup"
- namespace: "bluetooth"
- description: "Enable dedup in CachedBluetoothDevice"
- bug: "319197962"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "bluetooth_qs_tile_dialog_auto_on_toggle"
namespace: "bluetooth"
description: "Displays the auto on toggle in the bluetooth QS tile dialog"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5c4cdb2..687c728 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -881,6 +881,11 @@
<!-- UI debug setting: show physical key presses summary [CHAR LIMIT=150] -->
<string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+ <!-- UI debug setting: Title text for a debug setting that enables a visualization of touchpad input in a window on the screen [CHAR LIMIT=50] -->
+ <string name="touchpad_visualizer">Show touchpad input</string>
+ <!-- UI debug setting: Summary text for a debug setting that enables a visualization of touchpad input in a window on the screen [CHAR LIMIT=150] -->
+ <string name="touchpad_visualizer_summary">Screen overlay displaying touchpad input data and recognized gestures</string>
+
<!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
<string name="show_screen_updates">Show surface updates</string>
<!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index e926b16..0dc772a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -16,8 +16,6 @@
package com.android.settingslib.bluetooth;
-import static com.android.settingslib.flags.Flags.enableCachedBluetoothDeviceDedup;
-
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -403,7 +401,7 @@
cachedDevice = mDeviceManager.addDevice(device);
}
- if (enableCachedBluetoothDeviceDedup() && bondState == BluetoothDevice.BOND_BONDED) {
+ if (bondState == BluetoothDevice.BOND_BONDED) {
mDeviceManager.removeDuplicateInstanceForIdentityAddress(device);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 2573907..de60fdc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -578,7 +578,7 @@
}
/**
- * Check if {@link CachedBluetoothDevice} has connected to a broadcast source.
+ * Check if {@link CachedBluetoothDevice} (lead or member) has connected to a broadcast source.
*
* @param cachedDevice The cached bluetooth device to check.
* @param localBtManager The BT manager to provide BT functions.
@@ -586,20 +586,10 @@
*/
@WorkerThread
public static boolean hasConnectedBroadcastSource(
- CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
- if (localBtManager == null) {
- Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
- return false;
- }
- LocalBluetoothLeBroadcastAssistant assistant =
- localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- if (assistant == null) {
- Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
- return false;
- }
- List<BluetoothLeBroadcastReceiveState> sourceList =
- assistant.getAllSources(cachedDevice.getDevice());
- if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) {
+ @Nullable CachedBluetoothDevice cachedDevice,
+ @Nullable LocalBluetoothManager localBtManager) {
+ if (cachedDevice == null) return false;
+ if (hasConnectedBroadcastSourceForBtDevice(cachedDevice.getDevice(), localBtManager)) {
Log.d(
TAG,
"Lead device has connected broadcast source, device = "
@@ -608,9 +598,7 @@
}
// Return true if member device is in broadcast.
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
- List<BluetoothLeBroadcastReceiveState> list =
- assistant.getAllSources(device.getDevice());
- if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) {
+ if (hasConnectedBroadcastSourceForBtDevice(device.getDevice(), localBtManager)) {
Log.d(
TAG,
"Member device has connected broadcast source, device = "
@@ -621,6 +609,28 @@
return false;
}
+ /**
+ * Check if {@link BluetoothDevice} has connected to a broadcast source.
+ *
+ * @param device The bluetooth device to check.
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return Whether the device has connected to a broadcast source.
+ */
+ @WorkerThread
+ public static boolean hasConnectedBroadcastSourceForBtDevice(
+ @Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager == null
+ ? null
+ : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (device == null || assistant == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(device);
+ return !sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected);
+ }
+
/** Checks the connectivity status based on the provided broadcast receive state. */
@WorkerThread
public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
@@ -805,8 +815,10 @@
ComponentName exclusiveManagerComponent =
ComponentName.unflattenFromString(exclusiveManagerName);
- String exclusiveManagerPackage = exclusiveManagerComponent != null
- ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName;
+ String exclusiveManagerPackage =
+ exclusiveManagerComponent != null
+ ? exclusiveManagerComponent.getPackageName()
+ : exclusiveManagerName;
if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) {
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
index d36e2fe..f533c95 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -28,6 +28,7 @@
import android.util.Log;
import androidx.annotation.DrawableRes;
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
@@ -61,6 +62,12 @@
return sInstance;
}
+ /** Replaces the singleton instance of {@link ZenModesBackend} by the provided one. */
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public static void setInstance(@Nullable ZenModesBackend backend) {
+ sInstance = backend;
+ }
+
ZenModesBackend(Context context) {
mContext = context;
mNotificationManager = context.getSystemService(NotificationManager.class);
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
index d69c87b..4b6bcf9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.os.OutcomeReceiver
import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.Log
import android.view.WindowManager
import androidx.lifecycle.LifecycleOwner
@@ -31,12 +32,19 @@
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeoutException
import kotlin.coroutines.resume
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOn
/** A util for Satellite dialog */
object SatelliteDialogUtils {
@@ -70,7 +78,7 @@
coroutineScope.launch {
var isSatelliteModeOn = false
try {
- isSatelliteModeOn = requestIsEnabled(context)
+ isSatelliteModeOn = requestIsSessionStarted(context)
} catch (e: InterruptedException) {
Log.w(TAG, "Error to get satellite status : $e")
} catch (e: ExecutionException) {
@@ -118,19 +126,107 @@
}
suspendCancellableCoroutine {continuation ->
- satelliteManager?.requestIsEnabled(Default.asExecutor(),
- object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
- override fun onResult(result: Boolean) {
- Log.i(TAG, "Satellite modem enabled status: $result")
- continuation.resume(result)
- }
+ try {
+ satelliteManager?.requestIsEnabled(Default.asExecutor(),
+ object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+ override fun onResult(result: Boolean) {
+ Log.i(TAG, "Satellite modem enabled status: $result")
+ continuation.resume(result)
+ }
- override fun onError(error: SatelliteManager.SatelliteException) {
- super.onError(error)
- Log.w(TAG, "Can't get satellite modem enabled status", error)
- continuation.resume(false)
- }
- })
+ override fun onError(error: SatelliteManager.SatelliteException) {
+ super.onError(error)
+ Log.w(TAG, "Can't get satellite modem enabled status", error)
+ continuation.resume(false)
+ }
+ })
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ continuation.resume(false)
+ }
+ }
+ }
+
+ private suspend fun requestIsSessionStarted(
+ context: Context
+ ): Boolean = withContext(Default) {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return@withContext false
+ }
+
+ getIsSessionStartedFlow(context).conflate().first()
+ }
+
+ /**
+ * Provides a Flow that emits the session state of the satellite modem. Updates are triggered
+ * when the modem state changes.
+ *
+ * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`).
+ * @return A Flow emitting `true` when the session is started and `false` otherwise.
+ */
+ private fun getIsSessionStartedFlow(
+ context: Context
+ ): Flow<Boolean> {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return flowOf(false)
+ }
+
+ return callbackFlow {
+ val callback = SatelliteModemStateCallback { state ->
+ val isSessionStarted = isSatelliteSessionStarted(state)
+ Log.i(TAG, "Satellite modem state changed: state=$state"
+ + ", isSessionStarted=$isSessionStarted")
+ trySend(isSessionStarted)
+ }
+
+ var registerResult = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
+ try {
+ registerResult = satelliteManager.registerForModemStateChanged(
+ Default.asExecutor(),
+ callback
+ )
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ }
+
+
+ if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+ // If the registration failed (e.g., device doesn't support satellite),
+ // SatelliteManager will not emit the current state by callback.
+ // We send `false` value by ourself to make sure the flow has initial value.
+ Log.w(TAG, "Failed to register for satellite modem state change: $registerResult")
+ trySend(false)
+ }
+
+ awaitClose {
+ try {
+ satelliteManager.unregisterForModemStateChanged(callback)
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ }
+ }
+ }.flowOn(Default)
+ }
+
+
+ /**
+ * Check if the modem is in a satellite session.
+ *
+ * @param state The SatelliteModemState provided by the SatelliteManager.
+ * @return `true` if the modem is in a satellite session, `false` otherwise.
+ */
+ fun isSatelliteSessionStarted(@SatelliteManager.SatelliteModemState state: Int): Boolean {
+ return when (state) {
+ SatelliteManager.SATELLITE_MODEM_STATE_OFF,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN -> false
+ else -> true
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 370d568..4551f1e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -56,7 +56,10 @@
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
@RunWith(RobolectricTestRunner.class)
public class BluetoothUtilsTest {
@@ -557,8 +560,14 @@
}
@Test
- public void testHasConnectedBroadcastSource_deviceConnectedToBroadcastSource() {
+ public void testHasConnectedBroadcastSource_leadDeviceConnectedToBroadcastSource() {
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
@@ -566,7 +575,8 @@
List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
sourceList.add(mLeBroadcastReceiveState);
- when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(Collections.emptyList());
assertThat(
BluetoothUtils.hasConnectedBroadcastSource(
@@ -575,6 +585,79 @@
}
@Test
+ public void testHasConnectedBroadcastSource_memberDeviceConnectedToBroadcastSource() {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
+
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(Collections.emptyList());
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSource_deviceNotConnectedToBroadcastSource() {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+ List<Long> bisSyncState = new ArrayList<>();
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSourceForBtDevice_deviceConnectedToBroadcastSource() {
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSourceForBtDevice_deviceNotConnectedToBroadcastSource() {
+ List<Long> bisSyncState = new ArrayList<>();
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
@@ -604,9 +687,19 @@
}
@Test
- public void getSecondaryDeviceForBroadcast_errorState_returnNull() {
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
- mLocalBluetoothManager))
+ public void getSecondaryDeviceForBroadcast_noBroadcast_returnNull() {
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
+ .isNull();
+ }
+
+ @Test
+ public void getSecondaryDeviceForBroadcast_nullProfile_returnNull() {
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
.isNull();
}
@@ -616,6 +709,7 @@
mContext.getContentResolver(),
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
1);
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager);
when(deviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
@@ -625,8 +719,9 @@
when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of(state));
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mBluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
- mLocalBluetoothManager))
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
.isNull();
}
@@ -636,6 +731,7 @@
mContext.getContentResolver(),
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
1);
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager);
CachedBluetoothDevice cachedBluetoothDevice = mock(CachedBluetoothDevice.class);
@@ -655,8 +751,9 @@
when(mAssistant.getAllConnectedDevices())
.thenReturn(ImmutableList.of(mBluetoothDevice, bluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
- mLocalBluetoothManager))
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
.isEqualTo(mCachedBluetoothDevice);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
index aeda1ed6..2078b36 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -17,11 +17,12 @@
package com.android.settingslib.satellite
import android.content.Context
-import android.content.Intent
-import android.os.OutcomeReceiver
import android.platform.test.annotations.RequiresFlagsEnabled
import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
+import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.AndroidRuntimeException
import androidx.test.core.app.ApplicationProvider
import com.android.internal.telephony.flags.Flags
@@ -67,26 +68,19 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onResult(true)
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(1)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_ENABLING_SATELLITE)
null
}
try {
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertTrue(it)
- })
+ assertTrue(it)
+ })
} catch (e: AndroidRuntimeException) {
// Catch exception of starting activity .
}
@@ -95,68 +89,61 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onResult(false)
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(1)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_OFF)
null
}
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
- `when`(context.getSystemService(SatelliteManager::class.java))
- .thenReturn(null)
+ `when`(context.getSystemService(SatelliteManager::class.java)).thenReturn(null)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
- .thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
- null
- }
-
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenReturn(SATELLITE_RESULT_MODEM_ERROR)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_phoneCrash_notShowWarningDialog() = runBlocking {
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenThrow(IllegalStateException("Telephony is null!!!"))
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any())
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 7b927d7..2823277 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -200,6 +200,7 @@
VALIDATORS.put(System.POINTER_LOCATION, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.SHOW_TOUCHES, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.SHOW_KEY_PRESSES, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.TOUCHPAD_VISUALIZER, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.SHOW_ROTARY_INPUT, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WINDOW_ORIENTATION_LISTENER_LOG, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCKSCREEN_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index cd37ad1..3c24f5c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2831,6 +2831,9 @@
Settings.System.SHOW_KEY_PRESSES,
SystemSettingsProto.DevOptions.SHOW_KEY_PRESSES);
dumpSetting(s, p,
+ Settings.System.TOUCHPAD_VISUALIZER,
+ SystemSettingsProto.DevOptions.TOUCHPAD_VISUALIZER);
+ dumpSetting(s, p,
Settings.System.POINTER_LOCATION,
SystemSettingsProto.DevOptions.POINTER_LOCATION);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 411decd..8c96484 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -918,6 +918,7 @@
Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
Settings.System.SHOW_TOUCHES,
Settings.System.SHOW_KEY_PRESSES,
+ Settings.System.TOUCHPAD_VISUALIZER,
Settings.System.SHOW_ROTARY_INPUT,
Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
Settings.System.SIP_ALWAYS, // value, not a setting
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 0dd9275..8c3cce4 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -255,8 +255,8 @@
p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
}
} else {
- // Make sure intents don't inject HTML elements.
- p.mTitle = Html.escapeHtml(p.mTitle.toString());
+ // Make sure intents don't inject spannable elements.
+ p.mTitle = p.mTitle.toString();
}
setupAlert();
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index bd1a442..e81d5d5 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -4,13 +4,6 @@
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
flag {
- name: "bp_talkback"
- namespace: "biometrics_framework"
- description: "Adds talkback directional guidance when using UDFPS with biometric prompt"
- bug: "310044658"
-}
-
-flag {
name: "constraint_bp"
namespace: "biometrics_framework"
description: "Refactors Biometric Prompt to use a ConstraintLayout"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0d337eb..0cbaf29 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -392,6 +392,17 @@
}
flag {
+ name: "status_bar_use_repos_for_call_chip"
+ namespace: "systemui"
+ description: "Use repositories as the source of truth for call notifications shown as a chip in"
+ "the status bar"
+ bug: "328584859"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index cce0600..d4bad23 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -36,7 +36,7 @@
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
@@ -62,7 +62,7 @@
object Communal {
object Elements {
- val Scrim = ElementKey("Scrim", scenePicker = LowestZIndexScenePicker)
+ val Scrim = ElementKey("Scrim", contentPicker = LowestZIndexContentPicker)
val Grid = ElementKey("CommunalContent")
val LockIcon = ElementKey("CommunalLockIcon")
val IndicationArea = ElementKey("CommunalIndicationArea")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 6feaf6d..9c72d93 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -71,7 +71,7 @@
applyPadding: Boolean,
modifier: Modifier = Modifier,
) {
- MovableElement(
+ Element(
key = if (isStart) StartButtonElementKey else EndButtonElementKey,
modifier = modifier,
) {
@@ -98,7 +98,7 @@
fun SceneScope.IndicationArea(
modifier: Modifier = Modifier,
) {
- MovableElement(
+ Element(
key = IndicationAreaElementKey,
modifier = modifier.indicationAreaPadding(),
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 218779d..bcdb259 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -120,7 +120,7 @@
)
}
- MovableElement(key = largeClockElementKey, modifier = modifier) {
+ Element(key = largeClockElementKey, modifier = modifier) {
content {
AndroidView(
factory = { context ->
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 44bda95..33ed14b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -65,68 +65,74 @@
) {
val resources = LocalContext.current.resources
- MovableElement(key = ClockElementKeys.smartspaceElementKey, modifier = modifier) {
- Column(
- modifier =
- modifier
- .onTopPlacementChanged(onTopChanged)
- .padding(
- top = { lockscreenContentViewModel.getSmartSpacePaddingTop(resources) },
- bottom = {
- resources.getDimensionPixelSize(
- R.dimen.keyguard_status_view_bottom_margin
- )
- }
- )
- ) {
- if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
- return@Column
- }
+ Element(key = ClockElementKeys.smartspaceElementKey, modifier = modifier) {
+ content {
+ Column(
+ modifier =
+ modifier
+ .onTopPlacementChanged(onTopChanged)
+ .padding(
+ top = {
+ lockscreenContentViewModel.getSmartSpacePaddingTop(resources)
+ },
+ bottom = {
+ resources.getDimensionPixelSize(
+ R.dimen.keyguard_status_view_bottom_margin
+ )
+ }
+ )
+ ) {
+ if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
+ return@Column
+ }
- val paddingBelowClockStart = dimensionResource(R.dimen.below_clock_padding_start)
- val paddingBelowClockEnd = dimensionResource(R.dimen.below_clock_padding_end)
+ val paddingBelowClockStart =
+ dimensionResource(R.dimen.below_clock_padding_start)
+ val paddingBelowClockEnd = dimensionResource(R.dimen.below_clock_padding_end)
- if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
+ if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.fillMaxWidth()
+ // All items will be constrained to be as tall as the shortest
+ // item.
+ .height(IntrinsicSize.Min)
+ .padding(
+ start = paddingBelowClockStart,
+ ),
+ ) {
+ Date(
+ modifier =
+ Modifier.burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
+ )
+ Spacer(modifier = Modifier.width(4.dp))
+ Weather(
+ modifier =
+ Modifier.burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
+ )
+ }
+ }
+
+ Card(
modifier =
Modifier.fillMaxWidth()
- // All items will be constrained to be as tall as the shortest item.
- .height(IntrinsicSize.Min)
.padding(
start = paddingBelowClockStart,
- ),
- ) {
- Date(
- modifier =
- Modifier.burnInAware(
+ end = paddingBelowClockEnd,
+ )
+ .burnInAware(
viewModel = aodBurnInViewModel,
params = burnInParams,
),
- )
- Spacer(modifier = Modifier.width(4.dp))
- Weather(
- modifier =
- Modifier.burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- ),
- )
- }
+ )
}
-
- Card(
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- start = paddingBelowClockStart,
- end = paddingBelowClockEnd,
- )
- .burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- ),
- )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
index 9a82da2..2e39524 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
@@ -128,7 +128,7 @@
elementKey: ElementKey,
modifier: Modifier = Modifier,
) {
- MovableElement(key = elementKey, modifier) {
+ Element(key = elementKey, modifier) {
content {
AndroidView(
factory = {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index f8bd633..26ab10b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -28,7 +28,7 @@
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -38,7 +38,10 @@
object MediaCarousel {
object Elements {
internal val Content =
- ElementKey(debugName = "MediaCarouselContent", scenePicker = MediaScenePicker)
+ MovableElementKey(
+ debugName = "MediaCarouselContent",
+ contentPicker = MediaContentPicker,
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
similarity index 62%
rename from packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
index 7b497e8..3f04f37 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
@@ -16,18 +16,19 @@
package com.android.systemui.media.controls.ui.composable
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementContentPicker
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementScenePicker
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutState
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.StaticElementContentPicker
+import com.android.compose.animation.scene.content.state.ContentState
import com.android.systemui.scene.shared.model.Scenes
-/** [ElementScenePicker] implementation for the media carousel object. */
-object MediaScenePicker : ElementScenePicker {
+/** [ElementContentPicker] implementation for the media carousel object. */
+object MediaContentPicker : StaticElementContentPicker {
const val SHADE_FRACTION = 0.66f
- private val scenes =
+ override val contents =
setOf(
Scenes.Lockscreen,
Scenes.Shade,
@@ -36,12 +37,12 @@
Scenes.Communal
)
- override fun sceneDuringTransition(
+ override fun contentDuringTransition(
element: ElementKey,
- transition: TransitionState.Transition,
- fromSceneZIndex: Float,
- toSceneZIndex: Float
- ): SceneKey? {
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float
+ ): ContentKey {
return when {
shouldElevateMedia(transition) -> {
Scenes.Shade
@@ -52,22 +53,23 @@
transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
Scenes.QuickSettings
}
+ transition.toContent in contents -> transition.toContent
else -> {
- when {
- scenes.contains(transition.toScene) -> transition.toScene
- scenes.contains(transition.fromScene) -> transition.fromScene
- else -> null
+ check(transition.fromContent in contents) {
+ "Media player should not be composed for the transition from " +
+ "${transition.fromContent} to ${transition.toContent}"
}
+ transition.fromContent
}
}
}
/** Returns true when the media should be laid on top of the rest for the given [transition]. */
- fun shouldElevateMedia(transition: TransitionState.Transition): Boolean {
+ fun shouldElevateMedia(transition: ContentState.Transition<*>): Boolean {
return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
}
}
-fun MediaScenePicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean {
+fun MediaContentPicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean {
return layoutState.currentTransition?.let { shouldElevateMedia(it) } ?: false
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 2eb7b3f..84782fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -78,7 +78,7 @@
import androidx.compose.ui.util.lerp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
@@ -105,7 +105,7 @@
val NotificationScrim = ElementKey("NotificationScrim")
val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder")
val HeadsUpNotificationPlaceholder =
- ElementKey("HeadsUpNotificationPlaceholder", scenePicker = LowestZIndexScenePicker)
+ ElementKey("HeadsUpNotificationPlaceholder", contentPicker = LowestZIndexContentPicker)
val ShelfSpace = ElementKey("ShelfSpace")
val NotificationStackCutoffGuideline = ElementKey("NotificationStackCutoffGuideline")
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 8058dcd..f399436 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -33,10 +33,11 @@
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.MovableElementScenePicker
+import com.android.compose.animation.scene.MovableElementContentPicker
+import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.ValueKey
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -55,7 +56,10 @@
object Elements {
val Content =
- ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES))
+ MovableElementKey(
+ "QuickSettingsContent",
+ contentPicker = MovableElementContentPicker(SCENES)
+ )
val QuickQuickSettings = ElementKey("QuickQuickSettings")
val SplitShadeQuickSettings = ElementKey("SplitShadeQuickSettings")
val FooterActions = ElementKey("QuickSettingsFooterActions")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 2800eee..cdcd840 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -68,11 +68,11 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index 2f8c248..a9da733 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -25,7 +25,7 @@
import com.android.compose.animation.scene.UserActionDistance
import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.media.controls.ui.composable.MediaScenePicker
+import com.android.systemui.media.controls.ui.composable.MediaContentPicker
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.Shade
@@ -54,7 +54,8 @@
fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
fractionRange(start = .33f) {
- val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION
+ val qsTranslation =
+ ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION
val qsExpansionDiff =
ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 7d46c75..21dfc49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -26,7 +26,7 @@
import com.android.compose.animation.scene.UserActionDistance
import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.media.controls.ui.composable.MediaScenePicker
+import com.android.systemui.media.controls.ui.composable.MediaContentPicker
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
@@ -62,7 +62,7 @@
fade(QuickSettings.Elements.FooterActions)
}
- val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION
+ val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION
val qsExpansionDiff =
ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 8656223..facbcaf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -49,7 +49,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.keyguard.ui.composable.LockscreenContent
@@ -186,10 +186,10 @@
object OverlayShade {
object Elements {
- val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker)
- val Panel = ElementKey("OverlayShadePanel", scenePicker = LowestZIndexScenePicker)
+ val Scrim = ElementKey("OverlayShadeScrim", contentPicker = LowestZIndexContentPicker)
+ val Panel = ElementKey("OverlayShadePanel", contentPicker = LowestZIndexContentPicker)
val PanelBackground =
- ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker)
+ ElementKey("OverlayShadePanelBackground", contentPicker = LowestZIndexContentPicker)
}
object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index b5a10ca..1cd48bf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -59,11 +59,11 @@
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateElementFloatAsState
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.settingslib.Utils
@@ -93,8 +93,8 @@
val ExpandedContent = ElementKey("ShadeHeaderExpandedContent")
val CollapsedContentStart = ElementKey("ShadeHeaderCollapsedContentStart")
val CollapsedContentEnd = ElementKey("ShadeHeaderCollapsedContentEnd")
- val PrivacyChip = ElementKey("PrivacyChip", scenePicker = LowestZIndexScenePicker)
- val Clock = ElementKey("ShadeHeaderClock", scenePicker = LowestZIndexScenePicker)
+ val PrivacyChip = ElementKey("PrivacyChip", contentPicker = LowestZIndexContentPicker)
+ val Clock = ElementKey("ShadeHeaderClock", contentPicker = LowestZIndexContentPicker)
val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup")
}
@@ -110,6 +110,7 @@
object Colors {
val ColorScheme.shadeHeaderText: Color
get() = Color.White
+
val ColorScheme.onScrimDim: Color
get() = Color.DarkGray
}
@@ -148,8 +149,8 @@
}
val isLargeScreenLayout =
- LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Medium ||
- LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded
+ LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Medium ||
+ LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
@@ -197,8 +198,8 @@
) {
if (isLargeScreenLayout) {
ShadeCarrierGroup(
- viewModel = viewModel,
- modifier = Modifier.align(Alignment.CenterVertically),
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.CenterVertically),
)
}
SystemIconContainer(
@@ -552,19 +553,20 @@
val interactionSource = remember { MutableInteractionSource() }
val isHovered by interactionSource.collectIsHoveredAsState()
- val hoverModifier = Modifier
- .clip(RoundedCornerShape(CollapsedHeight / 4))
+ val hoverModifier =
+ Modifier.clip(RoundedCornerShape(CollapsedHeight / 4))
.background(MaterialTheme.colorScheme.onScrimDim)
Row(
- modifier = modifier
+ modifier =
+ modifier
.height(CollapsedHeight)
.padding(vertical = CollapsedHeight / 4)
.thenIf(isClickable) {
Modifier.clickable(
- interactionSource = interactionSource,
- indication = null,
- onClick = { viewModel.onSystemIconContainerClicked() },
+ interactionSource = interactionSource,
+ indication = null,
+ onClick = { viewModel.onSystemIconContainerClicked() },
)
}
.thenIf(isHovered) { hoverModifier },
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 18ca0f7..77b48d3 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
@@ -63,14 +63,14 @@
import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
@@ -81,7 +81,7 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.media.controls.ui.composable.MediaScenePicker
+import com.android.systemui.media.controls.ui.composable.MediaContentPicker
import com.android.systemui.media.controls.ui.composable.shouldElevateMedia
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -118,7 +118,7 @@
object Elements {
val MediaCarousel = ElementKey("ShadeMediaCarousel")
val BackgroundScrim =
- ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
+ ElementKey("ShadeBackgroundScrim", contentPicker = LowestZIndexContentPicker)
val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn")
}
@@ -376,7 +376,7 @@
layout(constraints.maxWidth, constraints.maxHeight) {
val qsZIndex =
- if (MediaScenePicker.shouldElevateMedia(layoutState)) {
+ if (MediaContentPicker.shouldElevateMedia(layoutState)) {
1f
} else {
0f
@@ -563,7 +563,7 @@
mediaHost = mediaHost,
modifier =
Modifier.fillMaxWidth().thenIf(
- MediaScenePicker.shouldElevateMedia(layoutState)
+ MediaContentPicker.shouldElevateMedia(layoutState)
) {
Modifier.zIndex(1f)
},
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index afbc8e7..e13ca391 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -32,6 +32,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceIn
import androidx.compose.ui.util.fastLastOrNull
+import com.android.compose.animation.scene.content.state.TransitionState
import kotlin.math.roundToInt
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 82c85d1..1fc1f98 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -19,6 +19,7 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
+import com.android.compose.animation.scene.content.state.TransitionState
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 0f7e3eaf..a43028a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -30,8 +30,10 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.util.fastCoerceIn
-import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.animation.scene.content.Scene
+import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.ContentState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
@@ -608,7 +610,7 @@
var lastDistance: Float = DistanceUnspecified,
) :
TransitionState.Transition(_fromScene.key, _toScene.key, replacedTransition),
- TransitionState.HasOverscrollProperties {
+ ContentState.HasOverscrollProperties {
var _currentScene by mutableStateOf(_fromScene)
override val currentScene: SceneKey
get() = _currentScene.key
@@ -645,7 +647,7 @@
override val isInitiatedByUserInput = true
- override var bouncingScene: SceneKey? = null
+ override var bouncingContent: SceneKey? = null
/** The current offset caused by the drag gesture. */
var dragOffset by mutableFloatStateOf(0f)
@@ -764,7 +766,7 @@
animationSpec = swipeSpec,
initialVelocity = initialVelocity,
) {
- if (bouncingScene == null) {
+ if (bouncingContent == null) {
val isBouncing =
if (isTargetGreater) {
if (startedWhenOvercrollingTargetScene) {
@@ -781,7 +783,7 @@
}
if (isBouncing) {
- bouncingScene = targetScene
+ bouncingContent = targetScene
// Immediately stop this transition if we are bouncing on a
// scene that does not bounce.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 0b5e58f..ec7c77b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -49,13 +49,15 @@
import androidx.compose.ui.util.fastLastOrNull
import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.content.Content
+import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.ui.util.lerp
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
-/** An element on screen, that can be composed in one or more scenes. */
+/** An element on screen, that can be composed in one or more contents. */
@Stable
internal class Element(val key: ElementKey) {
/** The mapping between a content and the state this element has in that content, if any. */
@@ -67,7 +69,7 @@
* The last transition that was used when computing the state (size, position and alpha) of this
* element in any content, or `null` if it was last laid out when idle.
*/
- var lastTransition: TransitionState.Transition? = null
+ var lastTransition: ContentState.Transition<*>? = null
/** Whether this element was ever drawn in a content. */
var wasDrawnInAnyContent = false
@@ -86,7 +88,7 @@
var targetSize by mutableStateOf(SizeUnspecified)
var targetOffset by mutableStateOf(Offset.Unspecified)
- /** The last state this element had in this scene. */
+ /** The last state this element had in this content. */
var lastOffset = Offset.Unspecified
var lastSize = SizeUnspecified
var lastScale = Scale.Unspecified
@@ -103,7 +105,7 @@
/**
* The delta values to add to this element state to have smoother interruptions. These
* should be multiplied by the
- * [current interruption progress][TransitionState.Transition.interruptionProgress] so that
+ * [current interruption progress][ContentState.Transition.interruptionProgress] so that
* they nicely animate from their values down to 0.
*/
var offsetInterruptionDelta = Offset.Zero
@@ -310,7 +312,7 @@
}
private fun Placeable.PlacementScope.place(
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
placeable: Placeable,
) {
with(layoutImpl.lookaheadScope) {
@@ -475,7 +477,7 @@
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
transitions: List<TransitionState.Transition>,
-): TransitionState.Transition? {
+): ContentState.Transition<*>? {
val transition =
transitions.fastLastOrNull { transition ->
transition.fromScene in element.stateByContent ||
@@ -502,8 +504,8 @@
private fun prepareInterruption(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
- transition: TransitionState.Transition,
- previousTransition: TransitionState.Transition,
+ transition: ContentState.Transition<*>,
+ previousTransition: ContentState.Transition<*>,
) {
if (transition.replacedTransition == previousTransition) {
return
@@ -514,10 +516,10 @@
return stateByContent[key]?.also { it.selfUpdateValuesBeforeInterruption() }
}
- val previousFromState = updateStateInContent(previousTransition.fromScene)
- val previousToState = updateStateInContent(previousTransition.toScene)
- val fromState = updateStateInContent(transition.fromScene)
- val toState = updateStateInContent(transition.toScene)
+ val previousFromState = updateStateInContent(previousTransition.fromContent)
+ val previousToState = updateStateInContent(previousTransition.toContent)
+ val fromState = updateStateInContent(transition.fromContent)
+ val toState = updateStateInContent(transition.toContent)
reconcileStates(element, previousTransition)
reconcileStates(element, transition)
@@ -545,31 +547,31 @@
}
/**
- * Reconcile the state of [element] in the fromScene and toScene of [transition] so that the values
- * before interruption have their expected values, taking shared transitions into account.
+ * Reconcile the state of [element] in the formContent and toContent of [transition] so that the
+ * values before interruption have their expected values, taking shared transitions into account.
*/
private fun reconcileStates(
element: Element,
- transition: TransitionState.Transition,
+ transition: ContentState.Transition<*>,
) {
- val fromSceneState = element.stateByContent[transition.fromScene] ?: return
- val toSceneState = element.stateByContent[transition.toScene] ?: return
+ val fromContentState = element.stateByContent[transition.fromContent] ?: return
+ val toContentState = element.stateByContent[transition.toContent] ?: return
if (!isSharedElementEnabled(element.key, transition)) {
return
}
if (
- fromSceneState.offsetBeforeInterruption != Offset.Unspecified &&
- toSceneState.offsetBeforeInterruption == Offset.Unspecified
+ fromContentState.offsetBeforeInterruption != Offset.Unspecified &&
+ toContentState.offsetBeforeInterruption == Offset.Unspecified
) {
- // Element is shared and placed in fromScene only.
- toSceneState.updateValuesBeforeInterruption(fromSceneState)
+ // Element is shared and placed in fromContent only.
+ toContentState.updateValuesBeforeInterruption(fromContentState)
} else if (
- toSceneState.offsetBeforeInterruption != Offset.Unspecified &&
- fromSceneState.offsetBeforeInterruption == Offset.Unspecified
+ toContentState.offsetBeforeInterruption != Offset.Unspecified &&
+ fromContentState.offsetBeforeInterruption == Offset.Unspecified
) {
- // Element is shared and placed in toScene only.
- fromSceneState.updateValuesBeforeInterruption(toSceneState)
+ // Element is shared and placed in toContent only.
+ fromContentState.updateValuesBeforeInterruption(toContentState)
}
}
@@ -614,12 +616,12 @@
/**
* Compute what [value] should be if we take the
- * [interruption progress][TransitionState.Transition.interruptionProgress] of [transition] into
+ * [interruption progress][ContentState.Transition.interruptionProgress] of [transition] into
* account.
*/
private inline fun <T> computeInterruptedValue(
layoutImpl: SceneTransitionLayoutImpl,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
value: T,
unspecifiedValue: T,
zeroValue: T,
@@ -660,13 +662,13 @@
/**
* Set the interruption delta of a *placement/drawing*-related value (offset, alpha, scale). This
- * ensures that the delta is also set on the other scene in the transition for shared elements, so
- * that there is no jump cut if the scene where the element is placed has changed.
+ * ensures that the delta is also set on the other content in the transition for shared elements, so
+ * that there is no jump cut if the content where the element is placed has changed.
*/
private inline fun <T> setPlacementInterruptionDelta(
element: Element,
stateInContent: Element.State,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
delta: T,
setter: (Element.State, T) -> Unit,
) {
@@ -677,14 +679,14 @@
return
}
- // If the element is shared, also set the delta on the other scene so that it is used by that
- // scene if we start overscrolling it and change the scene where the element is placed.
- val otherScene =
- if (stateInContent.content == transition.fromScene) transition.toScene
- else transition.fromScene
- val otherSceneState = element.stateByContent[otherScene] ?: return
+ // If the element is shared, also set the delta on the other content so that it is used by that
+ // content if we start overscrolling it and change the content where the element is placed.
+ val otherContent =
+ if (stateInContent.content == transition.fromContent) transition.toContent
+ else transition.fromContent
+ val otherContentState = element.stateByContent[otherContent] ?: return
if (isSharedElementEnabled(element.key, transition)) {
- setter(otherSceneState, delta)
+ setter(otherContentState, delta)
}
}
@@ -692,7 +694,7 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
): Boolean {
// Always place the element if we are idle.
if (transition == null) {
@@ -701,14 +703,14 @@
// Don't place the element in this content if this content is not part of the current element
// transition.
- if (content != transition.fromScene && content != transition.toScene) {
+ if (content != transition.fromContent && content != transition.toContent) {
return false
}
// Place the element if it is not shared.
if (
- transition.fromScene !in element.stateByContent ||
- transition.toScene !in element.stateByContent
+ transition.fromContent !in element.stateByContent ||
+ transition.toContent !in element.stateByContent
) {
return true
}
@@ -730,7 +732,7 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: ElementKey,
- transition: TransitionState.Transition,
+ transition: ContentState.Transition<*>,
): Boolean {
// If we are overscrolling, only place/compose the element in the overscrolling scene.
val overscrollScene = transition.currentOverscrollSpec?.scene
@@ -738,45 +740,47 @@
return content == overscrollScene
}
- val scenePicker = element.scenePicker
- val fromScene = transition.fromScene
- val toScene = transition.toScene
-
+ val scenePicker = element.contentPicker
val pickedScene =
- scenePicker.sceneDuringTransition(
- element = element,
- transition = transition,
- fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
- toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
- ) ?: return false
+ when (transition) {
+ is TransitionState.Transition -> {
+ scenePicker.contentDuringTransition(
+ element = element,
+ transition = transition,
+ fromContentZIndex = layoutImpl.scene(transition.fromScene).zIndex,
+ toContentZIndex = layoutImpl.scene(transition.toScene).zIndex,
+ )
+ }
+ }
return pickedScene == content
}
private fun isSharedElementEnabled(
element: ElementKey,
- transition: TransitionState.Transition,
+ transition: ContentState.Transition<*>,
): Boolean {
return sharedElementTransformation(element, transition)?.enabled ?: true
}
internal fun sharedElementTransformation(
element: ElementKey,
- transition: TransitionState.Transition,
+ transition: ContentState.Transition<*>,
): SharedElementTransformation? {
val transformationSpec = transition.transformationSpec
- val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared
- val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared
+ val sharedInFromContent =
+ transformationSpec.transformations(element, transition.fromContent).shared
+ val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared
- // The sharedElement() transformation must either be null or be the same in both scenes.
- if (sharedInFromScene != sharedInToScene) {
+ // The sharedElement() transformation must either be null or be the same in both contents.
+ if (sharedInFromContent != sharedInToContent) {
error(
- "Different sharedElement() transformations matched $element (from=$sharedInFromScene " +
- "to=$sharedInToScene)"
+ "Different sharedElement() transformations matched $element " +
+ "(from=$sharedInFromContent to=$sharedInToContent)"
)
}
- return sharedInFromScene
+ return sharedInFromContent
}
/**
@@ -789,16 +793,14 @@
private fun isElementOpaque(
content: Content,
element: Element,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
): Boolean {
if (transition == null) {
return true
}
- val fromScene = transition.fromScene
- val toScene = transition.toScene
- val fromState = element.stateByContent[fromScene]
- val toState = element.stateByContent[toScene]
+ val fromState = element.stateByContent[transition.fromContent]
+ val toState = element.stateByContent[transition.toContent]
if (fromState == null && toState == null) {
// TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
@@ -825,7 +827,7 @@
private fun elementAlpha(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
stateInContent: Element.State,
): Float {
val alpha =
@@ -856,7 +858,7 @@
private fun interruptedAlpha(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
stateInContent: Element.State,
alpha: Float,
): Float {
@@ -886,7 +888,7 @@
private fun measure(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
stateInContent: Element.State,
measurable: Measurable,
constraints: Constraints,
@@ -950,7 +952,7 @@
private fun ContentDrawScope.getDrawScale(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
stateInContent: Element.State,
): Scale {
val scale =
@@ -1031,7 +1033,7 @@
* @param layoutImpl the [SceneTransitionLayoutImpl] associated to [element].
* @param currentContentState the content state of the content for which we are computing the value.
* Note that during interruptions, this could be the state of a content that is neither
- * [transition.toScene] nor [transition.fromScene].
+ * [transition.toContent] nor [transition.fromContent].
* @param element the element being animated.
* @param contentValue the value being animated.
* @param transformation the transformation associated to the value being animated.
@@ -1046,7 +1048,7 @@
layoutImpl: SceneTransitionLayoutImpl,
currentContentState: Element.State,
element: Element,
- transition: TransitionState.Transition?,
+ transition: ContentState.Transition<*>?,
contentValue: (Element.State) -> T,
transformation: (ElementTransformations) -> PropertyTransformation<T>?,
currentValue: () -> T,
@@ -1061,11 +1063,11 @@
return currentValue()
}
- val fromScene = transition.fromScene
- val toScene = transition.toScene
+ val fromContent = transition.fromContent
+ val toContent = transition.toContent
- val fromState = element.stateByContent[fromScene]
- val toState = element.stateByContent[toScene]
+ val fromState = element.stateByContent[fromContent]
+ val toState = element.stateByContent[toContent]
if (fromState == null && toState == null) {
// TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
@@ -1073,19 +1075,20 @@
return contentValue(currentContentState)
}
- val currentScene = currentContentState.content
- if (transition is TransitionState.HasOverscrollProperties) {
+ val currentContent = currentContentState.content
+ if (transition is ContentState.HasOverscrollProperties) {
val overscroll = transition.currentOverscrollSpec
- if (overscroll?.scene == currentScene) {
+ if (overscroll?.scene == currentContent) {
val elementSpec =
- overscroll.transformationSpec.transformations(element.key, currentScene)
+ overscroll.transformationSpec.transformations(element.key, currentContent)
val propertySpec = transformation(elementSpec) ?: return currentValue()
- val overscrollState = checkNotNull(if (currentScene == toScene) toState else fromState)
+ val overscrollState =
+ checkNotNull(if (currentContent == toContent) toState else fromState)
val idleValue = contentValue(overscrollState)
val targetValue =
propertySpec.transform(
layoutImpl,
- currentScene,
+ currentContent,
element,
overscrollState,
transition,
@@ -1101,8 +1104,8 @@
// TODO(b/290184746): Make sure that we don't overflow transformations associated to a
// range.
val directionSign = if (transition.isUpOrLeft) -1 else 1
- val isToScene = overscroll.scene == transition.toScene
- val linearProgress = transition.progress.let { if (isToScene) it - 1f else it }
+ val isToContent = overscroll.scene == transition.toContent
+ val linearProgress = transition.progress.let { if (isToContent) it - 1f else it }
val progress = directionSign * overscroll.progressConverter(linearProgress)
val rangeProgress = propertySpec.range?.progress(progress) ?: progress
@@ -1111,7 +1114,8 @@
}
}
- // The element is shared: interpolate between the value in fromScene and the value in toScene.
+ // The element is shared: interpolate between the value in fromContent and the value in
+ // toContent.
// TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
// elements follow the finger direction.
val isSharedElement = fromState != null && toState != null
@@ -1134,15 +1138,15 @@
val contentState =
checkNotNull(
when {
- isSharedElement && currentScene == fromScene -> fromState
+ isSharedElement && currentContent == fromContent -> fromState
isSharedElement -> toState
else -> fromState ?: toState
}
)
- // The scene for which we compute the transformation. Note that this is not necessarily
- // [currentScene] because [currentScene] could be a different scene than the transition
- // fromScene or toScene during interruptions.
+ // The content for which we compute the transformation. Note that this is not necessarily
+ // [currentContent] because [currentContent] could be a different content than the transition
+ // fromContent or toContent during interruptions.
val content = contentState.content
val transformation =
@@ -1156,7 +1160,7 @@
val isInPreviewStage = transition.isInPreviewStage
val idleValue = contentValue(contentState)
- val isEntering = content == toScene
+ val isEntering = content == toContent
val previewTargetValue =
previewTransformation.transform(
layoutImpl,
@@ -1262,7 +1266,7 @@
val rangeProgress = transformation.range?.progress(progress) ?: progress
// Interpolate between the value at rest and the value before entering/after leaving.
- val isEntering = content == toScene
+ val isEntering = content == toContent
return if (isEntering) {
lerp(targetValue, idleValue, rangeProgress)
} else {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
index 54c64fd..bf70ca9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
@@ -16,6 +16,8 @@
package com.android.compose.animation.scene
+import com.android.compose.animation.scene.content.state.TransitionState
+
/**
* A handler to specify how a transition should be interrupted.
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index a9edf0a..3cd5553 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -64,15 +64,15 @@
}
/** Key for an element. */
-class ElementKey(
+open class ElementKey(
debugName: String,
identity: Any = Object(),
/**
- * The [ElementScenePicker] to use when deciding in which scene we should draw shared Elements
+ * The [ElementContentPicker] to use when deciding in which scene we should draw shared Elements
* or compose MovableElements.
*/
- val scenePicker: ElementScenePicker = DefaultElementScenePicker,
+ open val contentPicker: ElementContentPicker = DefaultElementContentPicker,
) : Key(debugName, identity), ElementMatcher {
@VisibleForTesting
// TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
@@ -99,6 +99,32 @@
}
}
+/** Key for a movable element. */
+class MovableElementKey(
+ debugName: String,
+
+ /**
+ * The [StaticElementContentPicker] to use when deciding in which scene we should draw shared
+ * Elements or compose MovableElements.
+ *
+ * @see MovableElementContentPicker
+ */
+ override val contentPicker: StaticElementContentPicker,
+ identity: Any = Object(),
+) : ElementKey(debugName, identity, contentPicker) {
+ constructor(
+ debugName: String,
+
+ /** The exhaustive list of contents (scenes or overlays) that can contain this element. */
+ contents: Set<ContentKey>,
+ identity: Any = Object(),
+ ) : this(debugName, MovableElementContentPicker(contents), identity)
+
+ override fun toString(): String {
+ return "MovableElementKey(debugName=$debugName)"
+ }
+}
+
/** Key for a shared value of an element. */
class ValueKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) {
override fun toString(): String {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index e556f6f..abecdd7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -28,6 +28,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastLastOrNull
import com.android.compose.animation.scene.content.Content
+import com.android.compose.animation.scene.content.state.TransitionState
@Composable
internal fun Element(
@@ -53,7 +54,7 @@
internal fun MovableElement(
layoutImpl: SceneTransitionLayoutImpl,
sceneOrOverlay: Content,
- key: ElementKey,
+ key: MovableElementKey,
modifier: Modifier,
content: @Composable ElementScope<MovableElementContentScope>.() -> Unit,
) {
@@ -111,7 +112,7 @@
private class MovableElementScopeImpl(
private val layoutImpl: SceneTransitionLayoutImpl,
- private val element: ElementKey,
+ private val element: MovableElementKey,
private val content: Content,
private val baseContentScope: BaseContentScope,
private val boxScope: BoxScope,
@@ -169,7 +170,7 @@
private fun shouldComposeMovableElement(
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
- element: ElementKey,
+ element: MovableElementKey,
): Boolean {
val transitions = layoutImpl.state.currentTransitions
if (transitions.isEmpty()) {
@@ -181,14 +182,10 @@
// The current transition for this element is the last transition in which either fromScene or
// toScene contains the element.
+ val contents = element.contentPicker.contents
val transition =
transitions.fastLastOrNull { transition ->
- element.scenePicker.sceneDuringTransition(
- element = element,
- transition = transition,
- fromSceneZIndex = layoutImpl.scenes.getValue(transition.fromScene).zIndex,
- toSceneZIndex = layoutImpl.scenes.getValue(transition.toScene).zIndex,
- ) != null
+ transition.fromScene in contents || transition.toScene in contents
} ?: return false
// Always compose movable elements in the scene picked by their scene picker.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index f1b2249..ae5344f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.runtime.snapshotFlow
+import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index fd6762b..fe16ef751 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -25,6 +25,7 @@
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import com.android.compose.animation.scene.content.state.TransitionState
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 3401af8..65a7367 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -189,7 +189,7 @@
*/
@Composable
fun MovableElement(
- key: ElementKey,
+ key: MovableElementKey,
modifier: Modifier,
// TODO(b/317026105): As discussed in http://shortn/_gJVdltF8Si, remove the @Composable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 01ce211..a6c6a80 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -18,10 +18,6 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.spring
-import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -29,12 +25,12 @@
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
+import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transition.link.LinkedTransition
import com.android.compose.animation.scene.transition.link.StateLink
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
/**
* The state of a [SceneTransitionLayout].
@@ -146,221 +142,6 @@
)
}
-@Stable
-sealed interface TransitionState {
- /**
- * The current effective scene. If a new transition was triggered, it would start from this
- * scene.
- *
- * For instance, when swiping from scene A to scene B, the [currentScene] is A when the swipe
- * gesture starts, but then if the user flings their finger and commits the transition to scene
- * B, then [currentScene] becomes scene B even if the transition is not finished yet and is
- * still animating to settle to scene B.
- */
- val currentScene: SceneKey
-
- /** No transition/animation is currently running. */
- data class Idle(override val currentScene: SceneKey) : TransitionState
-
- /** There is a transition animating between two scenes. */
- abstract class Transition(
- /** The scene this transition is starting from. Can't be the same as toScene */
- val fromScene: SceneKey,
-
- /** The scene this transition is going to. Can't be the same as fromScene */
- val toScene: SceneKey,
-
- /** The transition that `this` transition is replacing, if any. */
- internal val replacedTransition: Transition? = null,
- ) : TransitionState {
- /**
- * The key of this transition. This should usually be null, but it can be specified to use a
- * specific set of transformations associated to this transition.
- */
- open val key: TransitionKey? = null
-
- /**
- * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
- * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
- * when flinging quickly during a swipe gesture.
- */
- abstract val progress: Float
-
- /** The current velocity of [progress], in progress units. */
- abstract val progressVelocity: Float
-
- /**
- * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can
- * also be less than `0` or greater than `1` when using transitions with a spring
- * AnimationSpec or when flinging quickly during a swipe gesture.
- */
- open val previewProgress: Float = 0f
-
- /** The current velocity of [previewProgress], in progress units. */
- open val previewProgressVelocity: Float = 0f
-
- /** Whether the transition is currently in the preview stage */
- open val isInPreviewStage: Boolean = false
-
- /** Whether the transition was triggered by user input rather than being programmatic. */
- abstract val isInitiatedByUserInput: Boolean
-
- /** Whether user input is currently driving the transition. */
- abstract val isUserInputOngoing: Boolean
-
- /**
- * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this
- * transition.
- *
- * Important: These will be set exactly once, when this transition is
- * [started][MutableSceneTransitionLayoutStateImpl.startTransition].
- */
- internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
- internal var previewTransformationSpec: TransformationSpecImpl? = null
- private var fromOverscrollSpec: OverscrollSpecImpl? = null
- private var toOverscrollSpec: OverscrollSpecImpl? = null
-
- /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */
- internal val currentOverscrollSpec: OverscrollSpecImpl?
- get() {
- if (this !is HasOverscrollProperties) return null
- val progress = progress
- val bouncingScene = bouncingScene
- return when {
- progress < 0f || bouncingScene == fromScene -> fromOverscrollSpec
- progress > 1f || bouncingScene == toScene -> toOverscrollSpec
- else -> null
- }
- }
-
- /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */
- internal fun isWithinProgressRange(progress: Float): Boolean {
- // If the properties are missing we assume that every [Transition] can overscroll
- if (this !is HasOverscrollProperties) return true
- // [OverscrollSpec] for the current scene, even if it hasn't started overscrolling yet.
- val specForCurrentScene =
- when {
- progress <= 0f -> fromOverscrollSpec
- progress >= 1f -> toOverscrollSpec
- else -> null
- } ?: return true
-
- return specForCurrentScene.transformationSpec.transformations.isNotEmpty()
- }
-
- /**
- * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
- * jump of values when this transitions interrupts another one.
- */
- private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
-
- init {
- check(fromScene != toScene)
- check(
- replacedTransition == null ||
- (replacedTransition.fromScene == fromScene &&
- replacedTransition.toScene == toScene)
- )
- }
-
- /**
- * Force this transition to finish and animate to [currentScene], so that this transition
- * progress will settle to either 0% (if [currentScene] == [fromScene]) or 100% (if
- * [currentScene] == [toScene]) in a finite amount of time.
- *
- * @return the [Job] that animates the progress to [currentScene]. It can be used to wait
- * until the animation is complete or cancel it to snap to [currentScene]. Calling
- * [finish] multiple times will return the same [Job].
- */
- abstract fun finish(): Job
-
- /**
- * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
- * match the scenes we are animating from and/or to.
- */
- fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
- return (from == null || fromScene == from) && (to == null || toScene == to)
- }
-
- /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
- fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
- return isTransitioning(from = scene, to = other) ||
- isTransitioning(from = other, to = scene)
- }
-
- internal fun updateOverscrollSpecs(
- fromSpec: OverscrollSpecImpl?,
- toSpec: OverscrollSpecImpl?,
- ) {
- fromOverscrollSpec = fromSpec
- toOverscrollSpec = toSpec
- }
-
- internal open fun interruptionProgress(
- layoutImpl: SceneTransitionLayoutImpl,
- ): Float {
- if (!layoutImpl.state.enableInterruptions) {
- return 0f
- }
-
- if (replacedTransition != null) {
- return replacedTransition.interruptionProgress(layoutImpl)
- }
-
- fun create(): Animatable<Float, AnimationVector1D> {
- val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
- layoutImpl.coroutineScope.launch {
- val swipeSpec = layoutImpl.state.transitions.defaultSwipeSpec
- val progressSpec =
- spring(
- stiffness = swipeSpec.stiffness,
- dampingRatio = swipeSpec.dampingRatio,
- visibilityThreshold = ProgressVisibilityThreshold,
- )
- animatable.animateTo(0f, progressSpec)
- }
-
- return animatable
- }
-
- val animatable = interruptionDecay ?: create().also { interruptionDecay = it }
- return animatable.value
- }
- }
-
- interface HasOverscrollProperties {
- /**
- * The position of the [Transition.toScene].
- *
- * Used to understand the direction of the overscroll.
- */
- val isUpOrLeft: Boolean
-
- /**
- * The relative orientation between [Transition.fromScene] and [Transition.toScene].
- *
- * Used to understand the orientation of the overscroll.
- */
- val orientation: Orientation
-
- /**
- * Scope which can be used in the Overscroll DSL to define a transformation based on the
- * distance between [Transition.fromScene] and [Transition.toScene].
- */
- val overscrollScope: OverscrollScope
-
- /**
- * The scene around which the transition is currently bouncing. When not `null`, this
- * transition is currently oscillating around this scene and will soon settle to that scene.
- */
- val bouncingScene: SceneKey?
-
- companion object {
- const val DistanceUnspecified = 0f
- }
- }
-}
-
/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
@@ -459,7 +240,7 @@
// Compute the [TransformationSpec] when the transition starts.
val fromScene = transition.fromScene
val toScene = transition.toScene
- val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
+ val orientation = (transition as? ContentState.HasOverscrollProperties)?.orientation
// Update the transition specs.
transition.transformationSpec =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index cfa4c70..3ded1de 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -157,16 +157,16 @@
val key: TransitionKey?
/**
- * The scene we are transitioning from. If `null`, this spec can be used to animate from any
- * scene.
+ * The content we are transitioning from. If `null`, this spec can be used to animate from any
+ * content.
*/
- val from: SceneKey?
+ val from: ContentKey?
/**
- * The scene we are transitioning to. If `null`, this spec can be used to animate from any
- * scene.
+ * The content we are transitioning to. If `null`, this spec can be used to animate from any
+ * content.
*/
- val to: SceneKey?
+ val to: ContentKey?
/**
* Return a reversed version of this [TransitionSpec] for a transition going from [to] to
@@ -231,8 +231,8 @@
internal class TransitionSpecImpl(
override val key: TransitionKey?,
- override val from: SceneKey?,
- override val to: SceneKey?,
+ override val from: ContentKey?,
+ override val to: ContentKey?,
private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null,
private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null,
private val transformationSpec: () -> TransformationSpecImpl
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index ecef0f4..38b8383 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -23,7 +23,7 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.animation.scene.content.state.ContentState
/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -47,9 +47,9 @@
var interruptionHandler: InterruptionHandler
/**
- * Define the default animation to be played when transitioning [to] the specified scene, from
- * any scene. For the animation specification to apply only when transitioning between two
- * specific scenes, use [from] instead.
+ * Define the default animation to be played when transitioning [to] the specified content, from
+ * any content. For the animation specification to apply only when transitioning between two
+ * specific contents, use [from] instead.
*
* If [key] is not `null`, then this transition will only be used if the same key is specified
* when triggering the transition.
@@ -61,7 +61,7 @@
* @see from
*/
fun to(
- to: SceneKey,
+ to: ContentKey,
key: TransitionKey? = null,
preview: (TransitionBuilder.() -> Unit)? = null,
reversePreview: (TransitionBuilder.() -> Unit)? = null,
@@ -69,12 +69,12 @@
): TransitionSpec
/**
- * Define the animation to be played when transitioning [from] the specified scene. For the
- * animation specification to apply only when transitioning between two specific scenes, pass
- * the destination scene via the [to] argument.
+ * Define the animation to be played when transitioning [from] the specified content. For the
+ * animation specification to apply only when transitioning between two specific contents, pass
+ * the destination content via the [to] argument.
*
- * When looking up which transition should be used when animating from scene A to scene B, we
- * pick the single transition with the given [key] and matching one of these predicates (in
+ * When looking up which transition should be used when animating from content A to content B,
+ * we pick the single transition with the given [key] and matching one of these predicates (in
* order of importance):
* 1. from == A && to == B
* 2. to == A && from == B, which is then treated in reverse.
@@ -86,8 +86,8 @@
* reversible with the reverse animation having a preview as well, define a [reversePreview].
*/
fun from(
- from: SceneKey,
- to: SceneKey? = null,
+ from: ContentKey,
+ to: ContentKey? = null,
key: TransitionKey? = null,
preview: (TransitionBuilder.() -> Unit)? = null,
reversePreview: (TransitionBuilder.() -> Unit)? = null,
@@ -233,99 +233,171 @@
/**
* An interface to decide where we should draw shared Elements or compose MovableElements.
*
- * @see DefaultElementScenePicker
- * @see HighestZIndexScenePicker
- * @see LowestZIndexScenePicker
- * @see MovableElementScenePicker
+ * @see DefaultElementContentPicker
+ * @see HighestZIndexContentPicker
+ * @see LowestZIndexContentPicker
+ * @see MovableElementContentPicker
*/
-interface ElementScenePicker {
+interface ElementContentPicker {
/**
- * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
+ * Return the content in which [element] should be drawn (when using `Modifier.element(key)`) or
* composed (when using `MovableElement(key)`) during the given [transition]. If this element
- * should not be drawn or composed in neither [transition.fromScene] nor [transition.toScene],
- * return `null`.
+ * should not be drawn or composed in neither [transition.fromContent] nor
+ * [transition.toContent], return `null`.
*
- * Important: For [MovableElements][ContentScope.MovableElement], this scene picker will
+ * Important: For [MovableElements][ContentScope.MovableElement], this content picker will
* *always* be used during transitions to decide whether we should compose that element in a
- * given scene or not. Therefore, you should make sure that the returned [SceneKey] contains the
- * movable element, otherwise that element will not be composed in any scene during the
+ * given content or not. Therefore, you should make sure that the returned [ContentKey] contains
+ * the movable element, otherwise that element will not be composed in any scene during the
* transition.
*/
- fun sceneDuringTransition(
+ fun contentDuringTransition(
element: ElementKey,
- transition: TransitionState.Transition,
- fromSceneZIndex: Float,
- toSceneZIndex: Float,
- ): SceneKey?
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float,
+ ): ContentKey
/**
- * Return [transition.fromScene] if it is in [scenes] and [transition.toScene] is not, or return
- * [transition.toScene] if it is in [scenes] and [transition.fromScene] is not. If neither
- * [transition.fromScene] and [transition.toScene] are in [scenes], return `null`. If both
- * [transition.fromScene] and [transition.toScene] are in [scenes], throw an exception.
+ * Return [transition.fromContent] if it is in [contents] and [transition.toContent] is not, or
+ * return [transition.toContent] if it is in [contents] and [transition.fromContent] is not. If
+ * neither [transition.toContent] and [transition.fromContent] are in [contents] or if both
+ * [transition.fromContent] and [transition.toContent] are in [contents], throw an exception.
*
- * This function can be useful when computing the scene in which a movable element should be
+ * This function can be useful when computing the content in which a movable element should be
* composed.
*/
- fun pickSingleSceneIn(
- scenes: Set<SceneKey>,
- transition: TransitionState.Transition,
+ fun pickSingleContentIn(
+ contents: Set<ContentKey>,
+ transition: ContentState.Transition<*>,
element: ElementKey,
- ): SceneKey? {
- val fromScene = transition.fromScene
- val toScene = transition.toScene
- val fromSceneInScenes = scenes.contains(fromScene)
- val toSceneInScenes = scenes.contains(toScene)
+ ): ContentKey {
+ val fromContent = transition.fromContent
+ val toContent = transition.toContent
+ val fromContentInContents = contents.contains(fromContent)
+ val toContentInContents = contents.contains(toContent)
- return when {
- fromSceneInScenes && toSceneInScenes -> {
- error(
- "Element $element can be in both $fromScene and $toScene. You should add a " +
- "special case for this transition before calling pickSingleSceneIn()."
- )
- }
- fromSceneInScenes -> fromScene
- toSceneInScenes -> toScene
- else -> null
+ if (fromContentInContents && toContentInContents) {
+ error(
+ "Element $element can be in both $fromContent and $toContent. You should add a " +
+ "special case for this transition before calling pickSingleSceneIn()."
+ )
}
- }
-}
-/** An [ElementScenePicker] that draws/composes elements in the scene with the highest z-order. */
-object HighestZIndexScenePicker : ElementScenePicker {
- override fun sceneDuringTransition(
- element: ElementKey,
- transition: TransitionState.Transition,
- fromSceneZIndex: Float,
- toSceneZIndex: Float
- ): SceneKey {
- return if (fromSceneZIndex > toSceneZIndex) {
- transition.fromScene
- } else {
- transition.toScene
+ if (!fromContentInContents && !toContentInContents) {
+ error(
+ "Element $element can be neither in $fromContent and $toContent. This either " +
+ "means that you should add one of them in the scenes set passed to " +
+ "pickSingleSceneIn(), or there is an internal error and this element was " +
+ "composed when it shouldn't be."
+ )
}
- }
-}
-/** An [ElementScenePicker] that draws/composes elements in the scene with the lowest z-order. */
-object LowestZIndexScenePicker : ElementScenePicker {
- override fun sceneDuringTransition(
- element: ElementKey,
- transition: TransitionState.Transition,
- fromSceneZIndex: Float,
- toSceneZIndex: Float
- ): SceneKey {
- return if (fromSceneZIndex < toSceneZIndex) {
- transition.fromScene
+ return if (fromContentInContents) {
+ fromContent
} else {
- transition.toScene
+ toContent
}
}
}
/**
- * An [ElementScenePicker] that draws/composes elements in the scene we are transitioning to, iff
- * that scene is in [scenes].
+ * An element picker on which we can query the set of contents (scenes or overlays) that contain the
+ * element. This is needed by [MovableElement], that needs to know at composition time on which of
+ * the candidate contents an element should be composed.
+ */
+interface StaticElementContentPicker : ElementContentPicker {
+ /** The exhaustive lists of contents that contain this element. */
+ val contents: Set<ContentKey>
+}
+
+/**
+ * An [ElementContentPicker] that draws/composes elements in the content with the highest z-order.
+ */
+object HighestZIndexContentPicker : ElementContentPicker {
+ override fun contentDuringTransition(
+ element: ElementKey,
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float
+ ): ContentKey {
+ return if (fromContentZIndex > toContentZIndex) {
+ transition.fromContent
+ } else {
+ transition.toContent
+ }
+ }
+
+ /**
+ * Return a [StaticElementContentPicker] that behaves like [HighestZIndexContentPicker] and can
+ * be used by [MovableElement].
+ */
+ operator fun invoke(contents: Set<ContentKey>): StaticElementContentPicker {
+ return object : StaticElementContentPicker {
+ override val contents: Set<ContentKey> = contents
+
+ override fun contentDuringTransition(
+ element: ElementKey,
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float
+ ): ContentKey {
+ return HighestZIndexContentPicker.contentDuringTransition(
+ element,
+ transition,
+ fromContentZIndex,
+ toContentZIndex,
+ )
+ }
+ }
+ }
+}
+
+/**
+ * An [ElementContentPicker] that draws/composes elements in the content with the lowest z-order.
+ */
+object LowestZIndexContentPicker : ElementContentPicker {
+ override fun contentDuringTransition(
+ element: ElementKey,
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float
+ ): ContentKey {
+ return if (fromContentZIndex < toContentZIndex) {
+ transition.fromContent
+ } else {
+ transition.toContent
+ }
+ }
+
+ /**
+ * Return a [StaticElementContentPicker] that behaves like [LowestZIndexContentPicker] and can
+ * be used by [MovableElement].
+ */
+ operator fun invoke(contents: Set<ContentKey>): StaticElementContentPicker {
+ return object : StaticElementContentPicker {
+ override val contents: Set<ContentKey> = contents
+
+ override fun contentDuringTransition(
+ element: ElementKey,
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float
+ ): ContentKey {
+ return LowestZIndexContentPicker.contentDuringTransition(
+ element,
+ transition,
+ fromContentZIndex,
+ toContentZIndex,
+ )
+ }
+ }
+ }
+}
+
+/**
+ * An [ElementContentPicker] that draws/composes elements in the content we are transitioning to,
+ * iff that content is in [contents].
*
* This picker can be useful for movable elements whose content size depends on its content (because
* it wraps it) in at least one scene. That way, the target size of the MovableElement will be
@@ -337,23 +409,30 @@
* is not the same as when going from scene B to scene A, so it's not usable in situations where
* z-ordering during the transition matters.
*/
-class MovableElementScenePicker(private val scenes: Set<SceneKey>) : ElementScenePicker {
- override fun sceneDuringTransition(
+class MovableElementContentPicker(
+ override val contents: Set<ContentKey>,
+) : StaticElementContentPicker {
+ override fun contentDuringTransition(
element: ElementKey,
- transition: TransitionState.Transition,
- fromSceneZIndex: Float,
- toSceneZIndex: Float,
- ): SceneKey? {
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float,
+ ): ContentKey {
return when {
- scenes.contains(transition.toScene) -> transition.toScene
- scenes.contains(transition.fromScene) -> transition.fromScene
- else -> null
+ transition.toContent in contents -> transition.toContent
+ else -> {
+ check(transition.fromContent in contents) {
+ "Neither ${transition.fromContent} nor ${transition.toContent} are in " +
+ "contents. This transition should not have been used for this element."
+ }
+ transition.fromContent
+ }
}
}
}
-/** The default [ElementScenePicker]. */
-val DefaultElementScenePicker = HighestZIndexScenePicker
+/** The default [ElementContentPicker]. */
+val DefaultElementContentPicker = HighestZIndexContentPicker
@TransitionDsl
interface PropertyTransformationBuilder {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index a0759c7..6515cb8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -60,7 +60,7 @@
val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>()
override fun to(
- to: SceneKey,
+ to: ContentKey,
key: TransitionKey?,
preview: (TransitionBuilder.() -> Unit)?,
reversePreview: (TransitionBuilder.() -> Unit)?,
@@ -70,8 +70,8 @@
}
override fun from(
- from: SceneKey,
- to: SceneKey?,
+ from: ContentKey,
+ to: ContentKey?,
key: TransitionKey?,
preview: (TransitionBuilder.() -> Unit)?,
reversePreview: (TransitionBuilder.() -> Unit)?,
@@ -120,8 +120,8 @@
}
private fun transition(
- from: SceneKey?,
- to: SceneKey?,
+ from: ContentKey?,
+ to: ContentKey?,
key: TransitionKey?,
preview: (TransitionBuilder.() -> Unit)?,
reversePreview: (TransitionBuilder.() -> Unit)?,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 492d211..6f608cb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -39,6 +39,7 @@
import com.android.compose.animation.scene.ElementStateScope
import com.android.compose.animation.scene.MovableElement
import com.android.compose.animation.scene.MovableElementContentScope
+import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.SceneTransitionLayoutState
@@ -130,7 +131,7 @@
@Composable
override fun MovableElement(
- key: ElementKey,
+ key: MovableElementKey,
modifier: Modifier,
content: @Composable (ElementScope<MovableElementContentScope>.() -> Unit)
) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt
new file mode 100644
index 0000000..add3934
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.compose.animation.scene.content.state
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.OverscrollScope
+import com.android.compose.animation.scene.OverscrollSpecImpl
+import com.android.compose.animation.scene.ProgressVisibilityThreshold
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransformationSpec
+import com.android.compose.animation.scene.TransformationSpecImpl
+import com.android.compose.animation.scene.TransitionKey
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/** The state associated to one or more contents. */
+@Stable
+sealed interface ContentState<out T : ContentKey> {
+ /** The [content] is idle, it does not animate. */
+ sealed class Idle<T : ContentKey>(val content: T) : ContentState<T>
+
+ /** The content is transitioning with another content. */
+ sealed class Transition<out T : ContentKey>(
+ val fromContent: T,
+ val toContent: T,
+ internal val replacedTransition: Transition<T>?,
+ ) : ContentState<T> {
+ /**
+ * The key of this transition. This should usually be null, but it can be specified to use a
+ * specific set of transformations associated to this transition.
+ */
+ open val key: TransitionKey? = null
+
+ /**
+ * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
+ * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
+ * when flinging quickly during a swipe gesture.
+ */
+ abstract val progress: Float
+
+ /** The current velocity of [progress], in progress units. */
+ abstract val progressVelocity: Float
+
+ /** Whether the transition was triggered by user input rather than being programmatic. */
+ abstract val isInitiatedByUserInput: Boolean
+
+ /** Whether user input is currently driving the transition. */
+ abstract val isUserInputOngoing: Boolean
+
+ /**
+ * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can
+ * also be less than `0` or greater than `1` when using transitions with a spring
+ * AnimationSpec or when flinging quickly during a swipe gesture.
+ */
+ internal open val previewProgress: Float = 0f
+
+ /** The current velocity of [previewProgress], in progress units. */
+ internal open val previewProgressVelocity: Float = 0f
+
+ /** Whether the transition is currently in the preview stage */
+ internal open val isInPreviewStage: Boolean = false
+
+ /**
+ * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this
+ * transition.
+ *
+ * Important: These will be set exactly once, when this transition is
+ * [started][MutableSceneTransitionLayoutStateImpl.startTransition].
+ */
+ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+ internal var previewTransformationSpec: TransformationSpecImpl? = null
+ private var fromOverscrollSpec: OverscrollSpecImpl? = null
+ private var toOverscrollSpec: OverscrollSpecImpl? = null
+
+ /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */
+ internal val currentOverscrollSpec: OverscrollSpecImpl?
+ get() {
+ if (this !is HasOverscrollProperties) return null
+ val progress = progress
+ val bouncingContent = bouncingContent
+ return when {
+ progress < 0f || bouncingContent == fromContent -> fromOverscrollSpec
+ progress > 1f || bouncingContent == toContent -> toOverscrollSpec
+ else -> null
+ }
+ }
+
+ /**
+ * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
+ * jump of values when this transitions interrupts another one.
+ */
+ private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
+
+ init {
+ check(fromContent != toContent)
+ check(
+ replacedTransition == null ||
+ (replacedTransition.fromContent == fromContent &&
+ replacedTransition.toContent == toContent)
+ )
+ }
+
+ /**
+ * Force this transition to finish and animate to an [Idle] state.
+ *
+ * Important: Once this is called, the effective state of the transition should remain
+ * unchanged. For instance, in the case of a [TransitionState.Transition], its
+ * [currentScene][TransitionState.Transition.currentScene] should never change once [finish]
+ * is called.
+ *
+ * @return the [Job] that animates to the idle state. It can be used to wait until the
+ * animation is complete or cancel it to snap the animation. Calling [finish] multiple
+ * times will return the same [Job].
+ */
+ abstract fun finish(): Job
+
+ /**
+ * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
+ * match the contents we are animating from and/or to.
+ */
+ fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean {
+ return (from == null || fromContent == from) && (to == null || toContent == to)
+ }
+
+ /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
+ fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
+ return isTransitioning(from = content, to = other) ||
+ isTransitioning(from = other, to = content)
+ }
+
+ internal fun updateOverscrollSpecs(
+ fromSpec: OverscrollSpecImpl?,
+ toSpec: OverscrollSpecImpl?,
+ ) {
+ fromOverscrollSpec = fromSpec
+ toOverscrollSpec = toSpec
+ }
+
+ /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */
+ internal fun isWithinProgressRange(progress: Float): Boolean {
+ // If the properties are missing we assume that every [Transition] can overscroll
+ if (this !is HasOverscrollProperties) return true
+ // [OverscrollSpec] for the current scene, even if it hasn't started overscrolling yet.
+ val specForCurrentScene =
+ when {
+ progress <= 0f -> fromOverscrollSpec
+ progress >= 1f -> toOverscrollSpec
+ else -> null
+ } ?: return true
+
+ return specForCurrentScene.transformationSpec.transformations.isNotEmpty()
+ }
+
+ internal open fun interruptionProgress(
+ layoutImpl: SceneTransitionLayoutImpl,
+ ): Float {
+ if (!layoutImpl.state.enableInterruptions) {
+ return 0f
+ }
+
+ if (replacedTransition != null) {
+ return replacedTransition.interruptionProgress(layoutImpl)
+ }
+
+ fun create(): Animatable<Float, AnimationVector1D> {
+ val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
+ layoutImpl.coroutineScope.launch {
+ val swipeSpec = layoutImpl.state.transitions.defaultSwipeSpec
+ val progressSpec =
+ spring(
+ stiffness = swipeSpec.stiffness,
+ dampingRatio = swipeSpec.dampingRatio,
+ visibilityThreshold = ProgressVisibilityThreshold,
+ )
+ animatable.animateTo(0f, progressSpec)
+ }
+
+ return animatable
+ }
+
+ val animatable = interruptionDecay ?: create().also { interruptionDecay = it }
+ return animatable.value
+ }
+ }
+
+ interface HasOverscrollProperties {
+ /**
+ * The position of the [Transition.toContent].
+ *
+ * Used to understand the direction of the overscroll.
+ */
+ val isUpOrLeft: Boolean
+
+ /**
+ * The relative orientation between [Transition.fromContent] and [Transition.toContent].
+ *
+ * Used to understand the orientation of the overscroll.
+ */
+ val orientation: Orientation
+
+ /**
+ * Scope which can be used in the Overscroll DSL to define a transformation based on the
+ * distance between [Transition.fromContent] and [Transition.toContent].
+ */
+ val overscrollScope: OverscrollScope
+
+ /**
+ * The content (scene or overlay) around which the transition is currently bouncing. When
+ * not `null`, this transition is currently oscillating around this content and will soon
+ * settle to that content.
+ */
+ val bouncingContent: ContentKey?
+
+ companion object {
+ const val DistanceUnspecified = 0f
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
new file mode 100644
index 0000000..77de22c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.content.state
+
+import androidx.compose.runtime.Stable
+import com.android.compose.animation.scene.SceneKey
+
+/** The state associated to one or more scenes. */
+// TODO(b/353679003): Rename to SceneState.
+@Stable
+sealed interface TransitionState : ContentState<SceneKey> {
+ /**
+ * The current effective scene. If a new transition was triggered, it would start from this
+ * scene.
+ *
+ * For instance, when swiping from scene A to scene B, the [currentScene] is A when the swipe
+ * gesture starts, but then if the user flings their finger and commits the transition to scene
+ * B, then [currentScene] becomes scene B even if the transition is not finished yet and is
+ * still animating to settle to scene B.
+ */
+ val currentScene: SceneKey
+
+ /** The scene [currentScene] is idle. */
+ data class Idle(
+ override val currentScene: SceneKey,
+ ) : TransitionState, ContentState.Idle<SceneKey>(currentScene)
+
+ /** There is a transition animating between [fromScene] and [toScene]. */
+ abstract class Transition(
+ /** The scene this transition is starting from. Can't be the same as toScene */
+ val fromScene: SceneKey,
+
+ /** The scene this transition is going to. Can't be the same as fromScene */
+ val toScene: SceneKey,
+
+ /** The transition that `this` transition is replacing, if any. */
+ replacedTransition: Transition? = null,
+ ) : TransitionState, ContentState.Transition<SceneKey>(fromScene, toScene, replacedTransition)
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 65d4d2d..538ce79 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -21,9 +21,8 @@
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
/** Anchor the size of an element to the size of another element. */
internal class AnchoredSize(
@@ -36,19 +35,19 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: IntSize,
): IntSize {
- fun anchorSizeIn(scene: SceneKey): IntSize {
+ fun anchorSizeIn(content: ContentKey): IntSize {
val size =
- layoutImpl.elements[anchor]?.stateByContent?.get(scene)?.targetSize?.takeIf {
+ layoutImpl.elements[anchor]?.stateByContent?.get(content)?.targetSize?.takeIf {
it != Element.SizeUnspecified
}
?: throwMissingAnchorException(
transformation = "AnchoredSize",
anchor = anchor,
- scene = scene,
+ content = content,
)
return IntSize(
@@ -60,10 +59,10 @@
// This simple implementation assumes that the size of [element] is the same as the size of
// the [anchor] in [scene], so simply transform to the size of the anchor in the other
// scene.
- return if (content == transition.fromScene) {
- anchorSizeIn(transition.toScene)
+ return if (content == transition.fromContent) {
+ anchorSizeIn(transition.toContent)
} else {
- anchorSizeIn(transition.fromScene)
+ anchorSizeIn(transition.fromContent)
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 8d7e1c9..258f541 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -22,9 +22,8 @@
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
/** Anchor the translation of an element to another element. */
internal class AnchoredTranslate(
@@ -35,33 +34,33 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: Offset,
): Offset {
- fun throwException(scene: SceneKey?): Nothing {
+ fun throwException(content: ContentKey?): Nothing {
throwMissingAnchorException(
transformation = "AnchoredTranslate",
anchor = anchor,
- scene = scene,
+ content = content,
)
}
- val anchor = layoutImpl.elements[anchor] ?: throwException(scene = null)
- fun anchorOffsetIn(scene: SceneKey): Offset? {
- return anchor.stateByContent[scene]?.targetOffset?.takeIf { it.isSpecified }
+ val anchor = layoutImpl.elements[anchor] ?: throwException(content = null)
+ fun anchorOffsetIn(content: ContentKey): Offset? {
+ return anchor.stateByContent[content]?.targetOffset?.takeIf { it.isSpecified }
}
// [element] will move the same amount as [anchor] does.
// TODO(b/290184746): Also support anchors that are not shared but translated because of
// other transformations, like an edge translation.
val anchorFromOffset =
- anchorOffsetIn(transition.fromScene) ?: throwException(transition.fromScene)
+ anchorOffsetIn(transition.fromContent) ?: throwException(transition.fromContent)
val anchorToOffset =
- anchorOffsetIn(transition.toScene) ?: throwException(transition.toScene)
+ anchorOffsetIn(transition.toContent) ?: throwException(transition.toContent)
val offset = anchorToOffset - anchorFromOffset
- return if (content == transition.toScene) {
+ return if (content == transition.toContent) {
Offset(
value.x - offset.x,
value.y - offset.y,
@@ -78,11 +77,11 @@
internal fun throwMissingAnchorException(
transformation: String,
anchor: ElementKey,
- scene: SceneKey?,
+ content: ContentKey?,
): Nothing {
error(
"""
- Anchor ${anchor.debugName} does not have a target state in scene ${scene?.debugName}.
+ Anchor ${anchor.debugName} does not have a target state in content ${content?.debugName}.
This either means that it was not composed at all during the transition or that it was
composed too late, for instance during layout/subcomposition. To avoid flickers in
$transformation, you should make sure that the composition and layout of anchor is *not*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index f010c3b..be8dac21 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -22,7 +22,7 @@
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
/**
* Scales the draw size of an element. Note this will only scale the draw inside of an element,
@@ -39,8 +39,8 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: Scale,
): Scale {
return Scale(scaleX, scaleY, pivot)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index dfce997..d72e43a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -22,7 +22,7 @@
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
/** Translate an element from an edge of the layout. */
internal class EdgeTranslate(
@@ -34,12 +34,12 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: Offset
): Offset {
val sceneSize = layoutImpl.content(content).targetSize
- val elementSize = sceneState.targetSize
+ val elementSize = stateInContent.targetSize
if (elementSize == Element.SizeUnspecified) {
return value
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index c1bb017..92ae30f8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -20,7 +20,7 @@
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
/** Fade an element in or out. */
internal class Fade(
@@ -30,8 +30,8 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: Float
): Float {
// Return the alpha value of [element] either when it starts fading in or when it finished
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index 5adbf7e..e8515dc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -21,7 +21,7 @@
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
import kotlin.math.roundToInt
/**
@@ -37,8 +37,8 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: IntSize,
): IntSize {
return IntSize(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 24b7194..77ec891 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -23,7 +23,7 @@
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
/** A transformation applied to one or more elements during a transition. */
sealed interface Transformation {
@@ -63,8 +63,8 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: T,
): T
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 123756a..fab4ced 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -24,7 +24,7 @@
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
internal class Translate(
override val matcher: ElementMatcher,
@@ -35,8 +35,8 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: Offset,
): Offset {
return with(layoutImpl.density) {
@@ -57,14 +57,14 @@
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- sceneState: Element.State,
- transition: TransitionState.Transition,
+ stateInContent: Element.State,
+ transition: ContentState.Transition<*>,
value: Offset,
): Offset {
// As this object is created by OverscrollBuilderImpl and we retrieve the current
// OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
// that this method was invoked after performing this check.
- val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+ val overscrollProperties = transition as ContentState.HasOverscrollProperties
return Offset(
x = value.x + overscrollProperties.overscrollScope.x(),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index ed98885..23bcf10 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -18,7 +18,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.Job
/** A linked transition which is driven by a [originalTransition]. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index 2018d6e..c0c40dd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -20,7 +20,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.TransitionState
/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 8716298..01895c9 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -88,11 +88,11 @@
@Composable
private fun ContentScope.MovableFoo(
+ key: MovableElementKey,
targetValues: Values,
onCurrentValueChanged: (Values) -> Unit,
) {
- val key = TestElements.Foo
- MovableElement(key = key, Modifier) {
+ MovableElement(key, Modifier) {
val int by animateElementIntAsState(targetValues.int, key = TestValues.Value1)
val float by animateElementFloatAsState(targetValues.float, key = TestValues.Value2)
val dp by animateElementDpAsState(targetValues.dp, key = TestValues.Value3)
@@ -183,15 +183,22 @@
var lastValueInFrom = fromValues
var lastValueInTo = toValues
+ val key = MovableElementKey("Foo", contents = setOf(SceneA, SceneB))
+
rule.testTransition(
fromSceneContent = {
MovableFoo(
+ key = key,
targetValues = fromValues,
- onCurrentValueChanged = { lastValueInFrom = it }
+ onCurrentValueChanged = { lastValueInFrom = it },
)
},
toSceneContent = {
- MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+ MovableFoo(
+ key = key,
+ targetValues = toValues,
+ onCurrentValueChanged = { lastValueInTo = it },
+ )
},
transition = {
// The transition lasts 64ms = 4 frames.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index be89b18..dc5b2f7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -35,7 +35,8 @@
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
-import com.android.compose.animation.scene.TransitionState.Transition
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.content.state.TransitionState.Transition
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.MonotonicClockTestScope
import com.android.compose.test.runMonotonicClockTest
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementContentPickerTest.kt
similarity index 93%
rename from packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementContentPickerTest.kt
index 3b022e8..96e521b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementContentPickerTest.kt
@@ -31,12 +31,12 @@
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class ElementScenePickerTest {
+class ElementContentPickerTest {
@get:Rule val rule = createComposeRule()
@Test
fun highestZIndexPicker() {
- val key = ElementKey("TestElement", scenePicker = HighestZIndexScenePicker)
+ val key = ElementKey("TestElement", contentPicker = HighestZIndexContentPicker)
rule.testTransition(
fromSceneContent = { Box(Modifier.element(key).size(10.dp)) },
toSceneContent = { Box(Modifier.element(key).size(10.dp)) },
@@ -62,7 +62,7 @@
@Test
fun lowestZIndexPicker() {
- val key = ElementKey("TestElement", scenePicker = LowestZIndexScenePicker)
+ val key = ElementKey("TestElement", contentPicker = LowestZIndexContentPicker)
rule.testTransition(
fromSceneContent = { Box(Modifier.element(key).size(10.dp)) },
toSceneContent = { Box(Modifier.element(key).size(10.dp)) },
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 63df7a9..75f44ff 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -1723,9 +1723,11 @@
val fooInA = "fooInA"
val fooInB = "fooInB"
+ val key = MovableElementKey("Foo", contents = setOf(SceneA, SceneB))
+
@Composable
fun ContentScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
- MovableElement(TestElements.Foo, modifier) { content { Text(text) } }
+ MovableElement(key, modifier) { content { Text(text) } }
}
rule.setContent {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index 3552d3d..ca72181 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -22,6 +22,7 @@
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Correspondence
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
similarity index 60%
rename from packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
index 6745fbe..e1d0945 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
@@ -18,20 +18,22 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class MovableElementScenePickerTest {
+class MovableElementContentPickerTest {
@Test
fun toSceneInScenes() {
- val picker = MovableElementScenePicker(scenes = setOf(TestScenes.SceneA, TestScenes.SceneB))
+ val picker =
+ MovableElementContentPicker(contents = setOf(TestScenes.SceneA, TestScenes.SceneB))
assertThat(
- picker.sceneDuringTransition(
+ picker.contentDuringTransition(
TestElements.Foo,
transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
- fromSceneZIndex = 0f,
- toSceneZIndex = 1f,
+ fromContentZIndex = 0f,
+ toContentZIndex = 1f,
)
)
.isEqualTo(TestScenes.SceneB)
@@ -39,13 +41,13 @@
@Test
fun fromSceneInScenes() {
- val picker = MovableElementScenePicker(scenes = setOf(TestScenes.SceneA))
+ val picker = MovableElementContentPicker(contents = setOf(TestScenes.SceneA))
assertThat(
- picker.sceneDuringTransition(
+ picker.contentDuringTransition(
TestElements.Foo,
transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
- fromSceneZIndex = 0f,
- toSceneZIndex = 1f,
+ fromContentZIndex = 0f,
+ toContentZIndex = 1f,
)
)
.isEqualTo(TestScenes.SceneA)
@@ -53,15 +55,14 @@
@Test
fun noneInScenes() {
- val picker = MovableElementScenePicker(scenes = emptySet())
- assertThat(
- picker.sceneDuringTransition(
- TestElements.Foo,
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
- fromSceneZIndex = 0f,
- toSceneZIndex = 1f,
- )
+ val picker = MovableElementContentPicker(contents = emptySet())
+ assertThrows(IllegalStateException::class.java) {
+ picker.contentDuringTransition(
+ TestElements.Foo,
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
+ fromContentZIndex = 0f,
+ toContentZIndex = 1f,
)
- .isEqualTo(null)
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 821cc29..520e759 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -43,6 +43,10 @@
import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
import com.google.common.truth.Truth.assertThat
@@ -62,7 +66,7 @@
}
@Composable
- private fun ContentScope.MovableCounter(key: ElementKey, modifier: Modifier) {
+ private fun ContentScope.MovableCounter(key: MovableElementKey, modifier: Modifier) {
MovableElement(key, modifier) { content { Counter() } }
}
@@ -74,8 +78,8 @@
},
toSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) { Counter() } },
transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
// Click 3 times on the counter.
@@ -103,7 +107,7 @@
rule
.onNode(
hasText("count: 3") and
- hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+ hasParent(isElement(TestElements.Foo, scene = SceneA))
)
.assertExists()
.assertIsNotDisplayed()
@@ -111,7 +115,7 @@
rule
.onNode(
hasText("count: 0") and
- hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+ hasParent(isElement(TestElements.Foo, scene = SceneB))
)
.assertIsDisplayed()
.assertSizeIsEqualTo(75.dp, 75.dp)
@@ -148,27 +152,30 @@
@Test
fun movableElementIsMovedAndComposedOnlyOnce() {
val key =
- ElementKey(
+ MovableElementKey(
"Foo",
- scenePicker =
- object : ElementScenePicker {
- override fun sceneDuringTransition(
+ contentPicker =
+ object : StaticElementContentPicker {
+ override val contents: Set<ContentKey> = setOf(SceneA, SceneB)
+
+ override fun contentDuringTransition(
element: ElementKey,
- transition: TransitionState.Transition,
- fromSceneZIndex: Float,
- toSceneZIndex: Float
- ): SceneKey {
- assertThat(transition).hasFromScene(TestScenes.SceneA)
- assertThat(transition).hasToScene(TestScenes.SceneB)
- assertThat(fromSceneZIndex).isEqualTo(0)
- assertThat(toSceneZIndex).isEqualTo(1)
+ transition: ContentState.Transition<*>,
+ fromContentZIndex: Float,
+ toContentZIndex: Float
+ ): ContentKey {
+ transition as TransitionState.Transition
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ assertThat(fromContentZIndex).isEqualTo(0)
+ assertThat(toContentZIndex).isEqualTo(1)
// Compose Foo in Scene A if progress < 0.65f, otherwise compose it
// in Scene B.
return if (transition.progress < 0.65f) {
- TestScenes.SceneA
+ SceneA
} else {
- TestScenes.SceneB
+ SceneB
}
}
}
@@ -178,8 +185,8 @@
fromSceneContent = { MovableCounter(key, Modifier.size(50.dp)) },
toSceneContent = { MovableCounter(key, Modifier.size(100.dp)) },
transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
// Click 3 times on the counter.
@@ -207,7 +214,7 @@
rule
.onNode(
hasText("count: 3") and
- hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+ hasParent(isElement(TestElements.Foo, scene = SceneA))
)
.assertIsDisplayed()
.assertSizeIsEqualTo(75.dp, 75.dp)
@@ -228,7 +235,7 @@
rule
.onNode(
hasText("count: 3") and
- hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+ hasParent(isElement(TestElements.Foo, scene = SceneB))
)
.assertIsDisplayed()
@@ -263,17 +270,19 @@
@Test
fun movableElementContentIsRecomposedIfContentParametersChange() {
+ val key = MovableElementKey("Foo", contents = setOf(SceneA, SceneB))
+
@Composable
fun ContentScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
- MovableElement(TestElements.Foo, modifier) { content { Text(text) } }
+ MovableElement(key, modifier) { content { Text(text) } }
}
rule.testTransition(
fromSceneContent = { MovableFoo(text = "fromScene") },
toSceneContent = { MovableFoo(text = "toScene") },
transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
// Before the transition, only fromScene is composed.
before {
@@ -314,9 +323,10 @@
@Test
fun movableElementScopeExtendsBoxScope() {
+ val key = MovableElementKey("Foo", contents = setOf(SceneA))
rule.setContent {
TestContentScope {
- MovableElement(TestElements.Foo, Modifier.size(200.dp)) {
+ MovableElement(key, Modifier.size(200.dp)) {
content {
Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd))
Box(Modifier.testTag("matchParentSize").matchParentSize())
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 52cceec..6b417ee 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -25,6 +25,7 @@
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.animation.scene.transition.link.StateLink
import com.android.compose.test.runMonotonicClockTest
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index 66d4059..91bd7e1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -17,6 +17,8 @@
package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
+import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
@@ -37,14 +39,14 @@
isInitiatedByUserInput: Boolean = false,
isUserInputOngoing: Boolean = false,
isUpOrLeft: Boolean = false,
- bouncingScene: SceneKey? = null,
+ bouncingContent: ContentKey? = null,
orientation: Orientation = Orientation.Horizontal,
onFinish: ((TransitionState.Transition) -> Job)? = null,
replacedTransition: TransitionState.Transition? = null,
): TransitionState.Transition {
return object :
TransitionState.Transition(from, to, replacedTransition),
- TransitionState.HasOverscrollProperties {
+ ContentState.HasOverscrollProperties {
override val currentScene: SceneKey
get() = current()
@@ -66,7 +68,7 @@
override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
override val isUserInputOngoing: Boolean = isUserInputOngoing
override val isUpOrLeft: Boolean = isUpOrLeft
- override val bouncingScene: SceneKey? = bouncingScene
+ override val bouncingContent: ContentKey? = bouncingContent
override val orientation: Orientation = orientation
override val overscrollScope: OverscrollScope =
object : OverscrollScope {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index f4e0073..68240b5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -59,7 +59,7 @@
assertThat(transitions.transitionSpecs)
.comparingElementsUsing(
- Correspondence.transforming<TransitionSpecImpl, Pair<SceneKey?, SceneKey?>>(
+ Correspondence.transforming<TransitionSpecImpl, Pair<ContentKey?, ContentKey?>>(
{ it?.from to it?.to },
"has (from, to) equal to"
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index e997a75..a12ab78 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -18,7 +18,8 @@
import com.android.compose.animation.scene.OverscrollSpec
import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
import com.google.common.truth.Fact.simpleFact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.Subject
@@ -132,12 +133,12 @@
}
fun hasBouncingScene(scene: SceneKey) {
- if (actual !is TransitionState.HasOverscrollProperties) {
- failWithActual(simpleFact("expected to be TransitionState.HasOverscrollProperties"))
+ if (actual !is ContentState.HasOverscrollProperties) {
+ failWithActual(simpleFact("expected to be ContentState.HasOverscrollProperties"))
}
- check("bouncingScene")
- .that((actual as TransitionState.HasOverscrollProperties).bouncingScene)
+ check("bouncingContent")
+ .that((actual as ContentState.HasOverscrollProperties).bouncingContent)
.isEqualTo(scene)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 6c3f3c1..f7f69d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -105,6 +105,7 @@
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -183,7 +184,7 @@
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(testableResources.resources)
- val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0)
+ val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0)
lp.gravity = 0
whenever(view.layoutParams).thenReturn(lp)
@@ -542,6 +543,7 @@
// THEN the next security method of None will dismiss keyguard.
verify(viewMediatorCallback, never()).keyguardDone(anyInt())
}
+
@Test
fun showNextSecurityScreenOrFinish_SimPin_Swipe_userNotSetup() {
// GIVEN the current security method is SimPin
@@ -635,14 +637,6 @@
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
clearInvocations(viewFlipperController)
configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged()
- verify(viewFlipperController).clearViews()
- verify(viewFlipperController)
- .asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(),
- onViewInflatedCallbackArgumentCaptor.capture()
- )
- onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
verify(view).onDensityOrFontScaleChanged()
}
@@ -771,7 +765,9 @@
underTest.reinflateViewFlipper(onViewInflatedCallback)
verify(viewFlipperController).clearViews()
verify(viewFlipperController)
- .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback))
+ .asynchronouslyInflateView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(view).updateSecurityViewFlipper()
}
@Test
@@ -935,8 +931,10 @@
underTest.onViewAttached()
verify(userSwitcherController)
.addUserSwitchCallback(capture(userSwitchCallbackArgumentCaptor))
+ reset(primaryBouncerInteractor)
userSwitchCallbackArgumentCaptor.value.onUserSwitched()
- verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
+
+ verify(primaryBouncerInteractor).setLastShownPrimarySecurityScreen(any())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index 4fd44cc..cfe0bec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -108,7 +109,9 @@
facePropertyRepository = kosmos.fakeFacePropertyRepository,
deviceEntryFingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository,
- securityModel = securityModel
+ securityModel = securityModel,
+ deviceEntryBiometricsAllowedInteractor =
+ kosmos.deviceEntryBiometricsAllowedInteractor,
)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(
fingerprintAuthCurrentlyAllowed
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
index d51d356..15c57d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.testKosmos
import kotlinx.coroutines.test.runTest
@@ -55,7 +56,7 @@
private val testIntent =
PendingIntent.getActivity(
context,
- /* requestCode = */ 0,
+ /* requestCode= */ 0,
Intent("action"),
PendingIntent.FLAG_IMMUTABLE
)
@@ -66,7 +67,12 @@
@Before
fun setUp() {
with(kosmos) {
- underTest = SmartspaceInteractionHandler(activityStarter, communalSceneInteractor)
+ underTest =
+ SmartspaceInteractionHandler(
+ activityStarter = activityStarter,
+ communalSceneInteractor = communalSceneInteractor,
+ logBuffer = logcatLogBuffer(),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index ea8b5ab..86c680a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.testKosmos
import kotlinx.coroutines.test.runTest
@@ -53,7 +54,7 @@
private val testIntent =
PendingIntent.getActivity(
context,
- /* requestCode = */ 0,
+ /* requestCode= */ 0,
Intent("action"),
PendingIntent.FLAG_IMMUTABLE
)
@@ -64,7 +65,12 @@
@Before
fun setUp() {
with(kosmos) {
- underTest = WidgetInteractionHandler(activityStarter, communalSceneInteractor)
+ underTest =
+ WidgetInteractionHandler(
+ activityStarter = activityStarter,
+ communalSceneInteractor = communalSceneInteractor,
+ logBuffer = logcatLogBuffer(),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
index 51f9957..605d125 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
@@ -21,8 +21,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -36,41 +34,29 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val underTest = kosmos.deviceEntryFingerprintAuthInteractor
- private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
- private val biometricSettingsRepository = kosmos.biometricSettingsRepository
@Test
- fun isFingerprintAuthCurrentlyAllowed_allowedOnlyWhenItIsNotLockedOutAndAllowedBySettings() =
+ fun isSensorUnderDisplay() =
testScope.runTest {
- val currentlyAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
- biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
- fingerprintAuthRepository.setLockedOut(true)
-
- assertThat(currentlyAllowed).isFalse()
-
- fingerprintAuthRepository.setLockedOut(false)
- assertThat(currentlyAllowed).isTrue()
-
- biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
- assertThat(currentlyAllowed).isFalse()
+ val isUdfps by collectLastValue(underTest.isSensorUnderDisplay)
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isUdfps).isTrue()
}
@Test
- fun isFingerprintCurrentlyAllowedInBouncer_trueForNonUdfpsSensorTypes() =
+ fun isSensorUnderDisplay_rear() =
testScope.runTest {
- biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
-
- val isFingerprintCurrentlyAllowedInBouncer by
- collectLastValue(underTest.isFingerprintCurrentlyAllowedOnBouncer)
-
- fingerprintPropertyRepository.supportsUdfps()
- assertThat(isFingerprintCurrentlyAllowedInBouncer).isFalse()
-
+ val isUdfps by collectLastValue(underTest.isSensorUnderDisplay)
fingerprintPropertyRepository.supportsRearFps()
- assertThat(isFingerprintCurrentlyAllowedInBouncer).isTrue()
+ assertThat(isUdfps).isFalse()
+ }
+ @Test
+ fun isSensorUnderDisplay_side() =
+ testScope.runTest {
+ val isUdfps by collectLastValue(underTest.isSensorUnderDisplay)
fingerprintPropertyRepository.supportsSideFps()
- assertThat(isFingerprintCurrentlyAllowedInBouncer).isTrue()
+ assertThat(isUdfps).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index cde703b..5e9badc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -156,6 +156,43 @@
assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f)
}
+ @Test
+ fun deviceEntryBackgroundView_onCancel() =
+ testScope.runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ val deviceEntryBackgroundViewAlpha by
+ collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ runCurrent()
+
+ // GIVEN transition START
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f)
+
+ // WHEN transition is cancelled
+ repository.sendTransitionStep(step(.1f, TransitionState.CANCELED))
+
+ // THEN alpha is immediately set to 1f (expected lockscreen alpha state)
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha_onCancel() =
+ testScope.runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ val deviceEntryBackgroundViewAlpha by
+ collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
+
+ // GIVEN transition START
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+ // WHEN transition is cancelled
+ repository.sendTransitionStep(step(.1f, TransitionState.CANCELED))
+
+ // THEN alpha is immediately set to 1f (expected lockscreen alpha state)
+ assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f)
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 6ce7e88..e6ea64f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -201,6 +201,24 @@
assertThat(actual).isEqualTo(0f)
}
+ @Test
+ fun deviceEntryParentViewAlpha_shadeNotExpanded_onCancel() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ shadeExpanded(false)
+ runCurrent()
+
+ // START transition
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(actual).isEqualTo(1f)
+
+ // WHEN transition is canceled
+ repository.sendTransitionStep(step(1f, TransitionState.CANCELED))
+
+ // THEN alpha is immediately set to 0f
+ assertThat(actual).isEqualTo(0f)
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 34fbcac..40fb769 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -60,7 +60,6 @@
import com.android.systemui.res.R;
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -107,7 +106,6 @@
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private UserTracker mUserTracker;
- @Mock private NotificationShadeWindowModel mNotificationShadeWindowModel;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
@@ -163,7 +161,7 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mNotificationShadeWindowModel,
+ mKosmos.getNotificationShadeWindowModel(),
mKosmos::getCommunalInteractor) {
@Override
protected boolean isDebuggable() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index bec8cfe..9f40f60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -24,8 +24,11 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -56,6 +59,117 @@
}
@Test
+ fun ongoingCallNotification_noCallNotifs_null() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.ongoingCallNotification)
+
+ val normalNotifs =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ callType = CallType.None,
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ callType = CallType.None,
+ )
+ )
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { normalNotifs.forEach(::addIndividualNotif) }
+ .build()
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun ongoingCallNotification_incomingCallNotif_null() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.ongoingCallNotification)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "incomingNotif",
+ callType = CallType.Incoming,
+ )
+ )
+ }
+ .build()
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun ongoingCallNotification_screeningCallNotif_null() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.ongoingCallNotification)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "screeningNotif",
+ callType = CallType.Screening,
+ )
+ )
+ }
+ .build()
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun ongoingCallNotification_ongoingCallNotif_hasNotif() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.ongoingCallNotification)
+
+ val ongoingNotif =
+ activeNotificationModel(
+ key = "ongoingNotif",
+ callType = CallType.Ongoing,
+ )
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { addIndividualNotif(ongoingNotif) }
+ .build()
+
+ assertThat(latest).isEqualTo(ongoingNotif)
+ }
+
+ @Test
+ fun ongoingCallNotification_multipleCallNotifs_usesEarlierNotif() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.ongoingCallNotification)
+
+ val earlierOngoingNotif =
+ activeNotificationModel(
+ key = "earlierOngoingNotif",
+ callType = CallType.Ongoing,
+ whenTime = 45L,
+ )
+ val laterOngoingNotif =
+ activeNotificationModel(
+ key = "laterOngoingNotif",
+ callType = CallType.Ongoing,
+ whenTime = 55L,
+ )
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { addIndividualNotif(earlierOngoingNotif) }
+ .apply { addIndividualNotif(laterOngoingNotif) }
+ .build()
+
+ assertThat(latest).isEqualTo(earlierOngoingNotif)
+ }
+
+ @Test
fun areAnyNotificationsPresent_isTrue() =
testScope.runTest {
val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index bf58eee..d3218ad 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -14,6 +14,7 @@
package com.android.systemui.plugins.qs;
+import android.graphics.Rect;
import android.view.View;
import androidx.annotation.FloatRange;
@@ -35,7 +36,7 @@
String ACTION = "com.android.systemui.action.PLUGIN_QS";
- int VERSION = 15;
+ int VERSION = 16;
String TAG = "QS";
@@ -89,8 +90,45 @@
*/
int getHeightDiff();
+ /**
+ * Returns the header view that contains QQS. This might return null (or throw) if there's no
+ * actual header view.
+ */
View getHeader();
+ /**
+ * Returns the top of the header view that contains QQS wrt to the container view
+ */
+ int getHeaderTop();
+
+ /**
+ * Returns the bottom of the header view that contains QQS wrt to the container view
+ */
+ int getHeaderBottom();
+
+ /**
+ * Returns the left bound of the header view that contains QQS wrt to the container view
+ */
+ int getHeaderLeft();
+
+ /**
+ * Fills outBounds with the bounds of the header view (container of QQS) on the screen
+ */
+ void getHeaderBoundsOnScreen(Rect outBounds);
+
+ /**
+ * Returns the height of the header view that contains QQS. It defaults to bottom - top.
+ */
+ default int getHeaderHeight() {
+ return getHeaderBottom() - getHeaderTop();
+ }
+
+ /**
+ * Returns whether the header view that contains QQS is shown on screen (similar semantics to
+ * View.isShown).
+ */
+ boolean isHeaderShown();
+
default void setHasNotifications(boolean hasNotifications) {
}
diff --git a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
index 4670f34..3b3ed39 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
@@ -10,7 +10,7 @@
android:id="@+id/background"
android:layout_width="0dp"
android:layout_height="0dp"
- android:contentDescription="@string/biometric_dialog_empty_space_description"
+ android:contentDescription="@string/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
index c599f9e..2a00495 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
@@ -11,7 +11,7 @@
android:id="@+id/background"
android:layout_width="0dp"
android:layout_height="0dp"
- android:contentDescription="@string/biometric_dialog_empty_space_description"
+ android:contentDescription="@string/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -87,20 +87,21 @@
android:id="@+id/logo_description"
style="@style/TextAppearance.AuthCredential.LogoDescription"
android:layout_width="0dp"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/biometric_prompt_logo_size"
+ android:gravity="start|center_vertical"
android:textAlignment="viewStart"
- android:paddingStart="16dp"
- app:layout_constraintBottom_toBottomOf="@+id/logo"
+ android:layout_marginStart="16dp"
+ app:layout_goneMarginStart="0dp"
+ app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/logo"
- app:layout_constraintTop_toTopOf="@+id/logo" />
+ app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
style="@style/TextAppearance.AuthCredential.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -108,7 +109,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/logo"
+ app:layout_constraintTop_toBottomOf="@+id/logoBarrier"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" />
@@ -159,6 +160,15 @@
app:layout_constraintVertical_bias="0.0" />
<androidx.constraintlayout.widget.Barrier
+ android:id="@+id/logoBarrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierMargin="12dp"
+ app:barrierAllowsGoneWidgets="false"
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="logo_description, logo" />
+
+ <androidx.constraintlayout.widget.Barrier
android:id="@+id/contentBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml
index 84462d0..ff2fbe07 100644
--- a/packages/SystemUI/res/layout/hearing_tool_item.xml
+++ b/packages/SystemUI/res/layout/hearing_tool_item.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:gravity="center"
+ android:gravity="top|center_horizontal"
android:focusable="true"
android:clickable="true"
android:layout_weight="1">
@@ -46,8 +46,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/hearing_devices_layout_margin"
android:ellipsize="end"
- android:maxLines="1"
android:textSize="12sp"
+ android:maxLines="3"
android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
android:focusable="false" />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
index 30d7b0a..e30ae6e 100644
--- a/packages/SystemUI/res/layout/record_issue_dialog.xml
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -35,7 +35,7 @@
android:layout_height="wrap_content"
android:text="@string/qs_record_issue_dropdown_prompt"
android:lines="1"
- android:drawableRight="@drawable/arrow_pointing_down"
+ android:drawableEnd="@drawable/arrow_pointing_down"
android:layout_marginTop="@dimen/qqs_layout_margin_top"
android:focusable="false"
android:clickable="true" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index c29c236..f0c8894 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1050,4 +1050,7 @@
<!-- Only applicable for dual shade - Allow Notifications/QS shade to anchor to the bottom. -->
<bool name="config_dualShadeAlignedToBottom">false</bool>
+
+ <!-- List of packages for which we want to use activity info (instead of application info) for biometric prompt logo. Empty for AOSP. [DO NOT TRANSLATE] -->
+ <string-array name="config_useActivityLogoForBiometricPrompt" translatable="false"/>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e590f15..0318458 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -432,8 +432,6 @@
<!-- Content description for the app logo icon on biometric prompt. [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_logo">App logo</string>
- <!-- List of packages for which we want to show overridden logo. For example, an app overrides its launcher logo, if it's in this array, biometric dialog shows the overridden logo; otherwise biometric dialog still shows the default application info icon. [CHAR LIMIT=NONE] -->
- <string-array name="biometric_dialog_package_names_for_logo_with_overrides" />
<!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
<string name="biometric_dialog_confirm">Confirm</string>
<!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c428705d..7fa7088 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -192,11 +192,10 @@
<style name="TextAppearance.AuthCredential.LogoDescription" parent="TextAppearance.Material3.LabelLarge" >
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
- <item name="android:ellipsize">marquee</item>
<item name="android:gravity">@integer/biometric_dialog_text_gravity</item>
- <item name="android:marqueeRepeatLimit">1</item>
- <item name="android:singleLine">true</item>
+ <item name="android:maxLines">1</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+ <item name="android:ellipsize">end</item>
</style>
<style name="TextAppearance.AuthCredential.Title" parent="TextAppearance.Material3.HeadlineSmall" >
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 4217820..bf905db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -647,7 +647,12 @@
@Override
public void onFinishInflate() {
super.onFinishInflate();
+ updateSecurityViewFlipper();
+ }
+
+ protected void updateSecurityViewFlipper() {
mSecurityViewFlipper = findViewById(R.id.view_flipper);
+ setupViewMode();
}
@Override
@@ -1004,10 +1009,10 @@
if (mUserSwitcherViewGroup == null) {
inflateUserSwitcher();
+ setupUserSwitcher();
+ mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback);
}
updateSecurityViewLocation();
- setupUserSwitcher();
- mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 93ee179..afd42cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -164,11 +164,6 @@
}
mCurrentUser = mSelectedUserInteractor.getSelectedUserId();
showPrimarySecurityScreen(false);
- if (mCurrentSecurityMode != SimPin
- && mCurrentSecurityMode != SimPuk) {
- reinflateViewFlipper((l) -> {
- });
- }
}
};
@@ -375,8 +370,7 @@
@Override
public void onDensityOrFontScaleChanged() {
- KeyguardSecurityContainerController.this
- .onDensityOrFontScaleOrOrientationChanged();
+ mView.onDensityOrFontScaleChanged();
}
@Override
@@ -1227,11 +1221,6 @@
mView.reloadColors();
}
- /** Handles density or font scale changes. */
- private void onDensityOrFontScaleOrOrientationChanged() {
- reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged());
- }
-
/**
* Reinflate the view flipper child view.
*/
@@ -1239,7 +1228,10 @@
KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedListener) {
mSecurityViewFlipperController.clearViews();
mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode,
- mKeyguardSecurityCallback, onViewInflatedListener);
+ mKeyguardSecurityCallback, (controller) -> {
+ mView.updateSecurityViewFlipper();
+ onViewInflatedListener.onViewInflated(controller);
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index d848602..073f33fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -33,7 +33,6 @@
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.CrossFadeHelper;
-import java.io.PrintWriter;
import java.util.Set;
/**
@@ -117,18 +116,6 @@
return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
}
- public void dump(PrintWriter pw, String[] args) {
- pw.println("KeyguardStatusView:");
- pw.println(" mDarkAmount: " + mDarkAmount);
- pw.println(" visibility: " + getVisibility());
- if (mClockView != null) {
- mClockView.dump(pw, args);
- }
- if (mKeyguardSlice != null) {
- mKeyguardSlice.dump(pw, args);
- }
- }
-
@Override
public ViewPropertyAnimator animate() {
if (Build.IS_DEBUGGABLE) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 603a47e..63a4af9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -48,9 +48,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.Dumpable;
import com.android.systemui.animation.ViewHierarchyAnimator;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.clocks.ClockController;
@@ -70,15 +68,12 @@
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.EmptyCoroutineContext;
-import java.io.PrintWriter;
-
import javax.inject.Inject;
/**
* Injectable controller for {@link KeyguardStatusView}.
*/
-public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> implements
- Dumpable {
+public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@VisibleForTesting static final String TAG = "KeyguardStatusViewController";
private static final long STATUS_AREA_HEIGHT_ANIMATION_MILLIS = 133;
@@ -108,7 +103,6 @@
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
- private DumpManager mDumpManager;
private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
new TransitionListenerAdapter() {
@@ -176,7 +170,6 @@
KeyguardLogger logger,
InteractionJankMonitor interactionJankMonitor,
KeyguardInteractor keyguardInteractor,
- DumpManager dumpManager,
PowerInteractor powerInteractor) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
@@ -188,7 +181,6 @@
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
logger.getBuffer());
mInteractionJankMonitor = interactionJankMonitor;
- mDumpManager = dumpManager;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
}
@@ -222,7 +214,6 @@
});
}
- mDumpManager.registerDumpable(getInstanceName(), this);
if (MigrateClocksToBlueprint.isEnabled()) {
startCoroutines(EmptyCoroutineContext.INSTANCE);
mView.setVisibility(View.GONE);
@@ -276,13 +267,6 @@
}
/**
- * Called in notificationPanelViewController to avoid leak
- */
- public void onDestroy() {
- mDumpManager.unregisterDumpable(getInstanceName());
- }
-
- /**
* Updates views on doze time tick.
*/
public void dozeTimeTick() {
@@ -604,11 +588,6 @@
return mKeyguardClockSwitchController.getClock();
}
- @Override
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- mView.dump(pw, args);
- }
-
String getInstanceName() {
return TAG + "#" + hashCode();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 430ff07..9521be1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -65,9 +65,6 @@
import android.window.OnBackInvokedDispatcher;
import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.core.view.AccessibilityDelegateCompat;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
@@ -385,19 +382,6 @@
mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview);
addView(mLayout);
mBackgroundView = mLayout.findViewById(R.id.background);
- ViewCompat.setAccessibilityDelegate(mBackgroundView, new AccessibilityDelegateCompat() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host,
- AccessibilityNodeInfoCompat info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.addAction(
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- AccessibilityNodeInfoCompat.ACTION_CLICK,
- mContext.getString(R.string.biometric_dialog_cancel_authentication)
- )
- );
- }
- });
mPanelView = mLayout.findViewById(R.id.panel);
if (!constraintBp()) {
@@ -428,7 +412,6 @@
});
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- setFocusableInTouchMode(true);
requestFocus();
}
@@ -480,7 +463,8 @@
}
}
- private void onBackInvoked() {
+ @VisibleForTesting
+ public void onBackInvoked() {
sendEarlyUserCanceled();
animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 9f6d565..3ef5572 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -34,6 +34,10 @@
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
+import androidx.core.view.AccessibilityDelegateCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -63,6 +67,7 @@
import kotlinx.coroutines.launch
private const val TAG = "BiometricViewBinder"
+private const val MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30
/** Top-most view binder for BiometricPrompt views. */
object BiometricViewBinder {
@@ -145,6 +150,25 @@
val confirmationButton = view.requireViewById<Button>(R.id.button_confirm)
val retryButton = view.requireViewById<Button>(R.id.button_try_again)
+ // Handles custom "Cancel Authentication" talkback action
+ val cancelDelegate: AccessibilityDelegateCompat =
+ object : AccessibilityDelegateCompat() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfoCompat
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ info.addAction(
+ AccessibilityActionCompat(
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
+ view.context.getString(R.string.biometric_dialog_cancel_authentication)
+ )
+ )
+ }
+ }
+ ViewCompat.setAccessibilityDelegate(backgroundView, cancelDelegate)
+ ViewCompat.setAccessibilityDelegate(cancelButton, cancelDelegate)
+
// TODO(b/330788871): temporary workaround for the unsafe callbacks & legacy controllers
val adapter =
Spaghetti(
@@ -172,8 +196,12 @@
}
}
- logoView.setImageDrawable(viewModel.logo.first())
- logoDescriptionView.text = viewModel.logoDescription.first()
+ val logoInfo = viewModel.logoInfo.first()
+ logoView.setImageDrawable(logoInfo.first)
+ // The ellipsize effect on xml happens only when the TextView does not have any free
+ // space on the screen to show the text. So we need to manually truncate.
+ logoDescriptionView.text =
+ logoInfo.second?.ellipsize(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER)
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
@@ -694,6 +722,9 @@
else -> ""
}
+private fun String.ellipsize(cutOffLength: Int) =
+ if (length <= cutOffLength) this else replaceRange(cutOffLength, length, "...")
+
private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
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 b1cba2f..d1ac681 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
@@ -36,7 +36,6 @@
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.launcher3.icons.IconProvider
-import com.android.systemui.Flags.bpTalkback
import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
@@ -471,26 +470,13 @@
}
}
- /** Logo for the prompt. */
- val logo: Flow<Drawable?> =
+ /** (logoIcon, logoDescription) for the prompt. */
+ val logoInfo: Flow<Pair<Drawable?, String>> =
promptSelectorInteractor.prompt
.map {
when {
- !(customBiometricPrompt() && constraintBp()) || it == null -> null
- it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
- else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager)
- }
- }
- .distinctUntilChanged()
-
- /** Logo description for the prompt. */
- val logoDescription: Flow<String> =
- promptSelectorInteractor.prompt
- .map {
- when {
- !(customBiometricPrompt() && constraintBp()) || it == null -> ""
- !it.logoDescription.isNullOrEmpty() -> it.logoDescription
- else -> context.getUserBadgedLabel(it, activityTaskManager)
+ !(customBiometricPrompt() && constraintBp()) || it == null -> Pair(null, "")
+ else -> context.getUserBadgedLogoInfo(it, iconProvider, activityTaskManager)
}
}
.distinctUntilChanged()
@@ -916,10 +902,9 @@
touchExplorationEnabled: Boolean,
): Boolean {
if (
- bpTalkback() &&
- modalities.first().hasUdfps &&
- touchExplorationEnabled &&
- !isAuthenticated.first().isAuthenticated
+ modalities.first().hasUdfps &&
+ touchExplorationEnabled &&
+ !isAuthenticated.first().isAuthenticated
) {
// TODO(b/315184924): Remove uses of UdfpsUtils
val scaledTouch =
@@ -987,43 +972,60 @@
}
}
-private fun Context.getUserBadgedIcon(
+/**
+ * The order of getting logo icon/description is:
+ * 1. If the app sets customized icon/description, use the passed-in value
+ * 2. If shouldShowLogoWithOverrides(), use activityInfo to get icon/description
+ * 3. Otherwise, use applicationInfo to get icon/description
+ */
+private fun Context.getUserBadgedLogoInfo(
prompt: BiometricPromptRequest.Biometric,
iconProvider: IconProvider,
activityTaskManager: ActivityTaskManager
-): Drawable? {
- var icon: Drawable? = null
- val componentName = prompt.getComponentNameForLogo(activityTaskManager)
- if (componentName != null && shouldShowLogoWithOverrides(componentName)) {
- val activityInfo = getActivityInfo(componentName)
- icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo)
+): Pair<Drawable?, String> {
+ var icon: Drawable? =
+ if (prompt.logoBitmap != null) BitmapDrawable(resources, prompt.logoBitmap) else null
+ var label = prompt.logoDescription ?: ""
+ if (icon != null && label.isNotEmpty()) {
+ return Pair(icon, label)
}
- if (icon == null) {
- val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
- if (appInfo == null) {
- Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
- return null
- } else {
- icon = packageManager.getApplicationIcon(appInfo)
+
+ // Use activityInfo if shouldUseActivityLogo() is true
+ val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+ if (componentName != null && shouldUseActivityLogo(componentName)) {
+ val activityInfo = getActivityInfo(componentName)
+ if (activityInfo != null) {
+ if (icon == null) {
+ icon = iconProvider.getIcon(activityInfo)
+ }
+ if (label.isEmpty()) {
+ label = activityInfo.loadLabel(packageManager).toString()
+ }
}
}
- return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId))
-}
-
-private fun Context.getUserBadgedLabel(
- prompt: BiometricPromptRequest.Biometric,
- activityTaskManager: ActivityTaskManager
-): String {
- val componentName = prompt.getComponentNameForLogo(activityTaskManager)
- val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
- return if (appInfo == null || packageManager.getApplicationLabel(appInfo).isNullOrEmpty()) {
- Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
- ""
- } else {
- packageManager
- .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId))
- .toString()
+ if (icon != null && label.isNotEmpty()) {
+ return Pair(icon, label)
}
+
+ // Use applicationInfo for other cases
+ val appInfo = prompt.getApplicationInfo(this, componentName)
+ if (appInfo == null) {
+ Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+ } else {
+ if (icon == null) {
+ icon = packageManager.getApplicationIcon(appInfo)
+ }
+ if (label.isEmpty()) {
+ label =
+ packageManager
+ .getUserBadgedLabel(
+ packageManager.getApplicationLabel(appInfo),
+ UserHandle.of(userId)
+ )
+ .toString()
+ }
+ }
+ return Pair(icon, label)
}
private fun BiometricPromptRequest.Biometric.getComponentNameForLogo(
@@ -1041,7 +1043,7 @@
}
}
-private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo(
+private fun BiometricPromptRequest.Biometric.getApplicationInfo(
context: Context,
componentNameForLogo: ComponentName?
): ApplicationInfo? {
@@ -1058,14 +1060,22 @@
Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName")
null
} else {
- context.getApplicationInfo(packageName)
+ try {
+ context.packageManager.getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
+ null
+ }
}
}
-private fun Context.shouldShowLogoWithOverrides(componentName: ComponentName): Boolean {
- return resources
- .getStringArray(R.array.biometric_dialog_package_names_for_logo_with_overrides)
- .find { componentName.packageName.contentEquals(it) } != null
+private fun Context.shouldUseActivityLogo(componentName: ComponentName): Boolean {
+ return resources.getStringArray(R.array.config_useActivityLogoForBiometricPrompt).find {
+ componentName.packageName.contentEquals(it)
+ } != null
}
private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? =
@@ -1076,17 +1086,6 @@
null
}
-private fun Context.getApplicationInfo(packageName: String): ApplicationInfo? =
- try {
- packageManager.getApplicationInfo(
- packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
- )
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
- null
- }
-
/** How the fingerprint sensor was started for the prompt. */
enum class FingerprintStartMode {
/** Fingerprint sensor has not started. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 4984fc6..373671d0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -21,7 +21,7 @@
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -60,7 +60,8 @@
private val biometricSettingsRepository: BiometricSettingsRepository,
private val systemClock: SystemClock,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val deviceEntryFingerprintAuthInteractor: Lazy<DeviceEntryFingerprintAuthInteractor>,
+ private val deviceEntryBiometricsAllowedInteractor:
+ Lazy<DeviceEntryBiometricsAllowedInteractor>,
private val keyguardInteractor: Lazy<KeyguardInteractor>,
keyguardTransitionInteractor: Lazy<KeyguardTransitionInteractor>,
sceneInteractor: Lazy<SceneInteractor>,
@@ -114,7 +115,7 @@
flowOf(false)
} else {
combine(
- deviceEntryFingerprintAuthInteractor
+ deviceEntryBiometricsAllowedInteractor
.get()
.isFingerprintAuthCurrentlyAllowed,
keyguardInteractor.get().isKeyguardDismissible,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index 35015e7..8e12646 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -33,6 +33,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
@@ -76,10 +77,11 @@
private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
faceAuthRepository: DeviceEntryFaceAuthRepository,
private val securityModel: KeyguardSecurityModel,
+ deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
) {
private val isFingerprintAuthCurrentlyAllowedOnBouncer =
- deviceEntryFingerprintAuthInteractor.isFingerprintCurrentlyAllowedOnBouncer.stateIn(
+ deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer.stateIn(
applicationScope,
SharingStarted.Eagerly,
false
@@ -87,6 +89,7 @@
private val currentSecurityMode
get() = securityModel.getSecurityMode(currentUserId)
+
private val currentUserId
get() = userRepository.getSelectedUserInfo().id
@@ -349,6 +352,7 @@
interface CountDownTimerCallback {
fun onFinish()
+
fun onTick(millisUntilFinished: Long)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/log/BouncerLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/bouncer/log/BouncerLoggerStartable.kt
index 29c4f2e..87ec254 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/log/BouncerLoggerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/log/BouncerLoggerStartable.kt
@@ -20,6 +20,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricSettingsInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.log.BouncerLogger
@@ -39,6 +40,7 @@
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val bouncerLogger: BouncerLogger,
+ private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
) : CoreStartable {
override fun start() {
if (!Build.isDebuggable()) {
@@ -69,13 +71,13 @@
}
}
applicationScope.launch {
- fingerprintAuthInteractor.isFingerprintCurrentlyAllowedOnBouncer.collectLatest {
- newValue ->
- bouncerLogger.interestedStateChanged(
- "fingerprintCurrentlyAllowedOnBouncer",
- newValue
- )
- }
+ deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
+ .collectLatest { newValue ->
+ bouncerLogger.interestedStateChanged(
+ "fingerprintCurrentlyAllowedOnBouncer",
+ newValue
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 810b6d1..31479f1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -30,8 +30,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
@@ -76,7 +76,7 @@
private val biometricMessageInteractor: BiometricMessageInteractor,
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
- private val fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
+ private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
flags: ComposeBouncerFlags,
) {
/**
@@ -121,7 +121,8 @@
combine(
deviceUnlockedInteractor.deviceEntryRestrictionReason,
lockoutMessage,
- fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer,
+ deviceEntryBiometricsAllowedInteractor
+ .isFingerprintCurrentlyAllowedOnBouncer,
resetToDefault,
) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ ->
lockoutMsg
@@ -168,7 +169,7 @@
biometricMessageInteractor.faceMessage
.sample(
authenticationInteractor.authenticationMethod,
- fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer,
+ deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer,
)
.collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) ->
val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong()
@@ -223,7 +224,7 @@
biometricMessageInteractor.fingerprintMessage
.sample(
authenticationInteractor.authenticationMethod,
- fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer
+ deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
)
.collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) ->
val defaultPrimaryMessage =
@@ -261,7 +262,7 @@
bouncerInteractor.onIncorrectBouncerInput
.sample(
authenticationInteractor.authenticationMethod,
- fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer
+ deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
)
.collectLatest { (_, authMethod, isFingerprintAllowed) ->
message.emit(
@@ -414,7 +415,7 @@
biometricMessageInteractor: BiometricMessageInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
deviceUnlockedInteractor: DeviceUnlockedInteractor,
- fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
+ deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
flags: ComposeBouncerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
): BouncerMessageViewModel {
@@ -428,7 +429,7 @@
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = faceAuthInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
- fingerprintInteractor = fingerprintInteractor,
+ deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
index 4e3d3ff..c4edcac 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -25,6 +25,9 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
@@ -34,12 +37,19 @@
constructor(
private val activityStarter: ActivityStarter,
communalSceneInteractor: CommunalSceneInteractor,
+ @CommunalLog val logBuffer: LogBuffer,
) : RemoteViews.InteractionHandler {
+
+ private companion object {
+ const val TAG = "SmartspaceInteractionHandler"
+ }
+
private val delegate =
InteractionHandlerDelegate(
communalSceneInteractor,
findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
intentStarter = this::startIntent,
+ logger = Logger(logBuffer, TAG),
)
override fun onInteraction(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
index 51a5fcd..d2029d5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -26,12 +26,14 @@
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
+import com.android.systemui.log.core.Logger
/** A delegate that can be used to launch activities from [RemoteViews] */
class InteractionHandlerDelegate(
private val communalSceneInteractor: CommunalSceneInteractor,
private val findViewToAnimate: (View) -> Boolean,
private val intentStarter: IntentStarter,
+ private val logger: Logger,
) : RemoteViews.InteractionHandler {
/** Responsible for starting the pending intent for launching activities. */
@@ -49,6 +51,10 @@
pendingIntent: PendingIntent,
response: RemoteViews.RemoteResponse
): Boolean {
+ logger.i({ "Starting $str1 ($str2)" }) {
+ str1 = pendingIntent.toLoggingString()
+ str2 = pendingIntent.creatorPackage
+ }
val launchOptions = response.getLaunchOptions(view)
return when {
pendingIntent.isActivity -> {
@@ -82,3 +88,12 @@
return null
}
}
+
+private fun PendingIntent.toLoggingString() =
+ when {
+ isActivity -> "activity"
+ isBroadcast -> "broadcast"
+ isForegroundService -> "fgService"
+ isService -> "service"
+ else -> "unknown"
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index 519903e..0eeb506 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -25,6 +25,9 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
@@ -33,14 +36,20 @@
@Inject
constructor(
private val activityStarter: ActivityStarter,
- private val communalSceneInteractor: CommunalSceneInteractor
+ communalSceneInteractor: CommunalSceneInteractor,
+ @CommunalLog val logBuffer: LogBuffer,
) : RemoteViews.InteractionHandler {
+ private companion object {
+ const val TAG = "WidgetInteractionHandler"
+ }
+
private val delegate =
InteractionHandlerDelegate(
communalSceneInteractor,
findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
intentStarter = this::startIntent,
+ logger = Logger(logBuffer, TAG),
)
override fun onInteraction(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a448072..609aa39 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -103,6 +103,7 @@
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
@@ -154,6 +155,7 @@
import com.android.systemui.util.kotlin.SysUICoroutinesModule;
import com.android.systemui.util.reference.ReferenceModule;
import com.android.systemui.util.sensors.SensorModule;
+import com.android.systemui.util.settings.SettingsProxy;
import com.android.systemui.util.settings.SettingsUtilModule;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClockImpl;
@@ -268,15 +270,15 @@
NoteTaskModule.class,
WalletModule.class,
ContextualEducationModule.class
- },
+},
subcomponents = {
- ComplicationComponent.class,
- DozeComponent.class,
- ExpandableNotificationRowComponent.class,
- KeyguardBouncerComponent.class,
- NavigationBarComponent.class,
- NotificationRowComponent.class,
- WindowRootViewComponent.class,
+ ComplicationComponent.class,
+ DozeComponent.class,
+ ExpandableNotificationRowComponent.class,
+ KeyguardBouncerComponent.class,
+ NavigationBarComponent.class,
+ NotificationRowComponent.class,
+ WindowRootViewComponent.class,
})
public abstract class SystemUIModule {
@@ -443,4 +445,9 @@
@Binds
abstract SceneDataSource bindSceneDataSource(SceneDataSourceDelegator delegator);
+
+ @Provides
+ static SettingsProxy.CurrentUserIdProvider provideCurrentUserId(UserTracker userTracker) {
+ return userTracker::getUserId;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
new file mode 100644
index 0000000..79b176c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Individual biometrics (ie: fingerprint or face) may not be allowed to be used based on the
+ * lockout states of biometrics of the same or higher sensor strength.
+ *
+ * This class coordinates the lockout states of each individual biometric based on the lockout
+ * states of other biometrics.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DeviceEntryBiometricsAllowedInteractor
+@Inject
+constructor(
+ deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ facePropertyRepository: FacePropertyRepository,
+) {
+
+ private val isStrongFaceAuth: Flow<Boolean> =
+ facePropertyRepository.sensorInfo.map { it?.strength == SensorStrength.STRONG }
+
+ private val isStrongFaceAuthLockedOut: Flow<Boolean> =
+ combine(isStrongFaceAuth, deviceEntryFaceAuthInteractor.isLockedOut) {
+ isStrongFaceAuth,
+ isFaceAuthLockedOut ->
+ isStrongFaceAuth && isFaceAuthLockedOut
+ }
+
+ /**
+ * Whether fingerprint authentication is currently allowed for the user. This is true if the
+ * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
+ * [com.android.systemui.keyguard.shared.model.AuthenticationFlags], not locked out due to too
+ * many incorrect attempts, and other biometrics at a higher or equal strenght are not locking
+ * fingerprint out.
+ */
+ val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
+ combine(
+ deviceEntryFingerprintAuthInteractor.isLockedOut,
+ biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed,
+ isStrongFaceAuthLockedOut,
+ ) { fpLockedOut, fpAllowedBySettings, strongAuthFaceAuthLockedOut ->
+ !fpLockedOut && fpAllowedBySettings && !strongAuthFaceAuthLockedOut
+ }
+
+ /** Whether fingerprint authentication is currently allowed while on the bouncer. */
+ val isFingerprintCurrentlyAllowedOnBouncer =
+ deviceEntryFingerprintAuthInteractor.isSensorUnderDisplay.flatMapLatest { sensorBelowDisplay
+ ->
+ if (sensorBelowDisplay) {
+ flowOf(false)
+ } else {
+ isFingerprintAuthCurrentlyAllowed
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index a5eafa9..969f53f 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -29,10 +29,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@OptIn(ExperimentalCoroutinesApi::class)
@@ -70,29 +67,9 @@
repository.authenticationStatus.filterIsInstance<SuccessFingerprintAuthenticationStatus>()
/**
- * Whether fingerprint authentication is currently allowed for the user. This is true if the
- * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
- * [com.android.systemui.keyguard.shared.model.AuthenticationFlags] and not locked out due to
- * too many incorrect attempts.
- */
- val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
- combine(isLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
- .map { (lockedOut, currentlyAllowed) -> !lockedOut && currentlyAllowed }
-
- /**
* Whether the fingerprint sensor is present under the display as opposed to being on the power
* button or behind/rear of the phone.
*/
val isSensorUnderDisplay =
fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps)
-
- /** Whether fingerprint authentication is currently allowed while on the bouncer. */
- val isFingerprintCurrentlyAllowedOnBouncer =
- isSensorUnderDisplay.flatMapLatest { sensorBelowDisplay ->
- if (sensorBelowDisplay) {
- flowOf(false)
- } else {
- isFingerprintAuthCurrentlyAllowed
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 6ed84e5..89fce4a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -293,11 +293,11 @@
if (shouldUseFloatBrightness()) {
mDozeService.setDozeScreenBrightnessFloat(
clampToDimBrightnessForScreenOffFloat(
- clampToUserSettingFloat(mDefaultDozeBrightnessFloat)));
+ clampToUserSettingOrAutoBrightnessFloat(mDefaultDozeBrightnessFloat)));
} else {
mDozeService.setDozeScreenBrightness(
clampToDimBrightnessForScreenOff(
- clampToUserSetting(mDefaultDozeBrightness)));
+ clampToUserSettingOrAutoBrightness(mDefaultDozeBrightness)));
}
mDozeHost.setAodDimmingScrim(0f);
}
@@ -310,10 +310,7 @@
return brightness;
}
- int userSetting = mSystemSettings.getIntForUser(
- Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE,
- UserHandle.USER_CURRENT);
- return Math.min(brightness, userSetting);
+ return Math.min(brightness, getScreenBrightness());
}
@SuppressLint("AndroidFrameworkRequiresPermission")
@@ -325,8 +322,33 @@
return brightness;
}
- float userSetting = mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY);
- return Math.min(brightness, userSetting);
+ return Math.min(brightness, getScreenBrightnessFloat());
+ }
+
+ private int clampToUserSettingOrAutoBrightness(int brightness) {
+ return Math.min(brightness, getScreenBrightness());
+ }
+
+ private float clampToUserSettingOrAutoBrightnessFloat(float brightness) {
+ return Math.min(brightness, getScreenBrightnessFloat());
+ }
+
+ /**
+ * Gets the current screen brightness that may have been set by manually by the user
+ * or by autobrightness.
+ */
+ private int getScreenBrightness() {
+ return mSystemSettings.getIntForUser(
+ Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Gets the current screen brightness that may have been set by manually by the user
+ * or by autobrightness.
+ */
+ private float getScreenBrightnessFloat() {
+ return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
index dbfea76..701d3da 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
@@ -18,7 +18,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.inputdevice.oobe.domain.interactor.OobeTutorialSchedulerInteractor
+import com.android.systemui.inputdevice.oobe.domain.interactor.OobeSchedulerInteractor
import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
import dagger.Lazy
import javax.inject.Inject
@@ -27,11 +27,10 @@
@SysUISingleton
class KeyboardTouchpadOobeTutorialCoreStartable
@Inject
-constructor(private val oobeTutorialSchedulerInteractor: Lazy<OobeTutorialSchedulerInteractor>) :
- CoreStartable {
+constructor(private val oobeSchedulerInteractor: Lazy<OobeSchedulerInteractor>) : CoreStartable {
override fun start() {
if (newTouchpadGesturesTutorial()) {
- oobeTutorialSchedulerInteractor.get().start()
+ oobeSchedulerInteractor.get().start()
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
similarity index 60%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
copy to packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
index 14b9e7f..e5aedc0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.windowdecor.common
-/** A callback to be invoked when a Task's window decor element is clicked. */
-fun interface OnTaskActionClickListener {
- /**
- * Called when a task's decor element has been clicked.
- *
- * @param taskId the id of the task.
- * @param tag a readable identifier for the element.
- */
- fun onClick(taskId: Int, tag: String)
+package com.android.systemui.inputdevice.oobe.data.model
+
+data class OobeSchedulerInfo(
+ val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(),
+ val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo()
+)
+
+data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectionTime: Long? = null) {
+ val wasEverConnected: Boolean
+ get() = connectionTime != null
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
new file mode 100644
index 0000000..b014c08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.inputdevice.oobe.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.inputdevice.oobe.data.model.DeviceSchedulerInfo
+import com.android.systemui.inputdevice.oobe.data.model.OobeSchedulerInfo
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import java.time.Duration
+import java.time.Instant
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * When the first time a keyboard or touchpad id connected, wait for [LAUNCH_DELAY], then launch the
+ * tutorial as soon as there's a connected device
+ */
+@SysUISingleton
+class OobeSchedulerInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ private val keyboardRepository: KeyboardRepository,
+ private val touchpadRepository: TouchpadRepository
+) {
+ private val info = OobeSchedulerInfo()
+
+ fun start() {
+ if (!info.keyboard.isLaunched) {
+ applicationScope.launch {
+ schedule(keyboardRepository.isAnyKeyboardConnected, info.keyboard)
+ }
+ }
+ if (!info.touchpad.isLaunched) {
+ applicationScope.launch {
+ schedule(touchpadRepository.isAnyTouchpadConnected, info.touchpad)
+ }
+ }
+ }
+
+ private suspend fun schedule(isAnyDeviceConnected: Flow<Boolean>, info: DeviceSchedulerInfo) {
+ if (!info.wasEverConnected) {
+ waitForDeviceConnection(isAnyDeviceConnected)
+ info.connectionTime = Instant.now().toEpochMilli()
+ }
+ delay(remainingTimeMillis(info.connectionTime!!))
+ waitForDeviceConnection(isAnyDeviceConnected)
+ info.isLaunched = true
+ launchOobe()
+ }
+
+ private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
+ return isAnyDeviceConnected.filter { it }.first()
+ }
+
+ private fun launchOobe() {
+ val intent = Intent(TUTORIAL_ACTION)
+ intent.addCategory(Intent.CATEGORY_DEFAULT)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+
+ private fun remainingTimeMillis(start: Long): Long {
+ val elapsed = Instant.now().toEpochMilli() - start
+ return LAUNCH_DELAY - elapsed
+ }
+
+ companion object {
+ const val TAG = "OobeSchedulerInteractor"
+ const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+ private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeTutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeTutorialSchedulerInteractor.kt
deleted file mode 100644
index 0d69081..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeTutorialSchedulerInteractor.kt
+++ /dev/null
@@ -1,58 +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.inputdevice.oobe.domain.interactor
-
-import android.content.Context
-import android.content.Intent
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyboard.data.repository.KeyboardRepository
-import com.android.systemui.touchpad.data.repository.TouchpadRepository
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-/** When keyboards or touchpads are connected, schedule a tutorial after given time has elapsed */
-@SysUISingleton
-class OobeTutorialSchedulerInteractor
-@Inject
-constructor(
- @Application private val context: Context,
- @Application private val applicationScope: CoroutineScope,
- keyboardRepository: KeyboardRepository,
- touchpadRepository: TouchpadRepository
-) {
- private val isAnyKeyboardConnected = keyboardRepository.isAnyKeyboardConnected
- private val isAnyTouchpadConnected = touchpadRepository.isAnyTouchpadConnected
-
- fun start() {
- applicationScope.launch { isAnyKeyboardConnected.collect { startOobe() } }
- applicationScope.launch { isAnyTouchpadConnected.collect { startOobe() } }
- }
-
- private fun startOobe() {
- val intent = Intent(TUTORIAL_ACTION)
- intent.addCategory(Intent.CATEGORY_DEFAULT)
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- context.startActivity(intent)
- }
-
- companion object {
- const val TAG = "OobeSchedulerInteractor"
- const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 58719fe..67aedde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -806,14 +806,27 @@
@Composable
private fun KeyboardSettings(onClick: () -> Unit) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isFocused by interactionSource.collectIsFocusedAsState()
+
Surface(
onClick = onClick,
shape = RoundedCornerShape(24.dp),
color = Color.Transparent,
- modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth()
+ modifier =
+ Modifier.semantics { role = Role.Button }
+ .fillMaxWidth()
+ .focusable(interactionSource = interactionSource)
) {
Row(
- modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp),
+ modifier =
+ Modifier.padding(horizontal = 12.dp, vertical = 16.dp)
+ .outlineFocusModifier(
+ isFocused = isFocused,
+ focusColor = MaterialTheme.colorScheme.secondary,
+ padding = 8.dp,
+ cornerRadius = 28.dp
+ ),
verticalAlignment = Alignment.CenterVertically
) {
Text(
@@ -825,7 +838,8 @@
Icon(
imageVector = Icons.AutoMirrored.Default.OpenInNew,
contentDescription = null,
- tint = MaterialTheme.colorScheme.onSurfaceVariant
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.size(24.dp)
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 91ee287..befcc9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -96,6 +96,8 @@
when (toState) {
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+ KeyguardState.ALTERNATE_BOUNCER -> TO_BOUNCER_DURATION
+ KeyguardState.PRIMARY_BOUNCER -> TO_BOUNCER_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -269,8 +271,16 @@
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
- val DEFAULT_DURATION = 1.seconds
- val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
+
+ /**
+ * DEFAULT_DURATION controls the timing for all animations other than those with overrides
+ * in [getDefaultAnimatorForTransitionsToState].
+ *
+ * Set at 400ms for parity with [FromLockscreenTransitionInteractor]
+ */
+ val DEFAULT_DURATION = 400.milliseconds
+ val TO_LOCKSCREEN_DURATION = 1.seconds
+ val TO_BOUNCER_DURATION = 400.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 23aa21c..0cb8dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -177,7 +177,12 @@
* Immediately (after 1ms) emits the given value for every step of the KeyguardTransition.
*/
fun immediatelyTransitionTo(value: Float): Flow<Float> {
- return sharedFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value })
+ return sharedFlow(
+ duration = 1.milliseconds,
+ onStep = { value },
+ onCancel = { value },
+ onFinish = { value }
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
index 9da11ce..c590f07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -51,6 +51,7 @@
transitionAnimation.sharedFlow(
duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
onStep = { 1 - it },
+ onCancel = { 0f },
onFinish = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index b267ecb..6b22c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -104,12 +104,10 @@
transitionAnimation.sharedFlow(
duration = 250.milliseconds,
onStep = { it },
+ onCancel = { 1f },
onFinish = { 1f },
)
override val deviceEntryParentViewAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = 500.milliseconds,
- onStep = { 1f },
- )
+ transitionAnimation.immediatelyTransitionTo(1f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
index 1ee0368..7562392 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
@@ -63,6 +63,7 @@
transitionAnimation.sharedFlow(
duration = FromDreamingTransitionInteractor.TO_AOD_DURATION,
onStep = { it },
+ onCancel = { 1f },
onFinish = { 1f },
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index ea8fe29..11ed52a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -95,7 +95,7 @@
startTime = 167.milliseconds,
duration = 167.milliseconds,
onStep = { it },
- onCancel = { 0f },
+ onCancel = { 1f },
onFinish = { 1f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 82381eb..b5ec7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -95,5 +95,11 @@
)
val deviceEntryBackgroundViewAlpha = transitionAnimation.immediatelyTransitionTo(1f)
- override val deviceEntryParentViewAlpha = lockscreenAlpha
+ override val deviceEntryParentViewAlpha =
+ transitionAnimation.sharedFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onCancel = { 1f },
+ onStep = { it },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
index 76d5a8d..f69f996 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
@@ -93,7 +93,7 @@
transitionAnimation.sharedFlow(
duration = 167.milliseconds,
onStep = { 1 - it },
- onCancel = { 1f },
+ onCancel = { 0f },
onFinish = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index 2bc8e51..43872b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -122,6 +122,7 @@
startTime = 1100.milliseconds,
duration = 200.milliseconds,
onStep = { it },
+ onCancel = { 1f },
onFinish = { 1f },
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt
index 6a3573a..6d95ade 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt
@@ -43,7 +43,7 @@
transitionAnimation.sharedFlow(
duration = 167.milliseconds,
onStep = { it },
- onCancel = { 0f },
+ onCancel = { 1f },
onFinish = { 1f },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
index 5408428..2f21ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -70,6 +70,7 @@
transitionAnimation.sharedFlow(
duration = 300.milliseconds,
onStep = { 1 - it },
+ onCancel = { 0f },
onFinish = { 0f },
),
)
@@ -153,6 +154,7 @@
transitionAnimation.sharedFlow(
duration = 300.milliseconds,
onStep = { it },
+ onCancel = { 1f },
onFinish = { 1f },
),
flowWhenShadeIsNotExpanded = transitionAnimation.immediatelyTransitionTo(1f),
@@ -164,6 +166,7 @@
transitionAnimation.sharedFlow(
duration = 200.milliseconds,
onStep = { 1f - it },
+ onCancel = { 0f },
onFinish = { 0f },
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index 579abeb..bbb55cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -74,7 +74,12 @@
override val deviceEntryParentViewAlpha: Flow<Float> =
shadeDependentFlows.transitionFlow(
- flowWhenShadeIsNotExpanded = lockscreenAlpha,
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ onCancel = { 0f },
+ ),
flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index fcf8c14f..8d9ccef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -83,7 +83,12 @@
override val deviceEntryParentViewAlpha: Flow<Float> =
shadeDependentFlows.transitionFlow(
- flowWhenShadeIsNotExpanded = lockscreenAlpha,
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ onCancel = { 0f },
+ ),
flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 23c44b0a..e64c614 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -65,6 +65,7 @@
transitionAnimation.sharedFlow(
duration = 250.milliseconds,
onStep = { 1f - it },
+ onCancel = { 0f },
onFinish = { 0f }
),
flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 36c7d5b..737bd7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -110,5 +110,11 @@
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
- override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ onCancel = { 1f },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index 009f85d..501feca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -70,6 +70,7 @@
transitionAnimation.sharedFlow(
duration = 300.milliseconds,
onStep = { it },
+ onCancel = { 1f },
onFinish = { 1f },
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt
new file mode 100644
index 0000000..723ff5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.mediaprojection
+
+import android.content.pm.PackageManager
+import com.android.systemui.util.Utils
+
+/** Various utility methods related to media projection. */
+object MediaProjectionUtils {
+ /**
+ * Returns true iff projecting to the given [packageName] means that we're casting media to a
+ * *different* device (as opposed to sharing media to some application on *this* device).
+ */
+ fun packageHasCastingCapabilities(
+ packageManager: PackageManager,
+ packageName: String
+ ): Boolean {
+ // The [isHeadlessRemoteDisplayProvider] check approximates whether a projection is to a
+ // different device or the same device, because headless remote display packages are the
+ // only kinds of packages that do cast-to-other-device. This isn't exactly perfect,
+ // because it means that any projection by those headless remote display packages will be
+ // marked as going to a different device, even if that isn't always true. See b/321078669.
+ return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index cc4a92c..3c83db3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -41,7 +41,6 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.graphics.Typeface;
import android.media.projection.IMediaProjection;
import android.media.projection.MediaProjectionConfig;
import android.media.projection.MediaProjectionManager;
@@ -50,10 +49,8 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.BidiFormatter;
-import android.text.SpannableString;
import android.text.TextPaint;
import android.text.TextUtils;
-import android.text.style.StyleSpan;
import android.util.Log;
import android.view.Window;
@@ -61,6 +58,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.MediaProjectionUtils;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
@@ -68,12 +66,13 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.util.Utils;
-import dagger.Lazy;
+import java.util.function.Consumer;
import javax.inject.Inject;
+import dagger.Lazy;
+
public class MediaProjectionPermissionActivity extends Activity
implements DialogInterface.OnClickListener {
private static final String TAG = "MediaProjectionPermissionActivity";
@@ -189,30 +188,14 @@
final String appName = extractAppName(aInfo, packageManager);
final boolean hasCastingCapabilities =
- Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
+ MediaProjectionUtils.INSTANCE.packageHasCastingCapabilities(
+ packageManager, mPackageName);
// Using application context for the dialog, instead of the activity context, so we get
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
- final boolean overrideDisableSingleAppOption =
- CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
- MediaProjectionPermissionDialogDelegate delegate =
- new MediaProjectionPermissionDialogDelegate(
- dialogContext,
- getMediaProjectionConfig(),
- dialog -> {
- ScreenShareOption selectedOption =
- dialog.getSelectedScreenShareOption();
- grantMediaProjectionPermission(selectedOption.getMode());
- },
- () -> finish(RECORD_CANCEL, /* projection= */ null),
- hasCastingCapabilities,
- appName,
- overrideDisableSingleAppOption,
- mUid,
- mMediaProjectionMetricsLogger);
+ BaseMediaProjectionPermissionDialogDelegate<AlertDialog> delegate =
+ createPermissionDialogDelegate(appName, hasCastingCapabilities, dialogContext);
mDialog =
new AlertDialogWithDelegate(
dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
@@ -274,6 +257,44 @@
return appName;
}
+ private BaseMediaProjectionPermissionDialogDelegate<AlertDialog> createPermissionDialogDelegate(
+ String appName,
+ boolean hasCastingCapabilities,
+ Context dialogContext) {
+ final boolean overrideDisableSingleAppOption =
+ CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
+ MediaProjectionConfig mediaProjectionConfig = getMediaProjectionConfig();
+ Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>> onStartRecordingClicked =
+ dialog -> {
+ ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
+ grantMediaProjectionPermission(selectedOption.getMode());
+ };
+ Runnable onCancelClicked = () -> finish(RECORD_CANCEL, /* projection= */ null);
+ if (hasCastingCapabilities) {
+ return new SystemCastPermissionDialogDelegate(
+ dialogContext,
+ mediaProjectionConfig,
+ onStartRecordingClicked,
+ onCancelClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ } else {
+ return new ShareToAppPermissionDialogDelegate(
+ dialogContext,
+ mediaProjectionConfig,
+ onStartRecordingClicked,
+ onCancelClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ }
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
deleted file mode 100644
index 6d1a458..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2022 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.mediaprojection.permission
-
-import android.app.AlertDialog
-import android.content.Context
-import android.media.projection.MediaProjectionConfig
-import android.os.Bundle
-import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
-import com.android.systemui.res.R
-import java.util.function.Consumer
-
-/** Dialog to select screen recording options */
-class MediaProjectionPermissionDialogDelegate(
- context: Context,
- mediaProjectionConfig: MediaProjectionConfig?,
- private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
- private val onCancelClicked: Runnable,
- private val hasCastingCapabilities: Boolean,
- appName: String,
- forceShowPartialScreenshare: Boolean,
- hostUid: Int,
- mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
-) :
- BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(
- context,
- appName,
- hasCastingCapabilities,
- mediaProjectionConfig,
- forceShowPartialScreenshare
- ),
- appName,
- hostUid,
- mediaProjectionMetricsLogger
- ) {
- override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
- super.onCreate(dialog, savedInstanceState)
- // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
- if (hasCastingCapabilities) {
- setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
- setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
- } else {
- setDialogTitle(R.string.media_projection_entry_app_permission_dialog_title)
- setStartButtonText(R.string.media_projection_entry_app_permission_dialog_continue)
- }
- setStartButtonOnClickListener {
- // Note that it is important to run this callback before dismissing, so that the
- // callback can disable the dialog exit animation if it wants to.
- onStartRecordingClicked.accept(this)
- dialog.dismiss()
- }
- setCancelButtonOnClickListener {
- onCancelClicked.run()
- dialog.dismiss()
- }
- }
-
- companion object {
- private fun createOptionList(
- context: Context,
- appName: String,
- hasCastingCapabilities: Boolean,
- mediaProjectionConfig: MediaProjectionConfig?,
- overrideDisableSingleAppOption: Boolean = false,
- ): List<ScreenShareOption> {
- val singleAppWarningText =
- if (hasCastingCapabilities) {
- R.string.media_projection_entry_cast_permission_dialog_warning_single_app
- } else {
- R.string.media_projection_entry_app_permission_dialog_warning_single_app
- }
- val entireScreenWarningText =
- if (hasCastingCapabilities) {
- R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
- } else {
- R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
- }
-
- // The single app option should only be disabled if the client has setup a
- // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
- // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
- val singleAppOptionDisabled =
- !overrideDisableSingleAppOption &&
- mediaProjectionConfig?.regionToCapture ==
- MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
-
- val singleAppDisabledText =
- if (singleAppOptionDisabled) {
- context.getString(
- R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
- appName
- )
- } else {
- null
- }
- val options =
- listOf(
- ScreenShareOption(
- mode = SINGLE_APP,
- spinnerText = R.string.screen_share_permission_dialog_option_single_app,
- warningText = singleAppWarningText,
- spinnerDisabledText = singleAppDisabledText,
- ),
- ScreenShareOption(
- mode = ENTIRE_SCREEN,
- spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
- warningText = entireScreenWarningText
- )
- )
- return if (singleAppOptionDisabled) {
- // Make sure "Entire screen" is the first option when "Single App" is disabled.
- options.reversed()
- } else {
- options
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
new file mode 100644
index 0000000..88cbc38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.permission
+
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import com.android.systemui.res.R
+
+/** Various utility methods related to media projection permissions. */
+object MediaProjectionPermissionUtils {
+ fun getSingleAppDisabledText(
+ context: Context,
+ appName: String,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean,
+ ): String? {
+ // The single app option should only be disabled if the client has setup a
+ // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
+ // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
+ val singleAppOptionDisabled =
+ !overrideDisableSingleAppOption &&
+ mediaProjectionConfig?.regionToCapture ==
+ MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+ return if (singleAppOptionDisabled) {
+ context.getString(
+ R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
+ appName,
+ )
+ } else {
+ null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
new file mode 100644
index 0000000..5a2d88c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import java.util.function.Consumer
+
+/**
+ * Dialog to select screen recording options for sharing the screen to another app on the same
+ * device.
+ */
+class ShareToAppPermissionDialogDelegate(
+ context: Context,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ private val onStartRecordingClicked:
+ Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>>,
+ private val onCancelClicked: Runnable,
+ appName: String,
+ forceShowPartialScreenshare: Boolean,
+ hostUid: Int,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+) :
+ BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
+ createOptionList(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption = forceShowPartialScreenshare,
+ ),
+ appName,
+ hostUid,
+ mediaProjectionMetricsLogger,
+ ) {
+ override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
+ super.onCreate(dialog, savedInstanceState)
+ // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
+ setDialogTitle(R.string.media_projection_entry_app_permission_dialog_title)
+ setStartButtonText(R.string.media_projection_entry_app_permission_dialog_continue)
+ setStartButtonOnClickListener {
+ // Note that it is important to run this callback before dismissing, so that the
+ // callback can disable the dialog exit animation if it wants to.
+ onStartRecordingClicked.accept(this)
+ dialog.dismiss()
+ }
+ setCancelButtonOnClickListener {
+ onCancelClicked.run()
+ dialog.dismiss()
+ }
+ }
+
+ companion object {
+ private fun createOptionList(
+ context: Context,
+ appName: String,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean,
+ ): List<ScreenShareOption> {
+ val singleAppDisabledText =
+ MediaProjectionPermissionUtils.getSingleAppDisabledText(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption,
+ )
+ val options =
+ listOf(
+ ScreenShareOption(
+ mode = SINGLE_APP,
+ spinnerText = R.string.screen_share_permission_dialog_option_single_app,
+ warningText =
+ R.string
+ .media_projection_entry_app_permission_dialog_warning_single_app,
+ spinnerDisabledText = singleAppDisabledText,
+ ),
+ ScreenShareOption(
+ mode = ENTIRE_SCREEN,
+ spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+ warningText =
+ R.string
+ .media_projection_entry_app_permission_dialog_warning_entire_screen,
+ )
+ )
+ return if (singleAppDisabledText != null) {
+ // Make sure "Entire screen" is the first option when "Single App" is disabled.
+ options.reversed()
+ } else {
+ options
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt
new file mode 100644
index 0000000..8af46f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionUtils.getSingleAppDisabledText
+import com.android.systemui.res.R
+import java.util.function.Consumer
+
+/** Dialog to select screen recording options for casting the screen to a different device. */
+class SystemCastPermissionDialogDelegate(
+ context: Context,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ private val onStartRecordingClicked:
+ Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>>,
+ private val onCancelClicked: Runnable,
+ appName: String,
+ forceShowPartialScreenshare: Boolean,
+ hostUid: Int,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+) :
+ BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
+ createOptionList(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption = forceShowPartialScreenshare,
+ ),
+ appName,
+ hostUid,
+ mediaProjectionMetricsLogger,
+ ) {
+ override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
+ super.onCreate(dialog, savedInstanceState)
+ // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
+ setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
+ setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
+ setStartButtonOnClickListener {
+ // Note that it is important to run this callback before dismissing, so that the
+ // callback can disable the dialog exit animation if it wants to.
+ onStartRecordingClicked.accept(this)
+ dialog.dismiss()
+ }
+ setCancelButtonOnClickListener {
+ onCancelClicked.run()
+ dialog.dismiss()
+ }
+ }
+
+ companion object {
+ private fun createOptionList(
+ context: Context,
+ appName: String,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean,
+ ): List<ScreenShareOption> {
+ val singleAppDisabledText =
+ getSingleAppDisabledText(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption
+ )
+ val options =
+ listOf(
+ ScreenShareOption(
+ mode = SINGLE_APP,
+ spinnerText = R.string.screen_share_permission_dialog_option_single_app,
+ warningText =
+ R.string
+ .media_projection_entry_cast_permission_dialog_warning_single_app,
+ spinnerDisabledText = singleAppDisabledText,
+ ),
+ ScreenShareOption(
+ mode = ENTIRE_SCREEN,
+ spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+ warningText =
+ R.string
+ .media_projection_entry_cast_permission_dialog_warning_entire_screen,
+ )
+ )
+ return if (singleAppDisabledText != null) {
+ // Make sure "Entire screen" is the first option when "Single App" is disabled.
+ options.reversed()
+ } else {
+ options
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 1dbdec9..8e46fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -406,9 +406,8 @@
@Override
public void onViewAttachedToWindow(View v) {
if (result != null) {
- navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
- result.mImeWindowVis, result.mImeBackDisposition,
- result.mShowImeSwitcher);
+ navBar.setImeWindowStatus(display.getDisplayId(), result.mImeWindowVis,
+ result.mImeBackDisposition, result.mShowImeSwitcher);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index d022c1c..15b1e4d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -42,7 +42,6 @@
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Log;
@@ -425,7 +424,7 @@
}
@Override
- public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+ public void setImeWindowStatus(int displayId, int vis, int backDisposition,
boolean showImeSwitcher) {
boolean imeShown = mNavBarHelper.isImeShown(vis);
if (!imeShown) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index afdfa59..7b248eb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -71,7 +71,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
import android.provider.DeviceConfig;
@@ -1095,7 +1094,7 @@
// ----- CommandQueue Callbacks -----
@Override
- public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+ public void setImeWindowStatus(int displayId, int vis, int backDisposition,
boolean showImeSwitcher) {
if (displayId != mDisplayId) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index 38d7290..37002ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.Trace;
import android.view.ContextThemeWrapper;
@@ -30,6 +31,7 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.util.LifecycleFragment;
@@ -103,6 +105,7 @@
@Override
public View getHeader() {
+ QSComposeFragment.assertInLegacyMode();
if (mQsImpl != null) {
return mQsImpl.getHeader();
} else {
@@ -111,6 +114,51 @@
}
@Override
+ public int getHeaderTop() {
+ if (mQsImpl != null) {
+ return mQsImpl.getHeaderTop();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public int getHeaderBottom() {
+ if (mQsImpl != null) {
+ return mQsImpl.getHeaderBottom();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public int getHeaderLeft() {
+ if (mQsImpl != null) {
+ return mQsImpl.getHeaderLeft();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public void getHeaderBoundsOnScreen(Rect outBounds) {
+ if (mQsImpl != null) {
+ mQsImpl.getHeaderBoundsOnScreen(outBounds);
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
+ @Override
+ public boolean isHeaderShown() {
+ if (mQsImpl != null) {
+ return mQsImpl.isHeaderShown();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
public void setHasNotifications(boolean hasNotifications) {
if (mQsImpl != null) {
mQsImpl.setHasNotifications(hasNotifications);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 8c0d122..a6fd35a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -54,6 +54,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSComponent;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
@@ -355,10 +356,36 @@
@Override
public View getHeader() {
+ QSComposeFragment.assertInLegacyMode();
return mHeader;
}
@Override
+ public int getHeaderTop() {
+ return mHeader.getTop();
+ }
+
+ @Override
+ public int getHeaderBottom() {
+ return mHeader.getBottom();
+ }
+
+ @Override
+ public int getHeaderLeft() {
+ return mHeader.getLeft();
+ }
+
+ @Override
+ public void getHeaderBoundsOnScreen(Rect outBounds) {
+ mHeader.getBoundsOnScreen(outBounds);
+ }
+
+ @Override
+ public boolean isHeaderShown() {
+ return mHeader.isShown();
+ }
+
+ @Override
public void setHasNotifications(boolean hasNotifications) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 91bfae3..257390f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1305,10 +1305,6 @@
/** Updates the StatusBarViewController and updates any that depend on it. */
public void updateStatusViewController() {
// Re-associate the KeyguardStatusViewController
- if (mKeyguardStatusViewController != null) {
- mKeyguardStatusViewController.onDestroy();
- }
-
if (MigrateClocksToBlueprint.isEnabled()) {
// Need a shared controller until mKeyguardStatusViewController can be removed from
// here, due to important state being set in that controller. Rebind in order to pick
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/packages/SystemUI/src/com/android/systemui/shade/QSHeaderBoundsProvider.kt
similarity index 60%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
rename to packages/SystemUI/src/com/android/systemui/shade/QSHeaderBoundsProvider.kt
index 14b9e7f..a447e55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QSHeaderBoundsProvider.kt
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.windowdecor.common
-/** A callback to be invoked when a Task's window decor element is clicked. */
-fun interface OnTaskActionClickListener {
- /**
- * Called when a task's decor element has been clicked.
- *
- * @param taskId the id of the task.
- * @param tag a readable identifier for the element.
- */
- fun onClick(taskId: Int, tag: String)
-}
+package com.android.systemui.shade
+
+import android.graphics.Rect
+
+class QSHeaderBoundsProvider(
+ val leftProvider: () -> Int,
+ val heightProvider: () -> Int,
+ val boundsOnScreenProvider: (Rect) -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 67032f7..9f61d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -43,7 +43,6 @@
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.VelocityTracker;
-import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -75,6 +74,7 @@
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.screenrecord.RecordingController;
@@ -110,6 +110,8 @@
import dagger.Lazy;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import javax.inject.Inject;
@@ -494,7 +496,15 @@
}
int getHeaderHeight() {
- return isQsFragmentCreated() ? mQs.getHeader().getHeight() : 0;
+ if (isQsFragmentCreated()) {
+ if (QSComposeFragment.isEnabled()) {
+ return mQs.getHeaderHeight();
+ } else {
+ return mQs.getHeader().getHeight();
+ }
+ } else {
+ return 0;
+ }
}
private boolean isRemoteInputActiveWithKeyboardUp() {
@@ -664,14 +674,26 @@
&& mKeyguardBypassController.getBypassEnabled()) || mSplitShadeEnabled) {
return false;
}
- View header = keyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+ int headerTop, headerBottom;
+ if (keyguardShowing || mQs == null) {
+ headerTop = mKeyguardStatusBar.getTop();
+ headerBottom = mKeyguardStatusBar.getBottom();
+ } else {
+ if (QSComposeFragment.isEnabled()) {
+ headerTop = mQs.getHeaderTop();
+ headerBottom = mQs.getHeaderBottom();
+ } else {
+ headerTop = mQs.getHeader().getTop();
+ headerBottom = mQs.getHeader().getBottom();
+ }
+ }
int frameTop = keyguardShowing
|| mQs == null ? 0 : mQsFrame.getTop();
mInterceptRegion.set(
/* left= */ (int) mQsFrame.getX(),
- /* top= */ header.getTop() + frameTop,
+ /* top= */ headerTop + frameTop,
/* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
- /* bottom= */ header.getBottom() + frameTop);
+ /* bottom= */ headerBottom + frameTop);
// Also allow QS to intercept if the touch is near the notch.
mStatusBarTouchableRegionManager.updateRegionForNotch(mInterceptRegion);
final boolean onHeader = mInterceptRegion.contains((int) x, (int) y);
@@ -718,9 +740,18 @@
if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) {
return false;
}
- View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+ int headerBottom;
+ if (mQs == null) {
+ headerBottom = mKeyguardStatusBar.getBottom();
+ } else {
+ if (QSComposeFragment.isEnabled()) {
+ headerBottom = mQs.getHeaderBottom();
+ } else {
+ headerBottom = mQs.getHeader().getBottom();
+ }
+ }
return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth()
- && downY <= header.getBottom();
+ && downY <= headerBottom;
}
/** Closes the Qs customizer. */
@@ -2192,14 +2223,27 @@
}
});
mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
- if (mQs.getHeader().isShown()) {
+ if (mQs.isHeaderShown()) {
setAnimateNextNotificationBounds(
StackStateAnimator.ANIMATION_DURATION_STANDARD, 0);
mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
}
});
mLockscreenShadeTransitionController.setQS(mQs);
- mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
+ if (QSComposeFragment.isEnabled()) {
+ QSHeaderBoundsProvider provider = new QSHeaderBoundsProvider(
+ mQs::getHeaderLeft,
+ mQs::getHeaderHeight,
+ rect -> {
+ mQs.getHeaderBoundsOnScreen(rect);
+ return Unit.INSTANCE;
+ }
+ );
+
+ mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(provider);
+ } else {
+ mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
+ }
mQs.setScrollListener(mQsScrollListener);
updateExpansion();
}
@@ -2211,6 +2255,9 @@
// non-fragment and fragment code. Once we are using a fragment for the notification
// panel, mQs will not need to be null cause it will be tied to the same lifecycle.
if (fragment == mQs) {
+ // Clear it to remove bindings to mQs from the provider.
+ mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(null);
+ mNotificationStackScrollLayoutController.setQsHeader(null);
mQs = null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index b9d24ab..cea97d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -257,12 +257,11 @@
* Called to notify IME window status changes.
*
* @param displayId The id of the display to notify.
- * @param token IME token.
* @param vis IME visibility.
* @param backDisposition Disposition mode of back button. It should be one of below flags:
* @param showImeSwitcher {@code true} to show IME switch button.
*/
- default void setImeWindowStatus(int displayId, IBinder token, int vis,
+ default void setImeWindowStatus(int displayId, int vis,
@BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
default void showRecentApps(boolean triggeredFromAltTab) { }
default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
@@ -745,7 +744,7 @@
}
@Override
- public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+ public void setImeWindowStatus(int displayId, int vis, int backDisposition,
boolean showImeSwitcher) {
synchronized (mLock) {
mHandler.removeMessages(MSG_SHOW_IME_BUTTON);
@@ -754,7 +753,6 @@
args.argi2 = vis;
args.argi3 = backDisposition;
args.argi4 = showImeSwitcher ? 1 : 0;
- args.arg1 = token;
Message m = mHandler.obtainMessage(MSG_SHOW_IME_BUTTON, args);
m.sendToTarget();
}
@@ -1208,7 +1206,7 @@
}
}
- private void handleShowImeButton(int displayId, IBinder token, int vis, int backDisposition,
+ private void handleShowImeButton(int displayId, int vis, int backDisposition,
boolean showImeSwitcher) {
if (displayId == INVALID_DISPLAY) return;
@@ -1224,17 +1222,15 @@
sendImeInvisibleStatusForPrevNavBar();
}
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).setImeWindowStatus(displayId, token, vis, backDisposition,
- showImeSwitcher);
+ mCallbacks.get(i).setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher);
}
mLastUpdatedImeDisplayId = displayId;
}
private void sendImeInvisibleStatusForPrevNavBar() {
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId,
- null /* token */, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT,
- false /* showImeSwitcher */);
+ mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, IME_INVISIBLE,
+ BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */);
}
}
@@ -1550,7 +1546,7 @@
break;
case MSG_SHOW_IME_BUTTON:
args = (SomeArgs) msg.obj;
- handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */,
+ handleShowImeButton(args.argi1 /* displayId */,
args.argi2 /* vis */, args.argi3 /* backDisposition */,
args.argi4 != 0 /* showImeSwitcher */);
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
new file mode 100644
index 0000000..69ebb76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+# Bug component: 78010
+
+caitlinshk@google.com
+evanlaird@google.com
+pixel@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 191c221..c5f78d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -21,11 +21,11 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediaprojection.MediaProjectionUtils.packageHasCastingCapabilities
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
-import com.android.systemui.util.Utils
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -60,7 +60,7 @@
}
is MediaProjectionState.Projecting -> {
val type =
- if (isProjectionToOtherDevice(state.hostPackage)) {
+ if (packageHasCastingCapabilities(packageManager, state.hostPackage)) {
ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE
} else {
ProjectionChipModel.Type.SHARE_TO_APP
@@ -86,19 +86,6 @@
scope.launch { mediaProjectionRepository.stopProjecting() }
}
- /**
- * Returns true iff projecting to the given [packageName] means that we're projecting to a
- * *different* device (as opposed to projecting to some application on *this* device).
- */
- private fun isProjectionToOtherDevice(packageName: String?): Boolean {
- // The [isHeadlessRemoteDisplayProvider] check approximates whether a projection is to a
- // different device or the same device, because headless remote display packages are the
- // only kinds of packages that do cast-to-other-device. This isn't exactly perfect,
- // because it means that any projection by those headless remote display packages will be
- // marked as going to a different device, even if that isn't always true. See b/321078669.
- return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
- }
-
companion object {
private const val TAG = "MediaProjection"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 0dbc8c0..1cb59f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -15,11 +15,13 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.shared.CallType
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -27,6 +29,7 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+@SysUISingleton
class ActiveNotificationsInteractor
@Inject
constructor(
@@ -71,6 +74,19 @@
val allNotificationsCountValue: Int
get() = repository.activeNotifications.value.individuals.size
+ /**
+ * The priority ongoing call notification, or null if there is no ongoing call.
+ *
+ * The output model is guaranteed to have [ActiveNotificationModel.callType] to be equal to
+ * [CallType.Ongoing].
+ */
+ val ongoingCallNotification: Flow<ActiveNotificationModel?> =
+ allRepresentativeNotifications.map { notifMap ->
+ // Once a call has started, its `whenTime` should stay the same, so we can use it as a
+ // stable sort value.
+ notifMap.values.filter { it.callType == CallType.Ongoing }.minByOrNull { it.whenTime }
+ }
+
/** Are any notifications being actively presented in the notification stack? */
val areAnyNotificationsPresent: Flow<Boolean> =
repository.activeNotifications
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index ab54bda..5d2d56a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -15,7 +15,14 @@
*/
package com.android.systemui.statusbar.notification.domain.interactor
+import android.app.Notification.CallStyle.CALL_TYPE_INCOMING
+import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
+import android.app.Notification.CallStyle.CALL_TYPE_SCREENING
+import android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN
+import android.app.Notification.EXTRA_CALL_TYPE
+import android.app.PendingIntent
import android.graphics.drawable.Icon
+import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import com.android.app.tracing.traceSection
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -27,6 +34,7 @@
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.shared.CallType
import javax.inject.Inject
import kotlinx.coroutines.flow.update
@@ -124,6 +132,7 @@
existingModels.createOrReuse(
key = key,
groupKey = sbn.groupKey,
+ whenTime = sbn.notification.`when`,
isAmbient = sectionStyleProvider.isMinimized(this),
isRowDismissed = isRowDismissed,
isSilent = sectionStyleProvider.isSilent(this),
@@ -135,15 +144,18 @@
statusBarIcon = icons.statusBarIcon?.sourceIcon,
uid = sbn.uid,
packageName = sbn.packageName,
+ contentIntent = sbn.notification.contentIntent,
instanceId = sbn.instanceId?.id,
isGroupSummary = sbn.notification.isGroupSummary,
bucket = bucket,
+ callType = sbn.toCallType(),
)
}
private fun ActiveNotificationsStore.createOrReuse(
key: String,
groupKey: String?,
+ whenTime: Long,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -155,14 +167,17 @@
statusBarIcon: Icon?,
uid: Int,
packageName: String,
+ contentIntent: PendingIntent?,
instanceId: Int?,
isGroupSummary: Boolean,
bucket: Int,
+ callType: CallType,
): ActiveNotificationModel {
return individuals[key]?.takeIf {
it.isCurrent(
key = key,
groupKey = groupKey,
+ whenTime = whenTime,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -176,12 +191,15 @@
instanceId = instanceId,
isGroupSummary = isGroupSummary,
packageName = packageName,
+ contentIntent = contentIntent,
bucket = bucket,
+ callType = callType,
)
}
?: ActiveNotificationModel(
key = key,
groupKey = groupKey,
+ whenTime = whenTime,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -195,13 +213,16 @@
instanceId = instanceId,
isGroupSummary = isGroupSummary,
packageName = packageName,
+ contentIntent = contentIntent,
bucket = bucket,
+ callType = callType,
)
}
private fun ActiveNotificationModel.isCurrent(
key: String,
groupKey: String?,
+ whenTime: Long,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -213,13 +234,16 @@
statusBarIcon: Icon?,
uid: Int,
packageName: String,
+ contentIntent: PendingIntent?,
instanceId: Int?,
isGroupSummary: Boolean,
bucket: Int,
+ callType: CallType,
): Boolean {
return when {
key != this.key -> false
groupKey != this.groupKey -> false
+ whenTime != this.whenTime -> false
isAmbient != this.isAmbient -> false
isRowDismissed != this.isRowDismissed -> false
isSilent != this.isSilent -> false
@@ -233,7 +257,9 @@
instanceId != this.instanceId -> false
isGroupSummary != this.isGroupSummary -> false
packageName != this.packageName -> false
+ contentIntent != this.contentIntent -> false
bucket != this.bucket -> false
+ callType != this.callType -> false
else -> true
}
}
@@ -259,3 +285,13 @@
else -> true
}
}
+
+private fun StatusBarNotification.toCallType(): CallType =
+ when (this.notification.extras.getInt(EXTRA_CALL_TYPE, -1)) {
+ -1 -> CallType.None
+ CALL_TYPE_INCOMING -> CallType.Incoming
+ CALL_TYPE_ONGOING -> CallType.Ongoing
+ CALL_TYPE_SCREENING -> CallType.Screening
+ CALL_TYPE_UNKNOWN -> CallType.Unknown
+ else -> CallType.Unknown
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index 9d0fcd3..245b267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -19,6 +19,7 @@
import android.app.Flags
import android.os.SystemProperties
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.util.Compile
import javax.inject.Inject
/**
@@ -43,5 +44,6 @@
/** developer setting to always show Minimal HUN, even if the device is not in full screen */
private fun alwaysShow() =
- SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false)
+ Compile.IS_DEBUG &&
+ SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 5527efc..6960791 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -15,6 +15,7 @@
package com.android.systemui.statusbar.notification.shared
+import android.app.PendingIntent
import android.graphics.drawable.Icon
import com.android.systemui.statusbar.notification.stack.PriorityBucket
@@ -32,6 +33,8 @@
val key: String,
/** Notification group key associated with this entry. */
val groupKey: String?,
+ /** When this notification was posted. */
+ val whenTime: Long,
/** Is this entry in the ambient / minimized section (lowest priority)? */
val isAmbient: Boolean,
/**
@@ -60,12 +63,16 @@
val uid: Int,
/** The notifying app's packageName. */
val packageName: String,
+ /** The intent to execute if UI related to this notification is clicked. */
+ val contentIntent: PendingIntent?,
/** A small per-notification ID, used for statsd logging. */
val instanceId: Int?,
/** If this notification is the group summary for a group of notifications. */
val isGroupSummary: Boolean,
/** Indicates in which section the notification is displayed in. @see [PriorityBucket]. */
@PriorityBucket val bucket: Int,
+ /** The call type set on the notification. */
+ val callType: CallType,
) : ActiveNotificationEntryModel()
/** Model for a group of notifications. */
@@ -74,3 +81,17 @@
val summary: ActiveNotificationModel,
val children: List<ActiveNotificationModel>,
) : ActiveNotificationEntryModel()
+
+/** Specifies the call type set on the notification. For most notifications, will be [None]. */
+enum class CallType {
+ /** This notification isn't a call-type notification. */
+ None,
+ /** See [android.app.Notification.CallStyle.CALL_TYPE_INCOMING]. */
+ Incoming,
+ /** See [android.app.Notification.CallStyle.CALL_TYPE_ONGOING]. */
+ Ongoing,
+ /** See [android.app.Notification.CallStyle.CALL_TYPE_SCREENING]. */
+ Screening,
+ /** See [android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN]. */
+ Unknown,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2f3b3a0..20b1fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -89,8 +89,10 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.QSHeaderBoundsProvider;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -351,6 +353,10 @@
private final NotificationSection[] mSections;
private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
protected ViewGroup mQsHeader;
+
+ @Nullable
+ private QSHeaderBoundsProvider mQSHeaderBoundsProvider;
+
// Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
private final Rect mQsHeaderBound = new Rect();
private boolean mContinuousShadowUpdate;
@@ -1179,7 +1185,21 @@
if (!SceneContainerFlag.isEnabled()) {
// Give The Algorithm information regarding the QS height so it can layout notifications
// properly. Needed for some devices that grows notifications down-to-top
- mStackScrollAlgorithm.updateQSFrameTop(mQsHeader == null ? 0 : mQsHeader.getHeight());
+ int height;
+ if (QSComposeFragment.isEnabled()) {
+ if (mQSHeaderBoundsProvider != null) {
+ height = mQSHeaderBoundsProvider.getHeightProvider().invoke();
+ } else {
+ height = 0;
+ }
+ } else {
+ if (mQsHeader != null) {
+ height = mQsHeader.getHeight();
+ } else {
+ height = 0;
+ }
+ }
+ mStackScrollAlgorithm.updateQSFrameTop(height);
}
// Once the layout has finished, we don't need to animate any scrolling clampings anymore.
@@ -1824,10 +1844,16 @@
}
public void setQsHeader(ViewGroup qsHeader) {
- SceneContainerFlag.assertInLegacyMode();
+ QSComposeFragment.assertInLegacyMode();
mQsHeader = qsHeader;
}
+ public void setQsHeaderBoundsProvider(QSHeaderBoundsProvider qsHeaderBoundsProvider) {
+ SceneContainerFlag.assertInLegacyMode();
+ QSComposeFragment.isUnexpectedlyInLegacyMode();
+ mQSHeaderBoundsProvider = qsHeaderBoundsProvider;
+ }
+
public static boolean isPinnedHeadsUp(View v) {
if (v instanceof ExpandableNotificationRow row) {
return row.isHeadsUp() && row.isPinned();
@@ -3745,7 +3771,20 @@
return ev.getY() < mAmbientState.getStackTop();
}
- mQsHeader.getBoundsOnScreen(mQsHeaderBound);
+ if (QSComposeFragment.isEnabled()) {
+ if (mQSHeaderBoundsProvider == null) {
+ return false;
+ } else {
+ mQSHeaderBoundsProvider.getBoundsOnScreenProvider().invoke(mQsHeaderBound);
+ }
+ } else {
+ if (mQsHeader == null) {
+ return false;
+ } else {
+ mQsHeader.getBoundsOnScreen(mQsHeaderBound);
+ }
+ }
+
/**
* One-handed mode defines a feature FEATURE_ONE_HANDED of DisplayArea {@link DisplayArea}
* that will translate down the Y-coordinate whole window screen type except for
@@ -3755,7 +3794,10 @@
* of DisplayArea into relative coordinates for all windows, we need to correct the
* QS Head bounds here.
*/
- final int xOffset = Math.round(ev.getRawX() - ev.getX() + mQsHeader.getLeft());
+ int left =
+ QSComposeFragment.isEnabled() ? mQSHeaderBoundsProvider.getLeftProvider().invoke()
+ : mQsHeader.getLeft();
+ final int xOffset = Math.round(ev.getRawX() - ev.getX() + left);
final int yOffset = Math.round(ev.getRawY() - ev.getY());
mQsHeaderBound.offsetTo(xOffset, yOffset);
return mQsHeaderBound.contains((int) ev.getRawX(), (int) ev.getRawY());
@@ -3809,6 +3851,7 @@
}
void handleEmptySpaceClick(MotionEvent ev) {
+ if (SceneContainerFlag.isEnabled()) return;
logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
mStatusBarState, mTouchIsClick);
switch (ev.getActionMasked()) {
@@ -4020,6 +4063,7 @@
}
public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
+ SceneContainerFlag.assertInLegacyMode();
mOnEmptySpaceClickListener = listener;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index fb1c525..608fe95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -80,9 +80,11 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.ui.view.WindowRootView;
+import com.android.systemui.shade.QSHeaderBoundsProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
@@ -1075,6 +1077,7 @@
public void setOnEmptySpaceClickListener(
OnEmptySpaceClickListener listener) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setOnEmptySpaceClickListener(listener);
}
@@ -1522,9 +1525,15 @@
* Sets the QS header. Used to check if a touch is within its bounds.
*/
public void setQsHeader(ViewGroup view) {
+ QSComposeFragment.assertInLegacyMode();
mView.setQsHeader(view);
}
+ public void setQsHeaderBoundsProvider(QSHeaderBoundsProvider qsHeaderBoundsProvider) {
+ QSComposeFragment.isUnexpectedlyInLegacyMode();
+ mView.setQsHeaderBoundsProvider(qsHeaderBoundsProvider);
+ }
+
public void setAnimationsEnabled(boolean enabled) {
mView.setAnimationsEnabled(enabled);
}
@@ -1630,6 +1639,7 @@
}
public void setMaxTopPadding(int padding) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setMaxTopPadding(padding);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b6de78e..37fdaeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -913,8 +913,8 @@
result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
// StatusBarManagerService has a back up of IME token and it's restored here.
- mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
- result.mImeWindowVis, result.mImeBackDisposition, result.mShowImeSwitcher);
+ mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeWindowVis,
+ result.mImeBackDisposition, result.mShowImeSwitcher);
// Set up the initial icon state
int numIcons = result.mIcons.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
index 4657e9b..92a333e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
@@ -1,6 +1,6 @@
per-file *Notification* = set noparent
per-file *Notification* = file:../notification/OWNERS
-per-file NotificationIcon* = ccassidy@google.com, evanlaird@google.com, pixel@google.com
+per-file NotificationIcon* = file:../OWNERS
per-file NotificationShadeWindowControllerImpl.java = dupin@google.com, cinek@google.com, beverlyt@google.com, pixel@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 3898088..7af0666 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -45,6 +45,9 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.policy.CallbackController
@@ -65,6 +68,7 @@
private val context: Context,
private val ongoingCallRepository: OngoingCallRepository,
private val notifCollection: CommonNotifCollection,
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val systemClock: SystemClock,
private val activityStarter: ActivityStarter,
@Main private val mainExecutor: Executor,
@@ -97,6 +101,7 @@
}
override fun onEntryUpdated(entry: NotificationEntry) {
+ StatusBarUseReposForCallChip.assertInLegacyMode()
// We have a new call notification or our existing call notification has been
// updated.
// TODO(b/183229367): This likely won't work if you take a call from one app then
@@ -157,7 +162,25 @@
override fun start() {
dumpManager.registerDumpable(this)
- notifCollection.addCollectionListener(notifListener)
+
+ if (Flags.statusBarUseReposForCallChip()) {
+ scope.launch {
+ // Listening to [ActiveNotificationsInteractor] instead of using
+ // [NotifCollectionListener#onEntryUpdated] is better for two reasons:
+ // 1. ActiveNotificationsInteractor automatically filters the notification list to
+ // just notifications for the current user, which ensures we don't show a call chip
+ // for User 1's call while User 2 is active (see b/328584859).
+ // 2. ActiveNotificationsInteractor only emits notifications that are currently
+ // present in the shade, which means we know we've already inflated the icon that we
+ // might use for the call chip (see b/354930838).
+ activeNotificationsInteractor.ongoingCallNotification.collect {
+ updateInfoFromNotifModel(it)
+ }
+ }
+ } else {
+ notifCollection.addCollectionListener(notifListener)
+ }
+
scope.launch {
statusBarModeRepository.defaultDisplay.isInFullscreenMode.collect {
isFullscreen = it
@@ -221,6 +244,35 @@
synchronized(mListeners) { mListeners.remove(listener) }
}
+ private fun updateInfoFromNotifModel(notifModel: ActiveNotificationModel?) {
+ if (notifModel == null) {
+ removeChip()
+ } else if (notifModel.callType != CallType.Ongoing) {
+ logger.log(
+ TAG,
+ LogLevel.ERROR,
+ { str1 = notifModel.callType.name },
+ { "Notification Interactor sent ActiveNotificationModel with callType=$str1" },
+ )
+ removeChip()
+ } else {
+ val newOngoingCallInfo =
+ CallNotificationInfo(
+ notifModel.key,
+ notifModel.whenTime,
+ notifModel.contentIntent,
+ notifModel.uid,
+ isOngoing = true,
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+ )
+ if (newOngoingCallInfo == callNotificationInfo) {
+ return
+ }
+ callNotificationInfo = newOngoingCallInfo
+ updateChip()
+ }
+ }
+
private fun updateChip() {
val currentCallNotificationInfo = callNotificationInfo ?: return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt
new file mode 100644
index 0000000..4bdd90e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the status bar use repos for call chip flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object StatusBarUseReposForCallChip {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.statusBarUseReposForCallChip()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/TouchpadModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/TouchpadModule.kt
index c86ac2f..2f74d29 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/TouchpadModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/TouchpadModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.touchpad
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.touchpad.data.repository.TouchpadRepository
import com.android.systemui.touchpad.data.repository.TouchpadRepositoryImpl
import dagger.Binds
@@ -25,5 +26,6 @@
abstract class TouchpadModule {
@Binds
+ @SysUISingleton
abstract fun bindTouchpadRepository(repository: TouchpadRepositoryImpl): TouchpadRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 51dfef0..3dca6fc 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -20,10 +20,18 @@
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.activity.compose.BackHandler
+import androidx.annotation.RawRes
import androidx.annotation.StringRes
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -50,7 +58,6 @@
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
@@ -63,10 +70,11 @@
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
data class TutorialScreenColors(
@@ -83,7 +91,7 @@
) {
val screenColors = rememberScreenColors()
BackHandler(onBack = onBack)
- var gestureState by remember { mutableStateOf(GestureState.NOT_STARTED) }
+ var gestureState by remember { mutableStateOf(NOT_STARTED) }
val swipeDistanceThresholdPx =
LocalContext.current.resources.getDimensionPixelSize(
com.android.internal.R.dimen.system_gestures_distance_threshold
@@ -91,9 +99,10 @@
val gestureHandler =
remember(swipeDistanceThresholdPx) {
TouchpadGestureHandler(
- BACK,
- swipeDistanceThresholdPx,
- onGestureStateChanged = { gestureState = it }
+ BackGestureMonitor(
+ swipeDistanceThresholdPx,
+ gestureStateChangedCallback = { gestureState = it }
+ ),
)
}
TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
@@ -226,31 +235,74 @@
animationProperties: LottieDynamicProperties,
modifier: Modifier = Modifier
) {
- Column(modifier = modifier.fillMaxWidth()) {
- val resId =
- if (gestureState == FINISHED) R.raw.trackpad_back_success else R.raw.trackpad_back_edu
- val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(resId))
- val progress = progressForGestureState(composition, gestureState)
- LottieAnimation(
- composition = composition,
- progress = progress,
- dynamicProperties = animationProperties
- )
+ Box(modifier = modifier.fillMaxWidth()) {
+ AnimatedContent(
+ targetState = gestureState,
+ transitionSpec = {
+ if (initialState == NOT_STARTED && targetState == IN_PROGRESS) {
+ val transitionDurationMillis = 150
+ fadeIn(
+ animationSpec = tween(transitionDurationMillis, easing = LinearEasing)
+ ) togetherWith
+ fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis))
+ } else {
+ // empty transition works because all remaining transitions are from IN_PROGRESS
+ // state which shares initial animation frame with both FINISHED and NOT_STARTED
+ EnterTransition.None togetherWith ExitTransition.None
+ }
+ }
+ ) { gestureState ->
+ @RawRes val successAnimationId = R.raw.trackpad_back_success
+ @RawRes val educationAnimationId = R.raw.trackpad_back_edu
+ when (gestureState) {
+ NOT_STARTED -> EducationAnimation(educationAnimationId, animationProperties)
+ IN_PROGRESS -> FrozenSuccessAnimation(successAnimationId, animationProperties)
+ FINISHED -> SuccessAnimation(successAnimationId, animationProperties)
+ }
+ }
}
}
@Composable
-private fun progressForGestureState(
- composition: LottieComposition?,
- gestureState: GestureState
-): () -> Float {
- if (gestureState == IN_PROGRESS) {
- return { 0f } // when gesture is in progress, animation should freeze on 1st frame
- } else {
- val iterations = if (gestureState == FINISHED) 1 else LottieConstants.IterateForever
- val animationState by animateLottieCompositionAsState(composition, iterations = iterations)
- return { animationState }
- }
+private fun FrozenSuccessAnimation(
+ @RawRes successAnimationId: Int,
+ animationProperties: LottieDynamicProperties
+) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
+ LottieAnimation(
+ composition = composition,
+ progress = { 0f }, // animation should freeze on 1st frame
+ dynamicProperties = animationProperties,
+ )
+}
+
+@Composable
+private fun EducationAnimation(
+ @RawRes educationAnimationId: Int,
+ animationProperties: LottieDynamicProperties
+) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
+ val progress by
+ animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+ LottieAnimation(
+ composition = composition,
+ progress = { progress },
+ dynamicProperties = animationProperties,
+ )
+}
+
+@Composable
+private fun SuccessAnimation(
+ @RawRes successAnimationId: Int,
+ animationProperties: LottieDynamicProperties
+) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
+ val progress by animateLottieCompositionAsState(composition, iterations = 1)
+ LottieAnimation(
+ composition = composition,
+ progress = { progress },
+ dynamicProperties = animationProperties,
+ )
}
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
index 6fa9bcd..e3666ce 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
@@ -16,55 +16,26 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
-import android.view.MotionEvent
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import kotlin.math.abs
-/**
- * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState]
- * changes. All tracked motion events should be passed to [processTouchpadEvent]
- */
-interface TouchpadGestureMonitor {
-
- val gestureDistanceThresholdPx: Int
- val gestureStateChangedCallback: (GestureState) -> Unit
-
- fun processTouchpadEvent(event: MotionEvent)
-}
-
+/** Monitors for touchpad back gesture, that is three fingers swiping left or right */
class BackGestureMonitor(
override val gestureDistanceThresholdPx: Int,
override val gestureStateChangedCallback: (GestureState) -> Unit
-) : TouchpadGestureMonitor {
-
- private var xStart = 0f
-
- override fun processTouchpadEvent(event: MotionEvent) {
- val action = event.actionMasked
- when (action) {
- MotionEvent.ACTION_DOWN -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- xStart = event.x
- gestureStateChangedCallback(IN_PROGRESS)
+) :
+ TouchpadGestureMonitor by ThreeFingerGestureMonitor(
+ gestureDistanceThresholdPx = gestureDistanceThresholdPx,
+ gestureStateChangedCallback = gestureStateChangedCallback,
+ donePredicate =
+ object : GestureDonePredicate {
+ override fun wasGestureDone(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float
+ ): Boolean {
+ val distance = abs(endX - startX)
+ return distance >= gestureDistanceThresholdPx
}
}
- MotionEvent.ACTION_UP -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- val distance = abs(event.x - xStart)
- if (distance >= gestureDistanceThresholdPx) {
- gestureStateChangedCallback(FINISHED)
- } else {
- gestureStateChangedCallback(NOT_STARTED)
- }
- }
- }
- }
- }
-
- private fun isThreeFingerTouchpadSwipe(event: MotionEvent): Boolean {
- return event.classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE &&
- event.getAxisValue(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3f
- }
-}
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
new file mode 100644
index 0000000..a410f99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+/** Monitors for touchpad home gesture, that is three fingers swiping up */
+class HomeGestureMonitor(
+ override val gestureDistanceThresholdPx: Int,
+ override val gestureStateChangedCallback: (GestureState) -> Unit
+) :
+ TouchpadGestureMonitor by ThreeFingerGestureMonitor(
+ gestureDistanceThresholdPx = gestureDistanceThresholdPx,
+ gestureStateChangedCallback = gestureStateChangedCallback,
+ donePredicate =
+ object : GestureDonePredicate {
+ override fun wasGestureDone(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float
+ ): Boolean {
+ val distance = startY - endY
+ return distance >= gestureDistanceThresholdPx
+ }
+ }
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt
new file mode 100644
index 0000000..377977c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+
+interface GestureDonePredicate {
+ /**
+ * Should return if gesture was finished. The only events this predicate receives are ACTION_UP.
+ */
+ fun wasGestureDone(startX: Float, startY: Float, endX: Float, endY: Float): Boolean
+}
+
+/** Common implementation for all three-finger gesture monitors */
+class ThreeFingerGestureMonitor(
+ override val gestureDistanceThresholdPx: Int,
+ override val gestureStateChangedCallback: (GestureState) -> Unit,
+ private val donePredicate: GestureDonePredicate
+) : TouchpadGestureMonitor {
+
+ private var xStart = 0f
+ private var yStart = 0f
+
+ override fun processTouchpadEvent(event: MotionEvent) {
+ val action = event.actionMasked
+ when (action) {
+ MotionEvent.ACTION_DOWN -> {
+ if (isThreeFingerTouchpadSwipe(event)) {
+ xStart = event.x
+ yStart = event.y
+ gestureStateChangedCallback(GestureState.IN_PROGRESS)
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ if (isThreeFingerTouchpadSwipe(event)) {
+ if (donePredicate.wasGestureDone(xStart, yStart, event.x, event.y)) {
+ gestureStateChangedCallback(GestureState.FINISHED)
+ } else {
+ gestureStateChangedCallback(GestureState.NOT_STARTED)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGesture.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGesture.kt
deleted file mode 100644
index 190da62..0000000
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGesture.kt
+++ /dev/null
@@ -1,32 +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.touchpad.tutorial.ui.gesture
-
-enum class TouchpadGesture {
- BACK,
- HOME;
-
- fun toMonitor(
- swipeDistanceThresholdPx: Int,
- onStateChanged: (GestureState) -> Unit
- ): TouchpadGestureMonitor {
- return when (this) {
- BACK -> BackGestureMonitor(swipeDistanceThresholdPx, onStateChanged)
- else -> throw IllegalArgumentException("Not implemented yet")
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
index cac2a99..bf85b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
@@ -24,14 +24,9 @@
* motion events passed to [onMotionEvent] and will filter touchpad events accordingly
*/
class TouchpadGestureHandler(
- touchpadGesture: TouchpadGesture,
- swipeDistanceThresholdPx: Int,
- onGestureStateChanged: (GestureState) -> Unit
+ private val gestureMonitor: TouchpadGestureMonitor,
) {
- private val gestureRecognition =
- touchpadGesture.toMonitor(swipeDistanceThresholdPx, onStateChanged = onGestureStateChanged)
-
fun onMotionEvent(event: MotionEvent): Boolean {
// events from touchpad have SOURCE_MOUSE and not SOURCE_TOUCHPAD because of legacy reasons
val isFromTouchpad =
@@ -41,7 +36,7 @@
event.actionMasked == MotionEvent.ACTION_DOWN &&
event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
return if (isFromTouchpad && !buttonClick) {
- gestureRecognition.processTouchpadEvent(event)
+ gestureMonitor.processTouchpadEvent(event)
true
} else {
false
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
new file mode 100644
index 0000000..1d2097d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+
+/**
+ * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState]
+ * changes. All tracked motion events should be passed to [processTouchpadEvent]
+ */
+interface TouchpadGestureMonitor {
+
+ val gestureDistanceThresholdPx: Int
+ val gestureStateChangedCallback: (GestureState) -> Unit
+
+ fun processTouchpadEvent(event: MotionEvent)
+}
+
+fun isThreeFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 3)
+
+fun isFourFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 4)
+
+private fun isNFingerTouchpadSwipe(event: MotionEvent, fingerCount: Int): Boolean {
+ return event.classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE &&
+ event.getAxisValue(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT) == fingerCount.toFloat()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index 9c98f43..f1da27f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -22,23 +22,24 @@
import androidx.annotation.NonNull;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
-import javax.inject.Inject;
-
import kotlinx.coroutines.CoroutineDispatcher;
+import javax.inject.Inject;
+
class SecureSettingsImpl implements SecureSettings {
private final ContentResolver mContentResolver;
- private final UserTracker mUserTracker;
+ private final CurrentUserIdProvider mCurrentUserProvider;
private final CoroutineDispatcher mBgDispatcher;
@Inject
- SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker,
+ SecureSettingsImpl(
+ ContentResolver contentResolver,
+ CurrentUserIdProvider currentUserProvider,
@SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) {
mContentResolver = contentResolver;
- mUserTracker = userTracker;
+ mCurrentUserProvider = currentUserProvider;
mBgDispatcher = bgDispatcher;
}
@@ -48,8 +49,8 @@
}
@Override
- public UserTracker getUserTracker() {
- return mUserTracker;
+ public CurrentUserIdProvider getCurrentUserProvider() {
+ return mCurrentUserProvider;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 9125a91..0ee997e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.util.settings
+import android.annotation.UserIdInt
import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
@@ -629,4 +630,8 @@
}
}
}
+
+ fun interface CurrentUserIdProvider {
+ @UserIdInt fun getUserId(): Int
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 406d95b..1e80357 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -22,23 +22,23 @@
import androidx.annotation.NonNull;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
-import javax.inject.Inject;
-
import kotlinx.coroutines.CoroutineDispatcher;
+import javax.inject.Inject;
+
class SystemSettingsImpl implements SystemSettings {
private final ContentResolver mContentResolver;
- private final UserTracker mUserTracker;
+ private final CurrentUserIdProvider mCurrentUserProvider;
private final CoroutineDispatcher mBgCoroutineDispatcher;
@Inject
- SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker,
+ SystemSettingsImpl(ContentResolver contentResolver,
+ CurrentUserIdProvider currentUserProvider,
@SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) {
mContentResolver = contentResolver;
- mUserTracker = userTracker;
+ mCurrentUserProvider = currentUserProvider;
mBgCoroutineDispatcher = bgDispatcher;
}
@@ -48,8 +48,8 @@
}
@Override
- public UserTracker getUserTracker() {
- return mUserTracker;
+ public CurrentUserIdProvider getCurrentUserProvider() {
+ return mCurrentUserProvider;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index ac7c1ce..9ae8f03 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -23,7 +23,6 @@
import android.os.UserHandle
import android.provider.Settings.SettingNotFoundException
import com.android.app.tracing.TraceUtils.trace
-import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat
import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow
import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow
@@ -46,8 +45,8 @@
* instances, unifying setting related actions in one place.
*/
interface UserSettingsProxy : SettingsProxy {
- /** Returns that [UserTracker] this instance was constructed with. */
- val userTracker: UserTracker
+ val currentUserProvider: SettingsProxy.CurrentUserIdProvider
+
/** Returns the user id for the associated [ContentResolver]. */
var userId: Int
get() = getContentResolver().userId
@@ -64,7 +63,7 @@
fun getRealUserHandle(userHandle: Int): Int {
return if (userHandle != UserHandle.USER_CURRENT) {
userHandle
- } else userTracker.userId
+ } else currentUserProvider.getUserId()
}
@WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index efaca7a..5d8b6f1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -19,8 +19,8 @@
import android.content.ContentResolver
import android.content.Context
import android.media.AudioManager
-import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -80,7 +80,7 @@
@Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
): AudioSharingRepository =
- if (BluetoothUtils.isAudioSharingEnabled() && localBluetoothManager != null) {
+ if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
AudioSharingRepositoryImpl(
contentResolver,
localBluetoothManager,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 14cd202..9ca0591 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -34,7 +34,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
@@ -379,8 +378,8 @@
}
@Override
- public void setImeWindowStatus(int displayId, IBinder token, int vis,
- int backDisposition, boolean showImeSwitcher) {
+ public void setImeWindowStatus(int displayId, int vis, int backDisposition,
+ boolean showImeSwitcher) {
if (displayId == mDisplayTracker.getDefaultDisplayId()
&& (vis & InputMethodService.IME_VISIBLE) != 0) {
oneHanded.stopOneHanded(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 07504c7..2b4fc5b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -26,7 +26,6 @@
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -59,7 +58,6 @@
@Mock protected KeyguardLogger mKeyguardLogger;
@Mock protected KeyguardStatusViewController mControllerMock;
@Mock protected ViewTreeObserver mViewTreeObserver;
- @Mock protected DumpManager mDumpManager;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected FakePowerRepository mFakePowerRepository;
@@ -90,7 +88,6 @@
mKeyguardLogger,
mKosmos.getInteractionJankMonitor(),
deps.getKeyguardInteractor(),
- mDumpManager,
PowerInteractorFactory.create(
mFakePowerRepository
).getPowerInteractor()) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 0696a4b..8e441a3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -140,14 +140,6 @@
}
@Test
- public void correctlyDump() {
- mController.onInit();
- verify(mDumpManager).registerDumpable(eq(mController.getInstanceName()), eq(mController));
- mController.onDestroy();
- verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName()));
- }
-
- @Test
public void onInit_addsOnLayoutChangeListenerToClockSwitch() {
when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
mMediaHostContainer);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 1e3ee28..dc69cda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,7 +35,6 @@
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
-import android.view.KeyEvent
import android.view.View
import android.view.WindowInsets
import android.view.WindowManager
@@ -202,8 +201,7 @@
val root = container.rootView
// Simulate back invocation
- container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
- container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+ container.onBackInvoked()
waitForIdleSync()
assertThat(container.parent).isNull()
@@ -217,8 +215,7 @@
val root = container.rootView
// Simulate back invocation
- container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
- container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+ container.onBackInvoked()
waitForIdleSync()
assertThat(container.parent).isNull()
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 e603db4..6ba9b32 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
@@ -44,7 +44,6 @@
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.app.activityTaskManager
-import com.android.systemui.Flags.FLAG_BP_TALKBACK
import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
@@ -100,9 +99,10 @@
private const val REQUEST_ID = 4L
private const val CHALLENGE = 2L
private const val DELAY = 1000L
-private const val OP_PACKAGE_NAME = "biometric.testapp"
+private const val OP_PACKAGE_NAME_WITH_APP_LOGO = "biometric.testapp"
private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found"
+private const val OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO = "should.use.activiy.logo"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -112,19 +112,19 @@
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@Mock private lateinit var authController: AuthController
- @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
- @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
+ @Mock private lateinit var applicationInfoWithIconAndDescription: ApplicationInfo
+ @Mock private lateinit var applicationInfoNoIconOrDescription: ApplicationInfo
@Mock private lateinit var activityInfo: ActivityInfo
@Mock private lateinit var runningTaskInfo: RunningTaskInfo
- private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
- private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add)
+ private val defaultLogoIconFromAppInfo = context.getDrawable(R.drawable.ic_android)
+ private val defaultLogoIconFromActivityInfo = context.getDrawable(R.drawable.ic_add)
private val logoResFromApp = R.drawable.ic_cake
private val logoDrawableFromAppRes = context.getDrawable(logoResFromApp)
private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
- private val defaultLogoDescription = "Test Android App"
+ private val defaultLogoDescriptionFromAppInfo = "Test Android App"
+ private val defaultLogoDescriptionFromActivityInfo = "Test Coke App"
private val logoDescriptionFromApp = "Test Cake App"
- private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
/** Prompt panel size padding */
private val smallHorizontalGuidelinePadding =
context.resources.getDimensionPixelSize(
@@ -172,16 +172,21 @@
// Set up default logo info and app customized info
whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
- .thenReturn(applicationInfoNoIcon)
- whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt()))
- .thenReturn(applicationInfoWithIcon)
+ .thenReturn(applicationInfoNoIconOrDescription)
whenever(
kosmos.packageManager.getApplicationInfo(
- eq(packageNameForLogoWithOverrides),
+ eq(OP_PACKAGE_NAME_WITH_APP_LOGO),
anyInt()
)
)
- .thenReturn(applicationInfoWithIcon)
+ .thenReturn(applicationInfoWithIconAndDescription)
+ whenever(
+ kosmos.packageManager.getApplicationInfo(
+ eq(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
+ anyInt()
+ )
+ )
+ .thenReturn(applicationInfoWithIconAndDescription)
whenever(
kosmos.packageManager.getApplicationInfo(
eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND),
@@ -191,19 +196,27 @@
.thenThrow(NameNotFoundException())
whenever(kosmos.packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
- whenever(kosmos.iconProvider.getIcon(activityInfo)).thenReturn(defaultLogoIconWithOverrides)
- whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIcon))
- .thenReturn(defaultLogoIcon)
- whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIcon))
- .thenReturn(defaultLogoDescription)
+ whenever(kosmos.iconProvider.getIcon(activityInfo))
+ .thenReturn(defaultLogoIconFromActivityInfo)
+ whenever(activityInfo.loadLabel(kosmos.packageManager))
+ .thenReturn(defaultLogoDescriptionFromActivityInfo)
+
+ whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIconAndDescription))
+ .thenReturn(defaultLogoIconFromAppInfo)
+ whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIconAndDescription))
+ .thenReturn(defaultLogoDescriptionFromAppInfo)
+ whenever(kosmos.packageManager.getApplicationIcon(applicationInfoNoIconOrDescription))
+ .thenReturn(null)
+ whenever(kosmos.packageManager.getApplicationLabel(applicationInfoNoIconOrDescription))
+ .thenReturn("")
whenever(kosmos.packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) }
whenever(kosmos.packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) }
context.setMockPackageManager(kosmos.packageManager)
overrideResource(logoResFromApp, logoDrawableFromAppRes)
overrideResource(
- R.array.biometric_dialog_package_names_for_logo_with_overrides,
- arrayOf(packageNameForLogoWithOverrides)
+ R.array.config_useActivityLogoForBiometricPrompt,
+ arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO)
)
overrideResource(R.dimen.biometric_dialog_fingerprint_icon_width, mockFingerprintIconWidth)
@@ -1356,7 +1369,6 @@
}
@Test
- @EnableFlags(FLAG_BP_TALKBACK)
fun hint_for_talkback_guidance() = runGenericTest {
val hint by collectLastValue(kosmos.promptViewModel.accessibilityHint)
@@ -1379,7 +1391,6 @@
}
@Test
- @EnableFlags(FLAG_BP_TALKBACK)
fun no_hint_for_talkback_guidance_after_auth() = runGenericTest {
val hint by collectLastValue(kosmos.promptViewModel.accessibilityHint)
@@ -1440,36 +1451,41 @@
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_nullIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat(logo).isNull()
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isNull()
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
- fun logo_defaultWithOverrides() =
- runGenericTest(packageName = packageNameForLogoWithOverrides) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
+ fun logo_defaultFromActivityInfo() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
- // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
- // applicationInfoWithIcon with defaultLogoIcon,
- // 2. iconProvider.getIcon() is set to return defaultLogoIconForGMSCore
- // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
- assertThat(logo).isEqualTo(defaultLogoIconWithOverrides)
+ // 1. PM.getApplicationInfo(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) is set to return
+ // applicationInfoWithIconAndDescription with "defaultLogoIconFromAppInfo",
+ // 2. iconProvider.getIcon(activityInfo) is set to return
+ // "defaultLogoIconFromActivityInfo"
+ // For the apps with OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO, 2 should be called instead of 1
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromActivityInfo)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_defaultIsNull() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat(logo).isNull()
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isNull()
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_default() = runGenericTest {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat(logo).isEqualTo(defaultLogoIcon)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromAppInfo)
}
@Test
@@ -1477,47 +1493,60 @@
fun logo_resSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap()
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat((logo as BitmapDrawable).bitmap.sameAs(expectedBitmap)).isTrue()
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat((logoInfo!!.first as BitmapDrawable).bitmap.sameAs(expectedBitmap)).isTrue()
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_bitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat((logoInfo!!.first as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_emptyIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo("")
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo("")
+ }
+
+ @Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logoDescription_defaultFromActivityInfo() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
+ // applicationInfoWithIconAndDescription with defaultLogoDescription,
+ // 2. activityInfo.loadLabel() is set to return defaultLogoDescriptionWithOverrides
+ // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
+ assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromActivityInfo)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_defaultIsEmpty() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo("")
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo("")
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_default() = runGenericTest {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo(defaultLogoDescription)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_setByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo(logoDescriptionFromApp)
}
@Test
@@ -1692,7 +1721,7 @@
logoRes: Int = 0,
logoBitmap: Bitmap? = null,
logoDescription: String? = null,
- packageName: String = OP_PACKAGE_NAME,
+ packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
block: suspend TestScope.() -> Unit,
) {
val topActivity = ComponentName(packageName, "test app")
@@ -1951,7 +1980,7 @@
logoResFromApp: Int = 0,
logoBitmapFromApp: Bitmap? = null,
logoDescriptionFromApp: String? = null,
- packageName: String = OP_PACKAGE_NAME,
+ packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
) {
val info =
PromptInfo().apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorTest.kt
new file mode 100644
index 0000000..295a626
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FaceSensorInfo
+import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepository
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryBiometricsAllowedInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.deviceEntryBiometricsAllowedInteractor
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_true() =
+ testScope.runTest {
+ val fpAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+ // WHEN: not locked out, no face sensor, no strong auth requirements
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ kosmos.fakeFacePropertyRepository.setSensorInfo(null)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // THEN fp is allowed
+ assertThat(fpAllowed).isTrue()
+ }
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_strongFaceLockedOut() =
+ testScope.runTest {
+ val fpAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+ // WHEN: not locked out, face is strong & locked out, no strong auth requirements
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ kosmos.fakeFacePropertyRepository.setSensorInfo(
+ FaceSensorInfo(
+ id = 0,
+ strength = SensorStrength.STRONG,
+ )
+ )
+ kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // THEN fp is NOT allowed
+ assertThat(fpAllowed).isFalse()
+ }
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_convenienceFaceLockedOut() =
+ testScope.runTest {
+ val fpAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+ // WHEN: not locked out, face is convenience & locked out, no strong auth requirements
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ kosmos.fakeFacePropertyRepository.setSensorInfo(
+ FaceSensorInfo(
+ id = 0,
+ strength = SensorStrength.CONVENIENCE,
+ )
+ )
+ kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // THEN fp is allowed
+ assertThat(fpAllowed).isTrue()
+ }
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_primaryAuthRequired() =
+ testScope.runTest {
+ val fpAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+ // WHEN: not locked out, no face sensor, no strong auth requirements
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+
+ // THEN fp is NOT allowed
+ assertThat(fpAllowed).isFalse()
+ }
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowedOnBouncer_sfps() =
+ testScope.runTest {
+ val fpAllowedOnBouncer by
+ collectLastValue(underTest.isFingerprintCurrentlyAllowedOnBouncer)
+
+ // GIVEN fingerprint is generally allowed
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN side fps
+ kosmos.fakeFingerprintPropertyRepository.supportsSideFps()
+
+ // THEN fp is allowed on the primary bouncer
+ assertThat(fpAllowedOnBouncer).isTrue()
+ }
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowedOnBouncer_rearFps() =
+ testScope.runTest {
+ val fpAllowedOnBouncer by
+ collectLastValue(underTest.isFingerprintCurrentlyAllowedOnBouncer)
+
+ // GIVEN fingerprint is generally allowed
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN rear fps
+ kosmos.fakeFingerprintPropertyRepository.supportsRearFps()
+
+ // THEN fp is allowed on the primary bouncer
+ assertThat(fpAllowedOnBouncer).isTrue()
+ }
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowedOnBouncer_udfps() =
+ testScope.runTest {
+ val fpAllowedOnBouncer by
+ collectLastValue(underTest.isFingerprintCurrentlyAllowedOnBouncer)
+
+ // GIVEN fp is generally allowed
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+
+ // WHEN UDFPS
+ kosmos.fakeFingerprintPropertyRepository.supportsUdfps()
+
+ // THEN fp is never allowed on the primary bouncer
+ assertThat(fpAllowedOnBouncer).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 4818119..86da203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -58,7 +58,6 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.feature.flags.Flags;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -105,8 +104,6 @@
@Mock
DozeParameters mDozeParameters;
@Mock
- DockManager mDockManager;
- @Mock
DevicePostureController mDevicePostureController;
@Mock
DozeLog mDozeLog;
@@ -114,8 +111,8 @@
SystemSettings mSystemSettings;
@Mock
DisplayManager mDisplayManager;
- private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
- private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ private final FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
private DozeScreenBrightness mScreen;
@@ -249,32 +246,35 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DOZE_BRIGHTNESS_FLOAT)
- public void testAod_usesLightSensorNotClampingToAutoBrightnessValue_Int() {
- int maxBrightness = 3;
- when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
- eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness);
+ @RequiresFlagsEnabled(Flags.FLAG_DOZE_BRIGHTNESS_FLOAT)
+ public void initialBrightness_clampsToAutoBrightnessValue_Float() {
+ float maxBrightnessFromAutoBrightness = DEFAULT_BRIGHTNESS_FLOAT / 2;
+ when(mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY)).thenReturn(
+ maxBrightnessFromAutoBrightness
+ );
when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
eq(UserHandle.USER_CURRENT)))
.thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- assertEquals(DEFAULT_BRIGHTNESS_INT, mServiceFake.screenBrightnessInt);
- assertTrue(Float.isNaN(mServiceFake.screenBrightnessFloat));
+ assertEquals(maxBrightnessFromAutoBrightness, mServiceFake.screenBrightnessFloat,
+ DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_DEFAULT, mServiceFake.screenBrightnessInt);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DOZE_BRIGHTNESS_FLOAT)
- public void testAod_usesLightSensorNotClampingToAutoBrightnessValue_Float() {
- float maxBrightness = DEFAULT_BRIGHTNESS_FLOAT / 2;
- when(mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY)).thenReturn(maxBrightness);
+ @RequiresFlagsDisabled(Flags.FLAG_DOZE_BRIGHTNESS_FLOAT)
+ public void initialBrightness_clampsToAutoBrightnessValue_Int() {
+ int maxBrightnessFromAutoBrightness = DEFAULT_BRIGHTNESS_INT / 2;
+ when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
+ eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightnessFromAutoBrightness);
when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
eq(UserHandle.USER_CURRENT)))
.thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- assertEquals(DEFAULT_BRIGHTNESS_FLOAT, mServiceFake.screenBrightnessFloat, DELTA);
- assertEquals(PowerManager.BRIGHTNESS_DEFAULT, mServiceFake.screenBrightnessInt);
+ assertEquals(maxBrightnessFromAutoBrightness, mServiceFake.screenBrightnessInt);
+ assertTrue(Float.isNaN(mServiceFake.screenBrightnessFloat));
}
@Test
@@ -378,6 +378,54 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DOZE_BRIGHTNESS_FLOAT)
+ public void lightSensorChangesInAod_doesNotClampToAutoBrightnessValue_Float() {
+ // GIVEN auto brightness reports low brightness
+ float maxBrightnessFromAutoBrightness = DEFAULT_BRIGHTNESS_FLOAT / 2;
+ when(mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY))
+ .thenReturn(maxBrightnessFromAutoBrightness);
+ when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+ eq(UserHandle.USER_CURRENT)))
+ .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ // GIVEN the device is DOZE_AOD and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(SENSOR_TO_BRIGHTNESS_FLOAT[3], mServiceFake.screenBrightnessFloat, DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_DEFAULT, mServiceFake.screenBrightnessInt);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_DOZE_BRIGHTNESS_FLOAT)
+ public void lightSensorChangesInAod_doesNotClampToAutoBrightnessValue_Int() {
+ // GIVEN auto brightness reports low brightness
+ int maxBrightnessFromAutoBrightness = 1;
+ when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
+ eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightnessFromAutoBrightness);
+ when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+ eq(UserHandle.USER_CURRENT)))
+ .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ // GIVEN the device is DOZE_AOD and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(SENSOR_TO_BRIGHTNESS_INT[3], mServiceFake.screenBrightnessInt);
+ assertTrue(Float.isNaN(mServiceFake.screenBrightnessFloat));
+ }
+
+ @Test
@RequiresFlagsDisabled(Flags.FLAG_DOZE_BRIGHTNESS_FLOAT)
public void docked_usesLightSensor_Int() {
// GIVEN the device is docked and the display state changes to ON
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index ff65887..e3a38a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -111,7 +111,6 @@
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeWindowLogger;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -226,7 +225,6 @@
private @Mock DreamViewModel mDreamViewModel;
private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel;
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
- @Mock private NotificationShadeWindowModel mNotificationShadeWindowModel;
private FakeFeatureFlags mFeatureFlags;
private final int mDefaultUserId = 100;
@@ -274,7 +272,7 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mNotificationShadeWindowModel,
+ mKosmos.getNotificationShadeWindowModel(),
mKosmos::getCommunalInteractor);
mFeatureFlags = new FakeFeatureFlags();
mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index a8271c1..2a2a82d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -31,8 +31,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -112,7 +112,7 @@
biometricSettingsRepository,
FakeSystemClock(),
keyguardUpdateMonitor,
- { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ { mock(DeviceEntryBiometricsAllowedInteractor::class.java) },
{ mock(KeyguardInteractor::class.java) },
{ mock(KeyguardTransitionInteractor::class.java) },
{ kosmos.sceneInteractor },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
index f884b87..c57aa36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
@@ -29,16 +29,16 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
+import kotlin.test.assertEquals
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: AlertDialog
@@ -115,39 +115,16 @@
assertEquals(context.getString(resIdFullScreen), secondOptionText)
}
- @Test
- fun showDialog_disableSingleApp_hasCastingCapabilities() {
- setUpAndShowDialog(
- mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
- hasCastingCapabilities = true
- )
-
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
- val secondOptionWarningText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text2)
- ?.text
-
- // check that the first option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
-
- // check that the second option is single app and disabled
- assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
- }
-
private fun setUpAndShowDialog(
mediaProjectionConfig: MediaProjectionConfig? = null,
overrideDisableSingleAppOption: Boolean = false,
- hasCastingCapabilities: Boolean = false,
) {
val delegate =
- MediaProjectionPermissionDialogDelegate(
+ ShareToAppPermissionDialogDelegate(
context,
mediaProjectionConfig,
onStartRecordingClicked = {},
onCancelClicked = {},
- hasCastingCapabilities,
appName,
overrideDisableSingleAppOption,
hostUid = 12345,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
similarity index 80%
copy from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
copy to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
index f884b87..bdbe5eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
@@ -29,16 +29,16 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
+import kotlin.test.assertEquals
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: AlertDialog
@@ -98,7 +98,7 @@
fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
setUpAndShowDialog(
mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
- overrideDisableSingleAppOption = true
+ overrideDisableSingleAppOption = true,
)
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
@@ -115,43 +115,20 @@
assertEquals(context.getString(resIdFullScreen), secondOptionText)
}
- @Test
- fun showDialog_disableSingleApp_hasCastingCapabilities() {
- setUpAndShowDialog(
- mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
- hasCastingCapabilities = true
- )
-
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
- val secondOptionWarningText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text2)
- ?.text
-
- // check that the first option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
-
- // check that the second option is single app and disabled
- assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
- }
-
private fun setUpAndShowDialog(
mediaProjectionConfig: MediaProjectionConfig? = null,
overrideDisableSingleAppOption: Boolean = false,
- hasCastingCapabilities: Boolean = false,
) {
val delegate =
- MediaProjectionPermissionDialogDelegate(
+ SystemCastPermissionDialogDelegate(
context,
mediaProjectionConfig,
onStartRecordingClicked = {},
onCancelClicked = {},
- hasCastingCapabilities,
appName,
overrideDisableSingleAppOption,
hostUid = 12345,
- mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+ mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>(),
)
dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
@@ -159,7 +136,7 @@
SystemUIDialog.setDialogSize(dialog)
dialog.window?.addSystemFlags(
- WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
)
delegate.onCreate(dialog, savedInstanceState = null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 45d77f6..a8cbbd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -498,7 +498,7 @@
defaultNavBar.init();
externalNavBar.init();
- defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+ defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
// Verify IME window state will be updated in default NavBar & external NavBar state reset.
@@ -510,10 +510,10 @@
assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN)
!= 0);
- externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE,
+ externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
- defaultNavBar.setImeWindowStatus(
- DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false);
+ defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_INVISIBLE,
+ BACK_DISPOSITION_DEFAULT, false);
// Verify IME window state will be updated in external NavBar & default NavBar state reset.
assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
| NAVIGATION_HINT_IME_SWITCHER_SHOWN,
@@ -535,7 +535,7 @@
doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets();
// Verify navbar altered back icon when an app is showing IME
- mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+ mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
@@ -545,7 +545,7 @@
// Verify navbar didn't alter and showing back icon when the keyguard is showing without
// requesting IME insets visible.
doReturn(true).when(mKeyguardStateController).isShowing();
- mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+ mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
@@ -556,7 +556,7 @@
// requesting IME insets visible.
windowInsets = new WindowInsets.Builder().setVisible(ime(), true).build();
doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets();
- mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+ mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 0196f95..80a9e4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -62,9 +62,8 @@
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.FakeSettings
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
@@ -86,6 +85,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
/** atest SystemUITests:NoteTaskControllerTest */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -106,10 +106,10 @@
@Mock private lateinit var shortcutManager: ShortcutManager
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var secureSettings: SecureSettings
private val userTracker = FakeUserTracker()
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
+ private val secureSettings = FakeSettings(testDispatcher) { userTracker.userId }
@Before
fun setUp() {
@@ -139,7 +139,6 @@
whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
whenever(userManager.isManagedProfile(workUserInfo.id)).thenReturn(true)
whenever(context.resources).thenReturn(getContext().resources)
- whenever(secureSettings.userTracker).thenReturn(userTracker)
}
private fun createNoteTaskController(
@@ -245,6 +244,7 @@
verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
+
// endregion
// region showNoteTask
@@ -357,14 +357,11 @@
@Test
fun showNoteTask_defaultUserSet_shouldStartActivityWithExpectedUserAndLogUiEvent() {
- whenever(
- secureSettings.getIntForUser(
- /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
- /* def= */ any(),
- /* userHandle= */ any()
- )
- )
- .thenReturn(10)
+ secureSettings.putIntForUser(
+ /* name= */ Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+ /* value= */ 10,
+ /* userHandle= */ userTracker.userId
+ )
val user10 = UserHandle.of(/* userId= */ 10)
val expectedInfo =
@@ -458,6 +455,7 @@
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
+
// endregion
// region setNoteTaskShortcutEnabled
@@ -535,6 +533,7 @@
assertThat(argument.value.className)
.isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
}
+
// endregion
// region keyguard policy
@@ -601,6 +600,7 @@
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
}
+
// endregion
// region showNoteTask, COPE devices
@@ -626,14 +626,11 @@
@Test
fun showNoteTask_copeDevices_tailButtonEntryPoint_shouldStartBubbleInTheUserSelectedUser() {
- whenever(
- secureSettings.getIntForUser(
- /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
- /* def= */ any(),
- /* userHandle= */ any()
- )
- )
- .thenReturn(mainUserInfo.id)
+ secureSettings.putIntForUser(
+ /* name= */ Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+ /* value= */ mainUserInfo.id,
+ /* userHandle= */ userTracker.userId
+ )
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
@@ -661,6 +658,7 @@
verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle)
}
+
// endregion
private fun verifyNoteTaskOpenInBubbleInUser(userHandle: UserHandle) {
@@ -700,6 +698,7 @@
verify(controller).updateNoteTaskAsUser(user)
}
+
// endregion
// region updateNoteTaskAsUser
@@ -729,6 +728,7 @@
val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) }
assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java)
}
+
// endregion
// region internalUpdateNoteTaskAsUser
@@ -807,6 +807,7 @@
verify(shortcutManager, never()).enableShortcuts(any())
verify(shortcutManager, never()).updateShortcuts(any())
}
+
// endregion
// startregion updateNoteTaskForAllUsers
@@ -821,6 +822,7 @@
verify(controller).updateNoteTaskAsUser(mainUserInfo.userHandle)
verify(controller).updateNoteTaskAsUser(workUserInfo.userHandle)
}
+
// endregion
// region getUserForHandlingNotesTaking
@@ -836,14 +838,11 @@
@Test
fun getUserForHandlingNotesTaking_cope_userSelectedWorkProfile_tailButton_shouldReturnWorkProfileUser() { // ktlint-disable max-line-length
- whenever(
- secureSettings.getIntForUser(
- /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
- /* def= */ any(),
- /* userHandle= */ any()
- )
- )
- .thenReturn(workUserInfo.id)
+ secureSettings.putIntForUser(
+ /* name= */ Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+ /* value= */ workUserInfo.id,
+ /* userHandle= */ userTracker.userId
+ )
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
@@ -854,14 +853,11 @@
@Test
fun getUserForHandlingNotesTaking_cope_userSelectedMainProfile_tailButton_shouldReturnMainProfileUser() { // ktlint-disable max-line-length
- whenever(
- secureSettings.getIntForUser(
- /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
- /* def= */ any(),
- /* userHandle= */ any()
- )
- )
- .thenReturn(mainUserInfo.id)
+ secureSettings.putIntForUser(
+ /* name= */ Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+ /* value= */ mainUserInfo.id,
+ /* userHandle= */ userTracker.userId
+ )
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
@@ -906,6 +902,7 @@
assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
}
+
// endregion
// startregion startNotesRoleSetting
@@ -962,6 +959,7 @@
assertThat(intentCaptor.value).hasAction(ACTION_MANAGE_DEFAULT_APP)
assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
}
+
// endregion
private companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index c0d390a..206bbbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -554,6 +554,30 @@
assertThat(mUnderTest.isKeyguardState()).isFalse();
}
+ @Test
+ public void testHeaderBounds() {
+ int left = 20;
+ int top = 30;
+ int right = 200;
+ int bottom = 100;
+ setHeaderBounds(left, top, right, bottom);
+
+ assertThat(mUnderTest.getHeaderLeft()).isEqualTo(left);
+ assertThat(mUnderTest.getHeaderTop()).isEqualTo(top);
+ assertThat(mUnderTest.getHeaderBottom()).isEqualTo(bottom);
+ assertThat(mUnderTest.getHeaderHeight()).isEqualTo(bottom - top);
+ }
+
+ @Test
+ public void testHeaderBoundsOnScreen() {
+ Rect bounds = new Rect(0, 10, 100, 200);
+ setHeaderBoundsOnScreen(bounds);
+
+ Rect out = new Rect();
+ mUnderTest.getHeaderBoundsOnScreen(out);
+ assertThat(out).isEqualTo(bounds);
+ }
+
private QSImpl instantiate() {
setupQsComponent();
setUpViews();
@@ -672,4 +696,19 @@
private void setIsSmallScreen() {
mUnderTest.setIsNotificationPanelFullWidth(true);
}
+
+ private void setHeaderBounds(int left, int top, int right, int bottom) {
+ when(mHeader.getLeft()).thenReturn(left);
+ when(mHeader.getTop()).thenReturn(top);
+ when(mHeader.getRight()).thenReturn(right);
+ when(mHeader.getBottom()).thenReturn(bottom);
+ }
+
+ private void setHeaderBoundsOnScreen(Rect rect) {
+ doAnswer(invocation -> {
+ Rect bounds = invocation.getArgument(/* index= */ 0);
+ bounds.set(rect);
+ return null;
+ }).when(mHeader).getBoundsOnScreen(any(Rect.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 56fb43d..2803035 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -479,7 +479,6 @@
mKeyguardLogger,
mKosmos.getInteractionJankMonitor(),
mKeyguardInteractor,
- mDumpManager,
mPowerInteractor));
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index b7ce336..e57382d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -252,6 +252,8 @@
when(mQsFrame.getWidth()).thenReturn(QS_FRAME_WIDTH);
when(mQsHeader.getTop()).thenReturn(QS_FRAME_TOP);
when(mQsHeader.getBottom()).thenReturn(QS_FRAME_BOTTOM);
+ when(mQs.getHeaderTop()).thenReturn(QS_FRAME_TOP);
+ when(mQs.getHeaderBottom()).thenReturn(QS_FRAME_BOTTOM);
when(mPanelView.getY()).thenReturn((float) QS_FRAME_TOP);
when(mPanelView.getHeight()).thenReturn(QS_FRAME_BOTTOM);
when(mPanelView.findViewById(R.id.keyguard_status_view))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 7ab3e29..e7db469 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.shade;
+import static android.platform.test.flag.junit.FlagsParameterization.progressionOf;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
@@ -23,6 +24,8 @@
import static android.view.MotionEvent.BUTTON_SECONDARY;
import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
+import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR;
+import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -36,10 +39,10 @@
import static org.mockito.Mockito.when;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
import android.view.MotionEvent;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.plugins.qs.QS;
@@ -52,11 +55,24 @@
import java.util.List;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class QuickSettingsControllerImplTest extends QuickSettingsControllerImplBaseTest {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return progressionOf(FLAG_QS_UI_REFACTOR, FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT);
+ }
+
+ public QuickSettingsControllerImplTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Test
public void testCloseQsSideEffects() {
enableSplitShade(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 9df46e5..86d21e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -195,10 +195,9 @@
@Test
public void testShowImeButton() {
- mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, 1, 2, true);
+ mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, 1, 2, true);
waitForIdleSync();
- verify(mCallbacks).setImeWindowStatus(
- eq(DEFAULT_DISPLAY), eq(null), eq(1), eq(2), eq(true));
+ verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(1), eq(2), eq(true));
}
@Test
@@ -206,12 +205,11 @@
// First show in default display to update the "last updated ime display"
testShowImeButton();
- mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, null, 1, 2, true);
+ mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, 1, 2, true);
waitForIdleSync();
- verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(IME_INVISIBLE),
+ verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(IME_INVISIBLE),
eq(BACK_DISPOSITION_DEFAULT), eq(false));
- verify(mCallbacks).setImeWindowStatus(
- eq(SECONDARY_DISPLAY), eq(null), eq(1), eq(2), eq(true));
+ verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(1), eq(2), eq(true));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OWNERS
new file mode 100644
index 0000000..1c52b8d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 277b887..572a0c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -15,6 +15,8 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.app.Notification
+import android.os.Bundle
import android.service.notification.StatusBarNotification
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -139,9 +141,10 @@
}
private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
+ val mockNotification = mock<Notification> { this.extras = Bundle() }
val mockSbn =
mock<StatusBarNotification>() {
- whenever(notification).thenReturn(mock())
+ whenever(notification).thenReturn(mockNotification)
whenever(packageName).thenReturn("com.android")
}
return mock<NotificationEntry> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b799595..22b9887 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -77,7 +77,10 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.qs.flags.NewQsUI;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
+import com.android.systemui.shade.QSHeaderBoundsProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -99,6 +102,8 @@
import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import kotlin.Unit;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -807,6 +812,7 @@
}
@Test
+ @DisableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
@DisableSceneContainer // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_noOffset() {
ViewGroup qsHeader = mock(ViewGroup.class);
@@ -824,6 +830,7 @@
}
@Test
+ @DisableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
@DisableSceneContainer // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_Offset() {
ViewGroup qsHeader = mock(ViewGroup.class);
@@ -844,6 +851,63 @@
}
@Test
+ @EnableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
+ @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
+ public void testInsideQSHeader_noOffset_qsCompose() {
+ ViewGroup qsHeader = mock(ViewGroup.class);
+ Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
+ mockBoundsOnScreen(qsHeader, boundsOnScreen);
+
+ QSHeaderBoundsProvider provider = new QSHeaderBoundsProvider(
+ () -> 0,
+ boundsOnScreen::height,
+ rect -> {
+ qsHeader.getBoundsOnScreen(rect);
+ return Unit.INSTANCE;
+ }
+ );
+
+ mStackScroller.setQsHeaderBoundsProvider(provider);
+ mStackScroller.setLeftTopRightBottom(0, 0, 2000, 2000);
+
+ MotionEvent event1 = transformEventForView(createMotionEvent(100f, 100f), mStackScroller);
+ assertTrue(mStackScroller.isInsideQsHeader(event1));
+
+ MotionEvent event2 = transformEventForView(createMotionEvent(1100f, 100f), mStackScroller);
+ assertFalse(mStackScroller.isInsideQsHeader(event2));
+ }
+
+ @Test
+ @EnableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
+ @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
+ public void testInsideQSHeader_Offset_qsCompose() {
+ ViewGroup qsHeader = mock(ViewGroup.class);
+ Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
+ mockBoundsOnScreen(qsHeader, boundsOnScreen);
+
+ QSHeaderBoundsProvider provider = new QSHeaderBoundsProvider(
+ () -> 0,
+ boundsOnScreen::height,
+ rect -> {
+ qsHeader.getBoundsOnScreen(rect);
+ return Unit.INSTANCE;
+ }
+ );
+
+ mStackScroller.setQsHeaderBoundsProvider(provider);
+ mStackScroller.setLeftTopRightBottom(200, 200, 2000, 2000);
+
+ MotionEvent event1 = transformEventForView(createMotionEvent(50f, 50f), mStackScroller);
+ assertFalse(mStackScroller.isInsideQsHeader(event1));
+
+ MotionEvent event2 = transformEventForView(createMotionEvent(150f, 150f), mStackScroller);
+ assertFalse(mStackScroller.isInsideQsHeader(event2));
+
+ MotionEvent event3 = transformEventForView(createMotionEvent(250f, 250f), mStackScroller);
+ assertTrue(mStackScroller.isInsideQsHeader(event3));
+ }
+
+ @Test
@DisableSceneContainer // TODO(b/312473478): address disabled test
public void setFractionToShade_recomputesStackHeight() {
mStackScroller.setFractionToShade(1f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index c4371fd..597e2e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -32,9 +32,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
+import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -44,6 +46,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.window.StatusBarWindowController
@@ -84,7 +87,8 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
-class OngoingCallControllerTest : SysuiTestCase() {
+@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP)
+class OngoingCallControllerViaListenerTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val clock = FakeSystemClock()
@@ -121,6 +125,7 @@
context,
ongoingCallRepository,
notificationCollection,
+ kosmos.activeNotificationsInteractor,
clock,
mockActivityStarter,
mainExecutor,
@@ -129,7 +134,7 @@
mockStatusBarWindowController,
mockSwipeStatusBarAwayGestureHandler,
statusBarModeRepository,
- logcatLogBuffer("OngoingCallControllerTest"),
+ logcatLogBuffer("OngoingCallControllerViaListenerTest"),
)
controller.start()
controller.addCallback(mockOngoingCallListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
new file mode 100644
index 0000000..6c2e2c6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
@@ -0,0 +1,669 @@
+/*
+ * 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.statusbar.phone.ongoingcall
+
+import android.app.ActivityManager
+import android.app.IActivityManager
+import android.app.IUidObserver
+import android.app.PendingIntent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
+import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.shared.CallType
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@EnableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP)
+class OngoingCallControllerViaRepoTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+
+ private val clock = kosmos.fakeSystemClock
+ private val mainExecutor = kosmos.fakeExecutor
+ private val testScope = kosmos.testScope
+ private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
+ private val ongoingCallRepository = kosmos.ongoingCallRepository
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+
+ private lateinit var controller: OngoingCallController
+
+ private val mockSwipeStatusBarAwayGestureHandler = mock<SwipeStatusBarAwayGestureHandler>()
+ private val mockOngoingCallListener = mock<OngoingCallListener>()
+ private val mockActivityStarter = kosmos.activityStarter
+ private val mockIActivityManager = mock<IActivityManager>()
+ private val mockStatusBarWindowController = mock<StatusBarWindowController>()
+
+ private lateinit var chipView: View
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ TestableLooper.get(this).runWithLooper {
+ chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
+ }
+
+ controller =
+ OngoingCallController(
+ testScope.backgroundScope,
+ context,
+ ongoingCallRepository,
+ mock<CommonNotifCollection>(),
+ kosmos.activeNotificationsInteractor,
+ clock,
+ mockActivityStarter,
+ mainExecutor,
+ mockIActivityManager,
+ DumpManager(),
+ mockStatusBarWindowController,
+ mockSwipeStatusBarAwayGestureHandler,
+ statusBarModeRepository,
+ logcatLogBuffer("OngoingCallControllerViaRepoTest"),
+ )
+ controller.start()
+ controller.addCallback(mockOngoingCallListener)
+ controller.setChipView(chipView)
+
+ // Let the controller get the starting value from activeNotificationsInteractor
+ testScope.runCurrent()
+ reset(mockOngoingCallListener)
+
+ whenever(
+ mockIActivityManager.getUidProcessState(
+ eq(CALL_UID),
+ any(),
+ )
+ )
+ .thenReturn(PROC_STATE_INVISIBLE)
+ }
+
+ @After
+ fun tearDown() {
+ controller.tearDownChipView()
+ }
+
+ @Test
+ fun interactorHasOngoingCallNotif_listenerAndRepoNotified() =
+ testScope.runTest {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "ongoingNotif",
+ callType = CallType.Ongoing,
+ uid = CALL_UID,
+ whenTime = 567,
+ )
+ )
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(any())
+ val repoState = ongoingCallRepository.ongoingCallState.value
+ assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((repoState as OngoingCallModel.InCall).startTimeMs).isEqualTo(567)
+ }
+
+ @Test
+ fun notifRepoHasOngoingCallNotif_isOngoingCallNotif_windowControllerUpdated() {
+ setCallNotifOnRepo()
+
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true)
+ }
+
+ @Test
+ fun notifRepoHasNoCallNotif_listenerAndRepoNotNotified() {
+ setNoNotifsOnRepo()
+
+ verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(any())
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.NoCall::class.java)
+ }
+
+ @Test
+ fun notifRepoHasOngoingCallNotifThenScreeningNotif_listenerNotifiedTwice() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ )
+ )
+
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Screening,
+ )
+ )
+
+ verify(mockOngoingCallListener, times(2)).onOngoingCallStateChanged(any())
+ }
+
+ @Test
+ fun notifRepoHasOngoingCallNotifThenScreeningNotif_repoUpdated() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ )
+ )
+
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Screening,
+ )
+ )
+
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.NoCall::class.java)
+ }
+
+ /** Regression test for b/191472854. */
+ @Test
+ fun notifRepoHasCallNotif_notifHasNullContentIntent_noCrash() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ contentIntent = null,
+ )
+ )
+ }
+
+ /** Regression test for b/192379214. */
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME, FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun notifRepoHasCallNotif_notificationWhenIsZero_timeHidden() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ contentIntent = null,
+ whenTime = 0,
+ )
+ )
+
+ chipView.measure(
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ )
+
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
+ .isEqualTo(0)
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun notifRepoHasCallNotif_notificationWhenIsValid_timeShown() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ whenTime = clock.currentTimeMillis(),
+ )
+ )
+
+ chipView.measure(
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ )
+
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
+ .isGreaterThan(0)
+ }
+
+ /** Regression test for b/194731244. */
+ @Test
+ fun repoHasCallNotif_updatedManyTimes_uidObserverOnlyRegisteredOnce() {
+ for (i in 0 until 4) {
+ // Re-create the notification each time so that it's considered a different object and
+ // will re-trigger the whole flow.
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif$i",
+ callType = CallType.Ongoing,
+ whenTime = 44,
+ )
+ )
+ }
+
+ verify(mockIActivityManager, times(1)).registerUidObserver(any(), any(), any(), any())
+ }
+
+ /** Regression test for b/216248574. */
+ @Test
+ fun repoHasCallNotif_getUidProcessStateThrowsException_noCrash() {
+ whenever(
+ mockIActivityManager.getUidProcessState(
+ eq(CALL_UID),
+ any(),
+ )
+ )
+ .thenThrow(SecurityException())
+
+ // No assert required, just check no crash
+ setCallNotifOnRepo()
+ }
+
+ /** Regression test for b/216248574. */
+ @Test
+ fun repoHasCallNotif_registerUidObserverThrowsException_noCrash() {
+ whenever(
+ mockIActivityManager.registerUidObserver(
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
+ .thenThrow(SecurityException())
+
+ // No assert required, just check no crash
+ setCallNotifOnRepo()
+ }
+
+ /** Regression test for b/216248574. */
+ @Test
+ fun repoHasCallNotif_packageNameProvidedToActivityManager() {
+ setCallNotifOnRepo()
+
+ val packageNameCaptor = argumentCaptor<String>()
+ verify(mockIActivityManager)
+ .registerUidObserver(any(), any(), any(), packageNameCaptor.capture())
+ assertThat(packageNameCaptor.firstValue).isNotNull()
+ }
+
+ @Test
+ fun repo_callNotifAddedThenRemoved_listenerNotified() {
+ setCallNotifOnRepo()
+ reset(mockOngoingCallListener)
+
+ setNoNotifsOnRepo()
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(any())
+ }
+
+ @Test
+ fun repo_callNotifAddedThenRemoved_repoUpdated() {
+ setCallNotifOnRepo()
+
+ setNoNotifsOnRepo()
+
+ assertThat(ongoingCallRepository.ongoingCallState.value)
+ .isInstanceOf(OngoingCallModel.NoCall::class.java)
+ }
+
+ @Test
+ fun repo_callNotifAddedThenRemoved_windowControllerUpdated() {
+ reset(mockStatusBarWindowController)
+
+ setCallNotifOnRepo()
+
+ setNoNotifsOnRepo()
+
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false)
+ }
+
+ @Test
+ fun hasOngoingCall_noOngoingCallNotifSent_returnsFalse() {
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
+ fun hasOngoingCall_repoHasUnrelatedNotif_returnsFalse() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "unrelated",
+ callType = CallType.None,
+ uid = CALL_UID,
+ )
+ )
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
+ fun hasOngoingCall_repoHasScreeningCall_returnsFalse() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "unrelated",
+ callType = CallType.Screening,
+ uid = CALL_UID,
+ )
+ )
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
+ fun hasOngoingCall_repoHasCallNotifAndCallAppNotVisible_returnsTrue() {
+ whenever(
+ mockIActivityManager.getUidProcessState(
+ eq(CALL_UID),
+ any(),
+ )
+ )
+ .thenReturn(PROC_STATE_INVISIBLE)
+
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ uid = CALL_UID,
+ )
+ )
+
+ assertThat(controller.hasOngoingCall()).isTrue()
+ }
+
+ @Test
+ fun hasOngoingCall_repoHasCallNotifButCallAppVisible_returnsFalse() {
+ whenever(mockIActivityManager.getUidProcessState(eq(CALL_UID), any()))
+ .thenReturn(PROC_STATE_VISIBLE)
+
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ uid = CALL_UID,
+ )
+ )
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
+ fun hasOngoingCall_repoHasCallNotifButInvalidChipView_returnsFalse() {
+ val invalidChipView = LinearLayout(context)
+ controller.setChipView(invalidChipView)
+
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ callType = CallType.Ongoing,
+ uid = CALL_UID,
+ )
+ )
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
+ fun hasOngoingCall_repoHasCallNotifThenDoesNot_returnsFalse() {
+ setCallNotifOnRepo()
+ setNoNotifsOnRepo()
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
+ fun hasOngoingCall_repoHasCallNotifThenScreeningNotif_returnsFalse() {
+ setCallNotifOnRepo()
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "screening",
+ callType = CallType.Screening,
+ uid = CALL_UID,
+ )
+ )
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ /**
+ * This test fakes a theme change during an ongoing call.
+ *
+ * When a theme change happens,
+ * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment] and its views get
+ * re-created, so [OngoingCallController.setChipView] gets called with a new view. If there's an
+ * active ongoing call when the theme changes, the new view needs to be updated with the call
+ * information.
+ */
+ @Test
+ fun setChipView_whenRepoHasOngoingCall_listenerNotifiedWithNewView() {
+ // Start an ongoing call.
+ setCallNotifOnRepo()
+ reset(mockOngoingCallListener)
+
+ lateinit var newChipView: View
+ TestableLooper.get(this).runWithLooper {
+ newChipView =
+ LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
+ }
+
+ // Change the chip view associated with the controller.
+ controller.setChipView(newChipView)
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(any())
+ }
+
+ @Test
+ fun callProcessChangesToVisible_listenerNotified() {
+ // Start the call while the process is invisible.
+ whenever(mockIActivityManager.getUidProcessState(eq(CALL_UID), any()))
+ .thenReturn(PROC_STATE_INVISIBLE)
+ setCallNotifOnRepo()
+ reset(mockOngoingCallListener)
+
+ val captor = argumentCaptor<IUidObserver.Stub>()
+ verify(mockIActivityManager).registerUidObserver(captor.capture(), any(), any(), any())
+ val uidObserver = captor.firstValue
+
+ // Update the process to visible.
+ uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_VISIBLE, 0, 0)
+ mainExecutor.advanceClockToLast()
+ mainExecutor.runAllReady()
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(any())
+ }
+
+ @Test
+ fun callProcessChangesToInvisible_listenerNotified() {
+ // Start the call while the process is visible.
+ whenever(mockIActivityManager.getUidProcessState(eq(CALL_UID), any()))
+ .thenReturn(PROC_STATE_VISIBLE)
+ setCallNotifOnRepo()
+ reset(mockOngoingCallListener)
+
+ val captor = argumentCaptor<IUidObserver.Stub>()
+ verify(mockIActivityManager).registerUidObserver(captor.capture(), any(), any(), any())
+ val uidObserver = captor.firstValue
+
+ // Update the process to invisible.
+ uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_INVISIBLE, 0, 0)
+ mainExecutor.advanceClockToLast()
+ mainExecutor.runAllReady()
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(any())
+ }
+
+ /** Regression test for b/212467440. */
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun chipClicked_activityStarterTriggeredWithUnmodifiedIntent() {
+ val pendingIntent = mock<PendingIntent>()
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "notif",
+ uid = CALL_UID,
+ contentIntent = pendingIntent,
+ callType = CallType.Ongoing,
+ )
+ )
+
+ chipView.performClick()
+
+ // Ensure that the sysui didn't modify the notification's intent -- see b/212467440.
+ verify(mockActivityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun callNotificationAdded_chipIsClickable() {
+ setCallNotifOnRepo()
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun callNotificationAdded_newChipsEnabled_chipNotClickable() {
+ setCallNotifOnRepo()
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun fullscreenIsTrue_chipStillClickable() {
+ setCallNotifOnRepo()
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
+ testScope.runCurrent()
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
+ testScope.runCurrent()
+
+ setCallNotifOnRepo()
+
+ verify(mockSwipeStatusBarAwayGestureHandler).addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
+ testScope.runCurrent()
+
+ setCallNotifOnRepo()
+
+ verify(mockSwipeStatusBarAwayGestureHandler, never())
+ .addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
+ setCallNotifOnRepo()
+
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
+ testScope.runCurrent()
+
+ verify(mockSwipeStatusBarAwayGestureHandler).addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
+ testScope.runCurrent()
+
+ setCallNotifOnRepo()
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
+ testScope.runCurrent()
+
+ verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(any())
+ }
+
+ @Test
+ fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
+ testScope.runCurrent()
+ setCallNotifOnRepo()
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ setNoNotifsOnRepo()
+
+ verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(any())
+ }
+
+ private fun setCallNotifOnRepo() {
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "ongoingNotif",
+ callType = CallType.Ongoing,
+ uid = CALL_UID,
+ contentIntent = mock<PendingIntent>(),
+ )
+ )
+ }
+
+ private fun setNotifOnRepo(notif: ActiveNotificationModel) {
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder().apply { addIndividualNotif(notif) }.build()
+ testScope.runCurrent()
+ }
+
+ private fun setNoNotifsOnRepo() {
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder().build()
+ testScope.runCurrent()
+ }
+}
+
+private const val CALL_UID = 900
+
+// A process state that represents the process being visible to the user.
+private const val PROC_STATE_VISIBLE = ActivityManager.PROCESS_STATE_TOP
+
+// A process state that represents the process being invisible to the user.
+private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
index 8875b84..9064f7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
@@ -17,20 +17,13 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
-import android.view.MotionEvent.ACTION_DOWN
-import android.view.MotionEvent.ACTION_MOVE
-import android.view.MotionEvent.ACTION_POINTER_DOWN
-import android.view.MotionEvent.ACTION_POINTER_UP
-import android.view.MotionEvent.ACTION_UP
-import android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT
-import android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
-import android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,135 +39,50 @@
gestureStateChangedCallback = { gestureState = it }
)
- companion object {
- const val SWIPE_DISTANCE = 100f
- }
-
@Test
fun triggersGestureFinishedForThreeFingerGestureRight() {
- val events =
- listOf(
- threeFingerEvent(ACTION_DOWN, x = 0f, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = 0f, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = 0f, y = 0f),
- threeFingerEvent(ACTION_MOVE, x = SWIPE_DISTANCE / 2, y = 0f),
- threeFingerEvent(ACTION_POINTER_UP, x = SWIPE_DISTANCE, y = 0f),
- threeFingerEvent(ACTION_POINTER_UP, x = SWIPE_DISTANCE, y = 0f),
- threeFingerEvent(ACTION_UP, x = SWIPE_DISTANCE, y = 0f),
- )
-
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
-
- assertThat(gestureState).isEqualTo(FINISHED)
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeRight(), expectedState = FINISHED)
}
@Test
fun triggersGestureFinishedForThreeFingerGestureLeft() {
- val events =
- listOf(
- threeFingerEvent(ACTION_DOWN, x = SWIPE_DISTANCE, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = SWIPE_DISTANCE, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = SWIPE_DISTANCE, y = 0f),
- threeFingerEvent(ACTION_MOVE, x = SWIPE_DISTANCE / 2, y = 0f),
- threeFingerEvent(ACTION_POINTER_UP, x = 0f, y = 0f),
- threeFingerEvent(ACTION_POINTER_UP, x = 0f, y = 0f),
- threeFingerEvent(ACTION_UP, x = 0f, y = 0f),
- )
-
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
-
- assertThat(gestureState).isEqualTo(FINISHED)
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeLeft(), expectedState = FINISHED)
}
@Test
fun triggersGestureProgressForThreeFingerGestureStarted() {
- val events =
- listOf(
- threeFingerEvent(ACTION_DOWN, x = SWIPE_DISTANCE, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = SWIPE_DISTANCE, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = SWIPE_DISTANCE, y = 0f),
- )
-
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
-
- assertThat(gestureState).isEqualTo(IN_PROGRESS)
- }
-
- private fun threeFingerEvent(action: Int, x: Float, y: Float): MotionEvent {
- return motionEvent(
- action = action,
- x = x,
- y = y,
- classification = CLASSIFICATION_MULTI_FINGER_SWIPE,
- axisValues = mapOf(AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f)
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
+ expectedState = IN_PROGRESS
)
}
@Test
- fun doesntTriggerGestureFinished_onThreeFingersSwipeUp() {
- val events =
- listOf(
- threeFingerEvent(ACTION_DOWN, x = 0f, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = 0f, y = 0f),
- threeFingerEvent(ACTION_POINTER_DOWN, x = 0f, y = 0f),
- threeFingerEvent(ACTION_MOVE, x = 0f, y = SWIPE_DISTANCE / 2),
- threeFingerEvent(ACTION_POINTER_UP, x = 0f, y = SWIPE_DISTANCE),
- threeFingerEvent(ACTION_POINTER_UP, x = 0f, y = SWIPE_DISTANCE),
- threeFingerEvent(ACTION_UP, x = 0f, y = SWIPE_DISTANCE),
- )
+ fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.swipeLeft(distancePx = SWIPE_DISTANCE / 2),
+ expectedState = NOT_STARTED
+ )
+ }
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
-
- assertThat(gestureState).isEqualTo(NOT_STARTED)
+ @Test
+ fun doesntTriggerGestureFinished_onThreeFingersSwipeInOtherDirections() {
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NOT_STARTED)
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeDown(), expectedState = NOT_STARTED)
}
@Test
fun doesntTriggerGestureFinished_onTwoFingersSwipe() {
- fun twoFingerEvent(action: Int, x: Float, y: Float) =
- motionEvent(
- action = action,
- x = x,
- y = y,
- classification = CLASSIFICATION_TWO_FINGER_SWIPE,
- axisValues = mapOf(AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f)
- )
- val events =
- listOf(
- twoFingerEvent(ACTION_DOWN, x = 0f, y = 0f),
- twoFingerEvent(ACTION_MOVE, x = SWIPE_DISTANCE / 2, y = 0f),
- twoFingerEvent(ACTION_UP, x = SWIPE_DISTANCE, y = 0f),
- )
-
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
-
- assertThat(gestureState).isEqualTo(NOT_STARTED)
+ assertStateAfterEvents(events = TwoFingerGesture.swipeRight(), expectedState = NOT_STARTED)
}
@Test
fun doesntTriggerGestureFinished_onFourFingersSwipe() {
- fun fourFingerEvent(action: Int, x: Float, y: Float) =
- motionEvent(
- action = action,
- x = x,
- y = y,
- classification = CLASSIFICATION_MULTI_FINGER_SWIPE,
- axisValues = mapOf(AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f)
- )
- val events =
- listOf(
- fourFingerEvent(ACTION_DOWN, x = 0f, y = 0f),
- fourFingerEvent(ACTION_POINTER_DOWN, x = 0f, y = 0f),
- fourFingerEvent(ACTION_POINTER_DOWN, x = 0f, y = 0f),
- fourFingerEvent(ACTION_POINTER_DOWN, x = 0f, y = 0f),
- fourFingerEvent(ACTION_MOVE, x = SWIPE_DISTANCE / 2, y = 0f),
- fourFingerEvent(ACTION_POINTER_UP, x = SWIPE_DISTANCE, y = 0f),
- fourFingerEvent(ACTION_POINTER_UP, x = SWIPE_DISTANCE, y = 0f),
- fourFingerEvent(ACTION_POINTER_UP, x = SWIPE_DISTANCE, y = 0f),
- fourFingerEvent(ACTION_UP, x = SWIPE_DISTANCE, y = 0f),
- )
+ assertStateAfterEvents(events = FourFingerGesture.swipeRight(), expectedState = NOT_STARTED)
+ }
+ private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
events.forEach { gestureMonitor.processTouchpadEvent(it) }
-
- assertThat(gestureState).isEqualTo(NOT_STARTED)
+ assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
new file mode 100644
index 0000000..6aefbe9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeGestureMonitorTest : SysuiTestCase() {
+
+ private var gestureState = NOT_STARTED
+ private val gestureMonitor =
+ HomeGestureMonitor(
+ gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
+ gestureStateChangedCallback = { gestureState = it }
+ )
+
+ @Test
+ fun triggersGestureFinishedForThreeFingerGestureUp() {
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = FINISHED)
+ }
+
+ @Test
+ fun triggersGestureProgressForThreeFingerGestureStarted() {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
+ expectedState = IN_PROGRESS
+ )
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.swipeUp(distancePx = SWIPE_DISTANCE / 2),
+ expectedState = NOT_STARTED
+ )
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onThreeFingersSwipeInOtherDirections() {
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeDown(), expectedState = NOT_STARTED)
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeLeft(), expectedState = NOT_STARTED)
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.swipeRight(),
+ expectedState = NOT_STARTED
+ )
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onTwoFingersSwipe() {
+ assertStateAfterEvents(events = TwoFingerGesture.swipeUp(), expectedState = NOT_STARTED)
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onFourFingersSwipe() {
+ assertStateAfterEvents(events = FourFingerGesture.swipeUp(), expectedState = NOT_STARTED)
+ }
+
+ private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
+ events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ assertThat(gestureState).isEqualTo(expectedState)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
new file mode 100644
index 0000000..296d4dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_POINTER_DOWN
+import android.view.MotionEvent.ACTION_POINTER_UP
+import android.view.MotionEvent.ACTION_UP
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_X
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_Y
+
+/**
+ * Interface for gesture builders which support creating list of [MotionEvent] for common swipe
+ * gestures. For simple usage see swipe* methods or use [createEvents] for more specific scenarios.
+ */
+interface MultiFingerGesture {
+
+ companion object {
+ const val SWIPE_DISTANCE = 100f
+ const val DEFAULT_X = 500f
+ const val DEFAULT_Y = 500f
+ }
+
+ fun swipeUp(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = -distancePx) }
+
+ fun swipeDown(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = distancePx) }
+
+ fun swipeRight(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = distancePx) }
+
+ fun swipeLeft(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = -distancePx) }
+
+ /**
+ * Creates gesture with provided move events. Note that move event's x and y is always relative
+ * to the starting one
+ */
+ fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent>
+}
+
+object ThreeFingerGesture : MultiFingerGesture {
+ override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
+ return touchpadGesture(
+ startEvents = { x, y -> startEvents(x, y) },
+ moveEvents = GestureBuilder(::threeFingerEvent).apply { moveEvents() }.events,
+ endEvents = { x, y -> endEvents(x, y) }
+ )
+ }
+
+ fun startEvents(x: Float, y: Float): List<MotionEvent> {
+ return listOf(
+ threeFingerEvent(ACTION_DOWN, x, y),
+ threeFingerEvent(ACTION_POINTER_DOWN, x, y),
+ threeFingerEvent(ACTION_POINTER_DOWN, x, y)
+ )
+ }
+
+ private fun endEvents(x: Float, y: Float): List<MotionEvent> {
+ return listOf(
+ threeFingerEvent(ACTION_POINTER_UP, x, y),
+ threeFingerEvent(ACTION_POINTER_UP, x, y),
+ threeFingerEvent(ACTION_UP, x, y)
+ )
+ }
+
+ private fun threeFingerEvent(
+ action: Int,
+ x: Float = DEFAULT_X,
+ y: Float = DEFAULT_Y
+ ): MotionEvent {
+ return touchpadEvent(
+ action = action,
+ x = x,
+ y = y,
+ classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE,
+ axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f)
+ )
+ }
+}
+
+object FourFingerGesture : MultiFingerGesture {
+
+ override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
+ return touchpadGesture(
+ startEvents = { x, y -> startEvents(x, y) },
+ moveEvents = GestureBuilder(::fourFingerEvent).apply { moveEvents() }.events,
+ endEvents = { x, y -> endEvents(x, y) }
+ )
+ }
+
+ private fun startEvents(x: Float, y: Float): List<MotionEvent> {
+ return listOf(
+ fourFingerEvent(ACTION_DOWN, x, y),
+ fourFingerEvent(ACTION_POINTER_DOWN, x, y),
+ fourFingerEvent(ACTION_POINTER_DOWN, x, y),
+ fourFingerEvent(ACTION_POINTER_DOWN, x, y)
+ )
+ }
+
+ private fun endEvents(x: Float, y: Float): List<MotionEvent> {
+ return listOf(
+ fourFingerEvent(ACTION_POINTER_UP, x, y),
+ fourFingerEvent(ACTION_POINTER_UP, x, y),
+ fourFingerEvent(ACTION_POINTER_UP, x, y),
+ fourFingerEvent(ACTION_UP, x, y)
+ )
+ }
+
+ private fun fourFingerEvent(
+ action: Int,
+ x: Float = DEFAULT_X,
+ y: Float = DEFAULT_Y
+ ): MotionEvent {
+ return touchpadEvent(
+ action = action,
+ x = x,
+ y = y,
+ classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE,
+ axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f)
+ )
+ }
+}
+
+object TwoFingerGesture : MultiFingerGesture {
+
+ override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
+ return touchpadGesture(
+ startEvents = { x, y -> listOf(twoFingerEvent(ACTION_DOWN, x, y)) },
+ moveEvents = GestureBuilder(::twoFingerEvent).apply { moveEvents() }.events,
+ endEvents = { x, y -> listOf(twoFingerEvent(ACTION_UP, x, y)) }
+ )
+ }
+
+ private fun twoFingerEvent(
+ action: Int,
+ x: Float = DEFAULT_X,
+ y: Float = DEFAULT_Y
+ ): MotionEvent {
+ return touchpadEvent(
+ action = action,
+ x = x,
+ y = y,
+ classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE,
+ axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f)
+ )
+ }
+}
+
+private fun touchpadGesture(
+ startEvents: (Float, Float) -> List<MotionEvent>,
+ moveEvents: List<MotionEvent>,
+ endEvents: (Float, Float) -> List<MotionEvent>
+): List<MotionEvent> {
+ val lastX = moveEvents.last().x
+ val lastY = moveEvents.last().y
+ return startEvents(DEFAULT_X, DEFAULT_Y) + moveEvents + endEvents(lastX, lastY)
+}
+
+class GestureBuilder internal constructor(val eventBuilder: (Int, Float, Float) -> MotionEvent) {
+
+ val events = mutableListOf<MotionEvent>()
+
+ fun move(deltaX: Float = 0f, deltaY: Float = 0f) {
+ events.add(eventBuilder(ACTION_MOVE, DEFAULT_X + deltaX, DEFAULT_Y + deltaY))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
new file mode 100644
index 0000000..13ebb42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_POINTER_DOWN
+import android.view.MotionEvent.ACTION_POINTER_UP
+import android.view.MotionEvent.ACTION_UP
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_X
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_Y
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TouchpadGestureBuilderTest : SysuiTestCase() {
+
+ @Test
+ fun threeFingerSwipeProducesCorrectEvents() {
+ val events = ThreeFingerGesture.swipeRight()
+
+ events.forEach {
+ assertWithMessage("Event has three finger event parameters")
+ .that(isThreeFingerTouchpadSwipe(it))
+ .isTrue()
+ }
+ val actions = events.map { it.actionMasked }
+ assertWithMessage("Events have expected action type")
+ .that(actions)
+ .containsExactly(
+ ACTION_DOWN,
+ ACTION_POINTER_DOWN,
+ ACTION_POINTER_DOWN,
+ ACTION_MOVE,
+ ACTION_POINTER_UP,
+ ACTION_POINTER_UP,
+ ACTION_UP
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun fourFingerSwipeProducesCorrectEvents() {
+ val events = FourFingerGesture.swipeUp()
+
+ events.forEach {
+ assertWithMessage("Event has four finger event parameters")
+ .that(isFourFingerTouchpadSwipe(it))
+ .isTrue()
+ }
+ val actions = events.map { it.actionMasked }
+ assertWithMessage("Events have expected action type")
+ .that(actions)
+ .containsExactly(
+ ACTION_DOWN,
+ ACTION_POINTER_DOWN,
+ ACTION_POINTER_DOWN,
+ ACTION_POINTER_DOWN,
+ ACTION_MOVE,
+ ACTION_POINTER_UP,
+ ACTION_POINTER_UP,
+ ACTION_POINTER_UP,
+ ACTION_UP
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun twoFingerSwipeProducesCorrectEvents() {
+ val events = TwoFingerGesture.swipeLeft()
+
+ events.forEach {
+ assertWithMessage("Event has two finger event parameters")
+ .that(isTwoFingerGesture(it))
+ .isTrue()
+ }
+ val actions = events.map { it.actionMasked }
+ assertWithMessage("Events have expected action type")
+ .that(actions)
+ .containsExactlyElementsIn(listOf(ACTION_DOWN, ACTION_MOVE, ACTION_UP))
+ .inOrder()
+ }
+
+ private fun isTwoFingerGesture(event: MotionEvent): Boolean {
+ return event.classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE &&
+ event.getAxisValue(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT) == 2f
+ }
+
+ @Test
+ fun gestureBuilderProducesCorrectEventCoordinates() {
+ val events =
+ ThreeFingerGesture.createEvents {
+ move(deltaX = 50f)
+ move(deltaX = 100f)
+ }
+ val positions = events.map { it.x to it.y }
+ assertWithMessage("Events have expected coordinates")
+ .that(positions)
+ .containsExactly(
+ // down events
+ DEFAULT_X to DEFAULT_Y,
+ DEFAULT_X to DEFAULT_Y,
+ DEFAULT_X to DEFAULT_Y,
+ // move events
+ DEFAULT_X + 50f to DEFAULT_Y,
+ DEFAULT_X + 100f to DEFAULT_Y,
+ // up events
+ DEFAULT_X + 100f to DEFAULT_Y,
+ DEFAULT_X + 100f to DEFAULT_Y,
+ DEFAULT_X + 100f to DEFAULT_Y
+ )
+ .inOrder()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index dc4d5f6..3a01d4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -20,13 +20,6 @@
import android.view.InputDevice.SOURCE_TOUCHSCREEN
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
-import android.view.MotionEvent.ACTION_HOVER_ENTER
-import android.view.MotionEvent.ACTION_MOVE
-import android.view.MotionEvent.ACTION_POINTER_DOWN
-import android.view.MotionEvent.ACTION_POINTER_UP
-import android.view.MotionEvent.ACTION_UP
-import android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT
-import android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
import android.view.MotionEvent.TOOL_TYPE_FINGER
import android.view.MotionEvent.TOOL_TYPE_MOUSE
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -34,7 +27,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,11 +37,13 @@
class TouchpadGestureHandlerTest : SysuiTestCase() {
private var gestureState = NOT_STARTED
- private val handler = TouchpadGestureHandler(BACK, SWIPE_DISTANCE) { gestureState = it }
-
- companion object {
- const val SWIPE_DISTANCE = 100
- }
+ private val handler =
+ TouchpadGestureHandler(
+ BackGestureMonitor(
+ gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
+ gestureStateChangedCallback = { gestureState = it }
+ )
+ )
@Test
fun handlesEventsFromTouchpad() {
@@ -90,37 +85,10 @@
}
private fun backGestureEvents(): List<MotionEvent> {
- // list of motion events read from device while doing back gesture
- val y = 100f
- return listOf(
- touchpadEvent(ACTION_HOVER_ENTER, x = 759f, y = y, pointerCount = 1),
- threeFingerTouchpadEvent(ACTION_DOWN, x = 759f, y = y, pointerCount = 1),
- threeFingerTouchpadEvent(ACTION_POINTER_DOWN, x = 759f, y = y, pointerCount = 2),
- threeFingerTouchpadEvent(ACTION_POINTER_DOWN, x = 759f, y = y, pointerCount = 3),
- threeFingerTouchpadEvent(ACTION_MOVE, x = 767f, y = y, pointerCount = 3),
- threeFingerTouchpadEvent(ACTION_MOVE, x = 785f, y = y, pointerCount = 3),
- threeFingerTouchpadEvent(ACTION_MOVE, x = 814f, y = y, pointerCount = 3),
- threeFingerTouchpadEvent(ACTION_MOVE, x = 848f, y = y, pointerCount = 3),
- threeFingerTouchpadEvent(ACTION_MOVE, x = 943f, y = y, pointerCount = 3),
- threeFingerTouchpadEvent(ACTION_POINTER_UP, x = 943f, y = y, pointerCount = 3),
- threeFingerTouchpadEvent(ACTION_POINTER_UP, x = 943f, y = y, pointerCount = 2),
- threeFingerTouchpadEvent(ACTION_UP, x = 943f, y = y, pointerCount = 1)
- )
- }
-
- private fun threeFingerTouchpadEvent(
- action: Int,
- x: Float,
- y: Float,
- pointerCount: Int
- ): MotionEvent {
- return touchpadEvent(
- action = action,
- x = x,
- y = y,
- pointerCount = pointerCount,
- classification = CLASSIFICATION_MULTI_FINGER_SWIPE,
- axisValues = mapOf(AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f)
- )
+ return ThreeFingerGesture.createEvents {
+ move(deltaX = SWIPE_DISTANCE / 4)
+ move(deltaX = SWIPE_DISTANCE / 2)
+ move(deltaX = SWIPE_DISTANCE)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index ead9939..eaeece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.util.settings
import android.content.ContentResolver
-import android.content.pm.UserInfo
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
@@ -28,8 +27,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.settings.UserTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,29 +49,21 @@
@TestableLooper.RunWithLooper
class UserSettingsProxyTest : SysuiTestCase() {
- private var mUserTracker = FakeUserTracker()
+ private var userId = MAIN_USER_ID
private val testDispatcher = StandardTestDispatcher()
- private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker, testDispatcher)
+ private var mSettings: UserSettingsProxy = FakeUserSettingsProxy({ userId }, testDispatcher)
private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {}
private lateinit var testScope: TestScope
@Before
fun setUp() {
- mUserTracker.set(
- listOf(UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)),
- selectedUserIndex = 0
- )
testScope = TestScope(testDispatcher)
}
@Test
fun registerContentObserverForUser_inputString_success() =
testScope.runTest {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
+ mSettings.registerContentObserverForUserSync(TEST_SETTING, mContentObserver, userId)
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -87,11 +76,7 @@
@Test
fun registerContentObserverForUserSuspend_inputString_success() =
testScope.runTest {
- mSettings.registerContentObserverForUser(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
+ mSettings.registerContentObserverForUser(TEST_SETTING, mContentObserver, userId)
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -104,11 +89,7 @@
@Test
fun registerContentObserverForUserAsync_inputString_success() =
testScope.runTest {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
+ mSettings.registerContentObserverForUserAsync(TEST_SETTING, mContentObserver, userId)
testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
@@ -126,7 +107,7 @@
TEST_SETTING,
notifyForDescendants = true,
mContentObserver,
- mUserTracker.userId
+ userId
)
verify(mSettings.getContentResolver())
.registerContentObserver(
@@ -144,7 +125,7 @@
TEST_SETTING,
notifyForDescendants = true,
mContentObserver,
- mUserTracker.userId
+ userId
)
verify(mSettings.getContentResolver())
.registerContentObserver(
@@ -164,7 +145,7 @@
TEST_SETTING,
notifyForDescendants = true,
mContentObserver,
- mUserTracker.userId
+ userId
)
testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
@@ -179,11 +160,7 @@
@Test
fun registerContentObserverForUser_inputUri_success() =
testScope.runTest {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
+ mSettings.registerContentObserverForUserSync(TEST_SETTING_URI, mContentObserver, userId)
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -196,11 +173,7 @@
@Test
fun registerContentObserverForUserSuspend_inputUri_success() =
testScope.runTest {
- mSettings.registerContentObserverForUser(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
+ mSettings.registerContentObserverForUser(TEST_SETTING_URI, mContentObserver, userId)
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -216,7 +189,7 @@
mSettings.registerContentObserverForUserAsync(
TEST_SETTING_URI,
mContentObserver,
- mUserTracker.userId
+ userId
)
testScope.advanceUntilIdle()
@@ -239,7 +212,7 @@
mSettings.registerContentObserverForUserAsync(
TEST_SETTING_URI,
mContentObserver,
- mUserTracker.userId,
+ userId,
runnable
)
testScope.advanceUntilIdle()
@@ -253,7 +226,7 @@
TEST_SETTING_URI,
notifyForDescendants = true,
mContentObserver,
- mUserTracker.userId
+ userId
)
verify(mSettings.getContentResolver())
.registerContentObserver(
@@ -271,14 +244,12 @@
TEST_SETTING_URI,
notifyForDescendants = true,
mContentObserver,
- mUserTracker.userId
+ userId
)
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
- eq(
- true,
- ),
+ eq(true),
eq(mContentObserver),
eq(MAIN_USER_ID)
)
@@ -291,7 +262,7 @@
TEST_SETTING_URI,
notifyForDescendants = true,
mContentObserver,
- mUserTracker.userId
+ userId
)
testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
@@ -585,7 +556,7 @@
* This class uses a mock of [ContentResolver] to test the [ContentObserver] registration APIs.
*/
private class FakeUserSettingsProxy(
- override val userTracker: UserTracker,
+ override val currentUserProvider: SettingsProxy.CurrentUserIdProvider,
val testDispatcher: CoroutineDispatcher
) : UserSettingsProxy {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7f964d1..e5e04dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -349,10 +349,10 @@
private Display mDefaultDisplay;
@Mock
private Lazy<ViewCapture> mLazyViewCapture;
- @Mock private NotificationShadeWindowModel mNotificationShadeWindowModel;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private ShadeInteractor mShadeInteractor;
+ private NotificationShadeWindowModel mNotificationShadeWindowModel;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -411,6 +411,7 @@
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
mShadeInteractor = mKosmos.getShadeInteractor();
+ mNotificationShadeWindowModel = mKosmos.getNotificationShadeWindowModel();
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
index 9236bd2..63323b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -19,9 +19,10 @@
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -42,7 +43,7 @@
biometricSettingsRepository = biometricSettingsRepository,
systemClock = systemClock,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- deviceEntryFingerprintAuthInteractor = { deviceEntryFingerprintAuthInteractor },
+ deviceEntryBiometricsAllowedInteractor = { deviceEntryBiometricsAllowedInteractor },
keyguardInteractor = { keyguardInteractor },
keyguardTransitionInteractor = { keyguardTransitionInteractor },
scope = testScope.backgroundScope,
@@ -55,6 +56,7 @@
this.keyguardBouncerRepository.setPrimaryShow(false)
this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
this.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ this.deviceEntryFaceAuthRepository.setLockedOut(false)
whenever(this.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
whenever(this.keyguardStateController.isUnlocked).thenReturn(false)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index de5f0f3..e70631e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -22,8 +22,8 @@
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.bouncer.shared.flag.composeBouncerFlags
import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -45,7 +45,7 @@
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
- fingerprintInteractor = deviceEntryFingerprintAuthInteractor,
+ deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
flags = composeBouncerFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorKosmos.kt
new file mode 100644
index 0000000..4357289
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.biometrics.data.repository.facePropertyRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryBiometricsAllowedInteractor by
+ Kosmos.Fixture {
+ DeviceEntryBiometricsAllowedInteractor(
+ deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ facePropertyRepository = facePropertyRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index be8048e..9b7bca6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -27,8 +27,8 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -87,7 +87,7 @@
FakeBiometricSettingsRepository(),
FakeSystemClock(),
keyguardUpdateMonitor,
- { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ { mock(DeviceEntryBiometricsAllowedInteractor::class.java) },
{ mock(KeyguardInteractor::class.java) },
{ mock(KeyguardTransitionInteractor::class.java) },
{ mock(SceneInteractor::class.java) },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 8614fc9..e6bd24b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -59,6 +59,7 @@
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeController
+import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -140,6 +141,7 @@
val shadeController by lazy { kosmos.shadeController }
val shadeRepository by lazy { kosmos.shadeRepository }
val shadeInteractor by lazy { kosmos.shadeInteractor }
+ val notificationShadeWindowModel by lazy { kosmos.notificationShadeWindowModel }
val wifiInteractor by lazy { kosmos.wifiInteractor }
val fakeWifiRepository by lazy { kosmos.fakeWifiRepository }
val volumeDialogInteractor by lazy { kosmos.volumeDialogInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 9c5c486..37f1f13 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -16,14 +16,17 @@
package com.android.systemui.statusbar.notification.data.model
+import android.app.PendingIntent
import android.graphics.drawable.Icon
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
/** Simple ActiveNotificationModel builder for use in tests. */
fun activeNotificationModel(
key: String,
groupKey: String? = null,
+ whenTime: Long = 0L,
isAmbient: Boolean = false,
isRowDismissed: Boolean = false,
isSilent: Boolean = false,
@@ -37,11 +40,14 @@
instanceId: Int? = null,
isGroupSummary: Boolean = false,
packageName: String = "pkg",
+ contentIntent: PendingIntent? = null,
bucket: Int = BUCKET_UNKNOWN,
+ callType: CallType = CallType.None,
) =
ActiveNotificationModel(
key = key,
groupKey = groupKey,
+ whenTime = whenTime,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -53,7 +59,9 @@
statusBarIcon = statusBarIcon,
uid = uid,
packageName = packageName,
+ contentIntent = contentIntent,
instanceId = instanceId,
isGroupSummary = isGroupSummary,
bucket = bucket,
+ callType = callType,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index 3f0318b..d117466 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -28,9 +28,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.settings.FakeUserTracker;
-import com.android.systemui.settings.UserTracker;
-
import kotlinx.coroutines.CoroutineDispatcher;
import java.util.ArrayList;
@@ -44,12 +41,13 @@
new HashMap<>();
private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
private final CoroutineDispatcher mDispatcher;
- private final UserTracker mUserTracker;
public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
@UserIdInt
private int mUserId = UserHandle.USER_CURRENT;
+ private final CurrentUserIdProvider mCurrentUserProvider;
+
/**
* @deprecated Please use FakeSettings(testDispatcher) to provide the same dispatcher used
* by main test scope.
@@ -57,17 +55,17 @@
@Deprecated
public FakeSettings() {
mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
- mUserTracker = new FakeUserTracker();
+ mCurrentUserProvider = () -> mUserId;
}
public FakeSettings(CoroutineDispatcher dispatcher) {
mDispatcher = dispatcher;
- mUserTracker = new FakeUserTracker();
+ mCurrentUserProvider = () -> mUserId;
}
- public FakeSettings(CoroutineDispatcher dispatcher, UserTracker userTracker) {
+ public FakeSettings(CoroutineDispatcher dispatcher, CurrentUserIdProvider currentUserProvider) {
mDispatcher = dispatcher;
- mUserTracker = userTracker;
+ mCurrentUserProvider = currentUserProvider;
}
@VisibleForTesting
@@ -93,8 +91,8 @@
@NonNull
@Override
- public UserTracker getUserTracker() {
- return mUserTracker;
+ public CurrentUserIdProvider getCurrentUserProvider() {
+ return mCurrentUserProvider;
}
@NonNull
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
index 55044bf..76ef202 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
@@ -19,6 +19,8 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
-val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings(testDispatcher, fakeUserTracker) }
+val Kosmos.fakeSettings: FakeSettings by Fixture {
+ FakeSettings(testDispatcher) { userTracker.userId }
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index cbbce1a..8663593 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -120,6 +120,14 @@
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
+ static final String DEVICE_CONFIG_WIDTH = "device_config_width";
+
+ static final String DEVICE_CONFIG_HEIGHT = "device_config_height";
+
+ static final String DEVICE_CONFIG_SECONDARY_WIDTH = "device_config_secondary_width";
+
+ static final String DEVICE_CONFIG_SECONDARY_HEIGHT = "device_config_secondary_height";
+
static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f;
// If this file exists, it means we exceeded our quota last time
@@ -175,6 +183,16 @@
// disk churn.
final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1);
final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1);
+
+ final int deviceConfigWidth = sharedPrefs.getInt(
+ DEVICE_CONFIG_WIDTH, /* defValue= */ -1);
+ final int deviceConfigHeight = sharedPrefs.getInt(
+ DEVICE_CONFIG_HEIGHT, /* defValue= */ -1);
+ final int deviceConfigSecondaryWidth = sharedPrefs.getInt(
+ DEVICE_CONFIG_SECONDARY_WIDTH, /* defValue= */ -1);
+ final int deviceConfigSecondaryHeight = sharedPrefs.getInt(
+ DEVICE_CONFIG_SECONDARY_HEIGHT, /* defValue= */ -1);
+
final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM);
final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK);
final boolean sysChanged = (sysGeneration != lastSysGeneration);
@@ -195,7 +213,11 @@
backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
- backupDeviceInfoFile(data);
+
+ final boolean isDeviceConfigChanged = isDeviceConfigChanged(deviceConfigWidth,
+ deviceConfigHeight, deviceConfigSecondaryWidth, deviceConfigSecondaryHeight);
+
+ backupDeviceInfoFile(sharedPrefs, isDeviceConfigChanged, data);
} catch (Exception e) {
Slog.e(TAG, "Unable to back up wallpaper", e);
mEventLogger.onBackupException(e);
@@ -209,50 +231,72 @@
}
}
+ private boolean isDeviceConfigChanged(int width, int height, int secondaryWidth,
+ int secondaryHeight) {
+ Point currentDimensions = getScreenDimensions();
+ Display smallerDisplay = getSmallerDisplayIfExists();
+ Point currentSecondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+ new Point(0, 0);
+
+ return (currentDimensions.x != width
+ || currentDimensions.y != height
+ || currentSecondaryDimensions.x != secondaryWidth
+ || currentSecondaryDimensions.y != secondaryHeight);
+ }
+
/**
* This method backs up the device dimension information. The device data will always get
* overwritten when triggering a backup
*/
- private void backupDeviceInfoFile(FullBackupDataOutput data)
+ private void backupDeviceInfoFile(SharedPreferences sharedPrefs, boolean isDeviceConfigChanged,
+ FullBackupDataOutput data)
throws IOException {
final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
- // save the dimensions of the device with xml formatting
- Point dimensions = getScreenDimensions();
- Display smallerDisplay = getSmallerDisplayIfExists();
- Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
- new Point(0, 0);
+ if (isDeviceConfigChanged) {
+ // save the dimensions of the device with xml formatting
+ Point dimensions = getScreenDimensions();
+ Display smallerDisplay = getSmallerDisplayIfExists();
+ Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+ new Point(0, 0);
- deviceInfoStage.createNewFile();
- FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
- TypedXmlSerializer out = Xml.resolveSerializer(fstream);
- out.startDocument(null, true);
- out.startTag(null, "dimensions");
+ deviceInfoStage.createNewFile();
+ FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+ out.startTag(null, "dimensions");
- out.startTag(null, "width");
- out.text(String.valueOf(dimensions.x));
- out.endTag(null, "width");
+ out.startTag(null, "width");
+ out.text(String.valueOf(dimensions.x));
+ out.endTag(null, "width");
- out.startTag(null, "height");
- out.text(String.valueOf(dimensions.y));
- out.endTag(null, "height");
+ out.startTag(null, "height");
+ out.text(String.valueOf(dimensions.y));
+ out.endTag(null, "height");
- if (smallerDisplay != null) {
- out.startTag(null, "secondarywidth");
- out.text(String.valueOf(secondaryDimensions.x));
- out.endTag(null, "secondarywidth");
+ if (smallerDisplay != null) {
+ out.startTag(null, "secondarywidth");
+ out.text(String.valueOf(secondaryDimensions.x));
+ out.endTag(null, "secondarywidth");
- out.startTag(null, "secondaryheight");
- out.text(String.valueOf(secondaryDimensions.y));
- out.endTag(null, "secondaryheight");
+ out.startTag(null, "secondaryheight");
+ out.text(String.valueOf(secondaryDimensions.y));
+ out.endTag(null, "secondaryheight");
+ }
+
+ out.endTag(null, "dimensions");
+ out.endDocument();
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+
+ SharedPreferences.Editor editor = sharedPrefs.edit();
+ editor.putInt(DEVICE_CONFIG_WIDTH, dimensions.x);
+ editor.putInt(DEVICE_CONFIG_HEIGHT, dimensions.y);
+ editor.putInt(DEVICE_CONFIG_SECONDARY_WIDTH, secondaryDimensions.x);
+ editor.putInt(DEVICE_CONFIG_SECONDARY_HEIGHT, secondaryDimensions.y);
+ editor.apply();
}
-
- out.endTag(null, "dimensions");
- out.endDocument();
- fstream.flush();
- FileUtils.sync(fstream);
- fstream.close();
-
if (DEBUG) Slog.v(TAG, "Storing device dimension data");
backupFile(deviceInfoStage, data);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 988a213..84b5c39 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -28,7 +28,6 @@
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
-import android.companion.virtual.flags.Flags;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.AttributionSource;
@@ -108,12 +107,11 @@
private boolean mActivityLaunchAllowedByDefault;
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
- private final Set<ComponentName> mActivityPolicyExemptions;
+ private final ArraySet<ComponentName> mActivityPolicyExemptions;
private final boolean mCrossTaskNavigationAllowedByDefault;
@NonNull
private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
@Nullable
- private final ComponentName mPermissionDialogComponent;
private final Object mGenericWindowPolicyControllerLock = new Object();
@Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
@@ -178,7 +176,6 @@
@NonNull Set<ComponentName> activityPolicyExemptions,
boolean crossTaskNavigationAllowedByDefault,
@NonNull Set<ComponentName> crossTaskNavigationExemptions,
- @Nullable ComponentName permissionDialogComponent,
@Nullable ActivityListener activityListener,
@Nullable ActivityBlockedCallback activityBlockedCallback,
@Nullable SecureWindowCallback secureWindowCallback,
@@ -190,10 +187,9 @@
mAttributionSource = attributionSource;
mAllowedUsers = allowedUsers;
mActivityLaunchAllowedByDefault = activityLaunchAllowedByDefault;
- mActivityPolicyExemptions = activityPolicyExemptions;
+ mActivityPolicyExemptions = new ArraySet<>(activityPolicyExemptions);
mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
- mPermissionDialogComponent = permissionDialogComponent;
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
@@ -287,28 +283,15 @@
public boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
@Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
int launchingFromDisplayId, boolean isNewTask) {
- if (Flags.interceptIntentsBeforeApplyingPolicy()) {
- if (mIntentListenerCallback != null && intent != null
- && mIntentListenerCallback.shouldInterceptIntent(intent)) {
- logActivityLaunchBlocked("Virtual device intercepting intent");
- return false;
- }
- if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
- isNewTask)) {
- notifyActivityBlocked(activityInfo);
- return false;
- }
- } else {
- if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
- isNewTask)) {
- notifyActivityBlocked(activityInfo);
- return false;
- }
- if (mIntentListenerCallback != null && intent != null
- && mIntentListenerCallback.shouldInterceptIntent(intent)) {
- logActivityLaunchBlocked("Virtual device intercepting intent");
- return false;
- }
+ if (mIntentListenerCallback != null && intent != null
+ && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+ logActivityLaunchBlocked("Virtual device intercepting intent");
+ return false;
+ }
+ if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
+ isNewTask)) {
+ notifyActivityBlocked(activityInfo);
+ return false;
}
return true;
}
@@ -370,14 +353,6 @@
return false;
}
- // mPermissionDialogComponent being null means we don't want to block permission Dialogs
- // based on FLAG_STREAM_PERMISSIONS
- if (mPermissionDialogComponent != null
- && mPermissionDialogComponent.equals(activityComponent)) {
- logActivityLaunchBlocked("Permission dialog not allowed on virtual device");
- return false;
- }
-
return true;
}
@@ -487,11 +462,9 @@
&& displayId != INVALID_DISPLAY) {
mActivityBlockedCallback.onActivityBlocked(displayId, activityInfo);
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_activity_blocked_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_activity_blocked_count",
+ mAttributionSource.getUid());
}
private static boolean isAllowedByPolicy(boolean allowedByDefault,
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index d091ce8..8da58cf 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -842,11 +842,9 @@
deviceName, inputDeviceId));
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- String metricId = getMetricIdForInputType(type);
- if (metricId != null) {
- Counter.logIncrementWithUid(metricId, mAttributionSource.getUid());
- }
+ String metricId = getMetricIdForInputType(type);
+ if (metricId != null) {
+ Counter.logIncrementWithUid(metricId, mAttributionSource.getUid());
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index 0655685..8d075db 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -145,11 +145,9 @@
mSensorDescriptors.put(sensorToken, sensorDescriptor);
mVirtualSensors.put(handle, sensor);
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_sensors_created_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_sensors_created_count",
+ mAttributionSource.getUid());
}
boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ee7d0ae..ed2c90d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,8 +29,6 @@
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
-import static android.companion.virtualdevice.flags.Flags.intentInterceptionActionMatchingFix;
-import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -208,7 +206,6 @@
@GuardedBy("mVirtualDeviceLock")
@NonNull
private final Set<ComponentName> mActivityPolicyExemptions;
- private final ComponentName mPermissionDialogComponent;
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@@ -343,11 +340,6 @@
if (mCameraAccessController != null) {
mCameraAccessController.startObservingIfNeeded();
}
- if (!Flags.streamPermissions()) {
- mPermissionDialogComponent = getPermissionDialogComponent();
- } else {
- mPermissionDialogComponent = null;
- }
mVirtualCameraController = virtualCameraController;
try {
token.linkToDeath(this, 0);
@@ -558,6 +550,36 @@
}
}
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemptionForDisplay(
+ int displayId, @NonNull ComponentName componentName) {
+ super.addActivityPolicyExemptionForDisplay_enforcePermission();
+ if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ return;
+ }
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .addActivityPolicyExemption(componentName);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemptionForDisplay(
+ int displayId, @NonNull ComponentName componentName) {
+ super.removeActivityPolicyExemptionForDisplay_enforcePermission();
+ if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ return;
+ }
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .removeActivityPolicyExemption(componentName);
+ }
+ }
+
private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
throws PendingIntent.CanceledException {
final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
@@ -657,12 +679,7 @@
@Nullable IAudioConfigChangedCallback configChangedCallback) {
super.onAudioSessionStarting_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplays.contains(displayId)) {
- throw new SecurityException(
- "Cannot start audio session for a display not associated with this virtual "
- + "device");
- }
-
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
if (mVirtualAudioController == null) {
mVirtualAudioController = new VirtualAudioController(mContext, mAttributionSource);
GenericWindowPolicyController gwpc = mVirtualDisplays.get(
@@ -706,6 +723,9 @@
break;
case POLICY_TYPE_ACTIVITY:
synchronized (mVirtualDeviceLock) {
+ if (getDevicePolicy(policyType) != devicePolicy) {
+ mActivityPolicyExemptions.clear();
+ }
mDevicePolicies.put(policyType, devicePolicy);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
mVirtualDisplays.valueAt(i).getWindowPolicyController()
@@ -736,6 +756,33 @@
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setDevicePolicyForDisplay(int displayId,
+ @VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ super.setDevicePolicyForDisplay_enforcePermission();
+ if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ return;
+ }
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ switch (policyType) {
+ case POLICY_TYPE_RECENTS:
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ break;
+ case POLICY_TYPE_ACTIVITY:
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .setActivityLaunchDefaultAllowed(devicePolicy == DEVICE_POLICY_DEFAULT);
+ break;
+ default:
+ throw new IllegalArgumentException("Device policy " + policyType
+ + " cannot be changed for a specific display. ");
+ }
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
super.createVirtualDpad_enforcePermission();
Objects.requireNonNull(config);
@@ -1243,7 +1290,6 @@
/* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
- mPermissionDialogComponent,
mActivityListenerAdapter,
this::onActivityBlocked,
this::onSecureWindowShown,
@@ -1255,13 +1301,6 @@
return gwpc;
}
- private ComponentName getPermissionDialogComponent() {
- Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
- PackageManager packageManager = mContext.getPackageManager();
- intent.setPackage(packageManager.getPermissionControllerPackageName());
- return intent.resolveActivity(packageManager);
- }
-
int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback, String packageName) {
GenericWindowPolicyController gwpc;
@@ -1273,7 +1312,7 @@
this, gwpc, packageName);
gwpc.setDisplayId(displayId, /* isMirrorDisplay= */ Flags.interactiveScreenMirror()
&& mDisplayManagerInternal.getDisplayIdToMirror(displayId)
- != Display.INVALID_DISPLAY);
+ != Display.INVALID_DISPLAY);
boolean showPointer;
synchronized (mVirtualDeviceLock) {
@@ -1304,11 +1343,9 @@
Binder.restoreCallingIdentity(token);
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_display_created_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_display_created_count",
+ mAttributionSource.getUid());
return displayId;
}
@@ -1376,11 +1413,9 @@
showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
Toast.LENGTH_LONG, mContext.getMainLooper());
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_secure_window_blocked_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_secure_window_blocked_count",
+ mAttributionSource.getUid());
}
}
@@ -1441,20 +1476,24 @@
@SuppressWarnings("AndroidFrameworkRequiresPermission")
private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
+ // The INJECT_EVENTS permission allows for injecting input to any window / display.
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INJECT_EVENTS)
- == PackageManager.PERMISSION_GRANTED) {
- // The INJECT_EVENTS permission allows for injecting input to any window / display.
- return;
- }
- synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplays.contains(displayId)) {
- throw new SecurityException(
- "Cannot create a virtual input device for display " + displayId
- + " which not associated with this virtual device");
+ != PackageManager.PERMISSION_GRANTED) {
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
}
}
}
+ @GuardedBy("mVirtualDeviceLock")
+ private void checkDisplayOwnedByVirtualDeviceLocked(int displayId) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ throw new SecurityException(
+ "Invalid displayId: Display " + displayId
+ + " is not associated with this virtual device");
+ }
+ }
+
/**
* Release resources tied to virtual display owned by this VirtualDevice instance.
*
@@ -1579,8 +1618,8 @@
// Explicitly match the actions because the intent filter will match any intent
// without an explicit action. If the intent has no action, then require that there
// are no actions specified in the filter either.
- boolean explicitActionMatch = !intentInterceptionActionMatchingFix()
- || intent.getAction() != null || intentFilter.countActions() == 0;
+ boolean explicitActionMatch =
+ intent.getAction() != null || intentFilter.countActions() == 0;
if (explicitActionMatch && intentFilter.match(
intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(),
intent.getCategories(), TAG) >= 0) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 1be1d2b..3cd1ca4 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -474,9 +474,7 @@
deviceId,
cameraAccessController, mPendingTrampolineCallback, activityListener,
soundEffectListener, runningAppsChangedCallback, params);
- if (Flags.expressMetrics()) {
- Counter.logIncrement("virtual_devices.value_virtual_devices_created_count");
- }
+ Counter.logIncrement("virtual_devices.value_virtual_devices_created_count");
synchronized (mVirtualDeviceManagerLock) {
if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) {
@@ -500,11 +498,9 @@
}
});
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_devices_created_with_uid_count",
- attributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_devices_created_with_uid_count",
+ attributionSource.getUid());
return virtualDevice;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
index 4bffb76..d3b3945 100644
--- a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
+++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
@@ -77,11 +77,9 @@
mAudioPlaybackDetector = new AudioPlaybackDetector(context);
mAudioRecordingDetector = new AudioRecordingDetector(context);
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_audio_created_count",
- attributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_audio_created_count",
+ attributionSource.getUid());
}
/**
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 62efafb..7b81ef3 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -102,11 +102,9 @@
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_camera_created_count",
- attributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_camera_created_count",
+ attributionSource.getUid());
}
/**
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index ee7033e..415f78a 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -65,7 +65,6 @@
import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerService;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.wm.WindowProcessController;
@@ -1027,7 +1026,8 @@
isBackground &= (userId != profileId);
}
int visibleUserId = getVisibleUserId(userId);
- boolean isVisibleUser = isVisibleBackgroundUser(visibleUserId);
+ boolean isVisibleUser = LocalServices.getService(UserManagerInternal.class)
+ .isVisibleBackgroundFullUser(visibleUserId);
boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
if (isBackground && !showBackground && !isVisibleUser) {
@@ -1050,7 +1050,7 @@
mContext.getContentResolver(),
Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
0,
- mService.mUserController.getCurrentUserId()) != 0;
+ visibleUserId) != 0;
final String packageName = proc.info.packageName;
final boolean crashSilenced = mAppsNotReportingCrashes != null
&& mAppsNotReportingCrashes.contains(proc.info.packageName);
@@ -1183,26 +1183,6 @@
}
/**
- * Checks if the given user is a visible background user, which is a full, background user
- * assigned to secondary displays on the devices that have
- * {@link UserManager#isVisibleBackgroundUsersEnabled()
- * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
- * automotive builds, using the display associated with their seats).
- *
- * @see UserManager#isUserVisible()
- */
- private boolean isVisibleBackgroundUser(int userId) {
- if (!UserManager.isVisibleBackgroundUsersEnabled()) {
- return false;
- }
- boolean isForeground = mService.mUserController.getCurrentUserId() == userId;
- boolean isProfile = UserManagerService.getInstance().isProfile(userId);
- boolean isVisible = LocalServices.getService(UserManagerInternal.class)
- .isUserVisible(userId);
- return isVisible && !isForeground && !isProfile;
- }
-
- /**
* Information about a process that is currently marked as bad.
*/
static final class BadProcessInfo {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 03fbfd37..6333159 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -620,6 +620,10 @@
private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder,
String configString) {
+ if (configString == null) {
+ return;
+ }
+
Matcher matcher = Pattern.compile("([^:]+):(\\d+)\\s*").matcher(configString);
while (matcher.find()) {
String powerComponentName = matcher.group(1);
@@ -1256,7 +1260,14 @@
.setMinConsumedPowerThreshold(minConsumedPowerThreshold)
.build();
bus = getBatteryUsageStats(List.of(query)).get(0);
- return new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data);
+ final int pullResult =
+ new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data);
+ try {
+ bus.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failure close BatteryUsageStats", e);
+ }
+ return pullResult;
}
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index b517631..d6f04db 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -39,6 +39,7 @@
per-file ContentProviderHelper.java = varunshah@google.com, omakoto@google.com, jsharkey@google.com, yamasani@google.com
per-file CachedAppOptimizer.java = file:/PERFORMANCE_OWNERS
+per-file Freezer.java = file:/PERFORMANCE_OWNERS
# Multiuser
per-file User* = file:/MULTIUSER_OWNERS
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 8eef71e..6857b6b 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -19,9 +19,11 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SYSTEM_UID;
@@ -367,17 +369,6 @@
}
/**
- * Return true if the activity options allows PendingIntent to use caller's BAL permission.
- */
- public static boolean isPendingIntentBalAllowedByPermission(
- @Nullable ActivityOptions activityOptions) {
- if (activityOptions == null) {
- return false;
- }
- return activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission();
- }
-
- /**
* Return the {@link BackgroundStartPrivileges} the activity options grant the PendingIntent to
* use caller's BAL permission.
*/
@@ -404,6 +395,8 @@
return BackgroundStartPrivileges.NONE;
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE:
case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
default:
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 15c8850..b2e95aa 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -16,8 +16,11 @@
}
flag {
- name: "notify_fingerprint_loe"
+ name: "notify_fingerprints_loe"
namespace: "biometrics_framework"
description: "This flag controls whether a notification should be sent to notify user when loss of enrollment happens"
bug: "351036558"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 3b6aeef..77e27ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -108,7 +108,7 @@
}
if (mBiometricUtils.hasValidBiometricUserState(getContext(), getTargetUserId())
- && Flags.notifyFingerprintLoe()) {
+ && Flags.notifyFingerprintsLoe()) {
handleInvalidBiometricState();
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index ef7abdd..6cce722 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -227,6 +227,9 @@
onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
}
+ } else {
+ Slog.e(TAG, "Cancellation signal is null");
+ mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index d04afdb..dee4b4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -359,7 +359,8 @@
mCallback.onClientFinished(this, false /* success */);
}
} else {
- Slog.e(TAG, "cancellation signal was null");
+ Slog.e(TAG, "Cancellation signal was null");
+ mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 480c370..53d6768 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
@@ -802,15 +803,27 @@
@Override
public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) {
Slog.i(TAG, "New offload doze override: " + Display.stateToString(displayState));
- mHandler.postAtTime(() -> {
- if (mDisplayOffloadSession == null
- || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
- || displayState == Display.STATE_UNKNOWN)) {
- return;
+ if (mDisplayOffloadSession != null
+ && DisplayOffloadSession.isSupportedOffloadState(displayState)
+ && displayState != Display.STATE_UNKNOWN) {
+ if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
+ boolean acquired = mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE);
+ if (!acquired) {
+ Slog.i(TAG, "A request to override the doze screen state is already "
+ + "under process");
+ return;
+ }
}
- mDisplayStateController.overrideDozeScreenState(displayState, reason);
- updatePowerState();
- }, mClock.uptimeMillis());
+ mHandler.postAtTime(() -> {
+ mDisplayStateController.overrideDozeScreenState(displayState, reason);
+ updatePowerState();
+ if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
+ mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE);
+ }
+ }, mClock.uptimeMillis());
+ }
}
@Override
@@ -1337,30 +1350,6 @@
initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
}
- if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
- // Sometimes, a display-state change can come without an associated PowerRequest,
- // as with DisplayOffload. For those cases, we have to make sure to also mark the
- // display as "not ready" so that we can inform power-manager when the state-change is
- // complete.
- if (mPowerState.getScreenState() != state) {
- final boolean wasReady;
- synchronized (mLock) {
- wasReady = mDisplayReadyLocked;
- mDisplayReadyLocked = false;
- mustNotify = true;
- }
-
- if (wasReady) {
- // If we went from ready to not-ready from the state-change (instead of a
- // PowerRequest) there's a good chance that nothing is keeping PowerManager
- // from suspending. Grab the unfinished business suspend blocker to keep the
- // device awake until the display-state change goes into effect.
- mWakelockController.acquireWakelock(
- WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- }
- }
- }
-
// Animate the screen state change unless already animating.
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
@@ -1393,8 +1382,8 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController
.setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
- && mIsEnabled && (state == Display.STATE_OFF
- || (state == Display.STATE_DOZE && !allowAutoBrightnessWhileDozing))
+ && mIsEnabled && (mPowerRequest.policy == POLICY_OFF
+ || (mPowerRequest.policy == POLICY_DOZE && !allowAutoBrightnessWhileDozing))
&& mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
}
}
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
index 7bc7971..5b0229c 100644
--- a/services/core/java/com/android/server/display/WakelockController.java
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -20,6 +20,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.utils.DebugUtils;
@@ -37,7 +38,8 @@
public static final int WAKE_LOCK_PROXIMITY_NEGATIVE = 2;
public static final int WAKE_LOCK_PROXIMITY_DEBOUNCE = 3;
public static final int WAKE_LOCK_STATE_CHANGED = 4;
- public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 5;
+ public static final int WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE = 5;
+ public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 6;
@VisibleForTesting
static final int WAKE_LOCK_MAX = WAKE_LOCK_UNFINISHED_BUSINESS;
@@ -53,18 +55,23 @@
WAKE_LOCK_PROXIMITY_NEGATIVE,
WAKE_LOCK_PROXIMITY_DEBOUNCE,
WAKE_LOCK_STATE_CHANGED,
+ WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
WAKE_LOCK_UNFINISHED_BUSINESS
})
@Retention(RetentionPolicy.SOURCE)
public @interface WAKE_LOCK_TYPE {
}
+ private final Object mLock = new Object();
+
// Asynchronous callbacks into the power manager service.
// Only invoked from the handler thread while no locks are held.
private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
// Identifiers for suspend blocker acquisition requests
private final String mSuspendBlockerIdUnfinishedBusiness;
+ @GuardedBy("mLock")
+ private final String mSuspendBlockerOverrideDozeScreenState;
private final String mSuspendBlockerIdOnStateChanged;
private final String mSuspendBlockerIdProxPositive;
private final String mSuspendBlockerIdProxNegative;
@@ -73,6 +80,10 @@
// True if we have unfinished business and are holding a suspend-blocker.
private boolean mUnfinishedBusiness;
+ // True if we have are holding a suspend-blocker to override the doze screen state.
+ @GuardedBy("mLock")
+ private boolean mIsOverrideDozeScreenStateAcquired;
+
// True if we have have debounced the proximity change impact and are holding a suspend-blocker.
private boolean mHasProximityDebounced;
@@ -108,6 +119,7 @@
mTag = TAG + "[" + mDisplayId + "]";
mDisplayPowerCallbacks = callbacks;
mSuspendBlockerIdUnfinishedBusiness = "[" + displayId + "]unfinished business";
+ mSuspendBlockerOverrideDozeScreenState = "[" + displayId + "]override doze screen state";
mSuspendBlockerIdOnStateChanged = "[" + displayId + "]on state changed";
mSuspendBlockerIdProxPositive = "[" + displayId + "]prox positive";
mSuspendBlockerIdProxNegative = "[" + displayId + "]prox negative";
@@ -154,6 +166,10 @@
return acquireProxDebounceSuspendBlocker();
case WAKE_LOCK_STATE_CHANGED:
return acquireStateChangedSuspendBlocker();
+ case WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE:
+ synchronized (mLock) {
+ return acquireOverrideDozeScreenStateSuspendBlockerLocked();
+ }
case WAKE_LOCK_UNFINISHED_BUSINESS:
return acquireUnfinishedBusinessSuspendBlocker();
default:
@@ -171,6 +187,10 @@
return releaseProxDebounceSuspendBlocker();
case WAKE_LOCK_STATE_CHANGED:
return releaseStateChangedSuspendBlocker();
+ case WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE:
+ synchronized (mLock) {
+ return releaseOverrideDozeScreenStateSuspendBlockerLocked();
+ }
case WAKE_LOCK_UNFINISHED_BUSINESS:
return releaseUnfinishedBusinessSuspendBlocker();
default:
@@ -220,6 +240,42 @@
}
/**
+ * Acquires the suspend blocker to override the doze screen state and notifies the
+ * PowerManagerService about the changes. Note that this utility is syncronized because a
+ * request to override the doze screen state can come from a non-power thread.
+ */
+ @GuardedBy("mLock")
+ private boolean acquireOverrideDozeScreenStateSuspendBlockerLocked() {
+ // Grab a wake lock if we have unfinished business.
+ if (!mIsOverrideDozeScreenStateAcquired) {
+ if (DEBUG) {
+ Slog.d(mTag, "Acquiring suspend blocker to override the doze screen state...");
+ }
+ mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerOverrideDozeScreenState);
+ mIsOverrideDozeScreenStateAcquired = true;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Releases the override doze screen state suspend blocker and notifies the PowerManagerService
+ * about the changes.
+ */
+ @GuardedBy("mLock")
+ private boolean releaseOverrideDozeScreenStateSuspendBlockerLocked() {
+ if (mIsOverrideDozeScreenStateAcquired) {
+ if (DEBUG) {
+ Slog.d(mTag, "Finished overriding doze screen state...");
+ }
+ mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerOverrideDozeScreenState);
+ mIsOverrideDozeScreenStateAcquired = false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Acquires the unfinished business wakelock and notifies the PowerManagerService about the
* changes.
*/
@@ -366,6 +422,7 @@
pw.println(" mOnStateChangePending=" + isOnStateChangedPending());
pw.println(" mOnProximityPositiveMessages=" + isProximityPositiveAcquired());
pw.println(" mOnProximityNegativeMessages=" + isProximityNegativeAcquired());
+ pw.println(" mIsOverrideDozeScreenStateAcquired=" + isOverrideDozeScreenStateAcquired());
}
@VisibleForTesting
@@ -394,6 +451,13 @@
}
@VisibleForTesting
+ String getSuspendBlockerOverrideDozeScreenState() {
+ synchronized (mLock) {
+ return mSuspendBlockerOverrideDozeScreenState;
+ }
+ }
+
+ @VisibleForTesting
boolean hasUnfinishedBusiness() {
return mUnfinishedBusiness;
}
@@ -417,4 +481,11 @@
boolean hasProximitySensorDebounced() {
return mHasProximityDebounced;
}
+
+ @VisibleForTesting
+ boolean isOverrideDozeScreenStateAcquired() {
+ synchronized (mLock) {
+ return mIsOverrideDozeScreenStateAcquired;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index c632e77..e157b05 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -359,11 +359,11 @@
public void setUpAutoBrightness(AutomaticBrightnessController automaticBrightnessController,
SensorManager sensorManager,
DisplayDeviceConfig displayDeviceConfig, Handler handler,
- BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled,
+ BrightnessMappingStrategy brightnessMappingStrategy, boolean isDisplayEnabled,
int leadDisplayId) {
setAutomaticBrightnessController(automaticBrightnessController);
setUpAutoBrightnessFallbackStrategy(sensorManager, displayDeviceConfig, handler,
- brightnessMappingStrategy, isEnabled, leadDisplayId);
+ brightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
}
/**
@@ -534,14 +534,14 @@
private void setUpAutoBrightnessFallbackStrategy(SensorManager sensorManager,
DisplayDeviceConfig displayDeviceConfig, Handler handler,
- BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled,
+ BrightnessMappingStrategy brightnessMappingStrategy, boolean isDisplayEnabled,
int leadDisplayId) {
AutoBrightnessFallbackStrategy autoBrightnessFallbackStrategy =
getAutoBrightnessFallbackStrategy();
if (autoBrightnessFallbackStrategy != null) {
autoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(
sensorManager, displayDeviceConfig, handler, brightnessMappingStrategy,
- isEnabled, leadDisplayId);
+ isDisplayEnabled, leadDisplayId);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 12c3197..59fffe7 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -347,7 +347,7 @@
data.mDisplayDeviceConfig));
}
if (flags.useNewHdrBrightnessModifier()) {
- modifiers.add(new HdrBrightnessModifier(handler, listener, data));
+ modifiers.add(new HdrBrightnessModifier(handler, context, listener, data));
}
return modifiers;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
index ae1801c..4ab4336 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
@@ -21,10 +21,15 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.ContentObserver;
import android.hardware.display.DisplayManagerInternal;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,6 +49,11 @@
static final float DEFAULT_MAX_HDR_SDR_RATIO = 1.0f;
private static final float DEFAULT_HDR_LAYER_SIZE = -1.0f;
+ private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE);
+
+ private final ContentObserver mContentObserver;
+
private final SurfaceControlHdrLayerInfoListener mHdrListener =
new SurfaceControlHdrLayerInfoListener() {
@Override
@@ -52,7 +62,8 @@
boolean hdrLayerPresent = numberOfHdrLayers > 0;
mHandler.post(() -> HdrBrightnessModifier.this.onHdrInfoChanged(
hdrLayerPresent ? (float) (maxW * maxH) : DEFAULT_HDR_LAYER_SIZE,
- hdrLayerPresent ? maxDesiredHdrSdrRatio : DEFAULT_MAX_HDR_SDR_RATIO));
+ hdrLayerPresent ? Math.max(maxDesiredHdrSdrRatio,
+ DEFAULT_MAX_HDR_SDR_RATIO) : DEFAULT_MAX_HDR_SDR_RATIO));
}
};
@@ -62,6 +73,7 @@
private final Runnable mDebouncer;
private IBinder mRegisteredDisplayToken;
+ private boolean mContentObserverRegistered = false;
private DisplayDeviceConfig mDisplayDeviceConfig;
@Nullable
@@ -73,6 +85,8 @@
private float mAmbientLux = INVALID_LUX;
+ private boolean mLowPowerMode = false;
+
private Mode mMode = Mode.NO_HDR;
// The maximum brightness allowed for current lux
private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
@@ -81,17 +95,17 @@
private float mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
private float mPendingTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
- HdrBrightnessModifier(Handler handler,
+ HdrBrightnessModifier(Handler handler, Context context,
BrightnessClamperController.ClamperChangeListener clamperChangeListener,
BrightnessClamperController.DisplayDeviceData displayData) {
- this(new Handler(handler.getLooper()), clamperChangeListener, new Injector(), displayData);
+ this(new Handler(handler.getLooper()), clamperChangeListener,
+ new Injector(context), displayData);
}
@VisibleForTesting
HdrBrightnessModifier(Handler handler,
BrightnessClamperController.ClamperChangeListener clamperChangeListener,
- Injector injector,
- BrightnessClamperController.DisplayDeviceData displayData) {
+ Injector injector, BrightnessClamperController.DisplayDeviceData displayData) {
mHandler = handler;
mClamperChangeListener = clamperChangeListener;
mInjector = injector;
@@ -100,6 +114,12 @@
mMaxBrightness = mPendingMaxBrightness;
mClamperChangeListener.onChanged();
};
+ mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onLowPowerModeChange();
+ }
+ };
mHandler.post(() -> onDisplayChanged(displayData));
}
@@ -135,12 +155,14 @@
pw.println(" mMaxDesiredHdrRatio=" + mMaxDesiredHdrRatio);
pw.println(" mHdrLayerSize=" + mHdrLayerSize);
pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mLowPowerMode=" + mLowPowerMode);
pw.println(" mMode=" + mMode);
pw.println(" mMaxBrightness=" + mMaxBrightness);
pw.println(" mPendingMaxBrightness=" + mPendingMaxBrightness);
pw.println(" mTransitionRate=" + mTransitionRate);
pw.println(" mPendingTransitionRate=" + mPendingTransitionRate);
pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null));
+ pw.println(" mContentObserverRegistered=" + mContentObserverRegistered);
}
// Called in DisplayControllerHandler
@@ -182,7 +204,25 @@
} else {
registerHdrListener(displayData.mDisplayToken);
}
- recalculate(data, mMaxDesiredHdrRatio);
+ if (data == null || data.allowInLowPowerMode) {
+ unregisterContentObserver();
+ } else {
+ registerContentObserver();
+ }
+
+ Mode newMode = recalculateMode(data);
+ // mode changed, or mode was HDR and HdrBrightnessData changed
+ boolean needToNotifyChange = mMode != newMode
+ || (mMode != HdrBrightnessModifier.Mode.NO_HDR && data != mHdrBrightnessData);
+ mMode = newMode;
+ mHdrBrightnessData = data;
+ mMaxBrightness = findBrightnessLimit(mHdrBrightnessData, mAmbientLux);
+
+ if (needToNotifyChange) {
+ // data changed, reset custom transition rate
+ mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+ mClamperChangeListener.onChanged();
+ }
}
// Called in DisplayControllerHandler, when any modifier state changes
@@ -226,30 +266,6 @@
}
// Called in DisplayControllerHandler
- private void recalculate(@Nullable HdrBrightnessData data, float maxDesiredHdrRatio) {
- Mode newMode = recalculateMode(data);
- // if HDR mode changed, notify changed
- boolean needToNotifyChange = mMode != newMode;
- // If HDR mode is active, we need to check if other HDR params are changed
- if (mMode != HdrBrightnessModifier.Mode.NO_HDR) {
- if (!BrightnessSynchronizer.floatEquals(mMaxDesiredHdrRatio, maxDesiredHdrRatio)
- || data != mHdrBrightnessData) {
- needToNotifyChange = true;
- }
- }
-
- mMode = newMode;
- mHdrBrightnessData = data;
- mMaxDesiredHdrRatio = maxDesiredHdrRatio;
-
- if (needToNotifyChange) {
- // data or hdr layer changed, reset custom transition rate
- mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
- mClamperChangeListener.onChanged();
- }
- }
-
- // Called in DisplayControllerHandler
private Mode recalculateMode(@Nullable HdrBrightnessData data) {
// no config
if (data == null) {
@@ -259,6 +275,10 @@
if (mHdrLayerSize == DEFAULT_HDR_LAYER_SIZE) {
return Mode.NO_HDR;
}
+ // low power mode and not allowed in low power mode
+ if (!data.allowInLowPowerMode && mLowPowerMode) {
+ return Mode.NO_HDR;
+ }
// HDR layer < minHdr % for Nbm
if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForNbm) {
return Mode.NO_HDR;
@@ -271,6 +291,16 @@
return Mode.HBM_HDR;
}
+ private void onLowPowerModeChange() {
+ mLowPowerMode = mInjector.isLowPowerMode();
+ Mode newMode = recalculateMode(mHdrBrightnessData);
+ if (newMode != mMode) {
+ mMode = newMode;
+ mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+ mClamperChangeListener.onChanged();
+ }
+ }
+
private float getMaxBrightness(Mode mode, float maxBrightness, HdrBrightnessData data) {
if (mode == Mode.NBM_HDR) {
return Math.min(data.hbmTransitionPoint, maxBrightness);
@@ -282,7 +312,13 @@
}
// Called in DisplayControllerHandler
- private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) {
+ private float findBrightnessLimit(@Nullable HdrBrightnessData data, float ambientLux) {
+ if (data == null) {
+ return PowerManager.BRIGHTNESS_MAX;
+ }
+ if (ambientLux == INVALID_LUX) {
+ return PowerManager.BRIGHTNESS_MAX;
+ }
float foundAmbientBoundary = Float.MAX_VALUE;
float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
for (Map.Entry<Float, Float> brightnessPoint :
@@ -300,7 +336,17 @@
// Called in DisplayControllerHandler
private void onHdrInfoChanged(float hdrLayerSize, float maxDesiredHdrSdrRatio) {
mHdrLayerSize = hdrLayerSize;
- recalculate(mHdrBrightnessData, maxDesiredHdrSdrRatio);
+ Mode newMode = recalculateMode(mHdrBrightnessData);
+ // mode changed, or mode was HDR and maxDesiredHdrRatio changed
+ boolean needToNotifyChange = mMode != newMode
+ || (mMode != HdrBrightnessModifier.Mode.NO_HDR
+ && !BrightnessSynchronizer.floatEquals(mMaxDesiredHdrRatio, maxDesiredHdrSdrRatio));
+ mMode = newMode;
+ mMaxDesiredHdrRatio = maxDesiredHdrSdrRatio;
+ if (needToNotifyChange) {
+ mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+ mClamperChangeListener.onChanged();
+ }
}
// Called in DisplayControllerHandler
@@ -324,12 +370,36 @@
}
}
+ // Called in DisplayControllerHandler
+ private void registerContentObserver() {
+ if (!mContentObserverRegistered) {
+ mInjector.registerContentObserver(mContentObserver, mLowPowerModeSetting);
+ mContentObserverRegistered = true;
+ mLowPowerMode = mInjector.isLowPowerMode();
+ }
+ }
+
+ // Called in DisplayControllerHandler
+ private void unregisterContentObserver() {
+ if (mContentObserverRegistered) {
+ mInjector.unregisterContentObserver(mContentObserver);
+ mContentObserverRegistered = false;
+ mLowPowerMode = false;
+ }
+ }
+
private enum Mode {
NO_HDR, NBM_HDR, HBM_HDR
}
@SuppressLint("MissingPermission")
static class Injector {
+ private final Context mContext;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
void registerHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) {
listener.register(token);
}
@@ -337,5 +407,19 @@
void unregisterHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) {
listener.unregister(token);
}
+
+ void registerContentObserver(ContentObserver observer, Uri uri) {
+ mContext.getContentResolver().registerContentObserver(uri, false,
+ observer, UserHandle.USER_ALL);
+ }
+
+ void unregisterContentObserver(ContentObserver observer) {
+ mContext.getContentResolver().unregisterContentObserver(observer);
+ }
+
+ boolean isLowPowerMode() {
+ return Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
index d8b95ec..1db9bbe 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
@@ -16,6 +16,9 @@
package com.android.server.display.brightness.strategy;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.Sensor;
@@ -23,7 +26,6 @@
import android.os.Handler;
import android.os.SystemClock;
import android.util.IndentingPrintWriter;
-import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.BrightnessMappingStrategy;
@@ -53,7 +55,7 @@
Sensor mScreenOffBrightnessSensor;
// Indicates if the associated LogicalDisplay is enabled or not.
- private boolean mIsEnabled;
+ private boolean mIsDisplayEnabled;
// Represents if the associated display is a lead display or not. If not, the variable
// represents the lead display ID
@@ -97,7 +99,7 @@
public void dump(PrintWriter writer) {
writer.println("AutoBrightnessFallbackStrategy:");
writer.println(" mLeadDisplayId=" + mLeadDisplayId);
- writer.println(" mIsEnabled=" + mIsEnabled);
+ writer.println(" mIsDisplayEnabled=" + mIsDisplayEnabled);
if (mScreenOffBrightnessSensorController != null) {
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mScreenOffBrightnessSensorController.dump(ipw);
@@ -108,11 +110,10 @@
public void strategySelectionPostProcessor(
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
if (mScreenOffBrightnessSensorController != null) {
- int targetDisplayState = strategySelectionNotifyRequest.getTargetDisplayState();
+ int policy = strategySelectionNotifyRequest.getDisplayPowerRequest().policy;
mScreenOffBrightnessSensorController.setLightSensorEnabled(
- strategySelectionNotifyRequest.isAutoBrightnessEnabled() && mIsEnabled
- && (targetDisplayState == Display.STATE_OFF
- || (targetDisplayState == Display.STATE_DOZE
+ strategySelectionNotifyRequest.isAutoBrightnessEnabled() && mIsDisplayEnabled
+ && (policy == POLICY_OFF || (policy == POLICY_DOZE
&& !strategySelectionNotifyRequest
.isAllowAutoBrightnessWhileDozingConfig()))
&& mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
@@ -132,9 +133,9 @@
*/
public void setupAutoBrightnessFallbackSensor(SensorManager sensorManager,
DisplayDeviceConfig displayDeviceConfig, Handler handler,
- BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled,
+ BrightnessMappingStrategy brightnessMappingStrategy, boolean isDisplayEnabled,
int leadDisplayId) {
- mIsEnabled = isEnabled;
+ mIsDisplayEnabled = isDisplayEnabled;
mLeadDisplayId = leadDisplayId;
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 084e118..76380b7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -619,7 +619,7 @@
case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
if (!mNewInputMethodSwitcherMenuEnabled) {
if (userId == mCurrentUserId) {
- mMenuController.updateKeyboardFromSettingsLocked();
+ mMenuController.updateKeyboardFromSettingsLocked(userId);
}
}
break;
@@ -693,7 +693,7 @@
senderUserId);
}
} else {
- mMenuController.hideInputMethodMenu();
+ mMenuController.hideInputMethodMenu(senderUserId);
}
} else {
Slog.w(TAG, "Unexpected intent " + intent);
@@ -707,6 +707,7 @@
* <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
* the users.</p>
*/
+ @WorkerThread
void onActionLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales) {
if (DEBUG) {
Slog.d(TAG, "onActionLocaleChanged prev=" + prevLocales + " new=" + newLocales);
@@ -716,13 +717,19 @@
return;
}
for (int userId : mUserManagerInternal.getUserIds()) {
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext,
- userId,
- AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
-
+ // Does InputMethodInfo really have data dependency on system locale?
+ // TODO(b/356679261): Check if we really need to update RawInputMethodInfo here.
+ {
+ final var userData = getUserData(userId);
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+ final var rawMethodMap = queryRawInputMethodServiceMap(mContext, userId);
+ userData.mRawInputMethodMap.set(rawMethodMap);
+ final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
+ DirectBootAwareness.AUTO,
+ mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext, userId);
@@ -796,17 +803,13 @@
private void onFinishPackageChangesInternal() {
final int userId = getChangingUserId();
+ final var userData = getUserData(userId);
// Instantiating InputMethodInfo requires disk I/O.
// Do them before acquiring the lock to minimize the chances of ANR (b/340221861).
- final var newMethodMapWithoutAdditionalSubtypes =
- queryInputMethodServicesInternal(mContext, userId,
- AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO)
- .getMethodMap();
+ userData.mRawInputMethodMap.set(queryRawInputMethodServiceMap(mContext, userId));
synchronized (ImfLock.class) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
@@ -836,6 +839,7 @@
}
// Clear additional subtypes as a batch operation.
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final AdditionalSubtypeMap newAdditionalSubtypeMap =
additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
final boolean additionalSubtypeChanged =
@@ -845,8 +849,10 @@
settings.getMethodMap());
}
- final var newMethodMap = newMethodMapWithoutAdditionalSubtypes
- .applyAdditionalSubtypes(newAdditionalSubtypeMap);
+ final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ newAdditionalSubtypeMap,
+ DirectBootAwareness.AUTO,
+ mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
if (InputMethodMap.areSame(settings.getMethodMap(), newMethodMap)) {
// No update in the actual IME map.
@@ -987,8 +993,6 @@
@NonNull
private static InputMethodManagerService createServiceForProduction(
@NonNull Context context) {
- // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
- // additional subtypes in switchUserOnHandlerLocked().
final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME,
Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
thread.start();
@@ -1066,9 +1070,11 @@
final int userId = user.getUserIdentifier();
SecureSettingsWrapper.onUserUnlocking(userId);
mService.mIoHandler.post(() -> {
- final var settings = queryInputMethodServicesInternal(mService.mContext, userId,
- AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
+ final var userData = mService.getUserData(userId);
+ final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, true);
+ final var newSettings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, newSettings);
synchronized (ImfLock.class) {
if (!mService.mSystemReady) {
return;
@@ -1106,19 +1112,22 @@
for (int userId : userIds) {
Slog.d(TAG, "Start initialization for user=" + userId);
+ final var userData = mService.getUserData(userId);
+
AdditionalSubtypeMapRepository.initializeIfNecessary(userId);
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
- context, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO).getMethodMap();
- InputMethodSettingsRepository.put(userId,
- InputMethodSettings.create(settings, userId));
+ final var rawMethodMap = queryRawInputMethodServiceMap(context, userId);
+ userData.mRawInputMethodMap.set(rawMethodMap);
+ final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
+ DirectBootAwareness.AUTO,
+ userManagerInternal.isUserUnlockingOrUnlocked(userId));
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
final int profileParentId = userManagerInternal.getProfileParentId(userId);
final boolean value =
InputMethodDrawsNavBarResourceMonitor.evaluate(context,
profileParentId);
- final var userData = mService.getUserData(userId);
userData.mImeDrawsNavBar.set(value);
userData.mBackgroundLoadLatch.countDown();
@@ -1133,12 +1142,13 @@
// Called on ActivityManager thread.
SecureSettingsWrapper.onUserStopped(userId);
mService.mIoHandler.post(() -> {
+ final var userData = mService.getUserData(userId);
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
- mService.mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO).getMethodMap();
+ final var rawMethodMap = userData.mRawInputMethodMap.get();
+ final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
+ DirectBootAwareness.AUTO, false /* userUnlocked */);
InputMethodSettingsRepository.put(userId,
- InputMethodSettings.create(settings, userId));
+ InputMethodSettings.create(methodMap, userId));
});
}
}
@@ -1179,7 +1189,7 @@
mHandler = Handler.createAsync(uiLooper, this);
mIoHandler = ioHandler;
- SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
+ SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mIoHandler);
mImeTrackerService = new ImeTrackerService(mHandler);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -1223,12 +1233,6 @@
}
}
- @GuardedBy("ImfLock.class")
- @UserIdInt
- int getCurrentImeUserIdLocked() {
- return mCurrentUserId;
- }
-
private final class InkWindowInitializer implements Runnable {
public void run() {
synchronized (ImfLock.class) {
@@ -1240,12 +1244,12 @@
}
}
- private void onUpdateEditorToolType(int toolType) {
- synchronized (ImfLock.class) {
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- curMethod.updateEditorToolType(toolType);
- }
+ @GuardedBy("ImfLock.class")
+ private void onUpdateEditorToolTypeLocked(@MotionEvent.ToolType int toolType,
+ @UserIdInt int userId) {
+ final var curMethod = getInputMethodBindingController(userId).getCurMethod();
+ if (curMethod != null) {
+ curMethod.updateEditorToolType(toolType);
}
}
@@ -1642,15 +1646,11 @@
private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
- final InputMethodSettings settings;
- if (directBootAwareness == DirectBootAwareness.AUTO) {
- settings = InputMethodSettingsRepository.get(userId);
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- directBootAwareness);
- }
+ final var userData = getUserData(userId);
+ final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ AdditionalSubtypeMapRepository.get(userId), directBootAwareness,
+ mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+ final var settings = InputMethodSettings.create(methodMap, userId);
// Create a copy.
final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList());
// filter caller's access to input methods
@@ -1827,7 +1827,7 @@
if (mNewInputMethodSwitcherMenuEnabled) {
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
- mMenuController.hideInputMethodMenuLocked();
+ mMenuController.hideInputMethodMenuLocked(userId);
}
}
}
@@ -2779,7 +2779,7 @@
}
final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
if (targetWindow != null) {
- mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
+ mWindowManagerInternal.updateInputMethodTargetWindow(targetWindow);
}
mVisibilityStateComputer.setLastImeTargetWindow(targetWindow);
}
@@ -2849,8 +2849,8 @@
}
final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis, userId);
if (mStatusBarManagerInternal != null) {
- mStatusBarManagerInternal.setImeWindowStatus(curTokenDisplayId,
- curToken, vis, backDisposition, needsToShowImeSwitcher);
+ mStatusBarManagerInternal.setImeWindowStatus(curTokenDisplayId, vis,
+ backDisposition, needsToShowImeSwitcher);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2861,7 +2861,7 @@
void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
if (!mNewInputMethodSwitcherMenuEnabled) {
- mMenuController.updateKeyboardFromSettingsLocked();
+ mMenuController.updateKeyboardFromSettingsLocked(userId);
}
}
@@ -3464,9 +3464,9 @@
userData.mCurStatsToken = null;
if (Flags.useHandwritingListenerForTooltype()) {
- maybeReportToolType();
+ maybeReportToolType(userId);
} else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
- onUpdateEditorToolType(lastClickToolType);
+ onUpdateEditorToolTypeLocked(lastClickToolType, userId);
}
mVisibilityApplier.performShowIme(windowToken, statsToken,
mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
@@ -3481,7 +3481,8 @@
}
@GuardedBy("ImfLock.class")
- private void maybeReportToolType() {
+ private void maybeReportToolType(@UserIdInt int userId) {
+ // TODO(b/356638981): This needs to be compatible with visible background users.
int lastDeviceId = mInputManagerInternal.getLastUsedInputDeviceId();
final InputManager im = mContext.getSystemService(InputManager.class);
if (im == null) {
@@ -3500,7 +3501,7 @@
// other toolTypes are irrelevant and reported as unknown.
toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
}
- onUpdateEditorToolType(toolType);
+ onUpdateEditorToolTypeLocked(toolType, userId);
}
@Override
@@ -4348,6 +4349,7 @@
+ subtype.getLocale() + ", " + subtype.getMode());
}
}
+ final var userData = getUserData(userId);
synchronized (ImfLock.class) {
if (!mSystemReady) {
return;
@@ -4363,9 +4365,10 @@
settings.getMethodMap());
final long ident = Binder.clearCallingIdentity();
try {
- final InputMethodSettings newSettings = queryInputMethodServicesInternal(
- mContext, userId, AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
+ final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
+ mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+ final var newSettings = InputMethodSettings.create(methodMap, userId);
InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
@@ -5072,7 +5075,7 @@
mMenuControllerNew.show(menuItems, selectedIndex, displayId, userId);
} else {
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
- lastInputMethodId, lastInputMethodSubtypeId, imList);
+ lastInputMethodId, lastInputMethodSubtypeId, imList, userId);
}
}
@@ -5304,31 +5307,15 @@
}
@NonNull
- static InputMethodSettings queryInputMethodServicesInternal(Context context,
- @UserIdInt int userId, @NonNull AdditionalSubtypeMap additionalSubtypeMap,
- @DirectBootAwareness int directBootAwareness) {
+ static RawInputMethodMap queryRawInputMethodServiceMap(Context context, @UserIdInt int userId) {
final Context userAwareContext = context.getUserId() == userId
? context
: context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
- final int directBootAwarenessFlags;
- switch (directBootAwareness) {
- case DirectBootAwareness.ANY:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- break;
- case DirectBootAwareness.AUTO:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO;
- break;
- default:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO;
- Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness
- + ". Falling back to DirectBootAwareness.AUTO");
- break;
- }
final int flags = PackageManager.GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | directBootAwarenessFlags;
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
// Beware that package visibility filtering will be enforced based on the effective calling
// identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid()
@@ -5344,14 +5331,11 @@
final List<String> enabledInputMethodList =
InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId);
- final InputMethodMap methodMap = filterInputMethodServices(
- additionalSubtypeMap, enabledInputMethodList, userAwareContext, services);
- return InputMethodSettings.create(methodMap, userId);
+ return filterInputMethodServices(enabledInputMethodList, userAwareContext, services);
}
@NonNull
- static InputMethodMap filterInputMethodServices(
- @NonNull AdditionalSubtypeMap additionalSubtypeMap,
+ static RawInputMethodMap filterInputMethodServices(
List<String> enabledInputMethodList, Context userAwareContext,
List<ResolveInfo> services) {
final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
@@ -5372,7 +5356,7 @@
try {
final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri,
- additionalSubtypeMap.get(imeId));
+ Collections.emptyList());
if (imi.isVrOnly()) {
continue; // Skip VR-only IME, which isn't supported for now.
}
@@ -5395,7 +5379,7 @@
Slog.wtf(TAG, "Unable to load input method " + imeId, e);
}
}
- return InputMethodMap.of(methodMap);
+ return RawInputMethodMap.of(methodMap);
}
@GuardedBy("ImfLock.class")
@@ -5949,7 +5933,7 @@
final var bindingController = getInputMethodBindingController(userId);
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
- mMenuController.hideInputMethodMenuLocked();
+ mMenuController.hideInputMethodMenuLocked(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index ba5c13e..f16a5a0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -77,13 +78,12 @@
@GuardedBy("ImfLock.class")
void showInputMethodMenuLocked(boolean showAuxSubtypes, int displayId,
String preferredInputMethodId, int preferredInputMethodSubtypeId,
- @NonNull List<ImeSubtypeListItem> imList) {
+ @NonNull List<ImeSubtypeListItem> imList, @UserIdInt int userId) {
if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
- final int userId = mService.getCurrentImeUserIdLocked();
final var bindingController = mService.getInputMethodBindingController(userId);
- hideInputMethodMenuLocked();
+ hideInputMethodMenuLocked(userId);
if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
final InputMethodSubtype currentSubtype =
@@ -131,7 +131,7 @@
}
final Context dialogWindowContext = mDialogWindowContext.get(displayId);
mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
- mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
+ mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu(userId));
final Context dialogContext = mDialogBuilder.getContext();
final TypedArray a = dialogContext.obtainStyledAttributes(null,
@@ -162,7 +162,7 @@
isChecked, userId);
// Ensure that the input method dialog is dismissed when changing
// the hardware keyboard state.
- hideInputMethodMenu();
+ hideInputMethodMenu(userId);
});
// Fill the list items with onClick listener, which takes care of IME (and subtype)
@@ -185,7 +185,7 @@
}
mService.setInputMethodLocked(im.getId(), subtypeId, userId);
}
- hideInputMethodMenuLocked();
+ hideInputMethodMenuLocked(userId);
}
};
mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
@@ -209,10 +209,10 @@
mSwitchingDialog.show();
}
- void updateKeyboardFromSettingsLocked() {
+ void updateKeyboardFromSettingsLocked(@UserIdInt int userId) {
mShowImeWithHardKeyboard =
SecureSettingsWrapper.getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
- false, mService.getCurrentImeUserIdLocked());
+ false, userId);
if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
&& mSwitchingDialog.isShowing()) {
final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
@@ -223,18 +223,22 @@
/**
* Hides the input method switcher menu.
+ *
+ * @param userId user ID for this operation
*/
- void hideInputMethodMenu() {
+ void hideInputMethodMenu(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- hideInputMethodMenuLocked();
+ hideInputMethodMenuLocked(userId);
}
}
/**
* Hides the input method switcher menu, synchronised version of {@link #hideInputMethodMenu}.
+ *
+ * @param userId user ID for this operation
*/
@GuardedBy("ImfLock.class")
- void hideInputMethodMenuLocked() {
+ void hideInputMethodMenuLocked(@UserIdInt int userId) {
if (DEBUG) Slog.v(TAG, "Hide switching menu");
if (mSwitchingDialog != null) {
@@ -242,8 +246,6 @@
mSwitchingDialog = null;
mSwitchingDialogTitleView = null;
- // TODO(b/305849394): Make InputMethodMenuController multi-user aware
- final int userId = mService.getCurrentImeUserIdLocked();
mService.updateSystemUiLocked(userId);
mService.sendOnNavButtonFlagsChangedToAllImesLocked();
mDialogBuilder = null;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index 045414b..35fae18 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -140,6 +140,8 @@
// Indicate that the list can be scrolled.
recyclerView.setScrollIndicators(
hasLanguageSettingsButton ? View.SCROLL_INDICATOR_BOTTOM : 0);
+ // Request focus to enable rotary scrolling on watches.
+ recyclerView.requestFocus();
builder.setOnCancelListener(dialog -> hide(displayId, userId));
mMenuItems = items;
diff --git a/services/core/java/com/android/server/inputmethod/RawInputMethodMap.java b/services/core/java/com/android/server/inputmethod/RawInputMethodMap.java
new file mode 100644
index 0000000..4e39a3f
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/RawInputMethodMap.java
@@ -0,0 +1,105 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+
+import java.util.List;
+
+/**
+ * This is quite similar to {@link InputMethodMap} with two major differences.
+ *
+ * <ul>
+ * <li>Additional {@link android.view.inputmethod.InputMethodSubtype} is not included.</li>
+ * <li>Always include direct-boot unaware {@link android.inputmethodservice.InputMethodService}.
+ * </li>
+ * </ul>
+ *
+ * <p>As seen in {@link #toInputMethodMap(AdditionalSubtypeMap, int, boolean)}, you can consider
+ * this is a prototype data where you can always derive {@link InputMethodMap} with
+ * {@link AdditionalSubtypeMap} and a boolean information whether
+ * {@link com.android.server.pm.UserManagerInternal#isUserUnlockingOrUnlocked(int)} returns
+ * {@code true} or not.</p>
+ */
+final class RawInputMethodMap {
+ static final String TAG = "RawInputMethodMap";
+
+ private static final ArrayMap<String, InputMethodInfo> EMPTY_MAP = new ArrayMap<>();
+
+ private final ArrayMap<String, InputMethodInfo> mMap;
+
+ static RawInputMethodMap emptyMap() {
+ return new RawInputMethodMap(EMPTY_MAP);
+ }
+
+ static RawInputMethodMap of(@NonNull ArrayMap<String, InputMethodInfo> map) {
+ return new RawInputMethodMap(map);
+ }
+
+ private RawInputMethodMap(@NonNull ArrayMap<String, InputMethodInfo> map) {
+ mMap = map.isEmpty() ? EMPTY_MAP : new ArrayMap<>(map);
+ }
+
+ @AnyThread
+ @NonNull
+ List<InputMethodInfo> values() {
+ return List.copyOf(mMap.values());
+ }
+
+ @NonNull
+ InputMethodMap toInputMethodMap(@NonNull AdditionalSubtypeMap additionalSubtypeMap,
+ @DirectBootAwareness int directBootAwareness, boolean userUnlocked) {
+ final int size = mMap.size();
+ final var newMap = new ArrayMap<String, InputMethodInfo>(size);
+
+ final boolean requireDirectBootAwareFlag;
+ switch (directBootAwareness) {
+ case DirectBootAwareness.ANY -> requireDirectBootAwareFlag = false;
+ case DirectBootAwareness.AUTO -> requireDirectBootAwareFlag = !userUnlocked;
+ default -> {
+ requireDirectBootAwareFlag = !userUnlocked;
+ Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness
+ + ". Falling back to DirectBootAwareness.AUTO");
+ }
+ }
+
+ boolean updated = false;
+ for (int i = 0; i < size; ++i) {
+ final var imeId = mMap.keyAt(i);
+ final var imi = mMap.valueAt(i);
+ if (requireDirectBootAwareFlag && !imi.getServiceInfo().directBootAware) {
+ updated = true;
+ continue;
+ }
+ final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId);
+ if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) {
+ newMap.put(imi.getId(), imi);
+ } else {
+ updated = true;
+ newMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes));
+ }
+ }
+ // If newMap is semantically the same as mMap, we can reuse mMap and discard newMap.
+ return InputMethodMap.of(updated ? newMap : mMap);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
index be57321..4fb55e1 100644
--- a/services/core/java/com/android/server/inputmethod/UserData.java
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -30,6 +30,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
/** Placeholder for all IMMS user specific fields */
final class UserData {
@@ -43,6 +44,17 @@
@NonNull
final CountDownLatch mBackgroundLoadLatch = new CountDownLatch(1);
+ /**
+ * Contains non-null {@link RawInputMethodMap}, which represents the latest collections of
+ * {@link android.view.inputmethod.InputMethodInfo} for both direct-boot aware and unaware IMEs
+ * before taking {@link AdditionalSubtypeMap} into account.
+ *
+ * <p>See {@link RawInputMethodMap} for details on when to use this.</p>
+ */
+ @NonNull
+ final AtomicReference<RawInputMethodMap> mRawInputMethodMap =
+ new AtomicReference<>(RawInputMethodMap.emptyMap());
+
@NonNull
final InputMethodBindingController mBindingController;
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 0e7ce2e..14b0fc8 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -497,6 +497,17 @@
public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
/**
+ * Checks if the given user is a visible background full user, which is a full background user
+ * assigned to secondary displays on the devices that have
+ * {@link UserManager#isVisibleBackgroundUsersEnabled()
+ * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+ * automotive builds, using the display associated with their seats).
+ *
+ * @see UserManager#isUserVisible()
+ */
+ public abstract boolean isVisibleBackgroundFullUser(@UserIdInt int userId);
+
+ /**
* Returns the main display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
* user is not assigned to any main display.
*
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index dde9943..c902fb2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7927,6 +7927,17 @@
}
@Override
+ public boolean isVisibleBackgroundFullUser(@UserIdInt int userId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return false;
+ }
+ boolean isForeground = userId == getCurrentUserId();
+ boolean isProfile = isProfileUnchecked(userId);
+ boolean isVisible = isUserVisible(userId);
+ return isVisible && !isForeground && !isProfile;
+ }
+
+ @Override
public int getMainDisplayAssignedToUser(@UserIdInt int userId) {
return mUserVisibilityMediator.getMainDisplayAssignedToUser(userId);
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 40b2ff9..cefecbc 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -34,6 +34,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -55,6 +56,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -705,4 +707,61 @@
}
return context.getString(resid);
};
+
+ void dump(String prefix, PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", prefix);
+ ipw.println("ModifierShortcutManager shortcuts:");
+
+ ipw.increaseIndent();
+ ipw.println("Roles");
+ ipw.increaseIndent();
+ for (int i = 0; i < mRoleShortcuts.size(); i++) {
+ String role = mRoleShortcuts.valueAt(i);
+ char shortcutChar = (char) mRoleShortcuts.keyAt(i);
+ Intent intent = getRoleLaunchIntent(role);
+ ipw.println(shortcutChar + " " + role + " " + intent);
+ }
+
+ for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
+ String role = mShiftRoleShortcuts.valueAt(i);
+ char shortcutChar = (char) mShiftRoleShortcuts.keyAt(i);
+ Intent intent = getRoleLaunchIntent(role);
+ ipw.println("SHIFT+" + shortcutChar + " " + role + " " + intent);
+ }
+
+ ipw.decreaseIndent();
+ ipw.println("Selectors");
+ ipw.increaseIndent();
+ for (int i = 0; i < mIntentShortcuts.size(); i++) {
+ char shortcutChar = (char) mIntentShortcuts.keyAt(i);
+ Intent intent = mIntentShortcuts.valueAt(i);
+ ipw.println(shortcutChar + " " + intent);
+ }
+
+ for (int i = 0; i < mShiftShortcuts.size(); i++) {
+ char shortcutChar = (char) mShiftShortcuts.keyAt(i);
+ Intent intent = mShiftShortcuts.valueAt(i);
+ ipw.println("SHIFT+" + shortcutChar + " " + intent);
+
+ }
+
+ if (modifierShortcutManagerMultiuser()) {
+ ipw.decreaseIndent();
+ ipw.println("ComponentNames");
+ ipw.increaseIndent();
+ for (int i = 0; i < mComponentShortcuts.size(); i++) {
+ char shortcutChar = (char) mComponentShortcuts.keyAt(i);
+ ComponentName component = mComponentShortcuts.valueAt(i);
+ Intent intent = resolveComponentNameIntent(component);
+ ipw.println(shortcutChar + " " + component + " " + intent);
+ }
+
+ for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
+ char shortcutChar = (char) mShiftComponentShortcuts.keyAt(i);
+ ComponentName component = mShiftComponentShortcuts.valueAt(i);
+ Intent intent = resolveComponentNameIntent(component);
+ ipw.println("SHIFT+" + shortcutChar + " " + component + " " + intent);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d0706d2..c95be17 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -76,6 +76,7 @@
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
@@ -6663,6 +6664,9 @@
pw.print(prefix); pw.println("Looper state:");
mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
+ if (modifierShortcutDump()) {
+ mModifierShortcutManager.dump(prefix, pw);
+ }
}
private static String endcallBehaviorToString(int behavior) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ecb0c30b..699c9b5 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -35,6 +35,8 @@
import static com.android.server.deviceidle.Flags.disableWakelocksInLightIdle;
import static com.android.server.display.DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG;
import static com.android.server.display.brightness.BrightnessUtils.isValidBrightnessValue;
+import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN;
+import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_WAKE_LOCK_DEATH;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -1825,7 +1827,7 @@
return;
}
- removeWakeLockLocked(wakeLock, index);
+ removeWakeLockDeathLocked(wakeLock, index);
}
}
@@ -1857,6 +1859,12 @@
}
@GuardedBy("mLock")
+ private void removeWakeLockDeathLocked(WakeLock wakeLock, int index) {
+ removeWakeLockNoUpdateLocked(wakeLock, index, RELEASE_REASON_WAKE_LOCK_DEATH);
+ updatePowerStateLocked();
+ }
+
+ @GuardedBy("mLock")
private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0
&& isScreenLock(wakeLock)) {
@@ -2011,7 +2019,7 @@
@GuardedBy("mLock")
private void notifyWakeLockReleasedLocked(WakeLock wakeLock) {
- notifyWakeLockReleasedLocked(wakeLock, ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN);
+ notifyWakeLockReleasedLocked(wakeLock, RELEASE_REASON_UNKNOWN);
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java b/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java
index dcb3c39..8e08ce9 100644
--- a/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java
+++ b/services/core/java/com/android/server/power/ScreenTimeoutOverridePolicy.java
@@ -83,6 +83,11 @@
public static final int RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY = 7;
/**
+ * Release reason code: Release because wakelock dies.
+ */
+ public static final int RELEASE_REASON_WAKE_LOCK_DEATH = 8;
+
+ /**
* @hide
*/
@IntDef(prefix = { "RELEASE_REASON_" }, value = {
@@ -93,7 +98,8 @@
RELEASE_REASON_USER_ACTIVITY_OTHER,
RELEASE_REASON_USER_ACTIVITY_BUTTON,
RELEASE_REASON_USER_ACTIVITY_TOUCH,
- RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY
+ RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY,
+ RELEASE_REASON_WAKE_LOCK_DEATH
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReleaseReason{}
diff --git a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
index 3546565..c6b2602 100644
--- a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
+++ b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
@@ -31,6 +31,7 @@
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_BUTTON;
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_OTHER;
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_TOUCH;
+import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_WAKE_LOCK_DEATH;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -574,10 +575,13 @@
case RELEASE_REASON_USER_ACTIVITY_ACCESSIBILITY:
outcome = OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION;
break;
- case RELEASE_REASON_SCREEN_LOCK:
- case RELEASE_REASON_NON_INTERACTIVE:
+ case RELEASE_REASON_WAKE_LOCK_DEATH:
outcome = OVERRIDE_OUTCOME_CANCEL_CLIENT_DISCONNECT;
break;
+ case RELEASE_REASON_NON_INTERACTIVE:
+ case RELEASE_REASON_SCREEN_LOCK:
+ outcome = OVERRIDE_OUTCOME_CANCEL_OTHER;
+ break;
default:
outcome = OVERRIDE_OUTCOME_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 143b3ff..c878f14 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -16393,6 +16393,10 @@
* Callers will need to wait for the collection to complete on the handler thread.
*/
public void schedulePowerStatsSampleCollection() {
+ if (!mSystemReady) {
+ return;
+ }
+
mCpuPowerStatsCollector.forceSchedule();
mScreenPowerStatsCollector.forceSchedule();
mMobileRadioPowerStatsCollector.forceSchedule();
@@ -16400,6 +16404,7 @@
mBluetoothPowerStatsCollector.forceSchedule();
mCameraPowerStatsCollector.forceSchedule();
mGnssPowerStatsCollector.forceSchedule();
+ mCustomEnergyConsumerPowerStatsCollector.forceSchedule();
}
/**
diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
index 0273ba6..4bfe442 100644
--- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
@@ -68,6 +68,16 @@
}
@Override
+ public boolean forceSchedule() {
+ ensureInitialized();
+ boolean success = false;
+ for (int i = 0; i < mCollectors.size(); i++) {
+ success |= mCollectors.get(i).forceSchedule();
+ }
+ return success;
+ }
+
+ @Override
public void collectAndDump(PrintWriter pw) {
ensureInitialized();
for (int i = 0; i < mCollectors.size(); i++) {
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
index ce11fa0..79fbe8e 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -163,10 +163,7 @@
mLayout.setConsumedEnergy(mPowerStats.stats, 0, uJtoUc(energyDelta, averageVoltage));
- for (int i = mPowerStats.uidStats.size() - 1; i >= 0; i--) {
- mLayout.setUidConsumedEnergy(mPowerStats.uidStats.valueAt(i), 0, 0);
- }
-
+ mPowerStats.uidStats.clear();
if (energy != null) {
for (int i = energy.length - 1; i >= 0; i--) {
EnergyConsumerAttribution[] perUid = energy[i].attribution;
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 5f41090..bd75faa 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -216,6 +216,8 @@
PowerStatsLayout layout) {
AggregatedPowerStatsConfig.PowerComponent powerComponent = powerComponentStats.getConfig();
int powerComponentId = powerComponent.getPowerComponentId();
+ boolean isCustomComponent =
+ powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
long[] uidStats = new long[descriptor.uidStatsArrayLength];
@@ -223,7 +225,7 @@
boolean breakDownByProcState = batteryUsageStatsBuilder.isProcessStateDataNeeded()
&& powerComponent
.getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked()
- && powerComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ && !isCustomComponent;
ArrayList<Integer> uids = new ArrayList<>();
powerComponentStats.collectUids(uids);
@@ -237,7 +239,7 @@
}
for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
- if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+ if (batteryUsageStatsBuilder.isPowerStateDataNeeded() && !isCustomComponent) {
if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
continue;
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index e4f60ec..a4a29a0 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -54,15 +54,13 @@
* Used by InputMethodManagerService to notify the IME status.
*
* @param displayId The display to which the IME is bound to.
- * @param token The IME token.
* @param vis Bit flags about the IME visibility.
* (e.g. {@link android.inputmethodservice.InputMethodService#IME_ACTIVE})
* @param backDisposition Bit flags about the IME back disposition.
* (e.g. {@link android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT})
* @param showImeSwitcher {@code true} when the IME switcher button should be shown.
*/
- void setImeWindowStatus(int displayId, IBinder token, int vis,
- int backDisposition, boolean showImeSwitcher);
+ void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher);
/**
* See {@link android.app.StatusBarManager#setIcon(String, int, int, String)}.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e9423ce..c3601b3c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -534,9 +534,9 @@
}
@Override
- public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+ public void setImeWindowStatus(int displayId, int vis, int backDisposition,
boolean showImeSwitcher) {
- StatusBarManagerService.this.setImeWindowStatus(displayId, token, vis, backDisposition,
+ StatusBarManagerService.this.setImeWindowStatus(displayId, vis, backDisposition,
showImeSwitcher);
}
@@ -1351,25 +1351,24 @@
}
@Override
- public void setImeWindowStatus(int displayId, final IBinder token, final int vis,
- final int backDisposition, final boolean showImeSwitcher) {
+ public void setImeWindowStatus(int displayId, final int vis, final int backDisposition,
+ final boolean showImeSwitcher) {
enforceStatusBar();
if (SPEW) {
- Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
+ Slog.d(TAG, "setImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
}
synchronized(mLock) {
// In case of IME change, we need to call up setImeWindowStatus() regardless of
// mImeWindowVis because mImeWindowVis may not have been set to false when the
// previous IME was destroyed.
- getUiState(displayId).setImeWindowState(vis, backDisposition, showImeSwitcher, token);
+ getUiState(displayId).setImeWindowState(vis, backDisposition, showImeSwitcher);
mHandler.post(() -> {
if (mBar == null) return;
try {
- mBar.setImeWindowStatus(
- displayId, token, vis, backDisposition, showImeSwitcher);
+ mBar.setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher);
} catch (RemoteException ex) { }
});
}
@@ -1422,7 +1421,6 @@
private int mImeWindowVis = 0;
private int mImeBackDisposition = 0;
private boolean mShowImeSwitcher = false;
- private IBinder mImeToken = null;
private LetterboxDetails[] mLetterboxDetails = new LetterboxDetails[0];
private void setBarAttributes(@Appearance int appearance,
@@ -1465,11 +1463,10 @@
}
private void setImeWindowState(final int vis, final int backDisposition,
- final boolean showImeSwitcher, final IBinder token) {
+ final boolean showImeSwitcher) {
mImeWindowVis = vis;
mImeBackDisposition = backDisposition;
mShowImeSwitcher = showImeSwitcher;
- mImeToken = token;
}
}
@@ -1563,7 +1560,7 @@
return new RegisterStatusBarResult(icons, gatherDisableActionsLocked(mCurrentUserId, 1),
state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
state.mImeBackDisposition, state.mShowImeSwitcher,
- gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
+ gatherDisableActionsLocked(mCurrentUserId, 2),
state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes,
state.mPackageName, state.mTransientBarTypes, state.mLetterboxDetails);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index d5bea4a..5d01bc3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -601,8 +601,8 @@
.getDefaultDisplaySizes().get(orientation);
if (displayForThisOrientation == null) continue;
float sampleSizeForThisOrientation = Math.max(1f, Math.min(
- crop.width() / displayForThisOrientation.x,
- crop.height() / displayForThisOrientation.y));
+ (float) crop.width() / displayForThisOrientation.x,
+ (float) crop.height() / displayForThisOrientation.y));
sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation);
}
// If the total crop has more width or height than either the max texture size
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 4085ec9..fb2bf39 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1612,7 +1612,8 @@
int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
if (isAppCompateStateChangedToLetterboxed(state)) {
- positionToLog = activity.mLetterboxUiController.getLetterboxPositionForLogging();
+ positionToLog = activity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .getLetterboxPositionForLogging();
}
FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
packageUid, state, positionToLog);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8a768c0..eea3ab8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8633,8 +8633,8 @@
/**
* Adjusts position of resolved bounds if they don't fill the parent using gravity
* requested in the config or via an ADB command. For more context see {@link
- * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and
- * {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)}
+ * AppCompatReachabilityOverrides#getHorizontalPositionMultiplier(Configuration)} and
+ * {@link AppCompatReachabilityOverrides#getVerticalPositionMultiplier(Configuration)}
* <p>
* Note that this is the final step that can change the resolved bounds. After this method
* is called, the position of the bounds will be moved to app space as sandboxing if the
@@ -8663,11 +8663,13 @@
} else {
navBarInsets = Insets.NONE;
}
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mAppCompatController.getAppCompatReachabilityOverrides();
// Horizontal position
int offsetX = 0;
if (parentBounds.width() != screenResolvedBoundsWidth) {
if (screenResolvedBoundsWidth <= parentAppBoundsWidth) {
- float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
+ float positionMultiplier = reachabilityOverrides.getHorizontalPositionMultiplier(
newParentConfiguration);
// If in immersive mode, always align to right and overlap right insets (task bar)
// as they are transient and hidden. This removes awkward right spacing.
@@ -8688,7 +8690,7 @@
int offsetY = 0;
if (parentBoundsHeight != screenResolvedBoundsHeight) {
if (screenResolvedBoundsHeight <= parentAppBoundsHeight) {
- float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
+ float positionMultiplier = reachabilityOverrides.getVerticalPositionMultiplier(
newParentConfiguration);
// If in immersive mode, always align to bottom and overlap bottom insets (nav bar,
// task bar) as they are transient and hidden. This removes awkward bottom spacing.
@@ -10686,6 +10688,9 @@
return true;
}
}
+ if (mAtmService.mBackNavigationController.isStartingSurfaceShown(this)) {
+ return true;
+ }
if (!super.isSyncFinished(group)) return false;
if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
.isVisibilityUnknown(this)) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c89f3a3..cc195ac 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2540,9 +2540,7 @@
if (!mOptions.canTaskOverlayResume()) {
final Task task = mRootWindowContainer.anyTaskForId(
mOptions.getLaunchTaskId());
- final ActivityRecord top = task != null
- ? task.getTopNonFinishingActivity() : null;
- if (top != null && !top.isState(RESUMED)) {
+ if (task != null && !task.canBeResumed(r)) {
// The caller specifies that we'd like to be avoided to be moved to the
// front, so be it!
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1c14c5d..9f3bbd1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -7175,25 +7175,6 @@
}
/**
- * Checks if the given user is a visible background user, which is a full, background user
- * assigned to secondary displays on the devices that have
- * {@link UserManager#isVisibleBackgroundUsersEnabled()
- * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
- * automotive builds, using the display associated with their seats).
- *
- * @see UserManager#isUserVisible()
- */
- private boolean isVisibleBackgroundUser(int userId) {
- if (!UserManager.isVisibleBackgroundUsersEnabled()) {
- return false;
- }
- boolean isForeground = getCurrentUserId() == userId;
- boolean isProfile = getUserManager().isProfile(userId);
- boolean isVisible = mWindowManager.mUmInternal.isUserVisible(userId);
- return isVisible && !isForeground && !isProfile;
- }
-
- /**
* In a car environment, {@link ActivityTaskManagerService#mShowDialogs} is always set to
* {@code false} from {@link ActivityTaskManagerService#updateShouldShowDialogsLocked}
* because its UI mode is {@link Configuration#UI_MODE_TYPE_CAR}. Thus, error dialogs are
@@ -7208,7 +7189,7 @@
* @see ActivityTaskManagerService#updateShouldShowDialogsLocked
*/
private boolean shouldShowDialogsForVisibleBackgroundUserLocked(int userId) {
- if (!isVisibleBackgroundUser(userId)) {
+ if (!mWindowManager.mUmInternal.isVisibleBackgroundFullUser(userId)) {
return false;
}
final int displayId = mWindowManager.mUmInternal.getMainDisplayAssignedToUser(userId);
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 25cb134..d2f3d1d 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -50,8 +50,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.utils.OptPropFactory;
-import java.util.function.Function;
-
/**
* Encapsulates app compat configurations and overrides related to aspect ratio.
*/
@@ -76,20 +74,20 @@
@NonNull
private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
@NonNull
- private final Function<Boolean, Boolean> mIsDisplayFullScreenAndInPostureProvider;
+ private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
@NonNull
- private final Function<Configuration, Float> mGetHorizontalPositionMultiplierProvider;
+ private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
AppCompatAspectRatioOverrides(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder,
- @NonNull Function<Boolean, Boolean> isDisplayFullScreenAndInPostureProvider,
- @NonNull Function<Configuration, Float> getHorizontalPositionMultiplierProvider) {
+ @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery,
+ @NonNull AppCompatReachabilityOverrides appCompatReachabilityOverrides) {
mActivityRecord = activityRecord;
mAppCompatConfiguration = appCompatConfiguration;
+ mAppCompatDeviceStateQuery = appCompatDeviceStateQuery;
mUserAspectRatioState = new UserAspectRatioState();
- mIsDisplayFullScreenAndInPostureProvider = isDisplayFullScreenAndInPostureProvider;
- mGetHorizontalPositionMultiplierProvider = getHorizontalPositionMultiplierProvider;
+ mAppCompatReachabilityOverrides = appCompatReachabilityOverrides;
mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create(
@@ -245,12 +243,13 @@
}
private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
- final boolean isBookMode = mIsDisplayFullScreenAndInPostureProvider
- .apply(/* isTabletop */false);
- final boolean isNotCenteredHorizontally = mGetHorizontalPositionMultiplierProvider.apply(
- parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER;
- final boolean isTabletopMode = mIsDisplayFullScreenAndInPostureProvider
- .apply(/* isTabletop */ true);
+ final boolean isBookMode = mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */false);
+ final boolean isNotCenteredHorizontally =
+ mAppCompatReachabilityOverrides.getHorizontalPositionMultiplier(parentConfiguration)
+ != LETTERBOX_POSITION_MULTIPLIER_CENTER;
+ final boolean isTabletopMode = mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ true);
final boolean isLandscape = isFixedOrientationLandscape(
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 54223b6..d38edfc 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -35,7 +35,11 @@
@NonNull
private final AppCompatAspectRatioPolicy mAppCompatAspectRatioPolicy;
@NonNull
+ private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+ @NonNull
private final AppCompatOverrides mAppCompatOverrides;
+ @NonNull
+ private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -43,13 +47,16 @@
final PackageManager packageManager = wmService.mContext.getPackageManager();
final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
activityRecord.packageName);
+ mAppCompatDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord);
mTransparentPolicy = new TransparentPolicy(activityRecord,
wmService.mAppCompatConfiguration);
mAppCompatOverrides = new AppCompatOverrides(activityRecord,
- wmService.mAppCompatConfiguration, optPropBuilder);
+ wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery);
mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
mTransparentPolicy, mAppCompatOverrides);
+ mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord,
+ wmService.mAppCompatConfiguration);
}
@NonNull
@@ -101,7 +108,23 @@
}
@NonNull
+ AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
+ return mAppCompatReachabilityPolicy;
+ }
+
+ @NonNull
AppCompatFocusOverrides getAppCompatFocusOverrides() {
return mAppCompatOverrides.getAppCompatFocusOverrides();
}
+
+ @NonNull
+ AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+ return mAppCompatOverrides.getAppCompatReachabilityOverrides();
+ }
+
+ @NonNull
+ AppCompatDeviceStateQuery getAppCompatDeviceStateQuery() {
+ return mAppCompatDeviceStateQuery;
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/AppCompatDeviceStateQuery.java b/services/core/java/com/android/server/wm/AppCompatDeviceStateQuery.java
new file mode 100644
index 0000000..3abea24
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatDeviceStateQuery.java
@@ -0,0 +1,64 @@
+/*
+ * 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.NonNull;
+
+/**
+ * Provides information about the current state of the display in relation of
+ * fold/unfold and other positions.
+ */
+class AppCompatDeviceStateQuery {
+
+ @NonNull
+ final ActivityRecord mActivityRecord;
+
+ AppCompatDeviceStateQuery(@NonNull ActivityRecord activityRecord) {
+ mActivityRecord = activityRecord;
+ }
+
+ /**
+ * Check if we are in the given pose and in fullscreen mode.
+ *
+ * Note that we check the task rather than the parent as with ActivityEmbedding the parent
+ * might be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ * actually fullscreen. If display is still in transition e.g. unfolding, don't return true
+ * for HALF_FOLDED state or app will flicker.
+ */
+ boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
+ final Task task = mActivityRecord.getTask();
+ final DisplayContent dc = mActivityRecord.mDisplayContent;
+ return dc != null && task != null && !dc.inTransition()
+ && dc.getDisplayRotation().isDeviceInPosture(
+ DeviceStateController.DeviceState.HALF_FOLDED, isTabletop)
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+ /**
+ * Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+ * be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ * actually fullscreen.
+ */
+ boolean isDisplayFullScreenAndSeparatingHinge() {
+ final Task task = mActivityRecord.getTask();
+ return mActivityRecord.mDisplayContent != null && task != null
+ && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 4450011..80bbee3 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -35,19 +35,22 @@
private final AppCompatFocusOverrides mAppCompatFocusOverrides;
@NonNull
private final AppCompatResizeOverrides mAppCompatResizeOverrides;
+ @NonNull
+ private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
AppCompatOverrides(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration,
- @NonNull OptPropFactory optPropBuilder) {
+ @NonNull OptPropFactory optPropBuilder,
+ @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
- // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability.
+ mAppCompatReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
+ appCompatConfiguration, appCompatDeviceStateQuery);
mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
- appCompatConfiguration, optPropBuilder,
- activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture,
- activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier);
+ appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery,
+ mAppCompatReachabilityOverrides);
mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder);
@@ -77,4 +80,9 @@
AppCompatResizeOverrides getAppCompatResizeOverrides() {
return mAppCompatResizeOverrides;
}
+
+ @NonNull
+ AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+ return mAppCompatReachabilityOverrides;
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
new file mode 100644
index 0000000..b9bdc32
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
@@ -0,0 +1,343 @@
+/*
+ * 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
+
+/**
+ * Encapsulate overrides and configurations about app compat reachability.
+ */
+class AppCompatReachabilityOverrides {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatConfiguration mAppCompatConfiguration;
+ @NonNull
+ private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
+ @NonNull
+ private final ReachabilityState mReachabilityState;
+
+ AppCompatReachabilityOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatConfiguration appCompatConfiguration,
+ @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
+ mActivityRecord = activityRecord;
+ mAppCompatConfiguration = appCompatConfiguration;
+ mAppCompatDeviceStateQuery = appCompatDeviceStateQuery;
+ mReachabilityState = new ReachabilityState();
+ }
+
+ boolean isFromDoubleTap() {
+ return mReachabilityState.isFromDoubleTap();
+ }
+
+ boolean isDoubleTapEvent() {
+ return mReachabilityState.mIsDoubleTapEvent;
+ }
+
+ void setDoubleTapEvent() {
+ mReachabilityState.mIsDoubleTapEvent = true;
+ }
+
+ /**
+ * Provides the multiplier to use when calculating the position of a letterboxed app after
+ * an horizontal reachability event (double tap). The method takes the current state of the
+ * device (e.g. device in book mode) into account.
+ * </p>
+ * @param parentConfiguration The parent {@link Configuration}.
+ * @return The value to use for calculating the letterbox horizontal position.
+ */
+ float getHorizontalPositionMultiplier(@NonNull Configuration parentConfiguration) {
+ // Don't check resolved configuration because it may not be updated yet during
+ // configuration change.
+ boolean bookModeEnabled = isFullScreenAndBookModeEnabled();
+ return isHorizontalReachabilityEnabled(parentConfiguration)
+ // Using the last global dynamic position to avoid "jumps" when moving
+ // between apps or activities.
+ ? mAppCompatConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled)
+ : mAppCompatConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled);
+ }
+
+ /**
+ * Provides the multiplier to use when calculating the position of a letterboxed app after
+ * a vertical reachability event (double tap). The method takes the current state of the
+ * device (e.g. device posture) into account.
+ * </p>
+ * @param parentConfiguration The parent {@link Configuration}.
+ * @return The value to use for calculating the letterbox horizontal position.
+ */
+ float getVerticalPositionMultiplier(@NonNull Configuration parentConfiguration) {
+ // Don't check resolved configuration because it may not be updated yet during
+ // configuration change.
+ boolean tabletopMode = mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ true);
+ return isVerticalReachabilityEnabled(parentConfiguration)
+ // Using the last global dynamic position to avoid "jumps" when moving
+ // between apps or activities.
+ ? mAppCompatConfiguration.getVerticalMultiplierForReachability(tabletopMode)
+ : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
+ }
+
+ @VisibleForTesting
+ boolean isHorizontalReachabilityEnabled() {
+ return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ }
+
+ @VisibleForTesting
+ boolean isVerticalReachabilityEnabled() {
+ return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ }
+
+ boolean isLetterboxDoubleTapEducationEnabled() {
+ return isHorizontalReachabilityEnabled() || isVerticalReachabilityEnabled();
+ }
+
+ @AppCompatConfiguration.LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ final boolean isInFullScreenTabletopMode =
+ mAppCompatDeviceStateQuery.isDisplayFullScreenAndSeparatingHinge();
+ return mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(
+ isInFullScreenTabletopMode);
+ }
+
+ @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ final boolean isInFullScreenBookMode = isFullScreenAndBookModeEnabled();
+ return mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(
+ isInFullScreenBookMode);
+ }
+
+ int getLetterboxPositionForLogging() {
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+ if (isHorizontalReachabilityEnabled()) {
+ int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForHorizontalReachability(mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ false));
+ positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPositionForLogging(
+ letterboxPositionForHorizontalReachability);
+ } else if (isVerticalReachabilityEnabled()) {
+ int letterboxPositionForVerticalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForVerticalReachability(mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ true));
+ positionToLog = letterboxVerticalReachabilityPositionToLetterboxPositionForLogging(
+ letterboxPositionForVerticalReachability);
+ }
+ return positionToLog;
+ }
+
+ /**
+ * @return {@value true} if the vertical reachability should be allowed in case of
+ * thin letterboxing.
+ */
+ boolean allowVerticalReachabilityForThinLetterbox() {
+ if (!Flags.disableThinLetterboxingPolicy()) {
+ return true;
+ }
+ // When the flag is enabled we allow vertical reachability only if the
+ // app is not thin letterboxed vertically.
+ return !isVerticalThinLetterboxed();
+ }
+
+ /**
+ * @return {@value true} if the horizontal reachability should be enabled in case of
+ * thin letterboxing.
+ */
+ boolean allowHorizontalReachabilityForThinLetterbox() {
+ if (!Flags.disableThinLetterboxingPolicy()) {
+ return true;
+ }
+ // When the flag is enabled we allow horizontal reachability only if the
+ // app is not thin pillarboxed.
+ return !isHorizontalThinLetterboxed();
+ }
+
+ /**
+ * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
+ */
+ boolean isVerticalThinLetterboxed() {
+ final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx();
+ if (thinHeight < 0) {
+ return false;
+ }
+ final Task task = mActivityRecord.getTask();
+ if (task == null) {
+ return false;
+ }
+ final int padding = Math.abs(
+ task.getBounds().height() - mActivityRecord.getBounds().height()) / 2;
+ return padding <= thinHeight;
+ }
+
+ /**
+ * @return {@value true} if the resulting app is pillarboxed in a way defined as thin.
+ */
+ boolean isHorizontalThinLetterboxed() {
+ final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx();
+ if (thinWidth < 0) {
+ return false;
+ }
+ final Task task = mActivityRecord.getTask();
+ if (task == null) {
+ return false;
+ }
+ final int padding = Math.abs(
+ task.getBounds().width() - mActivityRecord.getBounds().width()) / 2;
+ return padding <= thinWidth;
+ }
+
+ // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+ // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ // actually fullscreen.
+ private boolean isDisplayFullScreenAndSeparatingHinge() {
+ Task task = mActivityRecord.getTask();
+ return mActivityRecord.mDisplayContent != null
+ && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
+ && task != null
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+ private int letterboxHorizontalReachabilityPositionToLetterboxPositionForLogging(
+ @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox horizontal reachability position type: "
+ + position);
+ }
+ }
+
+ private int letterboxVerticalReachabilityPositionToLetterboxPositionForLogging(
+ @AppCompatConfiguration.LetterboxVerticalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox vertical reachability position type: "
+ + position);
+ }
+ }
+
+ private boolean isFullScreenAndBookModeEnabled() {
+ return mAppCompatDeviceStateQuery.isDisplayFullScreenAndInPosture(/* isTabletop */ false)
+ && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
+ }
+
+ /**
+ * Whether horizontal reachability is enabled for an activity in the current configuration.
+ *
+ * <p>Conditions that needs to be met:
+ * <ul>
+ * <li>Windowing mode is fullscreen.
+ * <li>Horizontal Reachability is enabled.
+ * <li>First top opaque activity fills parent vertically, but not horizontally.
+ * </ul>
+ */
+ private boolean isHorizontalReachabilityEnabled(@NonNull Configuration parentConfiguration) {
+ if (!allowHorizontalReachabilityForThinLetterbox()) {
+ return false;
+ }
+ final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
+ final Rect parentAppBounds = parentAppBoundsOverride != null
+ ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
+ // Use screen resolved bounds which uses resolved bounds or size compat bounds
+ // as activity bounds can sometimes be empty
+ final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
+ .getTransparentPolicy().getFirstOpaqueActivity()
+ .map(ActivityRecord::getScreenResolvedBounds)
+ .orElse(mActivityRecord.getScreenResolvedBounds());
+ return mAppCompatConfiguration.getIsHorizontalReachabilityEnabled()
+ && parentConfiguration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN
+ // Check whether the activity fills the parent vertically.
+ && parentAppBounds.height() <= opaqueActivityBounds.height()
+ && parentAppBounds.width() > opaqueActivityBounds.width();
+ }
+
+ /**
+ * Whether vertical reachability is enabled for an activity in the current configuration.
+ *
+ * <p>Conditions that needs to be met:
+ * <ul>
+ * <li>Windowing mode is fullscreen.
+ * <li>Vertical Reachability is enabled.
+ * <li>First top opaque activity fills parent horizontally but not vertically.
+ * </ul>
+ */
+ private boolean isVerticalReachabilityEnabled(@NonNull Configuration parentConfiguration) {
+ if (!allowVerticalReachabilityForThinLetterbox()) {
+ return false;
+ }
+ final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
+ final Rect parentAppBounds = parentAppBoundsOverride != null
+ ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
+ // Use screen resolved bounds which uses resolved bounds or size compat bounds
+ // as activity bounds can sometimes be empty.
+ final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
+ .getTransparentPolicy().getFirstOpaqueActivity()
+ .map(ActivityRecord::getScreenResolvedBounds)
+ .orElse(mActivityRecord.getScreenResolvedBounds());
+ return mAppCompatConfiguration.getIsVerticalReachabilityEnabled()
+ && parentConfiguration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN
+ // Check whether the activity fills the parent horizontally.
+ && parentAppBounds.width() <= opaqueActivityBounds.width()
+ && parentAppBounds.height() > opaqueActivityBounds.height();
+ }
+
+ private static class ReachabilityState {
+ // If the current event is a double tap.
+ private boolean mIsDoubleTapEvent;
+
+ boolean isFromDoubleTap() {
+ final boolean isFromDoubleTap = mIsDoubleTapEvent;
+ mIsDoubleTapEvent = false;
+ return isFromDoubleTap;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
new file mode 100644
index 0000000..e4e7654
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.function.Supplier;
+
+/**
+ * Encapsulate logic about app compat reachability.
+ */
+class AppCompatReachabilityPolicy {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatConfiguration mAppCompatConfiguration;
+
+ AppCompatReachabilityPolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatConfiguration appCompatConfiguration) {
+ mActivityRecord = activityRecord;
+ mAppCompatConfiguration = appCompatConfiguration;
+ }
+
+ @VisibleForTesting
+ void handleHorizontalDoubleTap(int x, @NonNull Supplier<Rect> innerFrameSupplier) {
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+ if (!reachabilityOverrides.isHorizontalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
+ return;
+ }
+ final Rect letterboxInnerFrame = innerFrameSupplier.get();
+ if (letterboxInnerFrame.left <= x && letterboxInnerFrame.right >= x) {
+ // Only react to clicks at the sides of the letterboxed app window.
+ return;
+ }
+ final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController
+ .getAppCompatDeviceStateQuery();
+ final boolean isInFullScreenBookMode = deviceStateQuery
+ .isDisplayFullScreenAndSeparatingHinge()
+ && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
+ final int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
+ if (letterboxInnerFrame.left > x) {
+ // Moving to the next stop on the left side of the app window: right > center > left.
+ mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextLeftStop(
+ isInFullScreenBookMode);
+ int letterboxPositionChangeForLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ } else if (letterboxInnerFrame.right < x) {
+ // Moving to the next stop on the right side of the app window: left > center > right.
+ mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
+ isInFullScreenBookMode);
+ final int letterboxPositionChangeForLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ }
+ // TODO(197549949): Add animation for transition.
+ mActivityRecord.recomputeConfiguration();
+ }
+
+ @VisibleForTesting
+ void handleVerticalDoubleTap(int y, @NonNull Supplier<Rect> innerFrameSupplier) {
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+ if (!reachabilityOverrides.isVerticalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
+ return;
+ }
+ final Rect letterboxInnerFrame = innerFrameSupplier.get();
+ if (letterboxInnerFrame.top <= y && letterboxInnerFrame.bottom >= y) {
+ // Only react to clicks at the top and bottom of the letterboxed app window.
+ return;
+ }
+ final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController
+ .getAppCompatDeviceStateQuery();
+ final boolean isInFullScreenTabletopMode = deviceStateQuery
+ .isDisplayFullScreenAndSeparatingHinge();
+ final int letterboxPositionForVerticalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode);
+ if (letterboxInnerFrame.top > y) {
+ // Moving to the next stop on the top side of the app window: bottom > center > top.
+ mAppCompatConfiguration.movePositionForVerticalReachabilityToNextTopStop(
+ isInFullScreenTabletopMode);
+ final int letterboxPositionChangeForLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ } else if (letterboxInnerFrame.bottom < y) {
+ // Moving to the next stop on the bottom side of the app window: top > center > bottom.
+ mAppCompatConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
+ isInFullScreenTabletopMode);
+ final int letterboxPositionChangeForLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ }
+ // TODO(197549949): Add animation for transition.
+ mActivityRecord.recomputeConfiguration();
+ }
+
+ /**
+ * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
+ */
+ private void logLetterboxPositionChange(int letterboxPositionChangeForLog) {
+ mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
+ .logLetterboxPositionChange(mActivityRecord, letterboxPositionChangeForLog);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index fd816067..8c5193e 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -19,7 +19,13 @@
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
+import android.app.CameraCompatTaskInfo;
+import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -82,4 +88,73 @@
static boolean isChangeEnabled(@NonNull ActivityRecord activityRecord, long overrideChangeId) {
return activityRecord.info.isChangeEnabled(overrideChangeId);
}
+
+ static void fillAppCompatTaskInfo(@NonNull Task task, @NonNull TaskInfo info,
+ @Nullable ActivityRecord top) {
+ final AppCompatTaskInfo appCompatTaskInfo = info.appCompatTaskInfo;
+ appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
+ appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
+ appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+ appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
+ appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
+ CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
+ if (top == null) {
+ return;
+ }
+ final AppCompatReachabilityOverrides reachabilityOverrides = top.mAppCompatController
+ .getAppCompatReachabilityOverrides();
+ final boolean isTopActivityResumed = top.getOrganizedTask() == task && top.isState(RESUMED);
+ final boolean isTopActivityVisible = top.getOrganizedTask() == task && top.isVisible();
+ // Whether the direct top activity is in size compat mode.
+ appCompatTaskInfo.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode();
+ if (appCompatTaskInfo.topActivityInSizeCompat
+ && top.mWmService.mAppCompatConfiguration.isTranslucentLetterboxingEnabled()) {
+ // We hide the restart button in case of transparent activities.
+ appCompatTaskInfo.topActivityInSizeCompat = top.fillsParent();
+ }
+ // Whether the direct top activity is eligible for letterbox education.
+ appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed
+ && top.isEligibleForLetterboxEducation();
+ appCompatTaskInfo.isLetterboxEducationEnabled = top.mLetterboxUiController
+ .isLetterboxEducationEnabled();
+
+ appCompatTaskInfo.isUserFullscreenOverrideEnabled = top.mAppCompatController
+ .getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
+ appCompatTaskInfo.isSystemFullscreenOverrideEnabled = top.mAppCompatController
+ .getAppCompatAspectRatioOverrides().isSystemOverrideToFullscreenEnabled();
+
+ appCompatTaskInfo.isFromLetterboxDoubleTap = reachabilityOverrides.isFromDoubleTap();
+ appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
+ appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+ // We need to consider if letterboxed or pillarboxed.
+ // TODO(b/336807329) Encapsulate reachability logic
+ appCompatTaskInfo.isLetterboxDoubleTapEnabled = reachabilityOverrides
+ .isLetterboxDoubleTapEducationEnabled();
+ if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ if (appCompatTaskInfo.isTopActivityPillarboxed()) {
+ if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
+ // Pillarboxed.
+ appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
+ reachabilityOverrides.getLetterboxPositionForHorizontalReachability();
+ } else {
+ appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
+ }
+ } else {
+ if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
+ // Letterboxed.
+ appCompatTaskInfo.topActivityLetterboxVerticalPosition =
+ reachabilityOverrides.getLetterboxPositionForVerticalReachability();
+ } else {
+ appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
+ }
+ }
+ }
+ appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton =
+ !info.isTopActivityTransparent && !appCompatTaskInfo.topActivityInSizeCompat
+ && top.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings();
+ appCompatTaskInfo.topActivityBoundsLetterboxed = top.areBoundsLetterboxed();
+ appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController
+ .getAppCompatCameraOverrides().getFreeformCameraCompatMode();
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 78636a7..5a0cbf3 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -250,7 +250,7 @@
ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
- if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringTransition()) {
+ if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringFinishTransition()) {
tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
if (mDisplayContent.mAtmService.mBackNavigationController
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 0f8d68b..924f765 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
@@ -47,6 +48,7 @@
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.RemoteAnimationTarget;
@@ -83,11 +85,6 @@
private AnimationHandler mAnimationHandler;
- /**
- * The transition who match the back navigation targets,
- * release animation after this transition finish.
- */
- private Transition mWaitTransitionFinish;
private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>();
@@ -143,7 +140,7 @@
BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder();
synchronized (wmService.mGlobalLock) {
- if (isMonitoringTransition()) {
+ if (isMonitoringFinishTransition()) {
Slog.w(TAG, "Previous animation hasn't finish, status: " + mAnimationHandler);
// Don't start any animation for it.
return null;
@@ -308,6 +305,7 @@
backType = BackNavigationInfo.TYPE_CALLBACK;
} else if (prevTask.isActivityTypeHome()) {
removedWindowContainer = currentTask;
+ prevTask = prevTask.getRootTask();
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
final ActivityRecord ar = prevTask.getTopNonFinishingActivity();
mShowWallpaper = ar != null && ar.hasWallpaper();
@@ -562,10 +560,15 @@
return !prevActivities.isEmpty();
}
- boolean isMonitoringTransition() {
+ boolean isMonitoringFinishTransition() {
return mAnimationHandler.mComposed || mNavigationMonitor.isMonitorForRemote();
}
+ boolean isMonitoringPrepareTransition(Transition transition) {
+ return mAnimationHandler.mComposed
+ && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == transition;
+ }
+
private void scheduleAnimation(@NonNull AnimationHandler.ScheduleAnimationBuilder builder) {
mPendingAnimation = builder.build();
mWindowManagerService.mWindowPlacerLocked.requestTraversal();
@@ -576,7 +579,9 @@
}
private boolean isWaitBackTransition() {
- return mAnimationHandler.mComposed && mAnimationHandler.mWaitTransition;
+ // Ignore mWaitTransition while flag is enabled.
+ return mAnimationHandler.mComposed && (Flags.migratePredictiveBackTransition()
+ || mAnimationHandler.mWaitTransition);
}
boolean isKeyguardOccluded(WindowState focusWindow) {
@@ -626,7 +631,7 @@
*/
boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
ArraySet<ActivityRecord> closeApps) {
- if (!isMonitoringTransition()) {
+ if (!isMonitoringFinishTransition()) {
return false;
}
mTmpCloseApps.addAll(closeApps);
@@ -652,7 +657,6 @@
final ActivityRecord ar = openApps.valueAt(i);
if (mAnimationHandler.isTarget(ar, true /* open */)) {
openApps.removeAt(i);
- mAnimationHandler.markStartingSurfaceMatch(null /* reparentTransaction */);
}
}
for (int i = closeApps.size() - 1; i >= 0; --i) {
@@ -670,6 +674,12 @@
mAnimationHandler.markWindowHasDrawn(openActivity);
}
+ boolean isStartingSurfaceShown(ActivityRecord openActivity) {
+ if (!Flags.migratePredictiveBackTransition()) {
+ return false;
+ }
+ return mAnimationHandler.isStartingSurfaceDrawn(openActivity);
+ }
@VisibleForTesting
class NavigationMonitor {
// The window which triggering the back navigation.
@@ -767,8 +777,14 @@
* open or close list.
*/
void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets,
- SurfaceControl.Transaction startTransaction) {
- if (!isMonitoringTransition() || targets.isEmpty()) {
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ if (isMonitoringPrepareTransition(transition)) {
+ // Flag target matches and prepare to remove windowless surface.
+ mAnimationHandler.markStartingSurfaceMatch(startTransaction);
+ return;
+ }
+ if (!isMonitoringFinishTransition() || targets.isEmpty()) {
return;
}
if (mAnimationHandler.hasTargetDetached()) {
@@ -801,42 +817,40 @@
if (!matchAnimationTargets) {
mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
} else {
- if (mWaitTransitionFinish != null) {
+ if (mAnimationHandler.mPrepareCloseTransition != null) {
Slog.e(TAG, "Gesture animation is applied on another transition?");
}
- mWaitTransitionFinish = transition;
- // Flag target matches to defer remove the splash screen.
- for (int i = mTmpOpenApps.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mTmpOpenApps.get(i);
- if (mAnimationHandler.isTarget(wc, true /* open */)) {
- mAnimationHandler.markStartingSurfaceMatch(startTransaction);
- break;
- }
+ mAnimationHandler.mPrepareCloseTransition = transition;
+ if (!Flags.migratePredictiveBackTransition()) {
+ // Because the target will reparent to transition root, so it cannot be controlled
+ // by animation leash. Hide the close target when transition starts.
+ startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
}
+ // Flag target matches and prepare to remove windowless surface.
+ mAnimationHandler.markStartingSurfaceMatch(startTransaction);
// release animation leash
if (mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction != null) {
- startTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction);
+ finishTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction);
mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction = null;
}
- // Because the target will reparent to transition root, so it cannot be controlled by
- // animation leash. Hide the close target when transition starts.
- startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
}
mTmpOpenApps.clear();
mTmpCloseApps.clear();
}
boolean isMonitorTransitionTarget(WindowContainer wc) {
- if (!isWaitBackTransition() || mWaitTransitionFinish == null) {
- return false;
+ if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
+ || (mAnimationHandler.mOpenAnimAdaptor != null
+ && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null)) {
+ return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
}
- return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
+ return false;
}
boolean shouldPauseTouch(WindowContainer wc) {
// Once the close transition is ready, it means the onBackInvoked callback has invoked, and
// app is ready to trigger next transition, no matter what it will be.
- return mAnimationHandler.mComposed && mWaitTransitionFinish == null
+ return mAnimationHandler.mComposed && mAnimationHandler.mPrepareCloseTransition == null
&& mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
}
@@ -847,7 +861,6 @@
void clearBackAnimations(boolean cancel) {
mAnimationHandler.clearBackAnimateTarget(cancel);
mNavigationMonitor.stopMonitorTransition();
- mWaitTransitionFinish = null;
}
/**
@@ -858,7 +871,13 @@
*/
void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
@NonNull Transition finishedTransition) {
- if (finishedTransition == mWaitTransitionFinish) {
+ if (isMonitoringPrepareTransition(finishedTransition)) {
+ if (mAnimationHandler.mPrepareCloseTransition == null) {
+ clearBackAnimations(true /* cancel */);
+ }
+ return;
+ }
+ if (finishedTransition == mAnimationHandler.mPrepareCloseTransition) {
clearBackAnimations(false /* cancel */);
}
if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
@@ -938,6 +957,7 @@
// the opening target like starting window do.
private boolean mStartingSurfaceTargetMatch;
private ActivityRecord[] mOpenActivities;
+ Transition mPrepareCloseTransition;
AnimationHandler(WindowManagerService wms) {
mWindowManagerService = wms;
@@ -982,6 +1002,12 @@
@NonNull ActivityRecord[] openingActivities) {
if (isActivitySwitch(close, open)) {
mSwitchType = ACTIVITY_SWITCH;
+ if (Flags.migratePredictiveBackTransition()) {
+ final Pair<WindowContainer, WindowContainer[]> replaced =
+ promoteToTFIfNeeded(close, open);
+ close = replaced.first;
+ open = replaced.second;
+ }
} else if (isTaskSwitch(close, open)) {
mSwitchType = TASK_SWITCH;
} else if (isDialogClose(close)) {
@@ -1019,6 +1045,34 @@
mOpenActivities = openingActivities;
}
+ private Pair<WindowContainer, WindowContainer[]> promoteToTFIfNeeded(
+ WindowContainer close, WindowContainer[] open) {
+ WindowContainer replaceClose = close;
+ TaskFragment closeTF = close.asActivityRecord().getTaskFragment();
+ if (closeTF != null && !closeTF.isEmbedded()) {
+ closeTF = null;
+ }
+ final WindowContainer[] replaceOpen = new WindowContainer[open.length];
+ if (open.length >= 2) { // Promote to TaskFragment
+ for (int i = open.length - 1; i >= 0; --i) {
+ replaceOpen[i] = open[i].asActivityRecord().getTaskFragment();
+ replaceClose = closeTF != null ? closeTF : close;
+ }
+ } else {
+ TaskFragment openTF = open[0].asActivityRecord().getTaskFragment();
+ if (openTF != null && !openTF.isEmbedded()) {
+ openTF = null;
+ }
+ if (closeTF != openTF) {
+ replaceOpen[0] = openTF != null ? openTF : open[0];
+ replaceClose = closeTF != null ? closeTF : close;
+ } else {
+ replaceOpen[0] = open[0];
+ }
+ }
+ return new Pair<>(replaceClose, replaceOpen);
+ }
+
private boolean composeAnimations(@NonNull WindowContainer close,
@NonNull WindowContainer[] open, @NonNull ActivityRecord[] openingActivities) {
if (mComposed || mWaitTransition) {
@@ -1080,7 +1134,8 @@
}
void markWindowHasDrawn(ActivityRecord activity) {
- if (!mComposed || mWaitTransition) {
+ if (!mComposed || mWaitTransition || mOpenAnimAdaptor.mPreparedOpenTransition == null
+ || mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
return;
}
boolean allWindowDrawn = true;
@@ -1096,6 +1151,17 @@
}
}
+ boolean isStartingSurfaceDrawn(ActivityRecord activity) {
+ // Check whether we create windowless surface to prepare open transition
+ if (!mComposed || mOpenAnimAdaptor.mPreparedOpenTransition == null) {
+ return false;
+ }
+ if (isTarget(activity, true /* open */)) {
+ return mOpenAnimAdaptor.mStartingSurface != null;
+ }
+ return false;
+ }
+
private static boolean isAnimateTarget(@NonNull WindowContainer window,
@NonNull WindowContainer animationTarget, int switchType) {
if (switchType == TASK_SWITCH) {
@@ -1109,7 +1175,9 @@
&& window.hasChild(animationTarget));
} else if (switchType == ACTIVITY_SWITCH) {
return window == animationTarget
- || (window.asTaskFragment() != null && window.hasChild(animationTarget));
+ || (window.asTaskFragment() != null && window.hasChild(animationTarget))
+ || (animationTarget.asTaskFragment() != null
+ && animationTarget.hasChild(window));
}
return false;
}
@@ -1122,8 +1190,14 @@
resetActivity.mDisplayContent
.continueUpdateOrientationForDiffOrienLaunchingApp();
}
+ final Transition finishTransition =
+ resetActivity.mTransitionController.mFinishingTransition;
+ final boolean inFinishTransition = finishTransition != null
+ && (mPrepareCloseTransition == finishTransition
+ || (mOpenAnimAdaptor != null
+ && mOpenAnimAdaptor.mPreparedOpenTransition == finishTransition));
if (resetActivity.mLaunchTaskBehind) {
- restoreLaunchBehind(resetActivity, cancel);
+ restoreLaunchBehind(resetActivity, cancel, inFinishTransition);
}
}
}
@@ -1137,12 +1211,25 @@
}
}
- void markStartingSurfaceMatch(SurfaceControl.Transaction reparentTransaction) {
+ void markStartingSurfaceMatch(SurfaceControl.Transaction startTransaction) {
if (mStartingSurfaceTargetMatch) {
return;
}
mStartingSurfaceTargetMatch = true;
- mOpenAnimAdaptor.reparentWindowlessSurfaceToTarget(reparentTransaction);
+
+ if (mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
+ }
+ final SurfaceControl startingSurface = mOpenAnimAdaptor.mStartingSurface;
+ if (startingSurface != null && startingSurface.isValid()) {
+ startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
+ synchronized (mWindowManagerService.mGlobalLock) {
+ if (mOpenAnimAdaptor != null) {
+ mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
+ }
+ }
+ });
+ }
}
void clearBackAnimateTarget(boolean cancel) {
@@ -1150,6 +1237,7 @@
mComposed = false;
finishPresentAnimations(cancel);
}
+ mPrepareCloseTransition = null;
mWaitTransition = false;
mStartingSurfaceTargetMatch = false;
mSwitchType = UNKNOWN;
@@ -1239,6 +1327,9 @@
// requested one during animating.
private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
private SurfaceControl mStartingSurface;
+
+ private Transition mPreparedOpenTransition;
+
BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
@NonNull WindowContainer... targets) {
mAdaptors = new BackWindowAnimationAdaptor[targets.length];
@@ -1267,6 +1358,8 @@
mCloseTransaction.apply();
mCloseTransaction = null;
}
+
+ mPreparedOpenTransition = null;
}
private RemoteAnimationTarget createWrapTarget() {
@@ -1279,8 +1372,7 @@
unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
}
final WindowContainer wc = mAdaptors[0].mTarget;
- final Task task = wc.asActivityRecord() != null
- ? wc.asActivityRecord().getTask() : wc.asTask();
+ final Task task = mAdaptors[0].getTopTask();
final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
final SurfaceControl leashSurface = new SurfaceControl.Builder()
.setName("cross-animation-leash")
@@ -1293,7 +1385,7 @@
mCloseTransaction = new SurfaceControl.Transaction();
mCloseTransaction.reparent(leashSurface, null);
final SurfaceControl.Transaction pt = wc.getPendingTransaction();
- pt.setLayer(leashSurface, wc.getParent().getLastLayer());
+ pt.setLayer(leashSurface, wc.getLastLayer());
for (int i = mAdaptors.length - 1; i >= 0; --i) {
BackWindowAnimationAdaptor adaptor = mAdaptors[i];
pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
@@ -1323,15 +1415,20 @@
}
final WindowContainer mainOpen = mAdaptors[0].mTarget;
final int switchType = mAdaptors[0].mSwitchType;
- final Task openTask = switchType == TASK_SWITCH
- ? mainOpen.asTask() : switchType == ACTIVITY_SWITCH
- ? mainOpen.asActivityRecord().getTask() : null;
+ final Task openTask = mAdaptors[0].getTopTask();
if (openTask == null) {
return;
}
- final ActivityRecord mainActivity = switchType == ACTIVITY_SWITCH
- ? mainOpen.asActivityRecord()
- : openTask.getTopNonFinishingActivity();
+ ActivityRecord mainActivity = null;
+ if (switchType == ACTIVITY_SWITCH) {
+ mainActivity = mainOpen.asActivityRecord();
+ if (mainActivity == null && mainOpen.asTaskFragment() != null) {
+ mainActivity = mainOpen.asTaskFragment().getTopNonFinishingActivity();
+ }
+ }
+ if (mainActivity == null) {
+ mainActivity = openTask.getTopNonFinishingActivity();
+ }
if (mainActivity == null) {
return;
}
@@ -1363,6 +1460,8 @@
synchronized (openTask.mWmService.mGlobalLock) {
if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
mStartingSurface = sc;
+ openTask.mWmService.mWindowPlacerLocked
+ .requestTraversal();
} else {
sc.release();
}
@@ -1371,28 +1470,6 @@
});
}
- // When back gesture has triggered and transition target matches navigation target,
- // reparent the starting surface to the opening target as it's starting window.
- void reparentWindowlessSurfaceToTarget(SurfaceControl.Transaction reparentTransaction) {
- if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
- return;
- }
- // If open target matches, reparent to open activity or task
- if (mStartingSurface != null && mStartingSurface.isValid()) {
- SurfaceControl.Transaction transaction = reparentTransaction != null
- ? reparentTransaction : mAdaptors[0].mTarget.getPendingTransaction();
- if (mAdaptors.length != 1) {
- // More than one opening window, reparent starting surface to leaf task.
- final WindowContainer wc = mAdaptors[0].mTarget;
- final Task task = wc.asActivityRecord() != null
- ? wc.asActivityRecord().getTask() : wc.asTask();
- transaction.reparent(mStartingSurface, task != null
- ? task.getSurfaceControl()
- : mAdaptors[0].mTarget.getSurfaceControl());
- }
- }
- }
-
/**
* Ask shell to clear the starting surface.
* @param openTransitionMatch if true, shell will play the remove starting window
@@ -1430,6 +1507,22 @@
mSwitchType = switchType;
}
+ Task getTopTask() {
+ final Task asTask = mTarget.asTask();
+ if (asTask != null) {
+ return asTask;
+ }
+ final ActivityRecord ar = mTarget.asActivityRecord();
+ if (ar != null) {
+ return ar.getTask();
+ }
+ final TaskFragment tf = mTarget.asTaskFragment();
+ if (tf != null) {
+ return tf.getTask();
+ }
+ return null;
+ }
+
@Override
public boolean getShowWallpaper() {
return false;
@@ -1619,9 +1712,8 @@
needsLaunchBehind = snapshot == null;
}
if (needsLaunchBehind) {
- for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
- setLaunchBehind(visibleOpenActivities[i]);
- }
+ openAnimationAdaptor.mPreparedOpenTransition =
+ setLaunchBehind(visibleOpenActivities);
}
// Force update mLastSurfaceShowing for opening activity and its task.
if (mWindowManagerService.mRoot.mTransitionController.isShellTransitionsEnabled()) {
@@ -1677,13 +1769,22 @@
// animation was canceled
return;
}
- if (!triggerBack) {
- clearBackAnimateTarget(true /* cancel */);
+ if (Flags.migratePredictiveBackTransition()) {
+ if (mOpenAnimAdaptor == null
+ || mOpenAnimAdaptor.mPreparedOpenTransition == null) {
+ // no open nor close transition, this is window animation
+ if (!triggerBack) {
+ clearBackAnimateTarget(true /* cancel */);
+ }
+ }
} else {
- mWaitTransition = true;
+ if (!triggerBack) {
+ clearBackAnimateTarget(true /* cancel */);
+ } else {
+ mWaitTransition = true;
+ }
}
}
- // TODO Add timeout monitor if transition didn't happen
}
};
}
@@ -1740,28 +1841,75 @@
return openActivities;
}
- private static void setLaunchBehind(@NonNull ActivityRecord activity) {
- if (!activity.isVisibleRequested()) {
- // The transition could commit the visibility and in the finishing state, that could
- // skip commitVisibility call in setVisibility cause the activity won't visible here.
- // Call it again to make sure the activity could be visible while handling the pending
- // animation.
- // Do not performLayout during prepare animation, because it could cause focus window
- // change. Let that happen after the BackNavigationInfo has returned to shell.
- activity.commitVisibility(true, false /* performLayout */);
+ private static Transition setLaunchBehind(@NonNull ActivityRecord[] activities) {
+ final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
+ final ArrayList<ActivityRecord> affects = new ArrayList<>();
+ for (int i = activities.length - 1; i >= 0; --i) {
+ final ActivityRecord activity = activities[i];
+ if (activity.mLaunchTaskBehind || activity.isVisibleRequested()) {
+ continue;
+ }
+ affects.add(activity);
+ }
+ if (affects.isEmpty()) {
+ return null;
+ }
+
+ final TransitionController tc = activities[0].mTransitionController;
+ final Transition prepareOpen = migrateBackTransition && !tc.isCollecting()
+ ? tc.createTransition(TRANSIT_PREPARE_BACK_NAVIGATION) : null;
+
+ for (int i = affects.size() - 1; i >= 0; --i) {
+ final ActivityRecord activity = affects.get(i);
+ if (!migrateBackTransition && !activity.isVisibleRequested()) {
+ // The transition could commit the visibility and in the finishing state, that could
+ // skip commitVisibility call in setVisibility cause the activity won't visible
+ // here.
+ // Call it again to make sure the activity could be visible while handling the
+ // pending animation.
+ // Do not performLayout during prepare animation, because it could cause focus
+ // window change. Let that happen after the BackNavigationInfo has returned to
+ // shell.
+ activity.commitVisibility(true, false /* performLayout */);
+ }
activity.mTransitionController.mSnapshotController
.mActivitySnapshotController.addOnBackPressedActivity(activity);
- }
- activity.mLaunchTaskBehind = true;
+ activity.mLaunchTaskBehind = true;
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
- activity.mTaskSupervisor.mStoppingActivities.remove(activity);
- activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
- true /* notifyClients */);
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
+ activity.mTaskSupervisor.mStoppingActivities.remove(activity);
+
+ if (!migrateBackTransition) {
+ activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
+ true /* notifyClients */);
+ } else if (activity.shouldBeVisible()) {
+ activity.ensureActivityConfiguration(true /* ignoreVisibility */);
+ activity.makeVisibleIfNeeded(null /* starting */, true /* notifyToClient */);
+ }
+ }
+ boolean needTransition = false;
+ final DisplayContent dc = affects.get(0).getDisplayContent();
+ for (int i = affects.size() - 1; i >= 0; --i) {
+ final ActivityRecord activity = affects.get(i);
+ needTransition |= tc.isCollecting(activity);
+ }
+ if (prepareOpen != null) {
+ if (needTransition) {
+ tc.requestStartTransition(prepareOpen,
+ null /*startTask */, null /* remoteTransition */,
+ null /* displayChange */);
+ tc.setReady(dc);
+ return prepareOpen;
+ } else {
+ prepareOpen.abort();
+ }
+ }
+ return null;
}
- private static void restoreLaunchBehind(@NonNull ActivityRecord activity, boolean cancel) {
+ private static void restoreLaunchBehind(@NonNull ActivityRecord activity, boolean cancel,
+ boolean finishTransition) {
if (!activity.isAttached()) {
// The activity was detached from hierarchy.
return;
@@ -1771,9 +1919,15 @@
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
activity);
if (cancel) {
- // Restore the launch-behind state
- // TODO b/347168362 Change status directly during collecting for a transition.
- activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
+ final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
+ if (migrateBackTransition && finishTransition) {
+ activity.commitVisibility(false /* visible */, false /* performLayout */,
+ true /* fromTransition */);
+ } else {
+ // Restore the launch-behind state
+ // TODO b/347168362 Change status directly during collecting for a transition.
+ activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
+ }
// Ignore all change
activity.mTransitionController.mSnapshotController
.mActivitySnapshotController.clearOnBackPressedActivities();
@@ -1835,12 +1989,7 @@
&& ah.mSwitchType != AnimationHandler.ACTIVITY_SWITCH)) {
return;
}
- for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
- final ActivityRecord preDrawActivity = mAnimationHandler.mOpenActivities[i];
- if (!preDrawActivity.mLaunchTaskBehind) {
- setLaunchBehind(preDrawActivity);
- }
- }
+ setLaunchBehind(mAnimationHandler.mOpenActivities);
}
}
}
@@ -1853,10 +2002,15 @@
snapshot = task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
task.mTaskId, task.mUserId, false /* restoreFromDisk */,
false /* isLowResolution */);
- } else if (w.asActivityRecord() != null) {
- final ActivityRecord ar = w.asActivityRecord();
- snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
- .getSnapshot(visibleOpenActivities);
+ } else {
+ ActivityRecord ar = w.asActivityRecord();
+ if (ar == null && w.asTaskFragment() != null) {
+ ar = w.asTaskFragment().getTopNonFinishingActivity();
+ }
+ if (ar != null) {
+ snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
+ .getSnapshot(visibleOpenActivities);
+ }
}
return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 27e6e09..7135c3b 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -44,7 +44,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider;
-import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -145,9 +144,6 @@
* @see #DATA_DISPLAY_SETTINGS_FILE_PATH
*/
void setOverrideSettingsForUser(@UserIdInt int userId) {
- if (!Flags.perUserDisplayWindowSettings()) {
- return;
- }
final AtomicFile settingsFile = getOverrideSettingsFileForUser(userId);
setOverrideSettingsStorage(new AtomicFileStorage(settingsFile));
}
@@ -165,9 +161,6 @@
*/
void removeStaleDisplaySettingsLocked(@NonNull WindowManagerService wms,
@NonNull RootWindowContainer root) {
- if (!Flags.perUserDisplayWindowSettings()) {
- return;
- }
final Set<String> displayIdentifiers = new ArraySet<>();
final Consumer<DisplayInfo> addDisplayIdentifier =
displayInfo -> displayIdentifiers.add(mOverrideSettings.getIdentifier(displayInfo));
@@ -403,12 +396,9 @@
@NonNull
private static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) {
- final File directory;
- if (userId == USER_SYSTEM || !Flags.perUserDisplayWindowSettings()) {
- directory = Environment.getDataDirectory();
- } else {
- directory = Environment.getDataSystemCeDirectory(userId);
- }
+ final File directory = (userId == USER_SYSTEM)
+ ? Environment.getDataDirectory()
+ : Environment.getDataSystemCeDirectory(userId);
final File overrideSettingsFile = new File(directory, DATA_DISPLAY_SETTINGS_FILE_PATH);
return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 291eab1..eb8a637 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -16,37 +16,16 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString;
import android.annotation.NonNull;
@@ -68,7 +47,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -92,8 +70,6 @@
private boolean mLastShouldShowLetterboxUi;
- private boolean mDoubleTapEvent;
-
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mAppCompatConfiguration = wmService.mAppCompatConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
@@ -231,7 +207,8 @@
.getTransparentPolicy().isRunning()
? mActivityRecord.getBounds() : w.getFrame();
mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
- if (mDoubleTapEvent) {
+ if (mActivityRecord.mAppCompatController
+ .getAppCompatReachabilityOverrides().isDoubleTapEvent()) {
// We need to notify Shell that letterbox position has changed.
mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
}
@@ -240,12 +217,6 @@
}
}
- boolean isFromDoubleTap() {
- final boolean isFromDoubleTap = mDoubleTapEvent;
- mDoubleTapEvent = false;
- return isFromDoubleTap;
- }
-
SurfaceControl getLetterboxParentSurface() {
if (mActivityRecord.isInLetterboxAnimation()) {
return mActivityRecord.getTask().getSurfaceControl();
@@ -272,309 +243,35 @@
&& mActivityRecord.fillsParent();
}
- // Check if we are in the given pose and in fullscreen mode.
- // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
- // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
- // actually fullscreen. If display is still in transition e.g. unfolding, don't return true
- // for HALF_FOLDED state or app will flicker.
- boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
- Task task = mActivityRecord.getTask();
- return mActivityRecord.mDisplayContent != null && task != null
- && mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture(
- DeviceStateController.DeviceState.HALF_FOLDED, isTabletop)
- && !mActivityRecord.mDisplayContent.inTransition()
- && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ float getHorizontalPositionMultiplier(@NonNull Configuration parentConfiguration) {
+ return mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
+ .getHorizontalPositionMultiplier(parentConfiguration);
}
- // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
- // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
- // actually fullscreen.
- private boolean isDisplayFullScreenAndSeparatingHinge() {
- Task task = mActivityRecord.getTask();
- return mActivityRecord.mDisplayContent != null
- && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
- && task != null
- && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
- }
-
-
- float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
- // Don't check resolved configuration because it may not be updated yet during
- // configuration change.
- boolean bookModeEnabled = isFullScreenAndBookModeEnabled();
- return isHorizontalReachabilityEnabled(parentConfiguration)
- // Using the last global dynamic position to avoid "jumps" when moving
- // between apps or activities.
- ? mAppCompatConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled)
- : mAppCompatConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled);
- }
-
- private boolean isFullScreenAndBookModeEnabled() {
- return isDisplayFullScreenAndInPosture(/* isTabletop */ false)
- && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
- }
-
- float getVerticalPositionMultiplier(Configuration parentConfiguration) {
- // Don't check resolved configuration because it may not be updated yet during
- // configuration change.
- boolean tabletopMode = isDisplayFullScreenAndInPosture(/* isTabletop */ true);
- return isVerticalReachabilityEnabled(parentConfiguration)
- // Using the last global dynamic position to avoid "jumps" when moving
- // between apps or activities.
- ? mAppCompatConfiguration.getVerticalMultiplierForReachability(tabletopMode)
- : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
+ float getVerticalPositionMultiplier(@NonNull Configuration parentConfiguration) {
+ return mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
+ .getVerticalPositionMultiplier(parentConfiguration);
}
boolean isLetterboxEducationEnabled() {
return mAppCompatConfiguration.getIsEducationEnabled();
}
- /**
- * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
- */
- boolean isVerticalThinLetterboxed() {
- final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx();
- if (thinHeight < 0) {
- return false;
- }
- final Task task = mActivityRecord.getTask();
- if (task == null) {
- return false;
- }
- final int padding = Math.abs(
- task.getBounds().height() - mActivityRecord.getBounds().height()) / 2;
- return padding <= thinHeight;
- }
-
- /**
- * @return {@value true} if the resulting app is pillarboxed in a way defined as thin.
- */
- boolean isHorizontalThinLetterboxed() {
- final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx();
- if (thinWidth < 0) {
- return false;
- }
- final Task task = mActivityRecord.getTask();
- if (task == null) {
- return false;
- }
- final int padding = Math.abs(
- task.getBounds().width() - mActivityRecord.getBounds().width()) / 2;
- return padding <= thinWidth;
- }
-
-
- /**
- * @return {@value true} if the vertical reachability should be allowed in case of
- * thin letteboxing
- */
- boolean allowVerticalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingPolicy()) {
- return true;
- }
- // When the flag is enabled we allow vertical reachability only if the
- // app is not thin letterboxed vertically.
- return !isVerticalThinLetterboxed();
- }
-
- /**
- * @return {@value true} if the vertical reachability should be enabled in case of
- * thin letteboxing
- */
- boolean allowHorizontalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingPolicy()) {
- return true;
- }
- // When the flag is enabled we allow horizontal reachability only if the
- // app is not thin pillarboxed.
- return !isHorizontalThinLetterboxed();
- }
-
- boolean shouldOverrideMinAspectRatio() {
- return mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio();
- }
-
- @AppCompatConfiguration.LetterboxVerticalReachabilityPosition
- int getLetterboxPositionForVerticalReachability() {
- final boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge();
- return mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(
- isInFullScreenTabletopMode);
- }
-
- @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition
- int getLetterboxPositionForHorizontalReachability() {
- final boolean isInFullScreenBookMode = isFullScreenAndBookModeEnabled();
- return mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(
- isInFullScreenBookMode);
- }
-
@VisibleForTesting
void handleHorizontalDoubleTap(int x) {
- if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) {
- return;
- }
-
- if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) {
- // Only react to clicks at the sides of the letterboxed app window.
- return;
- }
-
- boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge()
- && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
- int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
- .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
- if (mLetterbox.getInnerFrame().left > x) {
- // Moving to the next stop on the left side of the app window: right > center > left.
- mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextLeftStop(
- isInFullScreenBookMode);
- int changeToLog =
- letterboxPositionForHorizontalReachability
- == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- } else if (mLetterbox.getInnerFrame().right < x) {
- // Moving to the next stop on the right side of the app window: left > center > right.
- mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
- isInFullScreenBookMode);
- int changeToLog =
- letterboxPositionForHorizontalReachability
- == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- }
- // TODO(197549949): Add animation for transition.
- mActivityRecord.recomputeConfiguration();
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ .handleHorizontalDoubleTap(x, mLetterbox::getInnerFrame);
}
@VisibleForTesting
void handleVerticalDoubleTap(int y) {
- if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) {
- return;
- }
-
- if (mLetterbox.getInnerFrame().top <= y && mLetterbox.getInnerFrame().bottom >= y) {
- // Only react to clicks at the top and bottom of the letterboxed app window.
- return;
- }
- boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge();
- int letterboxPositionForVerticalReachability = mAppCompatConfiguration
- .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode);
- if (mLetterbox.getInnerFrame().top > y) {
- // Moving to the next stop on the top side of the app window: bottom > center > top.
- mAppCompatConfiguration.movePositionForVerticalReachabilityToNextTopStop(
- isInFullScreenTabletopMode);
- int changeToLog =
- letterboxPositionForVerticalReachability
- == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- } else if (mLetterbox.getInnerFrame().bottom < y) {
- // Moving to the next stop on the bottom side of the app window: top > center > bottom.
- mAppCompatConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
- isInFullScreenTabletopMode);
- int changeToLog =
- letterboxPositionForVerticalReachability
- == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- }
- // TODO(197549949): Add animation for transition.
- mActivityRecord.recomputeConfiguration();
- }
-
- /**
- * Whether horizontal reachability is enabled for an activity in the current configuration.
- *
- * <p>Conditions that needs to be met:
- * <ul>
- * <li>Windowing mode is fullscreen.
- * <li>Horizontal Reachability is enabled.
- * <li>First top opaque activity fills parent vertically, but not horizontally.
- * </ul>
- */
- private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
- if (!allowHorizontalReachabilityForThinLetterbox()) {
- return false;
- }
- final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
- final Rect parentAppBounds = parentAppBoundsOverride != null
- ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
- // Use screen resolved bounds which uses resolved bounds or size compat bounds
- // as activity bounds can sometimes be empty
- final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
- .getTransparentPolicy().getFirstOpaqueActivity()
- .map(ActivityRecord::getScreenResolvedBounds)
- .orElse(mActivityRecord.getScreenResolvedBounds());
- return mAppCompatConfiguration.getIsHorizontalReachabilityEnabled()
- && parentConfiguration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN
- // Check whether the activity fills the parent vertically.
- && parentAppBounds.height() <= opaqueActivityBounds.height()
- && parentAppBounds.width() > opaqueActivityBounds.width();
- }
-
- @VisibleForTesting
- boolean isHorizontalReachabilityEnabled() {
- return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
- }
-
- boolean isLetterboxDoubleTapEducationEnabled() {
- return isHorizontalReachabilityEnabled() || isVerticalReachabilityEnabled();
- }
-
- // TODO(b/346264992): Remove after AppCompatController refactoring
- private AppCompatOverrides getAppCompatOverrides() {
- return mActivityRecord.mAppCompatController.getAppCompatOverrides();
- }
-
- /**
- * Whether vertical reachability is enabled for an activity in the current configuration.
- *
- * <p>Conditions that needs to be met:
- * <ul>
- * <li>Windowing mode is fullscreen.
- * <li>Vertical Reachability is enabled.
- * <li>First top opaque activity fills parent horizontally but not vertically.
- * </ul>
- */
- private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
- if (!allowVerticalReachabilityForThinLetterbox()) {
- return false;
- }
- final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
- final Rect parentAppBounds = parentAppBoundsOverride != null
- ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
- // Use screen resolved bounds which uses resolved bounds or size compat bounds
- // as activity bounds can sometimes be empty.
- final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
- .getTransparentPolicy().getFirstOpaqueActivity()
- .map(ActivityRecord::getScreenResolvedBounds)
- .orElse(mActivityRecord.getScreenResolvedBounds());
- return mAppCompatConfiguration.getIsVerticalReachabilityEnabled()
- && parentConfiguration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN
- // Check whether the activity fills the parent horizontally.
- && parentAppBounds.width() <= opaqueActivityBounds.width()
- && parentAppBounds.height() > opaqueActivityBounds.height();
- }
-
- @VisibleForTesting
- boolean isVerticalReachabilityEnabled() {
- return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ .handleVerticalDoubleTap(y, mLetterbox::getInnerFrame);
}
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- if (getAppCompatOverrides().getAppCompatOrientationOverrides()
+ if (mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged()) {
return mLastShouldShowLetterboxUi;
}
@@ -818,8 +515,10 @@
if (!shouldShowLetterboxUi) {
return;
}
- pw.println(prefix + " isVerticalThinLetterboxed=" + isVerticalThinLetterboxed());
- pw.println(prefix + " isHorizontalThinLetterboxed=" + isHorizontalThinLetterboxed());
+ pw.println(prefix + " isVerticalThinLetterboxed=" + mActivityRecord.mAppCompatController
+ .getAppCompatReachabilityOverrides().isVerticalThinLetterboxed());
+ pw.println(prefix + " isHorizontalThinLetterboxed=" + mActivityRecord.mAppCompatController
+ .getAppCompatReachabilityOverrides().isHorizontalThinLetterboxed());
pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString(
getLetterboxBackgroundColor().toArgb()));
pw.println(prefix + " letterboxBackgroundType="
@@ -836,10 +535,12 @@
pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius="
+ getLetterboxWallpaperBlurRadiusPx());
}
-
+ final AppCompatReachabilityOverrides reachabilityOverrides = mActivityRecord
+ .mAppCompatController.getAppCompatReachabilityOverrides();
pw.println(prefix + " isHorizontalReachabilityEnabled="
- + isHorizontalReachabilityEnabled());
- pw.println(prefix + " isVerticalReachabilityEnabled=" + isVerticalReachabilityEnabled());
+ + reachabilityOverrides.isHorizontalReachabilityEnabled());
+ pw.println(prefix + " isVerticalReachabilityEnabled="
+ + reachabilityOverrides.isVerticalReachabilityEnabled());
pw.println(prefix + " letterboxHorizontalPositionMultiplier="
+ getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
pw.println(prefix + " letterboxVerticalPositionMultiplier="
@@ -883,64 +584,6 @@
return "UNKNOWN_REASON";
}
- private int letterboxHorizontalReachabilityPositionToLetterboxPosition(
- @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition int position) {
- switch (position) {
- case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
- case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
- case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
- default:
- throw new AssertionError(
- "Unexpected letterbox horizontal reachability position type: "
- + position);
- }
- }
-
- private int letterboxVerticalReachabilityPositionToLetterboxPosition(
- @AppCompatConfiguration.LetterboxVerticalReachabilityPosition int position) {
- switch (position) {
- case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
- case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
- case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
- default:
- throw new AssertionError(
- "Unexpected letterbox vertical reachability position type: "
- + position);
- }
- }
-
- int getLetterboxPositionForLogging() {
- int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
- if (isHorizontalReachabilityEnabled()) {
- int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
- .getLetterboxPositionForHorizontalReachability(
- isDisplayFullScreenAndInPosture(/* isTabletop */ false));
- positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
- letterboxPositionForHorizontalReachability);
- } else if (isVerticalReachabilityEnabled()) {
- int letterboxPositionForVerticalReachability = mAppCompatConfiguration
- .getLetterboxPositionForVerticalReachability(
- isDisplayFullScreenAndInPosture(/* isTabletop */ true));
- positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
- letterboxPositionForVerticalReachability);
- }
- return positionToLog;
- }
-
- /**
- * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
- */
- private void logLetterboxPositionChange(int letterboxPositionChange) {
- mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
- .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
- }
-
@Nullable
LetterboxDetails getLetterboxDetails() {
final WindowState w = mActivityRecord.findMainWindow();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5cef3a1..d3df5fd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -130,9 +130,7 @@
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
-import android.app.AppCompatTaskInfo;
import android.app.AppGlobals;
-import android.app.CameraCompatTaskInfo;
import android.app.IActivityController;
import android.app.PictureInPictureParams;
import android.app.TaskInfo;
@@ -3380,25 +3378,6 @@
? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
-
- final boolean isTopActivityResumed = top != null
- && top.getOrganizedTask() == this && top.isState(RESUMED);
- final boolean isTopActivityVisible = top != null
- && top.getOrganizedTask() == this && top.isVisible();
- final AppCompatTaskInfo appCompatTaskInfo = info.appCompatTaskInfo;
- // Whether the direct top activity is in size compat mode
- appCompatTaskInfo.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode();
- if (appCompatTaskInfo.topActivityInSizeCompat
- && mWmService.mAppCompatConfiguration.isTranslucentLetterboxingEnabled()) {
- // We hide the restart button in case of transparent activities.
- appCompatTaskInfo.topActivityInSizeCompat = top.fillsParent();
- }
- // Whether the direct top activity is eligible for letterbox education.
- appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed
- && top.isEligibleForLetterboxEducation();
- appCompatTaskInfo.isLetterboxEducationEnabled = top != null
- && top.mLetterboxUiController.isLetterboxEducationEnabled();
-
final Task parentTask = getParent() != null ? getParent().asTask() : null;
info.parentTaskId = parentTask != null && parentTask.mCreatedByOrganizer
? parentTask.mTaskId
@@ -3410,57 +3389,7 @@
info.isTopActivityTransparent = top != null && !top.fillsParent();
info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
- appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.isUserFullscreenOverrideEnabled = top != null
- && top.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserFullscreenOverride();
- appCompatTaskInfo.isSystemFullscreenOverrideEnabled = top != null
- && top.mAppCompatController.getAppCompatAspectRatioOverrides()
- .isSystemOverrideToFullscreenEnabled();
- appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
- && top.mLetterboxUiController.isFromDoubleTap();
- if (top != null) {
- appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
- appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
- }
- // We need to consider if letterboxed or pillarboxed
- // TODO(b/336807329) Encapsulate reachability logic
- appCompatTaskInfo.isLetterboxDoubleTapEnabled = top != null
- && top.mLetterboxUiController.isLetterboxDoubleTapEducationEnabled();
- if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
- if (appCompatTaskInfo.isTopActivityPillarboxed()) {
- if (top.mLetterboxUiController.allowHorizontalReachabilityForThinLetterbox()) {
- // Pillarboxed
- appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
- top.mLetterboxUiController
- .getLetterboxPositionForHorizontalReachability();
- } else {
- appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
- }
- } else {
- if (top.mLetterboxUiController.allowVerticalReachabilityForThinLetterbox()) {
- // Letterboxed
- appCompatTaskInfo.topActivityLetterboxVerticalPosition =
- top.mLetterboxUiController
- .getLetterboxPositionForVerticalReachability();
- } else {
- appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
- }
- }
- }
- appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = top != null
- && !appCompatTaskInfo.topActivityInSizeCompat
- && top.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldEnableUserAspectRatioSettings()
- && !info.isTopActivityTransparent;
- appCompatTaskInfo.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
- appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top == null
- ? CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE
- : top.mAppCompatController.getAppCompatCameraOverrides()
- .getFreeformCameraCompatMode();
+ AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0b79f14..5698750 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1764,7 +1764,7 @@
// Check whether the participants were animated from back navigation.
mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets,
- transaction);
+ transaction, mFinishTransaction);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
info.setDebugId(mSyncId);
mController.assignTrack(this, info);
@@ -2202,6 +2202,11 @@
&& !wallpaperIsOwnTarget(wallpaper)) {
wallpaper.setVisibleRequested(false);
}
+ if (showWallpaper && wallpaper.isVisibleRequested()) {
+ for (int j = wallpaper.mChildren.size() - 1; j >= 0; --j) {
+ wallpaper.mChildren.get(j).mWinAnimator.prepareSurfaceLocked(t);
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 2c58c61..34b9913 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -97,10 +97,10 @@
/**
* @return If a window animation has outsets applied to it.
- * @see Animation#getExtensionEdges()
+ * @see Animation#hasExtension()
*/
public boolean hasExtension() {
- return mAnimation.getExtensionEdges() != 0;
+ return mAnimation.hasExtension();
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 48a5050..a574845 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -690,15 +690,10 @@
* <p>Only {@link com.android.server.inputmethod.InputMethodManagerService} is the expected and
* tested caller of this method.</p>
*
- * @param imeToken token to track the active input method. Corresponding IME windows can be
- * identified by checking {@link android.view.WindowManager.LayoutParams#token}.
- * Note that there is no guarantee that the corresponding window is already
- * created
* @param imeTargetWindowToken token to identify the target window that the IME is associated
* with
*/
- public abstract void updateInputMethodTargetWindow(@NonNull IBinder imeToken,
- @NonNull IBinder imeTargetWindowToken);
+ public abstract void updateInputMethodTargetWindow(@NonNull IBinder imeTargetWindowToken);
/**
* Returns true when the hardware keyboard is available.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 464afe0..d73d509 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8094,11 +8094,10 @@
}
@Override
- public void updateInputMethodTargetWindow(@NonNull IBinder imeToken,
- @NonNull IBinder imeTargetWindowToken) {
+ public void updateInputMethodTargetWindow(@NonNull IBinder imeTargetWindowToken) {
// TODO (b/34628091): Use this method to address the window animation issue.
if (DEBUG_INPUT_METHOD) {
- Slog.w(TAG_WM, "updateInputMethodTargetWindow: imeToken=" + imeToken
+ Slog.w(TAG_WM, "updateInputMethodTargetWindow:"
+ " imeTargetWindowToken=" + imeTargetWindowToken);
}
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 51d5bc0..092a751 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -284,30 +284,28 @@
private int runDisplayDensity(PrintWriter pw) throws RemoteException {
String densityStr = getNextArg();
- String option = getNextOption();
String arg = getNextArg();
int density;
int displayId = Display.DEFAULT_DISPLAY;
- if ("-d".equals(option) && arg != null) {
+ if ("-d".equals(densityStr) && arg != null) {
try {
displayId = Integer.parseInt(arg);
} catch (NumberFormatException e) {
getErrPrintWriter().println("Error: bad number " + e);
}
- } else if ("-u".equals(option) && arg != null) {
+ densityStr = getNextArg();
+ } else if ("-u".equals(densityStr) && arg != null) {
displayId = mInterface.getDisplayIdByUniqueId(arg);
if (displayId == Display.INVALID_DISPLAY) {
getErrPrintWriter().println("Error: the uniqueId is invalid ");
return -1;
}
+ densityStr = getNextArg();
}
if (densityStr == null) {
printInitialDisplayDensity(pw, displayId);
return 0;
- } else if ("-d".equals(densityStr)) {
- printInitialDisplayDensity(pw, displayId);
- return 0;
} else if ("reset".equals(densityStr)) {
density = -1;
} else {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 1667e27..b622751 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -27,7 +27,8 @@
per-file com_android_server_security_* = file:/core/java/android/security/OWNERS
per-file com_android_server_tv_* = file:/media/java/android/media/tv/OWNERS
per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
+per-file com_android_server_am_CachedAppOptimizer.cpp = file:/PERFORMANCE_OWNERS
+per-file com_android_server_am_Freezer.cpp = file:/PERFORMANCE_OWNERS
per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
# Memory
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3747850..9ed645b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3620,8 +3620,6 @@
}
default -> {
Slogf.wtf(LOG_TAG, "Unhandled password complexity: " + passwordComplexity);
- // The following line is unreachable as Slogf.wtf crashes the process.
- // But we need this to avoid compilation error missing return statement.
return DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_UNSPECIFIED;
}
}
@@ -13636,7 +13634,28 @@
setBackwardCompatibleUserRestriction(
caller, admin, key, enabledFromThisOwner, parent);
}
- logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
+ logUserRestrictionCall(key, enabledFromThisOwner, parent, caller, affectedUserId);
+ }
+
+ @Override
+ public void setUserRestrictionForUser(
+ @NonNull String systemEntity, String key, boolean enabled, @UserIdInt int targetUser) {
+ Objects.requireNonNull(systemEntity);
+
+ CallerIdentity caller = getCallerIdentity();
+ if (caller.getUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only system services can call setUserRestrictionForUser"
+ + " on a target user: " + targetUser);
+ }
+ if (VERBOSE_LOG) {
+ Slogf.v(LOG_TAG, "Creating SystemEnforcingAdmin %s for calling package %s",
+ systemEntity, caller.getPackageName());
+ }
+ EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
+
+ setLocalUserRestrictionInternal(admin, key, enabled, targetUser);
+
+ logUserRestrictionCall(key, enabled, /* parent= */ false, caller, targetUser);
}
private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) {
@@ -13757,7 +13776,8 @@
setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
- logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
+ logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller,
+ UserHandle.USER_ALL);
}
private void setLocalUserRestrictionInternal(
EnforcingAdmin admin, String key, boolean enabled, int userId) {
@@ -13793,7 +13813,7 @@
}
private void logUserRestrictionCall(
- String key, boolean enabled, boolean parent, CallerIdentity caller) {
+ String key, boolean enabled, boolean parent, CallerIdentity caller, int targetUserId) {
final int eventId = enabled
? DevicePolicyEnums.ADD_USER_RESTRICTION
: DevicePolicyEnums.REMOVE_USER_RESTRICTION;
@@ -13809,8 +13829,9 @@
SecurityLog.writeEvent(eventTag, caller.getPackageName(), caller.getUserId(), key);
}
- Slogf.i(LOG_TAG, "Changing user restriction %s to: %b caller: %s",
- key, enabled, caller.toString());
+ Slogf.i(LOG_TAG, "Changing user restriction %s on %s to: %b caller: %s",
+ key, (targetUserId == UserHandle.USER_ALL ? "all users" : ("user " + targetUserId)),
+ enabled, caller.toString());
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 02590f9..e65e513 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -21,7 +21,6 @@
import android.app.admin.Authority;
import android.app.admin.DeviceAdminAuthority;
import android.app.admin.DpcAuthority;
-import android.app.admin.PackagePermissionPolicyKey;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
import android.content.ComponentName;
@@ -57,23 +56,29 @@
static final String TAG = "EnforcingAdmin";
static final String ROLE_AUTHORITY_PREFIX = "role:";
+ static final String SYSTEM_AUTHORITY_PREFIX = "system:";
static final String DPC_AUTHORITY = "enterprise";
static final String DEVICE_ADMIN_AUTHORITY = "device_admin";
static final String DEFAULT_AUTHORITY = "default";
private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_SYSTEM_ENTITY = "system-entity";
private static final String ATTR_CLASS_NAME = "class-name";
private static final String ATTR_AUTHORITIES = "authorities";
private static final String ATTR_AUTHORITIES_SEPARATOR = ";";
private static final String ATTR_USER_ID = "user-id";
private static final String ATTR_IS_ROLE = "is-role";
+ private static final String ATTR_IS_SYSTEM = "is-system";
private final String mPackageName;
+ // Name of the system entity. Only used when mIsSystemAuthority is true.
+ private final String mSystemEntity;
// This is needed for DPCs and active admins
private final ComponentName mComponentName;
private Set<String> mAuthorities;
private final int mUserId;
private final boolean mIsRoleAuthority;
+ private final boolean mIsSystemAuthority;
private final ActiveAdmin mActiveAdmin;
static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId,
@@ -106,6 +111,11 @@
userId, activeAdmin);
}
+ static EnforcingAdmin createSystemEnforcingAdmin(@NonNull String systemEntity) {
+ Objects.requireNonNull(systemEntity);
+ return new EnforcingAdmin(systemEntity);
+ }
+
static EnforcingAdmin createEnforcingAdmin(android.app.admin.EnforcingAdmin admin) {
Objects.requireNonNull(admin);
Authority authority = admin.getAuthority();
@@ -127,6 +137,7 @@
/* activeAdmin = */ null,
/* isRoleAuthority = */ true);
}
+ // TODO(b/324899199): Consider supporting android.app.admin.SystemAuthority.
return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(),
Set.of(), admin.getUserHandle().getIdentifier(),
/* activeAdmin = */ null);
@@ -159,9 +170,11 @@
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
- // Role authorities should not be using this constructor
+ // Role/System authorities should not be using this constructor
mIsRoleAuthority = false;
+ mIsSystemAuthority = false;
mPackageName = packageName;
+ mSystemEntity = null;
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
@@ -173,7 +186,9 @@
// Only role authorities use this constructor.
mIsRoleAuthority = true;
+ mIsSystemAuthority = false;
mPackageName = packageName;
+ mSystemEntity = null;
mUserId = userId;
mComponentName = null;
// authorities will be loaded when needed
@@ -181,6 +196,21 @@
mActiveAdmin = activeAdmin;
}
+ /** Constructor for System authorities. */
+ private EnforcingAdmin(@NonNull String systemEntity) {
+ Objects.requireNonNull(systemEntity);
+
+ // Only system authorities use this constructor.
+ mIsSystemAuthority = true;
+ mIsRoleAuthority = false;
+ mPackageName = null;
+ mSystemEntity = systemEntity;
+ mUserId = UserHandle.USER_SYSTEM;
+ mComponentName = null;
+ mAuthorities = getSystemAuthority(systemEntity);
+ mActiveAdmin = null;
+ }
+
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) {
@@ -188,7 +218,9 @@
Objects.requireNonNull(authorities);
mIsRoleAuthority = isRoleAuthority;
+ mIsSystemAuthority = false;
mPackageName = packageName;
+ mSystemEntity = null;
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
@@ -204,6 +236,18 @@
return authorities.isEmpty() ? Set.of(DEFAULT_AUTHORITY) : authorities;
}
+ /**
+ * Returns a set of authorities for system authority.
+ *
+ * <p>Note that a system authority enforcing admin has only one authority that has the package
+ * name of the calling system service. Therefore, the returned set always contains one element.
+ */
+ private static Set<String> getSystemAuthority(String systemEntity) {
+ Set<String> authorities = new HashSet<>();
+ authorities.add(SYSTEM_AUTHORITY_PREFIX + systemEntity);
+ return authorities;
+ }
+
// TODO(b/259042794): move this logic to RoleManagerLocal
private static Set<String> getRoles(String packageName, int userId) {
RoleManagerLocal roleManagerLocal = LocalManagerRegistry.getManager(
@@ -264,6 +308,7 @@
} else if (mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
authority = DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY;
} else {
+ // For now, System Authority returns UNKNOWN_AUTHORITY.
authority = UnknownAuthority.UNKNOWN_AUTHORITY;
}
return new android.app.admin.EnforcingAdmin(
@@ -291,8 +336,10 @@
if (o == null || getClass() != o.getClass()) return false;
EnforcingAdmin other = (EnforcingAdmin) o;
return Objects.equals(mPackageName, other.mPackageName)
+ && Objects.equals(mSystemEntity, other.mSystemEntity)
&& Objects.equals(mComponentName, other.mComponentName)
&& Objects.equals(mIsRoleAuthority, other.mIsRoleAuthority)
+ && (mIsSystemAuthority == other.mIsSystemAuthority)
&& hasMatchingAuthorities(this, other);
}
@@ -307,6 +354,8 @@
public int hashCode() {
if (mIsRoleAuthority) {
return Objects.hash(mPackageName, mUserId);
+ } else if (mIsSystemAuthority) {
+ return Objects.hash(mSystemEntity);
} else {
return Objects.hash(
mComponentName == null ? mPackageName : mComponentName,
@@ -318,8 +367,12 @@
void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_ROLE, mIsRoleAuthority);
+ serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_SYSTEM, mIsSystemAuthority);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
- if (!mIsRoleAuthority) {
+ if (mIsSystemAuthority) {
+ serializer.attribute(/* namespace= */ null, ATTR_SYSTEM_ENTITY, mSystemEntity);
+ }
+ if (!mIsRoleAuthority && !mIsSystemAuthority) {
if (mComponentName != null) {
serializer.attribute(
/* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
@@ -336,7 +389,10 @@
static EnforcingAdmin readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException {
String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+ String systemEntity = parser.getAttributeValue(/* namespace= */ null, ATTR_SYSTEM_ENTITY);
boolean isRoleAuthority = parser.getAttributeBoolean(/* namespace= */ null, ATTR_IS_ROLE);
+ boolean isSystemAuthority = parser.getAttributeBoolean(
+ /* namespace= */ null, ATTR_IS_SYSTEM, /* defaultValue= */ false);
String authoritiesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_AUTHORITIES);
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
@@ -348,6 +404,13 @@
}
// TODO(b/281697976): load active admin
return new EnforcingAdmin(packageName, userId, null);
+ } else if (isSystemAuthority) {
+ if (systemEntity == null) {
+ Slogf.wtf(TAG, "Error parsing EnforcingAdmin with SystemAuthority, "
+ + "systemEntity is null.");
+ return null;
+ }
+ return new EnforcingAdmin(systemEntity);
} else {
if (packageName == null || authoritiesStr == null) {
Slogf.wtf(TAG, "Error parsing EnforcingAdmin, packageName is "
@@ -381,6 +444,10 @@
sb.append(mUserId);
sb.append(", mIsRoleAuthority= ");
sb.append(mIsRoleAuthority);
+ sb.append(", mIsSystemAuthority= ");
+ sb.append(mIsSystemAuthority);
+ sb.append(", mSystemEntity = ");
+ sb.append(mSystemEntity);
sb.append(" }");
return sb.toString();
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index 1e3b7e9..e552621 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -79,10 +79,8 @@
// from system.
synchronized (ImfLock.class) {
mBindingController =
- new InputMethodBindingController(
- mInputMethodManagerService.getCurrentImeUserIdLocked(),
- mInputMethodManagerService, mImeConnectionBindFlags,
- mCountDownLatch);
+ new InputMethodBindingController(mUserId, mInputMethodManagerService,
+ mImeConnectionBindFlags, mCountDownLatch);
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
index 2ea2e22..a86d61bb 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
@@ -122,8 +122,8 @@
private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
List<String> enabledComponents) {
- final InputMethodMap methodMap = InputMethodManagerService.filterInputMethodServices(
- AdditionalSubtypeMap.EMPTY_MAP, enabledComponents, mContext, resolveInfoList);
+ final var methodMap = InputMethodManagerService.filterInputMethodServices(
+ enabledComponents, mContext, resolveInfoList);
return methodMap.values();
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 461697c..9a25104 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -269,15 +269,21 @@
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
lifecycle.onStart();
+ final var userData = mInputMethodManagerService.getUserData(mUserId);
+
// Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
// TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
AdditionalSubtypeMapRepository.initializeIfNecessary(mUserId);
- final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
- mUserId, AdditionalSubtypeMapRepository.get(mUserId), DirectBootAwareness.AUTO);
+ final var rawMethodMap = InputMethodManagerService.queryRawInputMethodServiceMap(mContext,
+ mUserId);
+ userData.mRawInputMethodMap.set(rawMethodMap);
+ final var settings = InputMethodSettings.create(rawMethodMap.toInputMethodMap(
+ AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO, true /* userUnlocked */),
+ mUserId);
InputMethodSettingsRepository.put(mUserId, settings);
// Emulate that the user initialization is done.
- mInputMethodManagerService.getUserData(mUserId).mBackgroundLoadLatch.countDown();
+ userData.mBackgroundLoadLatch.countDown();
// After this boot phase, services can broadcast Intents.
lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 8ed38a6..6394b27 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -936,6 +936,9 @@
@Test
public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
@@ -948,6 +951,9 @@
@Test
public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -960,6 +966,24 @@
}
@Test
+ public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessInDoze() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
// New display device
setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
@@ -1589,16 +1613,21 @@
advanceTime(1); // Run updatePowerState
reset(mHolder.wakelockController);
+ when(mHolder.wakelockController
+ .acquireWakelock(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE))
+ .thenReturn(true);
mHolder.dpc.overrideDozeScreenState(
supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
- advanceTime(1); // Run updatePowerState
// Should get a wakelock to notify powermanager
- verify(mHolder.wakelockController, atLeastOnce()).acquireWakelock(
- eq(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS));
+ verify(mHolder.wakelockController).acquireWakelock(
+ eq(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE));
+ advanceTime(1); // Run updatePowerState
verify(mHolder.displayPowerState)
.setScreenState(supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
+ verify(mHolder.wakelockController).releaseWakelock(
+ eq(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE));
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
index c23d4b1..019b70e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
@@ -64,6 +64,8 @@
"[" + DISPLAY_ID + "]prox negative");
assertEquals(mWakelockController.getSuspendBlockerProxDebounceId(),
"[" + DISPLAY_ID + "]prox debounce");
+ assertEquals(mWakelockController.getSuspendBlockerOverrideDozeScreenState(),
+ "[" + DISPLAY_ID + "]override doze screen state");
}
@Test
@@ -162,6 +164,28 @@
}
@Test
+ public void acquireOverrideDozeScreenStateSuspendBlocker() throws Exception {
+ // Acquire the suspend blocker
+ verifyWakelockAcquisitionAndReaquisition(WakelockController
+ .WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
+ () -> mWakelockController.isOverrideDozeScreenStateAcquired());
+
+ // Verify acquire happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .acquireSuspendBlocker(mWakelockController
+ .getSuspendBlockerOverrideDozeScreenState());
+
+ // Release the suspend blocker
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
+ () -> mWakelockController.isOverrideDozeScreenStateAcquired());
+
+ // Verify suspend blocker was released only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .releaseSuspendBlocker(mWakelockController
+ .getSuspendBlockerOverrideDozeScreenState());
+ }
+
+ @Test
public void proximityPositiveRunnableWorksAsExpected() {
// Acquire the suspend blocker twice
assertTrue(mWakelockController.acquireWakelock(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index d7936fe..c069875 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -545,17 +545,18 @@
DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
Handler handler = mock(Handler.class);
BrightnessMappingStrategy brightnessMappingStrategy = mock(BrightnessMappingStrategy.class);
- boolean isEnabled = true;
+ boolean isDisplayEnabled = true;
int leadDisplayId = 2;
mDisplayBrightnessController.setUpAutoBrightness(automaticBrightnessController,
- sensorManager, displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled,
- leadDisplayId);
+ sensorManager, displayDeviceConfig, handler, brightnessMappingStrategy,
+ isDisplayEnabled, leadDisplayId);
assertEquals(automaticBrightnessController,
mDisplayBrightnessController.mAutomaticBrightnessController);
verify(automaticBrightnessStrategy).setAutomaticBrightnessController(
automaticBrightnessController);
verify(autoBrightnessFallbackStrategy).setupAutoBrightnessFallbackSensor(sensorManager,
- displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled, leadDisplayId);
+ displayDeviceConfig, handler, brightnessMappingStrategy, isDisplayEnabled,
+ leadDisplayId);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
index 0ed96ae..bb025cc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
@@ -16,7 +16,10 @@
package com.android.server.display.brightness.clamper
+import android.content.Context
+import android.database.ContentObserver
import android.hardware.display.DisplayManagerInternal
+import android.net.Uri
import android.os.IBinder
import android.os.PowerManager.BRIGHTNESS_MAX
import android.util.Spline
@@ -51,7 +54,7 @@
private val stoppedClock = OffsettableClock.Stopped()
private val testHandler = TestHandler(null, stoppedClock)
- private val testInjector = TestInjector()
+ private val testInjector = TestInjector(mock<Context>())
private val mockChangeListener = mock<ClamperChangeListener>()
private val mockDisplayDeviceConfig = mock<DisplayDeviceConfig>()
private val mockDisplayBinder = mock<IBinder>()
@@ -63,14 +66,14 @@
private val dummyData = createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinder)
@Test
- fun `change listener is not called on init`() {
+ fun changeListenerIsNotCalledOnInit() {
initHdrModifier()
verify(mockChangeListener, never()).onChanged()
}
@Test
- fun `hdr listener registered on init if hdr data is present`() {
+ fun hdrListenerRegisteredOnInit_hdrDataPresent() {
initHdrModifier()
assertThat(testInjector.registeredHdrListener).isNotNull()
@@ -78,22 +81,19 @@
}
@Test
- fun `hdr listener not registered on init if hdr data is missing`() {
- initHdrModifier(null)
-
- testHandler.flush()
+ fun hdrListenerNotRegisteredOnInit_hdrDataMissing() {
+ initHdrModifier(hdrBrightnessData = null)
assertThat(testInjector.registeredHdrListener).isNull()
assertThat(testInjector.registeredToken).isNull()
}
@Test
- fun `unsubscribes hdr listener when display changed with no hdr data`() {
+ fun unsubscribeHdrListener_displayChangedWithNoHdrData() {
initHdrModifier()
whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(null)
modifier.onDisplayChanged(dummyData)
- testHandler.flush()
assertThat(testInjector.registeredHdrListener).isNull()
assertThat(testInjector.registeredToken).isNull()
@@ -101,12 +101,11 @@
}
@Test
- fun `resubscribes hdr listener when display changed with different token`() {
+ fun resubscribesHdrListener_displayChangedWithDifferentToken() {
initHdrModifier()
modifier.onDisplayChanged(
createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinderOther))
- testHandler.flush()
assertThat(testInjector.registeredHdrListener).isNotNull()
assertThat(testInjector.registeredToken).isEqualTo(mockDisplayBinderOther)
@@ -114,7 +113,28 @@
}
@Test
- fun `test NO_HDR mode`() {
+ fun contentObserverNotRegisteredOnInit_hdrDataMissing() {
+ initHdrModifier(null)
+
+ assertThat(testInjector.registeredContentObserver).isNull()
+ }
+
+ @Test
+ fun contentObserverNotRegisteredOnInit_allowedInLowPowerMode() {
+ initHdrModifier(createHdrBrightnessData(allowInLowPowerMode = true))
+
+ assertThat(testInjector.registeredContentObserver).isNull()
+ }
+
+ @Test
+ fun contentObserverRegisteredOnInit_notAllowedInLowPowerMode() {
+ initHdrModifier(createHdrBrightnessData(allowInLowPowerMode = false))
+
+ assertThat(testInjector.registeredContentObserver).isNotNull()
+ }
+
+ @Test
+ fun testNoHdrMode() {
initHdrModifier()
// screen size = 10_000
setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
@@ -131,7 +151,7 @@
}
@Test
- fun `test NBM_HDR mode`() {
+ fun testNbmHdrMode() {
initHdrModifier()
// screen size = 10_000
val transitionPoint = 0.55f
@@ -157,7 +177,7 @@
}
@Test
- fun `test HBM_HDR mode`() {
+ fun testHbmHdrMode() {
initHdrModifier()
// screen size = 10_000
setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
@@ -182,7 +202,7 @@
}
@Test
- fun `test display change no HDR content`() {
+ fun testDisplayChange_noHdrContent() {
initHdrModifier()
setupDisplay(width = 100, height = 100)
assertModifierState()
@@ -195,7 +215,7 @@
}
@Test
- fun `test display change with HDR content`() {
+ fun testDisplayChange_hdrContent() {
initHdrModifier()
setupDisplay(width = 100, height = 100)
setupHdrLayer(width = 100, height = 100, maxHdrRatio = 5f)
@@ -218,7 +238,7 @@
}
@Test
- fun `test ambient lux decrease above maxBrightnessLimits no HDR`() {
+ fun testSetAmbientLux_decreaseAboveMaxBrightnessLimitNoHdr() {
initHdrModifier()
modifier.setAmbientLux(1000f)
setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
@@ -234,7 +254,7 @@
}
@Test
- fun `test ambient lux decrease above maxBrightnessLimits with HDR`() {
+ fun testSetAmbientLux_decreaseAboveMaxBrightnessLimitWithHdr() {
initHdrModifier()
modifier.setAmbientLux(1000f)
setupDisplay(width = 200, height = 200, hdrBrightnessData = createHdrBrightnessData(
@@ -260,7 +280,7 @@
}
@Test
- fun `test ambient lux decrease below maxBrightnessLimits no HDR`() {
+ fun testSetAmbientLux_decreaseBelowMaxBrightnessLimitNoHdr() {
initHdrModifier()
modifier.setAmbientLux(1000f)
setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
@@ -276,7 +296,7 @@
}
@Test
- fun `test ambient lux decrease below maxBrightnessLimits with HDR`() {
+ fun testSetAmbientLux_decreaseBelowMaxBrightnessLimitWithHdr() {
initHdrModifier()
modifier.setAmbientLux(1000f)
val maxBrightness = 0.6f
@@ -322,6 +342,23 @@
)
}
+ @Test
+ fun testLowPower_notAllowedInLowPower() {
+ initHdrModifier()
+ setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+ allowInLowPowerMode = false
+ ))
+ setupHdrLayer(width = 100, height = 100)
+ clearInvocations(mockChangeListener)
+
+ testInjector.isLowPower = true
+ testInjector.registeredContentObserver!!.onChange(true)
+
+ verify(mockChangeListener).onChanged()
+ assertModifierState()
+ }
+
+ // Helper functions
private fun setupHdrLayer(width: Int = 100, height: Int = 100, maxHdrRatio: Float = 0.8f) {
testInjector.registeredHdrListener!!.onHdrInfoChanged(
mockDisplayBinder, 1, width, height, 0, maxHdrRatio
@@ -345,7 +382,6 @@
width = width,
height = height
))
- testHandler.flush()
}
private fun initHdrModifier(hdrBrightnessData: HdrBrightnessData? = createHdrBrightnessData()) {
@@ -384,9 +420,12 @@
assertThat(stateBuilder.customAnimationRate).isEqualTo(animationRate)
}
- internal class TestInjector : Injector() {
+ internal class TestInjector(context: Context) : Injector(context) {
var registeredHdrListener: SurfaceControlHdrLayerInfoListener? = null
var registeredToken: IBinder? = null
+ var registeredContentObserver: ContentObserver? = null
+
+ var isLowPower: Boolean = false
override fun registerHdrListener(
listener: SurfaceControlHdrLayerInfoListener, token: IBinder
@@ -401,5 +440,17 @@
registeredHdrListener = null
registeredToken = null
}
+
+ override fun registerContentObserver(observer: ContentObserver, uri: Uri) {
+ registeredContentObserver = observer
+ }
+
+ override fun unregisterContentObserver(observer: ContentObserver) {
+ registeredContentObserver = null
+ }
+
+ override fun isLowPowerMode(): Boolean {
+ return isLowPower
+ }
}
}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
index bb24c0f..99dfa73 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
@@ -16,15 +16,22 @@
package com.android.server.display.brightness.strategy;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+
+import static com.android.server.display.layout.Layout.NO_LEAD_DISPLAY;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
+import android.os.PowerManager;
+import android.view.Display;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -35,6 +42,7 @@
import com.android.server.display.ScreenOffBrightnessSensorController;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.StrategyExecutionRequest;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import org.junit.Before;
import org.junit.Test;
@@ -53,9 +61,24 @@
@Mock
private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+ @Mock
+ private SensorManager mSensorManager;
+
+ @Mock
+ private DisplayDeviceConfig mDisplayDeviceConfig;
+
+ @Mock
+ private Handler mHandler;
+
+ @Mock
+ private BrightnessMappingStrategy mBrightnessMappingStrategy;
+
@Before
public void before() {
MockitoAnnotations.initMocks(this);
+ int[] sensorValueToLux = new int[]{50, 100};
+ when(mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux())
+ .thenReturn(sensorValueToLux);
mAutoBrightnessFallbackStrategy = new AutoBrightnessFallbackStrategy(
new AutoBrightnessFallbackStrategy.Injector() {
@Override
@@ -78,20 +101,11 @@
@Test
public void testUpdateBrightnessWhenScreenDozeStateIsRequested() {
- // Setup the argument mocks
- SensorManager sensorManager = mock(SensorManager.class);
- DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
- Handler handler = mock(Handler.class);
- BrightnessMappingStrategy brightnessMappingStrategy = mock(BrightnessMappingStrategy.class);
- boolean isEnabled = true;
+ boolean isDisplayEnabled = true;
int leadDisplayId = 2;
-
- int[] sensorValueToLux = new int[]{50, 100};
- when(displayDeviceConfig.getScreenOffBrightnessSensorValueToLux()).thenReturn(
- sensorValueToLux);
-
- mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(sensorManager,
- displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled, leadDisplayId);
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
assertEquals(mScreenOffBrightnessSensor,
mAutoBrightnessFallbackStrategy.mScreenOffBrightnessSensor);
@@ -119,4 +133,157 @@
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
+ @Test
+ public void testPostProcess_EnableSensor_PolicyOff() {
+ boolean isDisplayEnabled = true;
+ int leadDisplayId = NO_LEAD_DISPLAY;
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
+
+ DisplayManagerInternal.DisplayPowerRequest dpr =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ dpr.policy = POLICY_OFF;
+ StrategySelectionNotifyRequest ssnr = new StrategySelectionNotifyRequest(dpr,
+ Display.STATE_OFF, mAutoBrightnessFallbackStrategy,
+ /* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userSetBrightnessChanged= */ false,
+ /* allowAutoBrightnessWhileDozingConfig= */ false,
+ /* isAutoBrightnessEnabled= */ true);
+ mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
+
+ verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(true);
+ }
+
+ @Test
+ public void testPostProcess_EnableSensor_PolicyDoze() {
+ boolean isDisplayEnabled = true;
+ int leadDisplayId = NO_LEAD_DISPLAY;
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
+
+ DisplayManagerInternal.DisplayPowerRequest dpr =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ dpr.policy = POLICY_DOZE;
+ StrategySelectionNotifyRequest ssnr = new StrategySelectionNotifyRequest(dpr,
+ Display.STATE_DOZE, mAutoBrightnessFallbackStrategy,
+ /* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userSetBrightnessChanged= */ false,
+ /* allowAutoBrightnessWhileDozingConfig= */ false,
+ /* isAutoBrightnessEnabled= */ true);
+ mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
+
+ verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(true);
+ }
+
+ @Test
+ public void testPostProcess_DisableSensor_AutoBrightnessDisabled() {
+ boolean isDisplayEnabled = true;
+ int leadDisplayId = NO_LEAD_DISPLAY;
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
+
+ DisplayManagerInternal.DisplayPowerRequest dpr =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ dpr.policy = POLICY_OFF;
+ StrategySelectionNotifyRequest ssnr = new StrategySelectionNotifyRequest(dpr,
+ Display.STATE_OFF, mAutoBrightnessFallbackStrategy,
+ /* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userSetBrightnessChanged= */ false,
+ /* allowAutoBrightnessWhileDozingConfig= */ false,
+ /* isAutoBrightnessEnabled= */ false);
+ mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
+
+ verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testPostProcess_DisableSensor_DisplayDisabled() {
+ boolean isDisplayEnabled = false;
+ int leadDisplayId = NO_LEAD_DISPLAY;
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
+
+ DisplayManagerInternal.DisplayPowerRequest dpr =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ dpr.policy = POLICY_OFF;
+ StrategySelectionNotifyRequest ssnr = new StrategySelectionNotifyRequest(dpr,
+ Display.STATE_OFF, mAutoBrightnessFallbackStrategy,
+ /* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userSetBrightnessChanged= */ false,
+ /* allowAutoBrightnessWhileDozingConfig= */ false,
+ /* isAutoBrightnessEnabled= */ true);
+ mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
+
+ verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testPostProcess_DisableSensor_PolicyBright() {
+ boolean isDisplayEnabled = true;
+ int leadDisplayId = NO_LEAD_DISPLAY;
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
+
+ DisplayManagerInternal.DisplayPowerRequest dpr =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ dpr.policy = POLICY_BRIGHT;
+ StrategySelectionNotifyRequest ssnr = new StrategySelectionNotifyRequest(dpr,
+ Display.STATE_ON, mAutoBrightnessFallbackStrategy,
+ /* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userSetBrightnessChanged= */ false,
+ /* allowAutoBrightnessWhileDozingConfig= */ false,
+ /* isAutoBrightnessEnabled= */ true);
+ mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
+
+ verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testPostProcess_DisableSensor_AutoBrightnessInDoze() {
+ boolean isDisplayEnabled = true;
+ int leadDisplayId = NO_LEAD_DISPLAY;
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
+
+ DisplayManagerInternal.DisplayPowerRequest dpr =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ dpr.policy = POLICY_DOZE;
+ StrategySelectionNotifyRequest ssnr = new StrategySelectionNotifyRequest(dpr,
+ Display.STATE_DOZE, mAutoBrightnessFallbackStrategy,
+ /* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userSetBrightnessChanged= */ false,
+ /* allowAutoBrightnessWhileDozingConfig= */ true,
+ /* isAutoBrightnessEnabled= */ true);
+ mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
+
+ verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testPostProcess_DisableSensor_DisplayIsFollower() {
+ boolean isDisplayEnabled = true;
+ int leadDisplayId = 3;
+ mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(mSensorManager,
+ mDisplayDeviceConfig,
+ mHandler, mBrightnessMappingStrategy, isDisplayEnabled, leadDisplayId);
+
+ DisplayManagerInternal.DisplayPowerRequest dpr =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ dpr.policy = POLICY_OFF;
+ StrategySelectionNotifyRequest ssnr = new StrategySelectionNotifyRequest(dpr,
+ Display.STATE_OFF, mAutoBrightnessFallbackStrategy,
+ /* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userSetBrightnessChanged= */ false,
+ /* allowAutoBrightnessWhileDozingConfig= */ false,
+ /* isAutoBrightnessEnabled= */ true);
+ mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
+
+ verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index cb15d6f..b980ca0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -132,6 +132,7 @@
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
@@ -149,6 +150,7 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
@@ -176,6 +178,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -250,6 +253,8 @@
@Mock
private ActivityManagerInternal mActivityManagerInternal;
@Mock
+ private UserManagerInternal mUserManagerInternal;
+ @Mock
private ActivityManager mActivityManager;
@Mock
private PackageManagerInternal mPackageManagerInternal;
@@ -447,6 +452,8 @@
() -> LocalServices.getService(PermissionManagerServiceInternal.class));
doReturn(mActivityManagerInternal).when(
() -> LocalServices.getService(ActivityManagerInternal.class));
+ doReturn(mUserManagerInternal).when(
+ () -> LocalServices.getService(UserManagerInternal.class));
doReturn(mPackageManagerInternal).when(
() -> LocalServices.getService(PackageManagerInternal.class));
doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class));
@@ -1252,6 +1259,26 @@
}
@Test
+ public void wakeupShouldBeScheduledForFullUsers_skipsGuestSystemAndProfiles() {
+ final int systemUserId = 0;
+ final int fullUserId = 10;
+ final int privateProfileId = 12;
+ final int guestUserId = 13;
+ when(mUserManagerInternal.getUserInfo(fullUserId)).thenReturn(new UserInfo(fullUserId,
+ "TestUser2", UserInfo.FLAG_FULL));
+ when(mUserManagerInternal.getUserInfo(privateProfileId)).thenReturn(new UserInfo(
+ privateProfileId, "TestUser3", UserInfo.FLAG_PROFILE));
+ when(mUserManagerInternal.getUserInfo(guestUserId)).thenReturn(new UserInfo(
+ guestUserId, "TestUserGuest", null, 0, UserManager.USER_TYPE_FULL_GUEST));
+ when(mUserManagerInternal.getUserInfo(systemUserId)).thenReturn(new UserInfo(
+ systemUserId, "TestUserSystem", null, 0, UserManager.USER_TYPE_FULL_SYSTEM));
+ assertTrue(mService.shouldAddWakeupForUser(fullUserId));
+ assertFalse(mService.shouldAddWakeupForUser(systemUserId));
+ assertFalse(mService.shouldAddWakeupForUser(privateProfileId));
+ assertFalse(mService.shouldAddWakeupForUser(guestUserId));
+ }
+
+ @Test
public void sendsTimeTickOnInteractive() {
final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
// Stubbing so the handler doesn't actually run the runnable.
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
index 5bd919f..72883e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.testng.AssertJUnit.assertFalse;
import android.os.Environment;
import android.os.FileUtils;
@@ -52,7 +51,6 @@
private static final int USER_ID_1 = 10;
private static final int USER_ID_2 = 11;
private static final int USER_ID_3 = 12;
- private static final int USER_ID_SYSTEM = 0;
private static final long TEST_TIMESTAMP = 150_000;
private static final File TEST_SYSTEM_DIR = new File(InstrumentationRegistry
.getInstrumentation().getContext().getDataDir(), "alarmsTestDir");
@@ -112,14 +110,6 @@
}
@Test
- public void testAddWakeupForSystemUser_shouldDoNothing() {
- mUserWakeupStore.addUserWakeup(USER_ID_SYSTEM, TEST_TIMESTAMP - 19_000);
- assertEquals(0, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
- final File file = new File(ROOT_DIR , "usersWithAlarmClocks.xml");
- assertFalse(file.exists());
- }
-
- @Test
public void testAddMultipleWakeupsForUser_ensureOnlyLastWakeupRemains() {
final long finalAlarmTime = TEST_TIMESTAMP - 13_000;
mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 29_000);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
index 2cbc226..4fac647 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
@@ -1,3 +1,4 @@
include /services/core/java/com/android/server/am/OWNERS
per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com
+per-file CachedAppOptimizerTest.java = file:/PERFORMANCE_OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 3572d23..014b98c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -56,6 +56,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -238,6 +239,7 @@
/**
* Verify that a process start event is dispatched to process observers.
*/
+ @Ignore("b/323959187")
@Test
public void testNormal() throws Exception {
ProcessRecord app = startProcess();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 2d4dbb7..78c9372 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -479,6 +479,15 @@
eq(mContext.getOpPackageName()), anyInt(), anyInt(), any());
}
+ @Test
+ public void testCancelAuth_whenClientWaitingForCookie() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(true);
+ client.waitForCookie(mCallback);
+ client.cancel();
+
+ verify(mCallback).onClientFinished(client, false);
+ }
+
private FaceAuthenticationClient createClient() throws RemoteException {
return createClient(2 /* version */, mClientMonitorCallbackConverter,
false /* allowBackgroundAuthentication */, true /* isBiometricPrompt */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 6ec888c..7e1d421 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -117,7 +117,6 @@
private static final int TOUCH_Y = 20;
private static final float TOUCH_MAJOR = 4.4f;
private static final float TOUCH_MINOR = 5.5f;
- private static final int FINGER_UP = 111;
@Rule
public final TestableContext mContext = new TestableContext(
@@ -383,6 +382,8 @@
@Test
public void subscribeContextAndStartHal() throws RemoteException {
+ when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
+
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
@@ -691,6 +692,17 @@
verify(mLockoutTracker).addFailedAttemptForUser(USER_ID);
}
+ @Test
+ public void testCancelAuth_whenClientWaitingForCookie() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClientWithoutBackgroundAuth();
+ client.waitForCookie(mCallback);
+ client.cancel();
+ mLooper.moveTimeForward(10);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(client, false);
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
true /* isBiometricPrompt */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index a34e796..242880c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -178,7 +178,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINT_LOE)
+ @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINTS_LOE)
public void invalidBiometricUserState() throws Exception {
mClient = createClient();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index e078238..7eabfac 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -518,19 +518,6 @@
}
@Test
- public void canActivityBeLaunched_permissionComponent_isBlocked() {
- GenericWindowPolicyController gwpc = createGwpcWithPermissionComponent(BLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
-
- ActivityInfo activityInfo = getActivityInfo(
- BLOCKED_PACKAGE_NAME,
- BLOCKED_PACKAGE_NAME,
- /* displayOnRemoteDevices */ true,
- /* targetDisplayCategory */ null);
- assertActivityIsBlocked(gwpc, activityInfo);
- }
-
- @Test
public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
GenericWindowPolicyController gwpc = createGwpc();
@@ -740,7 +727,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ mSecureWindowCallback,
@@ -760,7 +746,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ mSecureWindowCallback,
@@ -781,7 +766,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -802,7 +786,6 @@
/* activityPolicyExemptions= */ Collections.singleton(blockedComponent),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -823,7 +806,6 @@
/* activityPolicyExemptions= */ Collections.singleton(allowedComponent),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -844,7 +826,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -865,7 +846,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -886,29 +866,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ false,
/* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent),
- /* permissionDialogComponent= */ null,
- /* activityListener= */ mActivityListener,
- /* activityBlockedCallback= */ mActivityBlockedCallback,
- /* secureWindowCallback= */ null,
- /* intentListenerCallback= */ mIntentListenerCallback,
- /* displayCategories= */ new ArraySet<>(),
- /* showTasksInHostDeviceRecents= */ true,
- /* customHomeComponent= */ null);
- }
-
- private GenericWindowPolicyController createGwpcWithPermissionComponent(
- ComponentName permissionComponent) {
- //TODO instert the component
- return new GenericWindowPolicyController(
- 0,
- 0,
- AttributionSource.myAttributionSource(),
- /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
- /* activityLaunchAllowedByDefault= */ true,
- /* activityPolicyExemptions= */ new ArraySet<>(),
- /* crossTaskNavigationAllowedByDefault= */ false,
- /* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ permissionComponent,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index da8961d..19712ea 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -1550,8 +1550,7 @@
}
@Test
- public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_startsWhenFlagIsEnabled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_starts() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
@@ -1572,8 +1571,7 @@
}
@Test
- public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_startsWhenFlagIsEnabled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_starts() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
@@ -1755,35 +1753,7 @@
}
@Test
- public void canActivityBeLaunched_permissionDialog_flagDisabled_isBlocked() {
- mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
- VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
- mDeviceImpl.close();
- mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
- doNothing().when(mContext).startActivityAsUser(any(), any(), any());
-
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
- DISPLAY_ID_1);
- ComponentName permissionComponent = getPermissionDialogComponent();
- ActivityInfo activityInfo = getActivityInfo(
- permissionComponent.getPackageName(),
- permissionComponent.getClassName(),
- /* displayOnRemoteDevices */ true,
- /* targetDisplayCategory */ null);
- assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
- .isFalse();
-
- Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
- activityInfo, mAssociationInfo.getDisplayName());
- verify(mContext).startActivityAsUser(argThat(intent ->
- intent.filterEquals(blockedAppIntent)), any(), any());
- }
-
- @Test
- public void canActivityBeLaunched_permissionDialog_flagEnabled_isStreamed() {
- mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ public void canActivityBeLaunched_permissionDialog_isStreamed() {
VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
mDeviceImpl.close();
mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index b946a43..c3db396 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -85,7 +85,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent */ null,
/* activityListener= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 1549b2d..197b366 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -41,30 +41,33 @@
category="android.intent.category.APP_CALCULATOR"
shortcut="u" />
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b"
+ shift="true" />
+
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="j"
+ shift="true" />
+
<!-- The following shortcuts will not be invoked by tests but are here to
provide test coverage of parsing the different types of shortcut. -->
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="a" />
+ shortcut="j" />
<bookmark
package="com.test2"
class="com.test.BookmarkTest"
shortcut="d" />
- <bookmark
- role="android.app.role.BROWSER"
- shortcut="b"
- shift="true" />
- <bookmark
- category="android.intent.category.APP_CONTACTS"
- shortcut="c"
- shift="true" />
- <bookmark
- package="com.test"
- class="com.test.BookmarkTest"
- shortcut="a"
- shift="true" />
<!-- It's intended that this package/class will NOT resolve so we test the resolution
failure case. -->
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 526c351..71f90a2 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -25,6 +25,7 @@
import static android.view.KeyEvent.KEYCODE_E;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.KeyEvent.KEYCODE_H;
+import static android.view.KeyEvent.KEYCODE_J;
import static android.view.KeyEvent.KEYCODE_K;
import static android.view.KeyEvent.KEYCODE_M;
import static android.view.KeyEvent.KEYCODE_META_LEFT;
@@ -40,6 +41,7 @@
import static android.view.KeyEvent.KEYCODE_Z;
import android.app.role.RoleManager;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -105,6 +107,17 @@
sendKeyCombination(new int[]{KEYCODE_META_LEFT, keyCode}, 0);
mPhoneWindowManager.assertLaunchRole(role);
}
+
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_B}, 0);
+ mPhoneWindowManager.assertLaunchRole(RoleManager.ROLE_BROWSER);
+
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_C}, 0);
+ mPhoneWindowManager.assertLaunchCategory(Intent.CATEGORY_APP_CONTACTS);
+
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_J}, 0);
+ mPhoneWindowManager.assertActivityTargetLaunched(
+ new ComponentName("com.test", "com.test.BookmarkTest"));
+
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index f5c8fb8..37e4fd6 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -43,11 +43,17 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static java.util.Collections.unmodifiableMap;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -76,6 +82,7 @@
public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule);
private Resources mResources;
+ private PackageManager mPackageManager;
TestPhoneWindowManager mPhoneWindowManager;
DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
Context mContext;
@@ -100,12 +107,25 @@
public void setup() {
mContext = spy(getInstrumentation().getTargetContext());
mResources = spy(mContext.getResources());
+ mPackageManager = spy(mContext.getPackageManager());
+ doReturn(mContext).when(mContext).createContextAsUser(anyObject(), anyInt());
doReturn(mResources).when(mContext).getResources();
doReturn(mSettingsProviderRule.mockContentResolver(mContext))
.when(mContext).getContentResolver();
XmlResourceParser testBookmarks = mResources.getXml(
com.android.frameworks.wmtests.R.xml.bookmarks);
doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks);
+
+ try {
+ // Keep packageName / className in sync with
+ // services/tests/wmtests/res/xml/bookmarks.xml
+ ActivityInfo testActivityInfo = new ActivityInfo();
+ testActivityInfo.applicationInfo = new ApplicationInfo();
+ testActivityInfo.packageName =
+ testActivityInfo.applicationInfo.packageName = "com.test";
+ doReturn(testActivityInfo).when(mPackageManager).getActivityInfo(
+ eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt());
+ } catch (PackageManager.NameNotFoundException ignored) { }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 2e85025..eed4b0b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -36,6 +36,7 @@
import android.provider.Settings;
import android.view.Display;
+import org.junit.Before;
import org.junit.Test;
/**
@@ -48,6 +49,13 @@
private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity";
+ @Before
+ public void setup() {
+ super.setup();
+ overrideResource(com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior,
+ LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
+ }
+
/**
* Stem single key should not launch behavior during set up.
*/
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 2b7e7ab..6f8c91c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -50,7 +50,6 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.withSettings;
@@ -311,13 +310,6 @@
eq(AccessibilityManager.class));
doReturn(false).when(mAccessibilityManager).isEnabled();
doReturn(false).when(mPackageManager).hasSystemFeature(any());
- try {
- doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager)
- .getActivityInfo(any(), anyInt());
- doReturn(new String[] { "testPackage" }).when(mPackageManager)
- .canonicalToCurrentPackageNames(any());
- } catch (PackageManager.NameNotFoundException ignored) { }
-
doReturn(false).when(mTelecomManager).isInCall();
doReturn(false).when(mTelecomManager).isRinging();
doReturn(mTelecomManager).when(mPhoneWindowManager).getTelecommService();
@@ -740,7 +732,6 @@
Mockito.clearInvocations(mContext);
}
-
void assertShowRecentApps() {
mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index a159ce3..44837d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -58,6 +58,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.util.ArraySet;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
@@ -72,6 +73,7 @@
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -612,6 +614,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_MIGRATE_PREDICTIVE_BACK_TRANSITION)
public void testTransitionHappensCancelNavigation() {
// Create a floating task and a fullscreen task, then navigating on fullscreen task.
// The navigation should not been cancelled when transition happens on floating task, and
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 2e0d4d4..2f2b473 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -31,7 +31,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.testng.Assert.assertFalse;
@@ -357,8 +356,6 @@
@Test
public void testRemovesStaleDisplaySettings_defaultDisplay_removesStaleDisplaySettings() {
- assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
-
// Write density setting for second display then remove it.
final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
@@ -387,8 +384,6 @@
@Test
public void testRemovesStaleDisplaySettings_displayNotInLayout_keepsDisplaySettings() {
- assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
-
// Write density setting for primary display.
final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
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 61a6f31..33df5d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -300,7 +300,9 @@
// Vertical thin letterbox disabled
doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
.getThinLetterboxHeightPx();
- assertFalse(mController.isVerticalThinLetterboxed());
+ final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
+ .getAppCompatReachabilityOverrides();
+ assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
// Define a Task 100x100
final Task task = mock(Task.class);
doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
@@ -309,21 +311,21 @@
// Vertical thin letterbox disabled without Task
doReturn(null).when(mActivity).getTask();
- assertFalse(mController.isVerticalThinLetterboxed());
+ assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
// Assign a Task for the Activity
doReturn(task).when(mActivity).getTask();
// (task.width() - act.width()) / 2 = 5 < 10
doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
- assertTrue(mController.isVerticalThinLetterboxed());
+ assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
// (task.width() - act.width()) / 2 = 10 = 10
doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
- assertTrue(mController.isVerticalThinLetterboxed());
+ assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
// (task.width() - act.width()) / 2 = 11 > 10
doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
- assertFalse(mController.isVerticalThinLetterboxed());
+ assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
}
@Test
@@ -331,7 +333,9 @@
// Horizontal thin letterbox disabled
doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
.getThinLetterboxWidthPx();
- assertFalse(mController.isHorizontalThinLetterboxed());
+ final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
+ .getAppCompatReachabilityOverrides();
+ assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
// Define a Task 100x100
final Task task = mock(Task.class);
doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
@@ -340,51 +344,55 @@
// Vertical thin letterbox disabled without Task
doReturn(null).when(mActivity).getTask();
- assertFalse(mController.isHorizontalThinLetterboxed());
+ assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
// Assign a Task for the Activity
doReturn(task).when(mActivity).getTask();
// (task.height() - act.height()) / 2 = 5 < 10
doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
- assertTrue(mController.isHorizontalThinLetterboxed());
+ assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
// (task.height() - act.height()) / 2 = 10 = 10
doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
- assertTrue(mController.isHorizontalThinLetterboxed());
+ assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
// (task.height() - act.height()) / 2 = 11 > 10
doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
- assertFalse(mController.isHorizontalThinLetterboxed());
+ assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
}
@Test
@EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
- spyOn(mController);
- doReturn(true).when(mController).isVerticalThinLetterboxed();
- assertFalse(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(true).when(mController).isHorizontalThinLetterboxed();
- assertFalse(mController.allowHorizontalReachabilityForThinLetterbox());
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ spyOn(reachabilityOverrides);
+ doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
+ assertFalse(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
+ doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
+ assertFalse(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
- doReturn(false).when(mController).isVerticalThinLetterboxed();
- assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(false).when(mController).isHorizontalThinLetterboxed();
- assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
+ doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
+ assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
+ doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
+ assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
}
@Test
@DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
- spyOn(mController);
- doReturn(true).when(mController).isVerticalThinLetterboxed();
- assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(true).when(mController).isHorizontalThinLetterboxed();
- assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ spyOn(reachabilityOverrides);
+ doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
+ assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
+ doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
+ assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
- doReturn(false).when(mController).isVerticalThinLetterboxed();
- assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(false).when(mController).isHorizontalThinLetterboxed();
- assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
+ doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
+ assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
+ doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
+ assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index ed93a8c..7dc3b07 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3431,9 +3431,10 @@
mActivity.getWindowConfiguration().setBounds(null);
setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ false);
-
- assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
- assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ assertFalse(reachabilityOverrides.isVerticalReachabilityEnabled());
+ assertFalse(reachabilityOverrides.isHorizontalReachabilityEnabled());
}
@Test
@@ -3456,7 +3457,8 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Horizontal reachability is disabled because the app is in split screen.
- assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3479,7 +3481,8 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Vertical reachability is disabled because the app is in split screen.
- assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3501,7 +3504,8 @@
// Vertical reachability is disabled because the app does not match parent width
assertNotEquals(mActivity.getScreenResolvedBounds().width(),
mActivity.mDisplayContent.getBounds().width());
- assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3518,7 +3522,8 @@
assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
// Vertical reachability is still enabled as resolved bounds is not empty
- assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3535,7 +3540,8 @@
assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
// Horizontal reachability is still enabled as resolved bounds is not empty
- assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3549,7 +3555,8 @@
prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3563,7 +3570,8 @@
prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
SCREEN_ORIENTATION_LANDSCAPE);
- assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3585,7 +3593,8 @@
// Horizontal reachability is disabled because the app does not match parent height
assertNotEquals(mActivity.getScreenResolvedBounds().height(),
mActivity.mDisplayContent.getBounds().height());
- assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3607,7 +3616,8 @@
// Horizontal reachability is enabled because the app matches parent height
assertEquals(mActivity.getScreenResolvedBounds().height(),
mActivity.mDisplayContent.getBounds().height());
- assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3629,7 +3639,8 @@
// Vertical reachability is enabled because the app matches parent width
assertEquals(mActivity.getScreenResolvedBounds().width(),
mActivity.mDisplayContent.getBounds().width());
- assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -4809,10 +4820,12 @@
}
private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
- spyOn(mActivity.mLetterboxUiController);
- doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController)
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ spyOn(reachabilityOverrides);
+ doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
.allowVerticalReachabilityForThinLetterbox();
- doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController)
+ doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
.allowHorizontalReachabilityForThinLetterbox();
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0468f48..c6959ae 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -51,6 +51,7 @@
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
+import android.telephony.satellite.SatelliteManager;
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.internal.telephony.flags.Flags;
@@ -10022,6 +10023,22 @@
"carrier_roaming_ntn_connect_type_int";
/**
+ * Indicates carrier roaming non-terrestrial network emergency call handover type that the
+ * device will use to perform a handover between ESOS or T911.
+ * If this key is set to {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS}
+ * then the handover will be made to ESOS. If this key is set to
+ * {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911} then the handover
+ * will be made to T911.
+ *
+ * The default value is {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911}.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String
+ KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT =
+ "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
+
+ /**
* The carrier roaming non-terrestrial network hysteresis time in seconds.
*
* If the device supports P2P satellite messaging which is defined by
@@ -10038,6 +10055,19 @@
"carrier_supported_satellite_notification_hysteresis_sec_int";
/**
+ * An integer key holds the timeout duration in seconds used to determine whether to exit
+ * carrier-roaming NB-IOT satellite mode.
+ *
+ * The timer is started when the device screen is turned off during a satellite session.
+ * When the timer expires, the device exits Carrier Roaming NB IOT NTN.
+ *
+ * The default value is 30 seconds.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT =
+ "satellite_screen_off_inactivity_timeout_duration_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.
*
@@ -11196,7 +11226,10 @@
(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_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
+ SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
+ sDefaults.putInt(KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT, 30);
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);
diff --git a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
index 9ff73e2..66a20ae 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
@@ -27,4 +27,11 @@
* @param state The current satellite modem state.
*/
void onSatelliteModemStateChanged(in int state);
+
+ /**
+ * Indicates that the satellite emergency mode has changed.
+ *
+ * @param isEmergency True means satellite enabled for emergency mode, false otherwise.
+ */
+ void onEmergencyModeChanged(in boolean isEmergency);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 4b83b65..0bd9270 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1531,6 +1531,12 @@
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onSatelliteModemStateChanged(state)));
}
+
+ @Override
+ public void onEmergencyModeChanged(boolean isEmergency) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onEmergencyModeChanged(isEmergency)));
+ }
};
sSatelliteModemStateCallbackMap.put(callback, internalCallback);
return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
index 8d33c88..423a785 100644
--- a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
@@ -35,4 +35,14 @@
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
+
+ /**
+ * Called when the satellite emergency mode has changed.
+ *
+ * @param isEmergency {@code true} enabled for emergency mode, {@code false} otherwise.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ default void onEmergencyModeChanged(boolean isEmergency) {};
}
diff --git a/tests/FlickerTests/AppLaunch/OWNERS b/tests/FlickerTests/AppLaunch/OWNERS
index 2c414a2..d16b57d 100644
--- a/tests/FlickerTests/AppLaunch/OWNERS
+++ b/tests/FlickerTests/AppLaunch/OWNERS
@@ -1,4 +1,2 @@
-# System UI > ... > Overview (recent apps) > UI
-# Bug template url: https://b.corp.google.com/issues/new?component=807991&template=1390280 = per-file *Overview*
# window manager > animations/transitions
# Bug template url: https://b.corp.google.com/issues/new?component=316275&template=1018192
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 45bf8e3..9444dd9 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -107,7 +107,7 @@
Visibility::Level visibility_level = Visibility::Level::kUndefined;
bool staged_api = false;
bool allow_new = false;
- FlagStatus flag_status;
+ FlagStatus flag_status = FlagStatus::NoFlag;
std::optional<OverlayableItem> overlayable_item;
std::optional<StagedId> staged_alias;
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 9530c17..4f76e7d 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -104,7 +104,7 @@
// The actual Value.
std::unique_ptr<Value> value;
- FlagStatus flag_status;
+ FlagStatus flag_status = FlagStatus::NoFlag;
ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
: config(config), product(product) {
@@ -271,7 +271,7 @@
std::optional<AllowNew> allow_new;
std::optional<StagedId> staged_id;
bool allow_mangled = false;
- FlagStatus flag_status;
+ FlagStatus flag_status = FlagStatus::NoFlag;
};
struct NewResourceBuilder {
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 67a4828..1942fc11 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -212,7 +212,11 @@
collision_result =
ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
} else {
- collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
+ collision_result = ResourceTable::ResolveFlagCollision(dst_config_value->flag_status,
+ src_config_value->flag_status);
+ if (collision_result == CollisionResult::kConflict) {
+ collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
+ }
}
if (collision_result == CollisionResult::kConflict) {
@@ -291,6 +295,7 @@
} else {
dst_config_value =
dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
+ dst_config_value->flag_status = src_config_value->flag_status;
}
// Continue if we're taking the new resource.
diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java
index 1a20a12..6ea20ec 100644
--- a/wifi/java/src/android/net/wifi/WifiMigration.java
+++ b/wifi/java/src/android/net/wifi/WifiMigration.java
@@ -574,7 +574,7 @@
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION)
+ @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static void migrateLegacyKeystoreToWifiBlobstore() {
final long identity = Binder.clearCallingIdentity();
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index 5a214b7..90d13e6 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -19,11 +19,12 @@
}
flag {
- name: "legacy_keystore_to_wifi_blobstore_migration"
+ name: "legacy_keystore_to_wifi_blobstore_migration_read_only"
is_exported: true
namespace: "wifi"
description: "Add API to migrate all values from Legacy Keystore to the new Wifi Blobstore database"
bug: "332560152"
+ is_fixed_read_only: true
}
flag {