Merge "Improve jank monitor debug overlay" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f1906b5..76d6e56 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -23,6 +23,7 @@
"aconfig_mediacodec_flags_java_lib",
"aconfig_settingslib_flags_java_lib",
"aconfig_trade_in_mode_flags_java_lib",
+ "adpf_flags_java_lib",
"android.adaptiveauth.flags-aconfig-java",
"android.app.appfunctions.flags-aconfig-java",
"android.app.assist.flags-aconfig-java",
@@ -438,6 +439,8 @@
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
+ "com.android.art",
+ "com.android.art.debug",
"com.android.btservices",
"com.android.mediaprovider",
"com.android.permission",
@@ -874,6 +877,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Adaptive Performance
+java_aconfig_library {
+ name: "adpf_flags_java_lib",
+ aconfig_declarations: "adpf_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Graphics
java_aconfig_library {
name: "hwui_flags_java_lib",
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 47a85498f..63624d8 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -29,6 +29,7 @@
namespace: "backstage_power"
description: "Detect, report and take action on jobs that maybe abandoned by the app without calling jobFinished."
bug: "372529068"
+ is_exported: true
}
flag {
@@ -36,6 +37,7 @@
namespace: "backstage_power"
description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
bug: "374175032"
+ is_exported: true
}
flag {
diff --git a/core/api/current.txt b/core/api/current.txt
index 6fb319b..d313a79 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2187,6 +2187,18 @@
public static final class R.dimen {
ctor public R.dimen();
field public static final int app_icon_size = 17104896; // 0x1050000
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialDamping;
field public static final int dialog_min_width_major = 17104899; // 0x1050003
field public static final int dialog_min_width_minor = 17104900; // 0x1050004
field public static final int notification_large_icon_height = 17104902; // 0x1050006
@@ -2483,6 +2495,18 @@
ctor public R.integer();
field public static final int config_longAnimTime = 17694722; // 0x10e0002
field public static final int config_mediumAnimTime = 17694721; // 0x10e0001
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialStiffness;
field public static final int config_shortAnimTime = 17694720; // 0x10e0000
field @Deprecated public static final int status_bar_notification_info_maxnum = 17694723; // 0x10e0003
}
@@ -10000,12 +10024,12 @@
method public int describeContents();
method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
method @FlaggedApi("android.companion.association_device_icon") @Nullable public android.graphics.drawable.Icon getDeviceIcon();
+ method @FlaggedApi("android.companion.association_tag") @Nullable public android.companion.DeviceId getDeviceId();
method @Nullable public android.net.MacAddress getDeviceMacAddress();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
method public int getId();
method public int getSystemDataSyncFlags();
- method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
method public boolean isSelfManaged();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
@@ -10078,7 +10102,6 @@
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
- method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
method public void disableSystemDataSyncForTypes(int, int);
method @Deprecated public void disassociate(@NonNull String);
@@ -10090,7 +10113,7 @@
method @FlaggedApi("android.companion.perm_sync_user_consent") public boolean isPermissionTransferUserConsented(int);
method @FlaggedApi("android.companion.unpair_associated_device") @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond(int);
method public void requestNotificationAccess(android.content.ComponentName);
- method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
+ method @FlaggedApi("android.companion.association_tag") public void setDeviceId(int, @Nullable android.companion.DeviceId);
method @Deprecated @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
@@ -10135,6 +10158,21 @@
public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
}
+ @FlaggedApi("android.companion.association_tag") public final class DeviceId implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getCustomId();
+ method @Nullable public android.net.MacAddress getMacAddress();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.DeviceId> CREATOR;
+ }
+
+ public static final class DeviceId.Builder {
+ ctor public DeviceId.Builder();
+ method @NonNull public android.companion.DeviceId build();
+ method @NonNull public android.companion.DeviceId.Builder setCustomId(@Nullable String);
+ method @NonNull public android.companion.DeviceId.Builder setMacAddress(@Nullable android.net.MacAddress);
+ }
+
public class DeviceNotAssociatedException extends java.lang.RuntimeException {
}
@@ -37626,10 +37664,6 @@
field public static final String TERTIARY_PHONE_TYPE = "tertiary_phone_type";
}
- @FlaggedApi("android.provider.new_default_account_api_enabled") public static class ContactsContract.LocalSimContactsWriteException extends java.lang.IllegalArgumentException {
- ctor public ContactsContract.LocalSimContactsWriteException(@NonNull String);
- }
-
public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns {
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final android.net.Uri ENTERPRISE_CONTENT_FILTER_URI;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7cb9087..612a48a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -516,6 +516,7 @@
field public static final int config_defaultCallScreening = 17039398; // 0x1040026
field public static final int config_defaultDialer = 17039395; // 0x1040023
field public static final int config_defaultNotes = 17039429; // 0x1040045
+ field @FlaggedApi("android.permission.flags.cross_user_role_platform_api_enabled") public static final int config_defaultReservedForTestingProfileGroupExclusivity;
field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo = 17039432; // 0x1040048
field public static final int config_defaultSms = 17039396; // 0x1040024
field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final int config_defaultWallet = 17039433; // 0x1040049
@@ -5253,9 +5254,9 @@
}
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
- method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
+ method public void close();
method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo();
- method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
+ method @NonNull public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
}
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
@@ -7191,8 +7192,8 @@
method public int getAudioCapabilities();
method @NonNull public byte[] getData();
method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphrases();
- method public boolean isAllowMultipleTriggers();
method public boolean isCaptureRequested();
+ method public boolean isMultipleTriggersAllowed();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
}
@@ -7200,11 +7201,11 @@
public static final class SoundTrigger.RecognitionConfig.Builder {
ctor public SoundTrigger.RecognitionConfig.Builder();
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig build();
- method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setMultipleTriggersAllowed(boolean);
}
public static class SoundTrigger.RecognitionEvent {
@@ -16270,6 +16271,7 @@
method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackageName();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
+ method @FlaggedApi("com.android.internal.telephony.flags.get_group_id_level2") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getGroupIdLevel2();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<java.lang.String> getImsPcscfAddresses();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @Nullable @RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER) public String getImsPrivateUserIdentity();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<android.net.Uri> getImsPublicUserIdentities();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3a62ac9..603677e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -934,6 +934,7 @@
method @NonNull public android.companion.AssociationInfo build();
method @NonNull public android.companion.AssociationInfo.Builder setAssociatedDevice(@Nullable android.companion.AssociatedDevice);
method @FlaggedApi("android.companion.association_device_icon") @NonNull public android.companion.AssociationInfo.Builder setDeviceIcon(@Nullable android.graphics.drawable.Icon);
+ method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setDeviceId(@Nullable android.companion.DeviceId);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceMacAddress(@Nullable android.net.MacAddress);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceProfile(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setDisplayName(@Nullable CharSequence);
@@ -942,7 +943,6 @@
method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
- method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 3cbea87..da33847 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -798,7 +798,7 @@
private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
mHasSystemFeatureCache = new PropertyInvalidatedCache<>(
new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
- .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false),
+ .api(HAS_SYSTEM_FEATURE_API).maxEntries(SDK_FEATURE_COUNT).isolateUids(false),
HAS_SYSTEM_FEATURE_API, null) {
@Override
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 1f31ab5..44940ae 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -164,4 +164,5 @@
name: "app_start_info_component"
description: "Control ApplicationStartInfo component field and API"
bug: "362537357"
+ is_exported: true
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 581efa5..22bc356 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -286,6 +286,16 @@
}
flag {
+ name: "unsuspend_not_suspended"
+ namespace: "enterprise"
+ description: "When admin unsuspends packages, pass previously not suspended packages to PM too"
+ bug: "378766314"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "backup_connected_apps_settings"
namespace: "enterprise"
description: "backup and restore connected work and personal apps user settings across devices"
@@ -367,6 +377,7 @@
namespace: "enterprise"
description: "API that removes a given managed profile."
bug: "372652841"
+ is_exported: true
}
flag {
@@ -390,6 +401,7 @@
namespace: "enterprise"
description: "Split up existing create and provision managed profile API."
bug: "375382324"
+ is_exported: true
}
flag {
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 5e09517..529b59a 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -6,6 +6,7 @@
namespace: "machine_learning"
description: "Flag to enable the service"
bug: "309689654"
+ is_exported: true
}
flag {
name: "enable_token_refresh"
diff --git a/core/java/android/app/jank/flags.aconfig b/core/java/android/app/jank/flags.aconfig
index 5657f7e..a62df1b 100644
--- a/core/java/android/app/jank/flags.aconfig
+++ b/core/java/android/app/jank/flags.aconfig
@@ -6,6 +6,7 @@
namespace: "system_performance"
description: "Control the API portion of Detailed Application Jank Metrics"
bug: "366264614"
+ is_exported: true
}
flag {
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 17bcdb0..9914ba2 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -111,4 +111,5 @@
namespace: "app_widgets"
description: "Enable collection of widget engagement metrics"
bug: "364655296"
+ is_exported: true
}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 1249734..2e108a1 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -54,8 +54,6 @@
@NonNull
private final String mPackageName;
@Nullable
- private final String mTag;
- @Nullable
private final MacAddress mDeviceMacAddress;
@Nullable
private final CharSequence mDisplayName;
@@ -85,6 +83,8 @@
*/
private final long mLastTimeConnectedMs;
private final int mSystemDataSyncFlags;
+ @Nullable
+ private final DeviceId mDeviceId;
/**
* A device icon displayed on a selfManaged association dialog.
@@ -97,11 +97,11 @@
* @hide
*/
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
- @Nullable String tag, @Nullable MacAddress macAddress,
- @Nullable CharSequence displayName, @Nullable String deviceProfile,
- @Nullable AssociatedDevice associatedDevice, boolean selfManaged,
- boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
- long lastTimeConnectedMs, int systemDataSyncFlags, @Nullable Icon deviceIcon) {
+ @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+ @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+ boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, boolean pending,
+ long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags,
+ @Nullable Icon deviceIcon, @Nullable DeviceId deviceId) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -115,7 +115,6 @@
mPackageName = packageName;
mDeviceMacAddress = macAddress;
mDisplayName = displayName;
- mTag = tag;
mDeviceProfile = deviceProfile;
mAssociatedDevice = associatedDevice;
mSelfManaged = selfManaged;
@@ -126,6 +125,7 @@
mLastTimeConnectedMs = lastTimeConnectedMs;
mSystemDataSyncFlags = systemDataSyncFlags;
mDeviceIcon = deviceIcon;
+ mDeviceId = deviceId;
}
/**
@@ -155,13 +155,13 @@
}
/**
- * @return the tag of this association.
- * @see CompanionDeviceManager#setAssociationTag(int, String)
+ * @return the {@link DeviceId} of this association.
+ * @see CompanionDeviceManager#setDeviceId(int, DeviceId)
*/
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@Nullable
- public String getTag() {
- return mTag;
+ public DeviceId getDeviceId() {
+ return mDeviceId;
}
/**
@@ -355,7 +355,6 @@
+ "mId=" + mId
+ ", mUserId=" + mUserId
+ ", mPackageName='" + mPackageName + '\''
- + ", mTag='" + mTag + '\''
+ ", mDeviceMacAddress=" + mDeviceMacAddress
+ ", mDisplayName='" + mDisplayName + '\''
+ ", mDeviceProfile='" + mDeviceProfile + '\''
@@ -369,6 +368,7 @@
mLastTimeConnectedMs == Long.MAX_VALUE
? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs))
+ ", mSystemDataSyncFlags=" + mSystemDataSyncFlags
+ + ", mDeviceId='" + mDeviceId
+ '}';
}
@@ -386,21 +386,22 @@
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
- && Objects.equals(mTag, that.mTag)
&& Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
&& Objects.equals(mDisplayName, that.mDisplayName)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSystemDataSyncFlags == that.mSystemDataSyncFlags
&& (mDeviceIcon == null ? that.mDeviceIcon == null
- : mDeviceIcon.sameAs(that.mDeviceIcon));
+ : mDeviceIcon.sameAs(that.mDeviceIcon))
+ && Objects.equals(mDeviceId, that.mDeviceId);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
+ return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
- mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon);
+ mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon,
+ mDeviceId);
}
@Override
@@ -413,7 +414,6 @@
dest.writeInt(mId);
dest.writeInt(mUserId);
dest.writeString(mPackageName);
- dest.writeString(mTag);
dest.writeTypedObject(mDeviceMacAddress, 0);
dest.writeCharSequence(mDisplayName);
dest.writeString(mDeviceProfile);
@@ -431,13 +431,19 @@
} else {
dest.writeInt(0);
}
+
+ if (Flags.associationTag() && mDeviceId != null) {
+ dest.writeInt(1);
+ dest.writeTypedObject(mDeviceId, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
private AssociationInfo(@NonNull Parcel in) {
mId = in.readInt();
mUserId = in.readInt();
mPackageName = in.readString();
- mTag = in.readString();
mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
mDisplayName = in.readCharSequence();
mDeviceProfile = in.readString();
@@ -454,6 +460,12 @@
} else {
mDeviceIcon = null;
}
+
+ if (Flags.associationTag() && in.readInt() == 1) {
+ mDeviceId = in.readTypedObject(DeviceId.CREATOR);
+ } else {
+ mDeviceId = null;
+ }
}
@NonNull
@@ -481,7 +493,6 @@
private final int mId;
private final int mUserId;
private final String mPackageName;
- private String mTag;
private MacAddress mDeviceMacAddress;
private CharSequence mDisplayName;
private String mDeviceProfile;
@@ -494,6 +505,7 @@
private long mLastTimeConnectedMs;
private int mSystemDataSyncFlags;
private Icon mDeviceIcon;
+ private DeviceId mDeviceId;
/** @hide */
@TestApi
@@ -509,7 +521,6 @@
mId = info.mId;
mUserId = info.mUserId;
mPackageName = info.mPackageName;
- mTag = info.mTag;
mDeviceMacAddress = info.mDeviceMacAddress;
mDisplayName = info.mDisplayName;
mDeviceProfile = info.mDeviceProfile;
@@ -522,6 +533,7 @@
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
mDeviceIcon = info.mDeviceIcon;
+ mDeviceId = info.mDeviceId;
}
/**
@@ -534,7 +546,6 @@
mId = id;
mUserId = userId;
mPackageName = packageName;
- mTag = info.mTag;
mDeviceMacAddress = info.mDeviceMacAddress;
mDisplayName = info.mDisplayName;
mDeviceProfile = info.mDeviceProfile;
@@ -547,14 +558,15 @@
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
mDeviceIcon = info.mDeviceIcon;
+ mDeviceId = info.mDeviceId;
}
/** @hide */
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@TestApi
@NonNull
- public Builder setTag(@Nullable String tag) {
- mTag = tag;
+ public Builder setDeviceId(@Nullable DeviceId deviceId) {
+ mDeviceId = deviceId;
return this;
}
@@ -684,7 +696,6 @@
mId,
mUserId,
mPackageName,
- mTag,
mDeviceMacAddress,
mDisplayName,
mDeviceProfile,
@@ -696,7 +707,8 @@
mTimeApprovedMs,
mLastTimeConnectedMs,
mSystemDataSyncFlags,
- mDeviceIcon
+ mDeviceIcon,
+ mDeviceId
);
}
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 0466847..a96ba11 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -278,12 +278,6 @@
public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
/**
- * The length limit of Association tag.
- * @hide
- */
- private static final int ASSOCIATION_TAG_LENGTH_LIMIT = 1024;
-
- /**
* Callback for applications to receive updates about and the outcome of
* {@link AssociationRequest} issued via {@code associate()} call.
*
@@ -1780,57 +1774,25 @@
}
/**
- * Sets the {@link AssociationInfo#getTag() tag} for this association.
+ * Sets the {@link DeviceId deviceId} for this association.
*
- * <p>The length of the tag must be at most 1024 characters to save disk space.
- *
- * <p>This allows to store useful information about the associated devices.
+ * <p>This device id helps the system uniquely identify your device for efficient device
+ * management and prevents duplicate entries.
*
* @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
- * of the companion device recorded by CompanionDeviceManager
- * @param tag the tag of this association
+ * of the companion device recorded by CompanionDeviceManager.
+ * @param deviceId to be used as device identifier to represent the associated device.
*/
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
- public void setAssociationTag(int associationId, @NonNull String tag) {
- if (mService == null) {
- Log.w(TAG, "CompanionDeviceManager service is not available.");
- return;
- }
-
- Objects.requireNonNull(tag, "tag cannot be null");
-
- if (tag.length() > ASSOCIATION_TAG_LENGTH_LIMIT) {
- throw new IllegalArgumentException("Length of the tag must be at most"
- + ASSOCIATION_TAG_LENGTH_LIMIT + " characters");
- }
-
- try {
- mService.setAssociationTag(associationId, tag);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Clears the {@link AssociationInfo#getTag() tag} for this association.
- *
- * <p>The tag will be set to null for this association when calling this API.
- *
- * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
- * of the companion device recorded by CompanionDeviceManager
- * @see CompanionDeviceManager#setAssociationTag(int, String)
- */
- @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
- @UserHandleAware
- public void clearAssociationTag(int associationId) {
+ public void setDeviceId(int associationId, @Nullable DeviceId deviceId) {
if (mService == null) {
Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
try {
- mService.clearAssociationTag(associationId);
+ mService.setDeviceId(associationId, deviceId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/DeviceId.aidl b/core/java/android/companion/DeviceId.aidl
new file mode 100644
index 0000000..d60d5f4
--- /dev/null
+++ b/core/java/android/companion/DeviceId.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.companion;
+
+parcelable DeviceId;
\ No newline at end of file
diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java
new file mode 100644
index 0000000..f66a1ae
--- /dev/null
+++ b/core/java/android/companion/DeviceId.java
@@ -0,0 +1,210 @@
+/*
+ * 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 android.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.MacAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * A device id represents a device identifier managed by the companion app.
+ */
+@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
+public final class DeviceId implements Parcelable {
+ /**
+ * The length limit of custom id.
+ */
+ private static final int CUSTOM_ID_LENGTH_LIMIT = 1024;
+
+ private final String mCustomId;
+ private final MacAddress mMacAddress;
+
+ /**
+ * @hide
+ */
+ public DeviceId(@Nullable String customId, @Nullable MacAddress macAddress) {
+ mCustomId = customId;
+ mMacAddress = macAddress;
+ }
+
+ /**
+ * Returns true if two Device ids are represent the same device. False otherwise.
+ * @hide
+ */
+ public boolean isSameDevice(@Nullable DeviceId other) {
+ if (other == null) {
+ return false;
+ }
+
+ if (this.mCustomId != null && other.mCustomId != null) {
+ return this.mCustomId.equals(other.mCustomId);
+ }
+ if (this.mMacAddress != null && other.mMacAddress != null) {
+ return this.mMacAddress.equals(other.mMacAddress);
+ }
+
+ return false;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getMacAddressAsString() {
+ return mMacAddress != null ? mMacAddress.toString().toUpperCase(Locale.US) : null;
+ }
+
+ /**
+ * @return the custom id that managed by the companion app.
+ */
+ @Nullable
+ public String getCustomId() {
+ return mCustomId;
+ }
+
+ /**
+ * @return the mac address that managed by the companion app.
+ */
+ @Nullable
+ public MacAddress getMacAddress() {
+ return mMacAddress;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (mCustomId != null) {
+ dest.writeInt(1);
+ dest.writeString8(mCustomId);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeTypedObject(mMacAddress, 0);
+
+ }
+
+ private DeviceId(@NonNull Parcel in) {
+ int flg = in.readInt();
+ if (flg == 1) {
+ mCustomId = in.readString8();
+ } else {
+ mCustomId = null;
+ }
+ mMacAddress = in.readTypedObject(MacAddress.CREATOR);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<DeviceId> CREATOR =
+ new Parcelable.Creator<DeviceId>() {
+ @Override
+ public DeviceId[] newArray(int size) {
+ return new DeviceId[size];
+ }
+
+ @Override
+ public DeviceId createFromParcel(@android.annotation.NonNull Parcel in) {
+ return new DeviceId(in);
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCustomId, mMacAddress);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DeviceId that)) return false;
+
+ return Objects.equals(mCustomId, that.mCustomId)
+ && Objects.equals(mMacAddress, that.mMacAddress);
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceId{"
+ + "," + "mCustomId= " + mCustomId
+ + "," + "mMacAddress= " + mMacAddress
+ + "}";
+ }
+
+ /**
+ * A builder for {@link DeviceId}
+ */
+ public static final class Builder extends OneTimeUseBuilder<DeviceId> {
+ private String mCustomId;
+ private MacAddress mMacAddress;
+
+ public Builder() {}
+
+ /**
+ * Sets the custom device id. This id is used by the Companion app to
+ * identify a specific device.
+ *
+ * @param customId the custom device id
+ * @throws IllegalArgumentException length of the custom id must more than 1024
+ * characters to save disk space.
+ */
+ @NonNull
+ public Builder setCustomId(@Nullable String customId) {
+ checkNotUsed();
+ if (customId != null
+ && customId.length() > CUSTOM_ID_LENGTH_LIMIT) {
+ throw new IllegalArgumentException("Length of the custom id must be at most "
+ + CUSTOM_ID_LENGTH_LIMIT + " characters");
+ }
+ this.mCustomId = customId;
+ return this;
+ }
+
+ /**
+ * Sets the mac address. This mac address is used by the Companion app to
+ * identify a specific device.
+ *
+ * @param macAddress the remote device mac address
+ * @throws IllegalArgumentException length of the custom id must more than 1024
+ * characters to save disk space.
+ */
+ @NonNull
+ public Builder setMacAddress(@Nullable MacAddress macAddress) {
+ checkNotUsed();
+ mMacAddress = macAddress;
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public DeviceId build() {
+ markUsed();
+ if (mCustomId == null && mMacAddress == null) {
+ throw new IllegalArgumentException("At least one device id property must be"
+ + "non-null to build a DeviceId.");
+ }
+ return new DeviceId(mCustomId, mMacAddress);
+ }
+ }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index de3ddec..a2b7dd9 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -28,6 +28,7 @@
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.os.ParcelUuid;
+import android.companion.DeviceId;
/**
@@ -134,9 +135,7 @@
@EnforcePermission("MANAGE_COMPANION_DEVICES")
void enableSecureTransport(boolean enabled);
- void setAssociationTag(int associationId, String tag);
-
- void clearAssociationTag(int associationId);
+ void setDeviceId(int associationId, in DeviceId deviceId);
byte[] getBackupPayload(int userId);
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 2539a12..2b9f700 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -46,6 +46,7 @@
namespace: "companion"
description: "Unpair with an associated bluetooth device"
bug: "322237619"
+ is_exported: true
}
flag {
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index fbe581c..8ba2dcc 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -341,6 +341,7 @@
description: "Feature flag to introduce a new way to change the launcher badging."
bug: "364760703"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -357,6 +358,7 @@
namespace: "package_manager_service"
description: "Block app installations that specify an incompatible minor SDK version"
bug: "377302905"
+ is_exported: true
}
flag {
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index d351ebc..f29e2e8 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -593,4 +593,5 @@
namespace: "profile_experiences"
description: "Add support for LauncherUserInfo configs"
bug: "346553745"
+ is_exported: true
}
diff --git a/core/java/android/content/pm/xr.aconfig b/core/java/android/content/pm/xr.aconfig
index 61835c1..a26f48e 100644
--- a/core/java/android/content/pm/xr.aconfig
+++ b/core/java/android/content/pm/xr.aconfig
@@ -6,4 +6,5 @@
name: "xr_manifest_entries"
description: "Adds manifest entries used by Android XR"
bug: "364416355"
+ is_exported: true
}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 7a23033..73b6417 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -53,6 +53,7 @@
namespace: "biometrics_framework"
description: "This flag is for API changes related to Identity Check"
bug: "373424727"
+ is_exported: true
}
flag {
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index b3d65c1..cf952cb 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -19,7 +19,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
import android.hardware.location.ContextHubTransaction;
@@ -71,7 +70,6 @@
* receiving the response for the message.
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public ContextHubTransaction<Void> sendMessage(@NonNull HubMessage message) {
if (mIsClosed.get()) {
throw new IllegalStateException("Session is already closed.");
@@ -122,7 +120,6 @@
* <p>When this function is invoked, the messaging associated with this session is invalidated.
* All futures messages targeted for this client are dropped.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void close() {
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index 4a724ce..1c98b4b 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -38,7 +38,6 @@
* @throws IllegalArgumentException If the HubEndpointInfo is not valid.
* @throws IllegalStateException If there are too many opened sessions.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo);
/**
@@ -49,7 +48,6 @@
*
* @throws IllegalStateException If the session wasn't opened.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void closeSession(int sessionId, int reason);
/**
@@ -61,13 +59,11 @@
*
* @throws IllegalStateException If the session wasn't opened.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void openSessionRequestComplete(int sessionId);
/**
* Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void unregister();
/**
@@ -79,7 +75,6 @@
* @param transactionCallback Nullable. If the hub message requires a reply, the transactionCallback
* will be set to non-null.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void sendMessage(int sessionId, in HubMessage message,
in @nullable IContextHubTransactionCallback transactionCallback);
@@ -91,6 +86,5 @@
* @param messageSeqNumber The message sequence number, this should match a previously received HubMessage.
* @param errorCode The message delivery status detail.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode);
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index dc4273d..ebb6172 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -167,6 +167,7 @@
namespace: "input"
description: "Enables new 25Q2 keycodes"
bug: "365920375"
+ is_exported: true
}
flag {
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 2ba1078..73c8e3e 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -157,7 +157,7 @@
SoundTrigger.RecognitionConfig apiConfig) {
RecognitionConfig aidlConfig = new RecognitionConfig();
aidlConfig.captureRequested = apiConfig.isCaptureRequested();
- // apiConfig.isAllowMultipleTriggers() is ignored by the lower layers.
+ // apiConfig.isMultipleTriggersAllowed() is ignored by the lower layers.
aidlConfig.phraseRecognitionExtras =
new PhraseRecognitionExtra[apiConfig.getKeyphrases().size()];
for (int i = 0; i < apiConfig.getKeyphrases().size(); ++i) {
@@ -178,7 +178,7 @@
}
return new SoundTrigger.RecognitionConfig.Builder()
.setCaptureRequested(aidlConfig.captureRequested)
- .setAllowMultipleTriggers(false)
+ .setMultipleTriggersAllowed(false)
.setKeyphrases(keyphrases)
.setData(Arrays.copyOf(aidlConfig.data, aidlConfig.data.length))
.setAudioCapabilities(aidl2apiAudioCapabilities(aidlConfig.audioCapabilities))
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 7745b03..7c4ddc6 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1518,7 +1518,7 @@
@FlaggedApi(android.media.soundtrigger.Flags.FLAG_MANAGER_API)
public static final class RecognitionConfig implements Parcelable {
private final boolean mCaptureRequested;
- private final boolean mAllowMultipleTriggers;
+ private final boolean mMultipleTriggersAllowed;
private final KeyphraseRecognitionExtra mKeyphrases[];
private final byte[] mData;
private final @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1529,7 +1529,7 @@
* {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
*
* @param captureRequested Whether the DSP should capture the trigger sound.
- * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
* triggers.
* @param keyphrases List of keyphrases in the sound model.
* @param data Opaque data for use by system applications who know about voice engine
@@ -1537,11 +1537,11 @@
* @param audioCapabilities Bit field encoding of the AudioCapabilities. See
* {@link ModuleProperties.AudioCapabilities} for details.
*/
- private RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ private RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
@Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
@ModuleProperties.AudioCapabilities int audioCapabilities) {
this.mCaptureRequested = captureRequested;
- this.mAllowMultipleTriggers = allowMultipleTriggers;
+ this.mMultipleTriggersAllowed = multipleTriggersAllowed;
this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
this.mData = data != null ? data : new byte[0];
this.mAudioCapabilities = audioCapabilities;
@@ -1553,7 +1553,7 @@
*
* @deprecated Use {@link Builder} instead.
* @param captureRequested Whether the DSP should capture the trigger sound.
- * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
* triggers.
* @param keyphrases List of keyphrases in the sound model.
* @param data Opaque data for use by system applications.
@@ -1563,9 +1563,9 @@
@UnsupportedAppUsage
@Deprecated
@TestApi
- public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ public RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
@Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
- this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
+ this(captureRequested, multipleTriggersAllowed, keyphrases, data, 0);
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1593,8 +1593,8 @@
* <p><b>Note:</b> This config flag is currently used at the service layer rather than by
* the DSP.
*/
- public boolean isAllowMultipleTriggers() {
- return mAllowMultipleTriggers;
+ public boolean isMultipleTriggersAllowed() {
+ return mMultipleTriggersAllowed;
}
/**
@@ -1627,19 +1627,19 @@
private static RecognitionConfig fromParcel(Parcel in) {
boolean captureRequested = in.readBoolean();
- boolean allowMultipleTriggers = in.readBoolean();
+ boolean multipleTriggersAllowed = in.readBoolean();
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
byte[] data = in.createByteArray();
int audioCapabilities = in.readInt();
- return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
- audioCapabilities);
+ return new RecognitionConfig(captureRequested, multipleTriggersAllowed, keyphrases,
+ data, audioCapabilities);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mCaptureRequested);
- dest.writeBoolean(mAllowMultipleTriggers);
+ dest.writeBoolean(mMultipleTriggersAllowed);
dest.writeTypedArray(mKeyphrases, flags);
dest.writeByteArray(mData);
dest.writeInt(mAudioCapabilities);
@@ -1653,7 +1653,7 @@
@Override
public String toString() {
return "RecognitionConfig [captureRequested=" + mCaptureRequested
- + ", allowMultipleTriggers=" + mAllowMultipleTriggers + ", keyphrases="
+ + ", multipleTriggersAllowed=" + mMultipleTriggersAllowed + ", keyphrases="
+ Arrays.toString(mKeyphrases) + ", data=" + Arrays.toString(mData)
+ ", audioCapabilities=" + Integer.toHexString(mAudioCapabilities) + "]";
}
@@ -1670,7 +1670,7 @@
if (mCaptureRequested != other.mCaptureRequested) {
return false;
}
- if (mAllowMultipleTriggers != other.mAllowMultipleTriggers) {
+ if (mMultipleTriggersAllowed != other.mMultipleTriggersAllowed) {
return false;
}
if (!Arrays.equals(mKeyphrases, other.mKeyphrases)) {
@@ -1690,7 +1690,7 @@
final int prime = 31;
int result = 1;
result = prime * result + (mCaptureRequested ? 1 : 0);
- result = prime * result + (mAllowMultipleTriggers ? 1 : 0);
+ result = prime * result + (mMultipleTriggersAllowed ? 1 : 0);
result = prime * result + Arrays.hashCode(mKeyphrases);
result = prime * result + Arrays.hashCode(mData);
result = prime * result + mAudioCapabilities;
@@ -1702,7 +1702,7 @@
*/
public static final class Builder {
private boolean mCaptureRequested;
- private boolean mAllowMultipleTriggers;
+ private boolean mMultipleTriggersAllowed;
@Nullable private KeyphraseRecognitionExtra[] mKeyphrases;
@Nullable private byte[] mData;
private @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1725,12 +1725,12 @@
/**
* Sets allow multiple triggers state.
- * @param allowMultipleTriggers Whether the service should restart listening after the
+ * @param multipleTriggersAllowed Whether the service should restart listening after the
* DSP triggers.
* @return the same Builder instance.
*/
- public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
- mAllowMultipleTriggers = allowMultipleTriggers;
+ public @NonNull Builder setMultipleTriggersAllowed(boolean multipleTriggersAllowed) {
+ mMultipleTriggersAllowed = multipleTriggersAllowed;
return this;
}
@@ -1779,7 +1779,7 @@
public @NonNull RecognitionConfig build() {
RecognitionConfig config = new RecognitionConfig(
/* captureRequested= */ mCaptureRequested,
- /* allowMultipleTriggers= */ mAllowMultipleTriggers,
+ /* multipleTriggersAllowed= */ mMultipleTriggersAllowed,
/* keyphrases= */ mKeyphrases,
/* data= */ mData,
/* audioCapabilities= */ mAudioCapabilities);
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index b719a7c..9403f78 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -30,6 +30,7 @@
namespace: "usb"
description: "Feature flag to enable exposing usb speed system api"
bug: "373653182"
+ is_exported: true
}
flag {
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index f7dc790..6799db3 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -27,4 +27,5 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index f1936b5..4a14a8d 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -64,4 +64,10 @@
* Get Maximum number of graphics pipeline threads allowed per-app.
*/
int getMaxGraphicsPipelineThreadsCount();
+
+ /**
+ * Used by the JNI to pass an interface to the SessionManager;
+ * for internal use only.
+ */
+ oneway void passSessionManagerBinder(in IBinder sessionManager);
}
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 6fd4f3c..e3f899d 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -27,4 +27,9 @@
void sendHint(int hint);
void setMode(int mode, boolean enabled);
void reportActualWorkDuration2(in WorkDuration[] workDurations);
+
+ /**
+ * Used by apps to associate a session to a given set of layers
+ */
+ oneway void associateToLayers(in IBinder[] layerTokens);
}
diff --git a/core/java/android/os/SessionCreationConfig.aidl b/core/java/android/os/SessionCreationConfig.aidl
index cdc0ef4..17147e4 100644
--- a/core/java/android/os/SessionCreationConfig.aidl
+++ b/core/java/android/os/SessionCreationConfig.aidl
@@ -36,4 +36,12 @@
* List of the modes to be enabled upon session creation.
*/
SessionMode[] modesToEnable;
+
+ /**
+ * List of layers to attach this session to.
+ *
+ * Note: DO NOT STORE THESE IN HintSessionManager, as
+ * it will break the layer lifecycle.
+ */
+ IBinder[] layerTokens;
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 3001fbd..6357baa 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -96,6 +96,7 @@
namespace: "crumpet"
description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
bug: "324046728"
+ is_exported: true
}
# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
@@ -178,6 +179,7 @@
namespace: "game"
description: "Feature flag for adding CPU/GPU headroom API"
bug: "346604998"
+ is_exported: true
}
flag {
@@ -223,6 +225,14 @@
}
flag {
+ name: "material_motion_tokens"
+ namespace: "systemui"
+ description: "Adding new Material Tokens for M3 Motion Spec"
+ bug: "324922198"
+ is_exported: true
+}
+
+flag {
name: "message_queue_tail_tracking"
namespace: "system_performance"
description: "track tail of message queue."
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index b5139b5..c2b8157 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -213,6 +213,7 @@
namespace: "permissions"
description: "Enable getDeviceId API in OpEventProxyInfo"
bug: "337340961"
+ is_exported: true
}
flag {
@@ -254,6 +255,7 @@
namespace: "permissions"
description: "New setOnOpNotedCallback API to allow subscribing to only sync ops."
bug: "372910217"
+ is_exported: true
}
flag {
@@ -463,3 +465,12 @@
description: "Enables text classifier for OTP detection that is updatable from mainline module"
bug: "377229653"
}
+
+flag {
+ name: "cross_user_role_platform_api_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable cross-user roles platform API"
+ bug: "367732307"
+}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 8e379e8..99ff38b 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -10859,28 +10859,6 @@
"vnd.android.cursor.item/contact_metadata_sync_state";
}
- /**
- * This exception is thrown when an attempt is made to perform a write operation
- * on a contact or contact group targeting a local account or a SIM account,
- * and the operation is not permitted under the current conditions.
- * The local account can be retrieved using {@link RawContacts#getLocalAccountName(Context)}
- * and {@link RawContacts#getLocalAccountType(Context)}.
- * SIM accounts can be retrieved using {@link SimContacts#getSimAccounts(ContentResolver)}.
- *
- * <p>Local and SIM accounts have limitations that may prevent write operations
- * due to their nature, underlying implementation, or the current system state.
- * For example, the SIM card may be full, read-only, or not present.
- *
- * <p>The specific conditions under which write operations are permitted on
- * local or SIM accounts can vary.
- */
- @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
- public static class LocalSimContactsWriteException extends IllegalArgumentException {
- public LocalSimContactsWriteException(@NonNull String s) {
- super(s);
- }
- }
-
private static Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull Uri uri,
@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
try (ContentProviderClient client = resolver.acquireContentProviderClient(uri)) {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 09004b3..34bae46 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -112,6 +112,7 @@
namespace: "hardware_backed_security"
description: "AFL feature"
bug: "365994454"
+ is_exported: true
}
flag {
@@ -127,6 +128,7 @@
namespace: "hardware_backed_security"
description: "Feature flag for exposing KeyStore grant APIs"
bug: "351158708"
+ is_exported: true
}
flag {
@@ -134,4 +136,5 @@
namespace: "biometrics"
description: "Feature flag for Secure Lockdown feature"
bug: "373422357"
+ is_exported: true
}
\ No newline at end of file
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 6228bc9..42dbd37 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -78,6 +78,7 @@
description: "Prevent intent redirect attacks"
bug: "361143368"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 5d0ec73..7256907 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2363,7 +2363,6 @@
// -- parcelable interface --
private RankingMap(Parcel in) {
- final ClassLoader cl = getClass().getClassLoader();
final int count = in.readInt();
mOrderedKeys.ensureCapacity(count);
mRankings.ensureCapacity(count);
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 7225f27..9736345 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -6,6 +6,7 @@
namespace: "wallet_integration"
description: "Option to launch the Wallet app on double-tap of the power button"
bug: "378469025"
+ is_exported: true
}
flag {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 7d79fd3..68fd115 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1541,7 +1541,7 @@
mInternalCallback,
new RecognitionConfig.Builder()
.setCaptureRequested(captureTriggerAudio)
- .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setMultipleTriggersAllowed(allowMultipleTriggers)
.setKeyphrases(recognitionExtra)
.setData(data)
.setAudioCapabilities(audioCapabilities)
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f43f172..c2e542c 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -91,6 +91,7 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
flag {
@@ -196,6 +197,7 @@
namespace: "text"
description: "Feature flag for adding a TYPE_DURATION to TtsSpan"
bug: "337103893"
+ is_exported: true
}
flag {
@@ -203,6 +205,7 @@
namespace: "text"
description: "Deprecate the Paint#elegantTextHeight API and stick it to true"
bug: "349519475"
+ is_exported: true
}
flag {
@@ -210,4 +213,5 @@
namespace: "text"
description: "Make Paint class work for vertical layout text."
bug: "355296926"
+ is_exported: true
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a0feccd..3133020 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,7 +16,9 @@
package android.view;
+import static android.adpf.Flags.adpfViewrootimplActionDownBoost;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
@@ -1208,6 +1210,8 @@
private long mRenderThreadDrawStartTimeNs = -1;
private long mFirstFramePresentedTimeNs = -1;
+ private final boolean mSendPerfHintOnTouch;
+
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1337,6 +1341,8 @@
CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock();
mIsSubscribeGranularDisplayEventsEnabled =
com.android.server.display.feature.flags.Flags.subscribeGranularDisplayEvents();
+
+ mSendPerfHintOnTouch = adpfViewrootimplActionDownBoost();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -2647,7 +2653,8 @@
mStopped = stopped;
final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
if (renderer != null) {
- if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
+ if (DEBUG_DRAW)
+ Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
renderer.setStopped(mStopped);
}
if (!mStopped) {
@@ -7110,6 +7117,10 @@
+ "touch mode is " + mAttachInfo.mInTouchMode);
if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+ if (inTouchMode && mAttachInfo.mThreadedRenderer != null && mSendPerfHintOnTouch) {
+ mAttachInfo.mThreadedRenderer.notifyExpensiveFrame();
+ }
+
// tell the window manager
try {
IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
@@ -7968,8 +7979,9 @@
}
private boolean moveFocusToAdjacentWindow(@FocusDirection int direction) {
- if (getConfiguration().windowConfiguration.getWindowingMode()
- != WINDOWING_MODE_MULTI_WINDOW) {
+ final int windowingMode = getConfiguration().windowConfiguration.getWindowingMode();
+ if (!(windowingMode == WINDOWING_MODE_MULTI_WINDOW
+ || windowingMode == WINDOWING_MODE_FREEFORM)) {
return false;
}
try {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 330e46a..97cf8fc 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -85,7 +85,7 @@
* @see WindowManagerGlobal
* @hide
*/
-public final class WindowManagerImpl implements WindowManager {
+public class WindowManagerImpl implements WindowManager {
private static final String TAG = "WindowManager";
@UnsupportedAppUsage
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e60fc3a..049ad20 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -8,6 +8,7 @@
namespace: "accessibility"
description: "Enables new APIs for an app to convey if a node is expanded or collapsed."
bug: "362782536"
+ is_exported: true
}
flag {
@@ -15,6 +16,7 @@
namespace: "accessibility"
description: "Adds an API to indicate whether a form field (or similar element) is required."
bug: "362784403"
+ is_exported: true
}
flag {
@@ -44,6 +46,7 @@
namespace: "accessibility"
description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
bug: "375429616"
+ is_exported: true
}
flag {
@@ -88,6 +91,7 @@
name: "deprecate_accessibility_announcement_apis"
description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
bug: "376727542"
+ is_exported: true
}
flag {
@@ -95,6 +99,7 @@
name: "deprecate_ani_label_for_apis"
description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
bug: "333783827"
+ is_exported: true
}
flag {
@@ -145,6 +150,7 @@
name: "global_action_menu"
description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
bug: "334954140"
+ is_exported: true
}
flag {
@@ -152,6 +158,7 @@
name: "global_action_media_play_pause"
description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
bug: "334954140"
+ is_exported: true
}
flag {
@@ -230,6 +237,7 @@
namespace: "accessibility"
description: "Feature flag for supplemental description api"
bug: "375266174"
+ is_exported: true
}
flag {
@@ -237,6 +245,7 @@
namespace: "accessibility"
description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
bug: "333780959"
+ is_exported: true
}
flag {
@@ -251,6 +260,7 @@
namespace: "accessibility"
description: "Feature flag for adding tri-state checked api"
bug: "333784774"
+ is_exported: true
}
flag {
@@ -261,11 +271,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
- }
+}
- flag {
+flag {
name: "indeterminate_range_info"
namespace: "accessibility"
description: "Creates a way to create an INDETERMINATE RangeInfo"
bug: "376108874"
- }
+ is_exported: true
+}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 905f350..d527007 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -603,7 +603,7 @@
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_ENABLE_RELAYOUT,
- false);
+ true);
}
/** @hide */
@@ -611,7 +611,7 @@
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_ENABLE_RELATIVE_LOCATION_FOR_RELAYOUT,
- false);
+ true);
}
/** @hide **/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 52c5af8..1de0474 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,6 +26,7 @@
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
+import static android.service.autofill.Flags.relayoutFix;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
@@ -1013,7 +1014,7 @@
AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
mRelayoutFixDeprecated = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
- mRelayoutFix = AutofillFeatureFlags.enableRelayoutFixes();
+ mRelayoutFix = relayoutFix() && AutofillFeatureFlags.enableRelayoutFixes();
mRelativePositionForRelayout = AutofillFeatureFlags.enableRelativeLocationForRelayout();
mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 1c7570e..675e5a1 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -127,6 +127,7 @@
description: "Feature flag to introduce new frame rate setting APIs on ViewGroup"
bug: "335874198"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 3b6343e..641b010 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -116,6 +116,7 @@
description: "Add a SurfaceView composition order control API."
bug: "341021569"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -124,6 +125,7 @@
description: "Add APIs to manage SurfacePackage of the parent SurfaceView."
bug: "341021569"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 73abc47..41567fb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -165,6 +165,7 @@
description: "Writing tools API"
bug: "373788889"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -191,4 +192,5 @@
description: "Verify KeyEvents in IME"
bug: "331730488"
is_fixed_read_only: true
+ is_exported: true
}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 7a01ad3..289c5cf 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -68,7 +68,8 @@
Flags::enableDesktopWindowingTaskbarRunningApps, true),
// TODO: b/369763947 - remove this once ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS is ramped up
ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
- ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
+ ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(
+ Flags::enableDesktopWindowingEnterTransitions, false),
ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
Flags::enableWindowingTransitionHandlersObservers, false),
@@ -77,7 +78,15 @@
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS(
Flags::enableDesktopAppLaunchTransitions, false),
ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
- ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true);
+ ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
+ ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopWindowingEnterTransitionBugfix, false),
+ ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopWindowingExitTransitionsBugfix, false),
+ ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false),
+ ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchTransitionsBugfix, false);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 65e5679..4fb5fa7 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -297,6 +297,7 @@
namespace: "lse_desktop_experience"
description: "Enables desktop windowing app-to-web education"
bug: "348205896"
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index b2f125d..d5ba32c 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -48,6 +48,7 @@
namespace: "responsible_apis"
description: "Introduce additional start modes."
bug: "352182359"
+ is_exported: true
}
flag {
@@ -55,6 +56,7 @@
namespace: "responsible_apis"
description: "Add options parameter to IntentSender.sendIntent."
bug: "339720406"
+ is_exported: true
}
flag {
@@ -63,6 +65,7 @@
description: "Strict mode flag"
bug: "324089586"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index efacc34..1ddfe87 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -14,6 +14,7 @@
namespace: "systemui"
description: "Support storing different wallpaper crops for different display dimensions. Only effective after rebooting."
bug: "281648899"
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ebbe483..0b034b6 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -368,6 +368,7 @@
description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -376,6 +377,7 @@
description: "expose timestamp in BackEvent (API extension)"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -384,6 +386,7 @@
description: "EDGE_NONE swipeEdge option in BackEvent"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -422,6 +425,7 @@
description: "Provide pre-make predictive back API extension"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b7012b6..81734a3 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -100,6 +100,7 @@
name: "touch_pass_through_opt_in"
description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in"
bug: "358129114"
+ is_exported: true
}
flag {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2f3d2a..7d77ff0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8696,7 +8696,7 @@
android:featureFlag="android.security.secure_lockdown" />
<!-- Allows app to enter trade-in-mode.
- <p>Protection level: signature|privileged
+ <p>Protection level: signature
@hide
-->
<permission android:name="android.permission.ENTER_TRADE_IN_MODE"
diff --git a/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml b/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml
new file mode 100644
index 0000000..665f47f
--- /dev/null
+++ b/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?attr/disabledAlpha"
+ android:color="?attr/materialColorOnSurface" />
+ <item android:color="?attr/materialColorOutline" />
+</selector>
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml b/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml
new file mode 100644
index 0000000..7bc4060
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml
@@ -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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+ <solid android:color="#fff"/>
+ <size
+ android:width="@dimen/btn_material_width"
+ android:height="@dimen/btn_material_height" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+ <stroke
+ android:width="1dp"
+ android:color="@color/btn_material_outlined_background_color" />
+ <size
+ android:width="@dimen/btn_material_width"
+ android:height="@dimen/btn_material_height" />
+ </shape>
+ </item>
+</ripple>
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_text.xml b/core/res/res/drawable-watch-v36/btn_background_material_text.xml
new file mode 100644
index 0000000..145685c
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/btn_background_material_text.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+ <solid android:color="#fff"/>
+ <size
+ android:width="@dimen/btn_material_width"
+ android:height="@dimen/btn_material_height" />
+ </shape>
+ </item>
+</ripple>
diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml
index fc9f669..6e5ef68 100644
--- a/core/res/res/values-watch-v36/styles_material.xml
+++ b/core/res/res/values-watch-v36/styles_material.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2024 The Android Open Source Project
~
@@ -17,13 +17,13 @@
<resources>
<!-- Button Styles -->
- <!-- Material Button - Filled -->
+ <!-- Material Button - Filled (primary colored) -->
<style name="Widget.DeviceDefault.Button.Filled" parent="Widget.DeviceDefault.Button.WearMaterial3">
<item name="android:background">@drawable/btn_background_material_filled</item>
<item name="textAppearance">@style/TextAppearance.Widget.Button.Material.Filled</item>
</style>
- <!-- Material Button - Filled Tonal(Override system default button styles) -->
+ <!-- Material Button - Filled Tonal (Override system default button styles) -->
<style name="Widget.DeviceDefault.Button.WearMaterial3">
<item name="background">@drawable/btn_background_material_filled_tonal</item>
<item name="textAppearance">@style/TextAppearance.Widget.Button.Material</item>
@@ -41,9 +41,19 @@
<item name="gravity">center_vertical</item>
</style>
+ <!-- Material Button - Outlined -->
+ <style name="Widget.DeviceDefault.Button.Outlined" parent="Widget.DeviceDefault.Button.WearMaterial3">
+ <item name="android:background">@drawable/btn_background_material_outlined</item>
+ </style>
+
+ <!-- Material Button - Text -->
+ <style name="Widget.DeviceDefault.Button.Text" parent="Widget.DeviceDefault.Button.WearMaterial3">
+ <item name="android:background">@drawable/btn_background_material_text</item>
+ </style>
+
<!-- Text Styles -->
<!-- TextAppearance for Material Button - Filled -->
- <style name="TextAppearance.Widget.Button.Material.Filled" parent="TextAppearance.Widget.Button.Material">
+ <style name="TextAppearance.Widget.Button.Material.Filled">
<item name="textColor">@color/btn_material_filled_content_color</item>
</style>
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 529f18b..8e9693a 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -33,4 +33,40 @@
<!-- Style the scrollbars accoridngly. -->
<drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</drawable>
<drawable name="config_scrollbarTrackVertical">@drawable/scrollbar_vertical_track</drawable>
+
+ <!--
+ Material motion physics configs
+ values from https://carbon.googleplex.com/wear-m3/pages/motion/tokens-and-specs/40358758-8b8c-4d46-9391-a8fff2d91197#15087d76-8a5a-4d52-a210-efc2cd479a66
+ -->
+ <!-- standard -->
+ <item name="config_motionStandardFastSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastSpatialStiffness">1400</integer>
+ <item name="config_motionStandardFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastEffectStiffness">1400</integer>
+
+ <item name="config_motionStandardDefaultSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultSpatialStiffness">500</integer>
+ <item name="config_motionStandardDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultEffectStiffness">500</integer>
+
+ <item name="config_motionStandardSlowSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowSpatialStiffness">260</integer>
+ <item name="config_motionStandardSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowEffectStiffness">260</integer>
+
+ <!-- expressive -->
+ <item name="config_motionExpressiveFastSpatialDamping" format="float" type="dimen">0.7</item>
+ <integer name="config_motionExpressiveFastSpatialStiffness">800</integer>
+ <item name="config_motionExpressiveFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveFastEffectStiffness">1400</integer>
+
+ <item name="config_motionExpressiveDefaultSpatialDamping" format="float" type="dimen">0.75</item>
+ <integer name="config_motionExpressiveDefaultSpatialStiffness">350</integer>
+ <item name="config_motionExpressiveDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveDefaultEffectStiffness">500</integer>
+
+ <item name="config_motionExpressiveSlowSpatialDamping" format="float" type="dimen">0.8</item>
+ <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
+ <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveSlowEffectStiffness">260</integer>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0d13ca8..f199159 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2174,6 +2174,16 @@
<item>com.android.location.fused</item>
</string-array>
+ <!-- Package name providing population density location support. -->
+ <string name="config_populationDensityProviderPackageName" translatable="false">com.android.location.populationdensity</string>
+
+ <!-- Whether to enable population density provider overlay, which allows the population density provider to
+ be replaced by an app at run-time. When disabled, only the
+ config_populationDensityProviderPackageName package will be searched for a population density
+ provider, otherwise any system package is eligible. Anyone who wants to disable the overlay
+ mechanism can set it to false. -->
+ <bool name="config_enablePopulationDensityProviderOverlay" translatable="false">true</bool>
+
<!-- Package name of the extension software fallback. -->
<string name="config_extensionFallbackPackageName" translatable="false"></string>
@@ -2451,6 +2461,9 @@
<string name="config_systemCallStreaming" translatable="false"></string>
<!-- The name of the package that will hold the default retail demo role. -->
<string name="config_defaultRetailDemo" translatable="false"></string>
+ <!-- The name of the package that will hold the default reserved for testing profile group
+ exclusivity role. -->
+ <string name="config_defaultReservedForTestingProfileGroupExclusivity" translatable="false">android.app.rolemultiuser.cts.app</string>
<!-- The component name of the wear service class that will be started by the system server. -->
<string name="config_wearServiceComponent" translatable="false"></string>
diff --git a/core/res/res/values/config_material.xml b/core/res/res/values/config_material.xml
index 64483f1..6034f9c 100644
--- a/core/res/res/values/config_material.xml
+++ b/core/res/res/values/config_material.xml
@@ -38,4 +38,41 @@
<!-- Style the scrollbars accoridngly. -->
<drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_handle_material</drawable>
<drawable name="config_scrollbarTrackVertical">@null</drawable>
+
+ <!--
+ Material motion physics configs
+ values from https://carbon.googleplex.com/google-material-3/pages/motion/how-it-works/1d566b15-2923-4e40-bd1e-25a867b96cbb#7520e861-2251-4ddb-af33-59df0d233d21
+ -->
+ <!-- standard -->
+ <item name="config_motionStandardFastSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastSpatialStiffness">1400</integer>
+ <item name="config_motionStandardFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastEffectStiffness">3800</integer>
+
+ <item name="config_motionStandardDefaultSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultSpatialStiffness">700</integer>
+ <item name="config_motionStandardDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultEffectStiffness">1600</integer>
+
+ <item name="config_motionStandardSlowSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowSpatialStiffness">300</integer>
+ <item name="config_motionStandardSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowEffectStiffness">800</integer>
+
+
+ <!-- expressive -->
+ <item name="config_motionExpressiveFastSpatialDamping" format="float" type="dimen">0.6</item>
+ <integer name="config_motionExpressiveFastSpatialStiffness">800</integer>
+ <item name="config_motionExpressiveFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveFastEffectStiffness">3800</integer>
+
+ <item name="config_motionExpressiveDefaultSpatialDamping" format="float" type="dimen">0.8</item>
+ <integer name="config_motionExpressiveDefaultSpatialStiffness">380</integer>
+ <item name="config_motionExpressiveDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveDefaultEffectStiffness">1600</integer>
+
+ <item name="config_motionExpressiveSlowSpatialDamping" format="float" type="dimen">0.8</item>
+ <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
+ <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveSlowEffectStiffness">800</integer>
</resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index e8063a2..bb76b9f 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -488,8 +488,12 @@
<java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
<!-- Whether to show the system notification to users whenever there is a change
- in the satellite availability state at the current location. -->
+ in the satellite availability state at the current location. -->
<bool name="config_satellite_should_notify_availability">true</bool>
<java-symbol type="bool" name="config_satellite_should_notify_availability" />
+ <!-- Whether to allow check message datagrams to be sent even when the satellite modem is in
+ not connected state. -->
+ <bool name="config_satellite_allow_check_message_in_not_connected">false</bool>
+ <java-symbol type="bool" name="config_satellite_allow_check_message_in_not_connected" />
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 76ff565..778d9f9 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -151,9 +151,36 @@
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
@hide @SystemApi -->
<public name="config_systemDependencyInstaller" />
+ <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_PLATFORM_API_ENABLED)
+ @hide @SystemApi -->
+ <public name="config_defaultReservedForTestingProfileGroupExclusivity" />
</staging-public-group>
<staging-public-group type="dimen" first-id="0x01b30000">
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowEffectDamping"/>
</staging-public-group>
<staging-public-group type="color" first-id="0x01b20000">
@@ -205,6 +232,30 @@
</staging-public-group>
<staging-public-group type="integer" first-id="0x01aa0000">
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowEffectStiffness"/>
</staging-public-group>
<staging-public-group type="transition" first-id="0x01a90000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 380b297..5d6a461 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2012,6 +2012,8 @@
<java-symbol type="array" name="config_locationProviderPackageNames" />
<java-symbol type="array" name="config_locationDriverAssistancePackageNames" />
<java-symbol type="array" name="config_locationExtraPackageNames" />
+ <java-symbol type="string" name="config_populationDensityProviderPackageName" />
+ <java-symbol type="bool" name="config_enablePopulationDensityProviderOverlay" />
<java-symbol type="array" name="config_testLocationProviders" />
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
<java-symbol type="array" name="config_defaultNotificationVibeWaveform" />
@@ -5828,4 +5830,31 @@
<java-symbol type="style" name="AlertDialog.DeviceDefault.WearMaterial3" />
<java-symbol type="bool" name="config_allowNormalBrightnessForDozePolicy" />
+
+ <!-- Material motion spec config tokens -->
+ <java-symbol type="integer" name="config_motionStandardFastSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardFastEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardDefaultSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardDefaultEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardSlowSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardSlowEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveFastSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveFastEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveDefaultSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveDefaultEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveSlowSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveSlowEffectStiffness"/>
+ <java-symbol type="dimen" name="config_motionStandardFastSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardFastEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardDefaultSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardDefaultEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardSlowSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardSlowEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveFastSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveFastEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveDefaultSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveDefaultEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveSlowSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveSlowEffectDamping"/>
+
</resources>
diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java
index a903ed9..335791c 100644
--- a/core/tests/coretests/src/android/os/BinderProxyTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.fail;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -42,7 +43,7 @@
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = PowerManager.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
public class BinderProxyTest {
private static class CountingListener implements Binder.ProxyTransactListener {
int mStartedCount;
@@ -62,7 +63,7 @@
public final RavenwoodRule mRavenwood = new RavenwoodRule();
private Context mContext;
- private PowerManager mPowerManager;
+ private ActivityManager mActivityManager;
/**
* Setup any common data for the upcoming tests.
@@ -70,7 +71,7 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
@Test
@@ -80,7 +81,7 @@
Binder.setProxyTransactListener(listener);
Binder.setProxyTransactListener(null);
- mPowerManager.isInteractive();
+ mActivityManager.isUserRunning(7); // something which does a binder call
assertEquals(0, listener.mStartedCount);
assertEquals(0, listener.mEndedCount);
@@ -92,7 +93,7 @@
CountingListener listener = new CountingListener();
Binder.setProxyTransactListener(listener);
- mPowerManager.isInteractive();
+ mActivityManager.isUserRunning(27); // something which does a binder call
assertEquals(1, listener.mStartedCount);
assertEquals(1, listener.mEndedCount);
@@ -112,7 +113,7 @@
});
// Check it does not throw..
- mPowerManager.isInteractive();
+ mActivityManager.isUserRunning(47); // something which does a binder call
}
private IBinder mRemoteBinder = null;
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 5a2a723..f9f43bc 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -85,6 +85,9 @@
public @interface SplitIndex {
}
+ /** Signifies that user is currently not in split screen. */
+ public static final int NOT_IN_SPLIT = -1;
+
/**
* A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT,
* only used on tablets.
@@ -152,6 +155,23 @@
public @interface PersistentSnapPosition {}
/**
+ * These are all the valid "states" that split screen can be in. It's the set of
+ * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}.
+ */
+ @IntDef(value = {
+ NOT_IN_SPLIT,
+ SNAP_TO_2_33_66,
+ SNAP_TO_2_50_50,
+ SNAP_TO_2_66_33,
+ SNAP_TO_2_90_10,
+ SNAP_TO_2_10_90,
+ SNAP_TO_3_33_33_33,
+ SNAP_TO_3_45_45_10,
+ SNAP_TO_3_10_45_45,
+ })
+ public @interface SplitScreenState {}
+
+ /**
* Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
*/
public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 6beff19..1852cda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -55,6 +55,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
+import android.util.Log;
import android.view.Display;
import android.view.InsetsController;
import android.view.InsetsSource;
@@ -142,6 +143,9 @@
@ShellMainThread
private final Handler mHandler;
+ /** Singleton source of truth for the current state of split screen on this device. */
+ private final SplitState mSplitState;
+
private int mDividerWindowWidth;
private int mDividerInsets;
private int mDividerSize;
@@ -204,7 +208,8 @@
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
DisplayController displayController, DisplayImeController displayImeController,
- ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) {
+ ShellTaskOrganizer taskOrganizer, int parallaxType, SplitState splitState,
+ @ShellMainThread Handler handler) {
mHandler = handler;
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
@@ -220,6 +225,7 @@
mTaskOrganizer = taskOrganizer;
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
+ mSplitState = splitState;
final Resources res = mContext.getResources();
mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
@@ -381,6 +387,11 @@
return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition);
}
+ /** Updates the {@link SplitState} using the current divider position. */
+ public void updateStateWithCurrentPosition() {
+ mSplitState.set(calculateCurrentSnapPosition());
+ }
+
/**
* Returns the divider position as a fraction from 0 to 1.
*/
@@ -413,7 +424,13 @@
removeTouchZones();
}
- int currentPosition = calculateCurrentSnapPosition();
+ int currentPosition = mSplitState.get();
+ // TODO (b/349828130): Can delete this warning after brief soak time.
+ if (currentPosition != calculateCurrentSnapPosition()) {
+ Log.wtf(TAG, "SplitState is " + mSplitState.get()
+ + ", expected " + calculateCurrentSnapPosition());
+ }
+
switch (currentPosition) {
case SNAP_TO_2_10_90:
case SNAP_TO_3_10_45_45:
@@ -764,7 +781,10 @@
break;
default:
flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
- () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
+ () -> {
+ setDividerPosition(snapTarget.position, true /* applyLayoutChange */);
+ mSplitState.set(snapTarget.snapPosition);
+ });
break;
}
}
@@ -836,10 +856,12 @@
/** Fling divider from current position to center position. */
public void flingDividerToCenter(@Nullable Runnable finishCallback) {
- final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
+ final SnapTarget target = mDividerSnapAlgorithm.getMiddleTarget();
+ final int pos = target.position;
flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, FAST_OUT_SLOW_IN,
() -> {
setDividerPosition(pos, true /* applyLayoutChange */);
+ mSplitState.set(target.snapPosition);
if (finishCallback != null) {
finishCallback.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
new file mode 100644
index 0000000..71758e0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;
+
+/**
+ * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions.
+ */
+public class SplitState {
+ private @SplitScreenState int mState = NOT_IN_SPLIT;
+
+ /** Updates the current state of split screen on this device. */
+ public void set(@SplitScreenState int newState) {
+ mState = newState;
+ }
+
+ /** Reports the current state of split screen on this device. */
+ public @SplitScreenState int get() {
+ return mState;
+ }
+
+ /** Sets NOT_IN_SPLIT when user exits split. */
+ public void exit() {
+ set(NOT_IN_SPLIT);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 33e4fd8..aebd94f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -30,6 +30,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.dagger.pip.TvPipModule;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
@@ -89,6 +90,7 @@
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
@ShellMainThread ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
@@ -96,6 +98,6 @@
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
displayImeController, displayInsetsController, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, multiInstanceHelper,
- mainExecutor, mainHandler, systemWindows);
+ splitState, mainExecutor, mainHandler, systemWindows);
}
}
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 47084e1..de86b22 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
@@ -72,6 +72,7 @@
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -862,6 +863,12 @@
return Optional.empty();
}
+ @WMSingleton
+ @Provides
+ static SplitState provideSplitState() {
+ return new SplitState();
+ }
+
//
// Starting window
//
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 fb89232..df2b849 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.dagger;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
@@ -69,6 +70,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.common.transition.TransitionStateHolder;
import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
import com.android.wm.shell.compatui.letterbox.LetterboxController;
@@ -503,6 +505,7 @@
Optional<WindowDecorViewModel> windowDecorViewModel,
Optional<DesktopTasksController> desktopTasksController,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new SplitScreenController(
@@ -526,6 +529,7 @@
desktopTasksController,
null /* stageCoordinator */,
multiInstanceHelper,
+ splitState,
mainExecutor,
mainHandler);
}
@@ -851,7 +855,8 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor) {
return (Flags.enableDesktopWindowingTransitions()
- || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue())
+ || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue()
+ || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue())
? new SpringDragToDesktopTransitionHandler(
context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
: new DefaultDragToDesktopTransitionHandler(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 33d94d5..36904fb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -78,7 +78,10 @@
/** Starts close transition and handles or delegates desktop task close animation. */
override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue) {
+ if (
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue
+ ) {
return freeformTaskTransitionHandler.startRemoveTransition(wct)
}
requireNotNull(wct)
@@ -102,7 +105,8 @@
): IBinder {
if (
!Flags.enableFullyImmersiveInDesktop() &&
- !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
) {
return transitions.startTransition(transitionType, wct, /* handler= */ null)
}
@@ -250,7 +254,10 @@
minimizeChange?.taskInfo?.taskId,
immersiveExitChange?.taskInfo?.taskId,
)
- if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue ||
+ DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+ ) {
// Only apply minimize change reparenting here if we implement the new app launch
// transitions, otherwise this reparenting is handled in the default handler.
minimizeChange?.let {
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 a94a40b..45425e2 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
@@ -136,6 +136,7 @@
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
import java.util.function.Consumer
/** Handles moving tasks in and out of desktop */
@@ -465,12 +466,15 @@
taskSurface: SurfaceControl,
) {
logV("startDragToDesktop taskId=%d", taskInfo.taskId)
- interactionJankMonitor.begin(
- taskSurface,
- context,
- handler,
- CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
- )
+ val jankConfigBuilder =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
+ context,
+ taskSurface,
+ handler,
+ )
+ .setTimeout(APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS)
+ interactionJankMonitor.begin(jankConfigBuilder)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
dragToDesktopValueAnimator,
@@ -1918,7 +1922,10 @@
launchTaskId: Int,
minimizeTaskId: Int?,
) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ if (
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+ ) {
return
}
// TODO b/359523924: pass immersive task here?
@@ -2635,6 +2642,10 @@
val DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+ // Timeout used for CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD, this is longer than the
+ // default timeout to avoid timing out in the middle of a drag action.
+ private val APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS: Long = TimeUnit.SECONDS.toMillis(10L)
+
private const val TAG = "DesktopTasksController"
}
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 6e0e696..fc757ef 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
@@ -94,6 +94,7 @@
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.SplitDragPolicy;
@@ -199,6 +200,7 @@
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
private final Optional<DesktopTasksController> mDesktopTasksController;
private final MultiInstanceHelper mMultiInstanceHelpher;
+ private final SplitState mSplitState;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
@VisibleForTesting
@@ -228,6 +230,7 @@
Optional<DesktopTasksController> desktopTasksController,
@Nullable StageCoordinator stageCoordinator,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
ShellExecutor mainExecutor,
Handler mainHandler) {
mShellCommandHandler = shellCommandHandler;
@@ -252,6 +255,7 @@
mDesktopTasksController = desktopTasksController;
mStageCoordinator = stageCoordinator;
mMultiInstanceHelpher = multiInstanceHelper;
+ mSplitState = splitState;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
@@ -296,7 +300,7 @@
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController,
- mWindowDecorViewModel);
+ mWindowDecorViewModel, mSplitState);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 07c157b..b40996f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -139,6 +139,7 @@
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
@@ -218,6 +219,8 @@
private final Optional<RecentTasksController> mRecentTasks;
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+ /** Singleton source of truth for the current state of split screen on this device. */
+ private final SplitState mSplitState;
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
@@ -344,7 +347,7 @@
TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor,
Handler mainHandler, Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -355,6 +358,7 @@
mRecentTasks = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
+ mSplitState = splitState;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
@@ -412,7 +416,7 @@
Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
Handler mainHandler, Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -432,6 +436,8 @@
mRecentTasks = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
+ mSplitState = splitState;
+
mDisplayController.addDisplayWindowListener(this);
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
@@ -1282,6 +1288,7 @@
setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(st -> {
+ mSplitLayout.updateStateWithCurrentPosition();
updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
// updateSurfaceBounds(), above, officially puts the two apps in their new
@@ -1437,6 +1444,7 @@
if (!isSplitActive() || mIsExiting) return;
onSplitScreenExit();
+ mSplitState.exit();
clearSplitPairedInRecents(exitReason);
mShouldUpdateRecents = false;
@@ -1632,6 +1640,7 @@
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
}
deactivateSplit(wct, stageToTop);
+ mSplitState.exit();
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -1730,6 +1739,7 @@
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
+ mSplitLayout.updateStateWithCurrentPosition();
mSplitLayout.update(null, true /* resetImePosition */);
if (enableFlexibleSplit()) {
runForActiveStages((stage) ->
@@ -1954,6 +1964,8 @@
}
}
+ int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition();
+
if (Flags.enableFlexibleTwoAppSplit()) {
// Split screen can be laid out in such a way that some of the apps are offscreen.
// For the purposes of passing SplitBounds up to launcher (for use in thumbnails
@@ -1966,10 +1978,17 @@
Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
bottomRightBounds.top =
Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
+
+ // TODO (b/349828130): Can change to getState() fully after brief soak time.
+ if (mSplitState.get() != currentSnapPosition) {
+ Log.wtf(TAG, "SplitState is " + mSplitState.get()
+ + ", expected " + currentSnapPosition);
+ currentSnapPosition = mSplitState.get();
+ }
}
SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
- leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
+ leftTopTaskId, rightBottomTaskId, currentSnapPosition);
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId,
@@ -2008,7 +2027,7 @@
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
mDisplayController, mDisplayImeController, mTaskOrganizer,
- PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler);
+ PARALLAX_ALIGN_CENTER /* parallaxType */, mSplitState, mMainHandler);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index 3468156..c5e158c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -32,6 +32,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -60,6 +61,7 @@
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final LaunchAdjacentController mLaunchAdjacentController;
+ private final SplitState mSplitState;
private final Handler mMainHandler;
private final SystemWindows mSystemWindows;
@@ -80,6 +82,7 @@
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
@@ -87,8 +90,8 @@
syncQueue, rootTDAOrganizer, displayController, displayImeController,
displayInsetsController, null, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
- Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor,
- mainHandler);
+ Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, splitState,
+ mainExecutor, mainHandler);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -102,6 +105,7 @@
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
+ mSplitState = splitState;
mMainHandler = mainHandler;
mSystemWindows = systemWindows;
@@ -117,7 +121,7 @@
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool,
mIconProvider, mMainExecutor, mMainHandler,
- mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows);
+ mRecentTasksOptional, mLaunchAdjacentController, mSplitState, mSystemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 4451ee8..ef1f88e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -28,6 +28,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.split.SplitScreenConstants;
@@ -53,10 +54,12 @@
Handler mainHandler,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
+ SplitState splitState,
SystemWindows systemWindows) {
super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty());
+ mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty(),
+ splitState);
mTvSplitMenuController = new TvSplitMenuController(context, this,
systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index a1e329a..1f03d75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -37,6 +37,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -44,6 +45,7 @@
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
@@ -53,6 +55,9 @@
* If the drag is repositioning, we update in the typical manner.
*/
public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
+ // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+ // timing out in the middle of a resize or drag action.
+ private static final long LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10L);
private DesktopModeWindowDecoration mDesktopWindowDecoration;
private ShellTaskOrganizer mTaskOrganizer;
@@ -106,8 +111,8 @@
mRepositionStartPoint.set(x, y);
if (isResizing()) {
// Capture CUJ for re-sizing window in DW mode.
- mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+ mInteractionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_RESIZE_WINDOW));
if (!mDesktopWindowDecoration.mHasGlobalFocus) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true /* onTop */,
@@ -153,8 +158,8 @@
}
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
// Begin window drag CUJ instrumentation only when drag position moves.
- mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW);
+ mInteractionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_DRAG_WINDOW));
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
@@ -207,6 +212,14 @@
}
}
+ private InteractionJankMonitor.Configuration.Builder createLongTimeoutJankConfigBuilder(
+ @Cuj.CujType int cujType) {
+ return InteractionJankMonitor.Configuration.Builder
+ .withSurface(cujType, mDesktopWindowDecoration.mContext,
+ mDesktopWindowDecoration.mTaskSurface, mHandler)
+ .setTimeout(LONG_CUJ_TIMEOUT_MS);
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index ddbc681..f40edae 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -266,5 +266,26 @@
test_suites: ["device-tests"],
}
+test_module_config {
+ name: "WMShellFlickerTestsPip-nonMatchParent",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.*"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaExpandButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaIntentTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest"],
+ test_suites: ["device-tests"],
+}
+
// End breakdowns for WMShellFlickerTestsPip module
////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
new file mode 100644
index 0000000..c405b66
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.Presubmit
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.BottomHalfPipAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
+import org.junit.Test
+
+/**
+ * Base test class to verify PIP exit animation with an activity layout to the bottom half of
+ * the container.
+ */
+abstract class BottomHalfExitPipToAppTransition(flicker: LegacyFlickerTest) :
+ ExitPipToAppTransition(flicker) {
+
+ override val pipApp: PipAppHelper = BottomHalfPipAppHelper(instrumentation)
+
+ @Presubmit
+ @Test
+ override fun showBothAppLayersThenHidePip() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ @Presubmit
+ @Test
+ override fun showBothAppWindowsThenHidePip() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ @Presubmit
+ @Test
+ override fun pipAppCoversFullScreenAtEnd() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ /**
+ * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+ * half of screen.
+ */
+ @Presubmit
+ @Test
+ fun showBothAppLayersDuringPipTransition() {
+ flicker.assertLayers {
+ isVisible(testApp)
+ .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
+ }
+ }
+
+ /**
+ * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+ * half of screen.
+ */
+ @Presubmit
+ @Test
+ fun showBothAppWindowsDuringPipTransition() {
+ flicker.assertWm {
+ isAppWindowVisible(testApp)
+ .isAppWindowOnTop(pipApp)
+ .isAppWindowVisible(pipApp)
+ }
+ }
+
+ /**
+ * Verify that the [testApp] and [pipApp] covers the entire screen at the end of PIP exit
+ * animation since the [pipApp] will use a bottom half layout.
+ */
+ @Presubmit
+ @Test
+ fun testPlusPipAppCoversWindowFrameAtEnd() {
+ flicker.assertLayersEnd {
+ val pipRegion = visibleRegion(pipApp).region
+ visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
new file mode 100644
index 0000000..2a3dc07
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via the expand button
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaExpandButtonTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in pip mode [bottomHalfPipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [bottomHalfPipApp] app to bottom half layout by clicking on the pip window and
+ * then on the expand button
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+ BottomHalfExitPipToAppTransition(flicker)
+{
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.expandPipWindowToApp(wmHelper)
+ // Wait until the transition idle and test and pip app still shows.
+ wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+ .withAppTransitionIdle().waitForAndVerify()
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
new file mode 100644
index 0000000..8ed9cd2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via an intent
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaIntentTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in pip mode [bottomHalfPipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [bottomHalfPipApp] app to bottom half layout via an intent
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) :
+ BottomHalfExitPipToAppTransition(flicker)
+{
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.exitPipToFullScreenViaIntent(wmHelper)
+ // Wait until the transition idle and test and pip app still shows.
+ wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+ .withAppTransitionIdle().waitForAndVerify()
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index cf69704..fd3d3b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -56,6 +56,7 @@
private @Mock DisplayController mDisplayController;
private @Mock DisplayImeController mDisplayImeController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private @Mock SplitState mSplitState;
private @Mock Handler mHandler;
private SplitLayout mSplitLayout;
private DividerView mDividerView;
@@ -67,7 +68,7 @@
Configuration configuration = getConfiguration();
mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
- mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler);
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE, mSplitState, mHandler);
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index dc0f213..1904c43 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -66,6 +66,7 @@
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock WindowContainerTransaction mWct;
+ @Mock SplitState mSplitState;
@Mock Handler mHandler;
@Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@@ -83,6 +84,7 @@
mDisplayImeController,
mTaskOrganizer,
SplitLayout.PARALLAX_NONE,
+ mSplitState,
mHandler));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 2d55445..267bbb6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -152,7 +152,9 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startRemoveTransition_callsFreeformTaskTransitionHandler() {
val wct = WindowContainerTransaction()
whenever(freeformTaskTransitionHandler.startRemoveTransition(wct))
@@ -164,7 +166,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startRemoveTransition_startsCloseTransition() {
val wct = WindowContainerTransaction()
whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -203,7 +207,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
@@ -231,7 +237,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
@@ -272,7 +280,8 @@
@Test
@DisableFlags(
Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -308,7 +317,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -407,7 +418,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -437,7 +450,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -469,7 +484,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -566,7 +583,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -598,7 +617,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 72a7a3f..7726c97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -71,6 +71,7 @@
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.split.SplitState;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
@@ -119,6 +120,7 @@
@Mock WindowDecorViewModel mWindowDecorViewModel;
@Mock DesktopTasksController mDesktopTasksController;
@Mock MultiInstanceHelper mMultiInstanceHelper;
+ @Mock SplitState mSplitState;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private ShellController mShellController;
@@ -136,7 +138,7 @@
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController,
Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController),
- mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler));
+ mStageCoordinator, mMultiInstanceHelper, mSplitState, mMainExecutor, mMainHandler));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index d13a888..1a2d60d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -35,6 +35,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.transition.Transitions;
@@ -80,11 +81,11 @@
ShellExecutor mainExecutor, Handler mainHandler,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
transitions, transactionPool, mainExecutor, mainHandler, recentTasks,
- launchAdjacentController, windowDecorViewModel);
+ launchAdjacentController, windowDecorViewModel, splitState);
// Prepare root task for testing.
mRootTask = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index e32cf38..de77837 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -78,6 +78,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.TestRemoteTransition;
@@ -108,6 +109,7 @@
@Mock private Transitions mTransitions;
@Mock private IconProvider mIconProvider;
@Mock private WindowDecorViewModel mWindowDecorViewModel;
+ @Mock private SplitState mSplitState;
@Mock private ShellExecutor mMainExecutor;
@Mock private Handler mMainHandler;
@Mock private LaunchAdjacentController mLaunchAdjacentController;
@@ -144,7 +146,7 @@
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(),
- mLaunchAdjacentController, Optional.empty());
+ mLaunchAdjacentController, Optional.empty(), mSplitState);
mStageCoordinator.setMixedHandler(mMixedHandler);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 1e739cd..7afcce1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -71,6 +71,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
@@ -116,6 +117,8 @@
private LaunchAdjacentController mLaunchAdjacentController;
@Mock
private DefaultMixedHandler mDefaultMixedHandler;
+ @Mock
+ private SplitState mSplitState;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -139,7 +142,7 @@
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController,
- Optional.empty()));
+ Optional.empty(), mSplitState));
mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build();
when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1);
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index fa27af6..e497ea1 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -6,6 +6,7 @@
namespace: "core_graphics"
description: "API for AGSL authored runtime color filters and blenders"
bug: "358126864"
+ is_exported: true
}
flag {
@@ -44,6 +45,7 @@
namespace: "accessibility"
description: "Draw a solid rectangle background behind text instead of a stroke outline"
bug: "186567103"
+ is_exported: true
}
flag {
@@ -96,6 +98,7 @@
namespace: "core_graphics"
description: "Add canvas#drawRegion API"
bug: "318612129"
+ is_exported: true
}
flag {
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index cf3f740..8cd08d3 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -642,6 +642,14 @@
method public void onFlushComplete();
}
+ @FlaggedApi("android.location.flags.population_density_provider") public abstract class PopulationDensityProviderBase {
+ ctor public PopulationDensityProviderBase(@NonNull android.content.Context, @NonNull String);
+ method @Nullable public final android.os.IBinder getBinder();
+ method public abstract void onGetCoarsenedS2Cell(double, double, @NonNull android.os.OutcomeReceiver<long[],java.lang.Throwable>);
+ method public abstract void onGetDefaultCoarseningLevel(@NonNull android.os.OutcomeReceiver<java.lang.Integer,java.lang.Throwable>);
+ field public static final String ACTION_POPULATION_DENSITY_PROVIDER = "com.android.location.service.PopulationDensityProvider";
+ }
+
public final class ProviderRequest implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=0) public long getIntervalMillis();
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 24e1d32..5395206 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -6,6 +6,7 @@
namespace: "location"
description: "Deprecates LocationManager ProviderChanged APIs"
bug: "361811782"
+ is_exported: true
}
flag {
@@ -27,6 +28,7 @@
namespace: "location"
description: "Flag for new Geocoder APIs"
bug: "229872126"
+ is_exported: true
}
flag {
@@ -56,6 +58,7 @@
namespace: "location"
description: "Flag for making geoid heights available via the Altitude HAL"
bug: "304375846"
+ is_exported: true
}
flag {
@@ -63,6 +66,7 @@
namespace: "location"
description: "Flag for GNSS API for NavIC L1"
bug: "302199306"
+ is_exported: true
}
flag {
@@ -70,6 +74,7 @@
namespace: "location"
description: "Flag for GnssMeasurementRequest WorkSource API"
bug: "295235160"
+ is_exported: true
}
flag {
@@ -129,6 +134,7 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
flag {
diff --git a/location/java/android/location/provider/IPopulationDensityProvider.aidl b/location/java/android/location/provider/IPopulationDensityProvider.aidl
new file mode 100644
index 0000000..9b5cb5a
--- /dev/null
+++ b/location/java/android/location/provider/IPopulationDensityProvider.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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 android.location.provider;
+
+import android.os.Bundle;
+
+import android.location.Location;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+
+/**
+ * Binder interface for services that implement a population density provider. Do not implement this
+ * directly, extend {@link PopulationDensityProviderBase} instead.
+ * @hide
+ */
+oneway interface IPopulationDensityProvider {
+ /**
+ * Gets the default S2 level to be used to coarsen any location, in case a more precise answer
+ * from the method below can't be obtained.
+ */
+ void getDefaultCoarseningLevel(in IS2LevelCallback callback);
+
+ /**
+ * Returns a list of IDs of the S2 cells to be used to coarsen a location. The answer should
+ * contain at least one S2 cell, which should contain the requested location. Its level
+ * represents the population density. Optionally, additional nearby cells can be also returned,
+ * to assist in coarsening nearby locations.
+ */
+ void getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees, in IS2CellIdsCallback
+ callback);
+}
diff --git a/location/java/android/location/provider/IS2CellIdsCallback.aidl b/location/java/android/location/provider/IS2CellIdsCallback.aidl
new file mode 100644
index 0000000..f583045
--- /dev/null
+++ b/location/java/android/location/provider/IS2CellIdsCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 cell IDs callbacks.
+ * @hide
+ */
+oneway interface IS2CellIdsCallback {
+
+ /**
+ * Called with the resulting list of S2 cell IDs. The first cell is expected to contain
+ * the requested latitude/longitude. Its level represent the population density. Optionally,
+ * the list can also contain additional nearby cells.
+ */
+ void onResult(in long[] s2CellIds);
+
+ /** Called if any error occurs while processing the query. */
+ void onError();
+}
diff --git a/location/java/android/location/provider/IS2LevelCallback.aidl b/location/java/android/location/provider/IS2LevelCallback.aidl
new file mode 100644
index 0000000..49f96ef
--- /dev/null
+++ b/location/java/android/location/provider/IS2LevelCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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 android.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 level callback.
+ * @hide
+ */
+oneway interface IS2LevelCallback {
+ /**
+ * Called with the resulting default S2 level for coarsening a location, in case a better
+ * answer cannot be obtained for a latitude/longitude.
+ */
+ void onResult(int s2Level);
+
+ /** Called if any error occurs while processing the query. */
+ void onError();
+}
diff --git a/location/java/android/location/provider/PopulationDensityProviderBase.java b/location/java/android/location/provider/PopulationDensityProviderBase.java
new file mode 100644
index 0000000..3907516
--- /dev/null
+++ b/location/java/android/location/provider/PopulationDensityProviderBase.java
@@ -0,0 +1,192 @@
+/*
+ * 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 android.location.provider;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.location.flags.Flags;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A provider for population density.
+ * The population density is defined as the S2 level at which the S2 cell around the latitude /
+ * longitude contains at least a thousand people.
+ * It exposes two methods: one about providing population density around a latitude / longitude,
+ * and one about providing a "default" population density to fall back to in case the first API
+ * can't be used or returns an error.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public abstract class PopulationDensityProviderBase {
+
+ final String mTag;
+ final @Nullable String mAttributionTag;
+ final IBinder mBinder;
+
+ /**
+ * The action the wrapping service should have in its intent filter to implement the
+ * PopulationDensity provider.
+ */
+ @SuppressLint("ActionValue")
+ public static final String ACTION_POPULATION_DENSITY_PROVIDER =
+ "com.android.location.service.PopulationDensityProvider";
+
+ public PopulationDensityProviderBase(@NonNull Context context, @NonNull String tag) {
+ mTag = tag;
+ mAttributionTag = context.getAttributionTag();
+ mBinder = new Service();
+ }
+
+ /**
+ * Returns the IBinder instance that should be returned from the
+ * {@link android.app.Service#onBind(Intent)} method of the wrapping service.
+ */
+ public final @Nullable IBinder getBinder() {
+ return mBinder;
+ }
+
+ /**
+ * Called upon receiving a new request for the default coarsening level.
+ * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case
+ * an error occurs, {@link OutcomeReceiver#onError} should be called.
+ * The callback is single-use, calling more than any one of these two methods throws an
+ * AssertionException.
+ *
+ * @param callback A single-use callback that either returns the coarsening level, or an error.
+ */
+ public abstract void onGetDefaultCoarseningLevel(@NonNull OutcomeReceiver<Integer, Throwable>
+ callback);
+
+ /**
+ * Called upon receiving a new request for population density at a specific latitude/longitude,
+ * expressed in degrees.
+ * The answer is at least one S2CellId corresponding to the coarsening level at the specified
+ * location. This must be the first element of the result array. Optionally, additional nearby
+ * S2CellIds can be returned. One use for the optional nearby cells is when the client has a
+ * local cache that needs to be filled with the local area around a certain latitude/longitude.
+ * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case
+ * an error occurs, {@link OutcomeReceiver#onError} should be called.
+ * The callback is single-use, calling more than any one of these two methods throws an
+ * AssertionException.
+ *
+ * @param callback A single-use callback that either returns S2CellIds, or an error.
+ */
+ public abstract void onGetCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees,
+ @NonNull OutcomeReceiver<long[], Throwable> callback);
+
+ private final class Service extends IPopulationDensityProvider.Stub {
+ @Override
+ public void getDefaultCoarseningLevel(@NonNull IS2LevelCallback callback) {
+ try {
+ onGetDefaultCoarseningLevel(new SingleUseS2LevelCallback(callback));
+ } catch (RuntimeException e) {
+ // exceptions on one-way binder threads are dropped - move to a different thread
+ Log.w(mTag, e);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ throw new AssertionError(e);
+ });
+ }
+ }
+
+ @Override
+ public void getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees,
+ @NonNull IS2CellIdsCallback callback) {
+ try {
+ onGetCoarsenedS2Cell(latitudeDegrees, longitudeDegrees,
+ new SingleUseS2CellIdsCallback(callback));
+ } catch (RuntimeException e) {
+ // exceptions on one-way binder threads are dropped - move to a different thread
+ Log.w(mTag, e);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ throw new AssertionError(e);
+ });
+ }
+ }
+ }
+
+ private static class SingleUseS2LevelCallback implements OutcomeReceiver<Integer, Throwable> {
+
+ private final AtomicReference<IS2LevelCallback> mCallback;
+
+ SingleUseS2LevelCallback(IS2LevelCallback callback) {
+ mCallback = new AtomicReference<>(callback);
+ }
+
+ @Override
+ public void onResult(Integer level) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onResult(level);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+ } catch (RemoteException r) {
+ throw r.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private static class SingleUseS2CellIdsCallback implements OutcomeReceiver<long[], Throwable> {
+
+ private final AtomicReference<IS2CellIdsCallback> mCallback;
+
+ SingleUseS2CellIdsCallback(IS2CellIdsCallback callback) {
+ mCallback = new AtomicReference<>(callback);
+ }
+
+ @Override
+ public void onResult(long[] s2CellIds) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onResult(s2CellIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+ } catch (RemoteException r) {
+ throw r.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index a14f1fd..547099f 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -19,6 +19,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +38,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -81,6 +83,22 @@
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
/**
+ * {@link Intent} action that indicates that the declaring service supports routing of the
+ * system media.
+ *
+ * <p>Providers must include this action if they intend to publish routes that support the
+ * system media, as described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+ *
+ * @see #onCreateSystemRoutingSession
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE_SYSTEM_MEDIA =
+ "android.media.MediaRoute2ProviderService.SYSTEM_MEDIA";
+
+ /**
* A category indicating that the associated provider is only intended for use within the app
* that hosts the provider.
*
@@ -138,12 +156,26 @@
public static final int REASON_INVALID_COMMAND = 4;
/**
+ * The request has failed because the requested operation is not implemented by the provider.
+ *
+ * @see #notifyRequestFailed
* @hide
*/
- @IntDef(prefix = "REASON_", value = {
- REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE,
- REASON_INVALID_COMMAND
- })
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final int REASON_UNIMPLEMENTED = 5;
+
+ /** @hide */
+ @IntDef(
+ prefix = "REASON_",
+ value = {
+ REASON_UNKNOWN_ERROR,
+ REASON_REJECTED,
+ REASON_NETWORK_ERROR,
+ REASON_ROUTE_NOT_AVAILABLE,
+ REASON_INVALID_COMMAND,
+ REASON_UNIMPLEMENTED
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -282,6 +314,32 @@
}
/**
+ * Notifies the system of the successful creation of a system media routing session.
+ *
+ * <p>This method can only be called as the result of a prior call to {@link
+ * #onCreateSystemRoutingSession}.
+ *
+ * @param requestId the ID of the {@link #onCreateSystemRoutingSession} request which this call
+ * is in response to.
+ * @param sessionInfo a {@link RoutingSessionInfo} that describes the newly created routing
+ * session.
+ * @param formats the {@link MediaStreamsFormats} that describes the format for the {@link
+ * MediaStreams} to return.
+ * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
+ * newly created routing session.
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @NonNull
+ public final MediaStreams notifySystemMediaSessionCreated(
+ long requestId,
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaStreamsFormats formats) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Notifies the existing session is updated. For example, when
* {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
*/
@@ -399,6 +457,43 @@
@NonNull String routeId, @Nullable Bundle sessionHints);
/**
+ * Called when the service receives a request to create a system routing session.
+ *
+ * <p>This method will only be called for routes that support routing of the system media, as
+ * described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+ *
+ * <p>Implementors of this method must call {@link #notifySystemMediaSessionCreated} with the
+ * given {@code requestId} to indicate a successful session creation. If the session creation
+ * fails (for example, if the connection to the receiver device fails), the implementor must
+ * call {@link #notifyRequestFailed}, passing the {@code requestId}.
+ *
+ * <p>Unlike {@link #onCreateSession}, system sessions route the system media (for example,
+ * audio and/or video) which is to be retrieved by calling {@link
+ * #notifySystemMediaSessionCreated}.
+ *
+ * <p>Changes to the session can be notified by calling {@link #notifySessionUpdated}.
+ *
+ * @param requestId the ID of this request
+ * @param packageName the package name of the application whose media to route.
+ * @param routeId the ID of the route initially being {@link
+ * RoutingSessionInfo#getSelectedRoutes() selected}.
+ * @param sessionHints an optional bundle of arguments sent by {@link MediaRouter2}, or null if
+ * none.
+ * @see RoutingSessionInfo.Builder
+ * @see #notifySystemMediaSessionCreated
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public void onCreateSystemRoutingSession(
+ long requestId,
+ @NonNull String packageName,
+ @NonNull String routeId,
+ @Nullable Bundle sessionHints) {
+ mHandler.post(() -> notifyRequestFailed(requestId, REASON_UNIMPLEMENTED));
+ }
+
+ /**
* Called when the session should be released. A client of the session or system can request
* a session to be released.
* <p>
@@ -735,4 +830,100 @@
MediaRoute2ProviderService.this, requestId, sessionId));
}
}
+
+ /**
+ * Holds the streams to be routed as part of a system media routing session.
+ *
+ * <p>The encoded data format matches the {@link MediaStreamsFormats} passed to {@link
+ * #notifySystemMediaSessionCreated}.
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class MediaStreams {
+
+ private final AudioRecord mAudioRecord;
+
+ // TODO: b/380431086: Add the video equivalent.
+
+ private MediaStreams(AudioRecord mAudioRecord) {
+ this.mAudioRecord = mAudioRecord;
+ }
+
+ /**
+ * Returns the {@link AudioRecord} from which to read the audio data to route, or null if
+ * the routing session doesn't include audio.
+ */
+ @Nullable
+ public AudioRecord getAudioRecord() {
+ return mAudioRecord;
+ }
+ }
+
+ /**
+ * Holds the formats to encode media data to be read from {@link MediaStreams}.
+ *
+ * @see MediaStreams
+ * @see #notifySystemMediaSessionCreated
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class MediaStreamsFormats {
+
+ private final AudioFormat mAudioFormat;
+
+ // TODO: b/380431086: Add the video equivalent.
+
+ private MediaStreamsFormats(Builder builder) {
+ this.mAudioFormat = builder.mAudioFormat;
+ }
+
+ /**
+ * Returns the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+ * return from {@link #notifySystemMediaSessionCreated}.
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public AudioFormat getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * Builder for {@link MediaStreamsFormats}
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class Builder {
+ private AudioFormat mAudioFormat;
+
+ /**
+ * Sets the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+ * return from {@link #notifySystemMediaSessionCreated}.
+ *
+ * @param audioFormat the audio format
+ * @return this builder
+ */
+ @NonNull
+ public Builder setAudioFormat(@NonNull AudioFormat audioFormat) {
+ this.mAudioFormat = Objects.requireNonNull(audioFormat);
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaStreamsFormats} instance.
+ *
+ * @return the built {@link MediaStreamsFormats} instance
+ */
+ @NonNull
+ public MediaStreamsFormats build() {
+ return new MediaStreamsFormats(this);
+ }
+ }
+ }
}
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index b4dee0c..fa1349c 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -16,4 +16,16 @@
name: "stop_media_projection_on_call_end"
description: "Stops MediaProjection sessions when a call ends"
bug: "368336349"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "media_projection_connected_display_no_virtual_device"
+ namespace: "media_projection"
+ description: "Filter out display associated with a virtual device for media projection use case"
+ bug: "362720120"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_exported: true
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 65e83b9..8fe5436 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -328,7 +328,7 @@
mRecognitionCallback,
new RecognitionConfig.Builder()
.setCaptureRequested(captureTriggerAudio)
- .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setMultipleTriggersAllowed(allowMultipleTriggers)
.setAudioCapabilities(audioCapabilities)
.build(),
runInBatterySaver);
diff --git a/native/android/OWNERS b/native/android/OWNERS
index f0db2ea..1fde7d2 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -2,7 +2,7 @@
# General NDK API reviewers
per-file libandroid.map.txt = danalbert@google.com, etalvala@google.com, michaelwr@google.com
-per-file libandroid.map.txt = jreck@google.com, zyy@google.com
+per-file libandroid.map.txt = jreck@google.com, zyy@google.com, mattbuckley@google.com
# Networking
per-file libandroid_net.map.txt, net.c = set noparent
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 7f555a8..e8644ee 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -301,7 +301,6 @@
ASurfaceTransaction_setEnableBackPressure; # introduced=31
ASurfaceTransaction_setFrameRate; # introduced=30
ASurfaceTransaction_setFrameRateWithChangeStrategy; # introduced=31
- ASurfaceTransaction_setFrameRateParams; # introduced=36
ASurfaceTransaction_clearFrameRate; # introduced=34
ASurfaceTransaction_setFrameTimeline; # introduced=Tiramisu
ASurfaceTransaction_setGeometry; # introduced=29
@@ -376,6 +375,7 @@
APerformanceHint_notifyWorkloadIncrease; # introduced=36
APerformanceHint_notifyWorkloadReset; # introduced=36
APerformanceHint_borrowSessionFromJava; # introduced=36
+ APerformanceHint_setNativeSurfaces; # introduced=36
AWorkDuration_create; # introduced=VanillaIceCream
AWorkDuration_release; # introduced=VanillaIceCream
AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
@@ -388,6 +388,8 @@
ASessionCreationConfig_setTargetWorkDurationNanos; # introduced=36
ASessionCreationConfig_setPreferPowerEfficiency; # introduced=36
ASessionCreationConfig_setGraphicsPipeline; # introduced=36
+ ASessionCreationConfig_setNativeSurfaces; # introduced=36
+ ASessionCreationConfig_setUseAutoTiming; # introduced=36
local:
*;
};
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 883e139..608c01c 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -29,13 +29,19 @@
#include <aidl/android/os/SessionCreationConfig.h>
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
+#include <android/binder_libbinder.h>
#include <android/binder_manager.h>
#include <android/binder_status.h>
+#include <android/native_window.h>
#include <android/performance_hint.h>
+#include <android/surface_control.h>
#include <android/trace.h>
#include <android_os.h>
#include <cutils/trace.h>
#include <fmq/AidlMessageQueue.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
#include <inttypes.h>
#include <jni_wrappers.h>
#include <performance_hint_private.h>
@@ -66,7 +72,12 @@
constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
struct AWorkDuration : public hal::WorkDuration {};
-struct ASessionCreationConfig : public SessionCreationConfig {};
+struct ASessionCreationConfig : public SessionCreationConfig {
+ std::vector<wp<IBinder>> layers{};
+ bool hasMode(hal::SessionMode&& mode) {
+ return std::find(modesToEnable.begin(), modesToEnable.end(), mode) != modesToEnable.end();
+ }
+};
bool kForceGraphicsPipeline = false;
@@ -158,6 +169,11 @@
FMQWrapper& getFMQWrapper();
bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex);
void initJava(JNIEnv* _Nonnull env);
+ ndk::ScopedAIBinder_Weak x;
+ template <class T>
+ static void layersFromNativeSurfaces(ANativeWindow** windows, int numWindows,
+ ASurfaceControl** controls, int numSurfaceControls,
+ std::vector<T>& out);
private:
// Necessary to create an empty binder object
@@ -203,6 +219,8 @@
int setPreferPowerEfficiency(bool enabled);
int reportActualWorkDuration(AWorkDuration* workDuration);
bool isJava();
+ status_t setNativeSurfaces(ANativeWindow** windows, int numWindows, ASurfaceControl** controls,
+ int numSurfaceControls);
private:
friend struct APerformanceHintManager;
@@ -231,7 +249,7 @@
static int64_t sIDCounter GUARDED_BY(sHintMutex);
// The most recent set of thread IDs
std::vector<int32_t> mLastThreadIDs GUARDED_BY(sHintMutex);
- std::optional<hal::SessionConfig> mSessionConfig GUARDED_BY(sHintMutex);
+ std::optional<hal::SessionConfig> mSessionConfig;
// Tracing helpers
void traceThreads(const std::vector<int32_t>& tids) REQUIRES(sHintMutex);
void tracePowerEfficient(bool powerEfficient);
@@ -329,14 +347,12 @@
ndk::ScopedAStatus ret;
hal::SessionConfig sessionConfig{.id = -1};
- SessionCreationConfig creationConfig{
+ ASessionCreationConfig creationConfig{{
.tids = std::vector<int32_t>(threadIds, threadIds + size),
.targetWorkDurationNanos = initialTargetWorkDurationNanos,
- };
+ }};
- return APerformanceHintManager::createSessionUsingConfig(static_cast<ASessionCreationConfig*>(
- &creationConfig),
- tag, isJava);
+ return APerformanceHintManager::createSessionUsingConfig(&creationConfig, tag, isJava);
}
APerformanceHintSession* APerformanceHintManager::createSessionUsingConfig(
@@ -345,11 +361,29 @@
hal::SessionConfig sessionConfig{.id = -1};
ndk::ScopedAStatus ret;
+ // Hold the tokens weakly until we actually need them,
+ // then promote them, then drop all strong refs after
+ if (!sessionCreationConfig->layers.empty()) {
+ for (auto&& layerIter = sessionCreationConfig->layers.begin();
+ layerIter != sessionCreationConfig->layers.end();) {
+ sp<IBinder> promoted = layerIter->promote();
+ if (promoted == nullptr) {
+ layerIter = sessionCreationConfig->layers.erase(layerIter);
+ } else {
+ sessionCreationConfig->layerTokens.push_back(
+ ndk::SpAIBinder(AIBinder_fromPlatformBinder(promoted.get())));
+ ++layerIter;
+ }
+ }
+ }
+
ret = mHintManager->createHintSessionWithConfig(mToken, tag,
*static_cast<SessionCreationConfig*>(
sessionCreationConfig),
&sessionConfig, &session);
+ sessionCreationConfig->layerTokens.clear();
+
if (!ret.isOk() || !session) {
ALOGE("%s: PerformanceHint cannot create session. %s", __FUNCTION__, ret.getMessage());
return nullptr;
@@ -679,6 +713,57 @@
return 0;
}
+status_t APerformanceHintSession::setNativeSurfaces(ANativeWindow** windows, int numWindows,
+ ASurfaceControl** controls,
+ int numSurfaceControls) {
+ if (!mSessionConfig.has_value()) {
+ return ENOTSUP;
+ }
+
+ std::vector<sp<IBinder>> layerHandles;
+ APerformanceHintManager::layersFromNativeSurfaces<sp<IBinder>>(windows, numWindows, controls,
+ numSurfaceControls,
+ layerHandles);
+
+ std::vector<ndk::SpAIBinder> ndkLayerHandles;
+ for (auto&& handle : layerHandles) {
+ ndkLayerHandles.emplace_back(ndk::SpAIBinder(AIBinder_fromPlatformBinder(handle)));
+ }
+
+ mHintSession->associateToLayers(ndkLayerHandles);
+ return 0;
+}
+
+template <class T>
+void APerformanceHintManager::layersFromNativeSurfaces(ANativeWindow** windows, int numWindows,
+ ASurfaceControl** controls,
+ int numSurfaceControls,
+ std::vector<T>& out) {
+ std::scoped_lock lock(sHintMutex);
+ if (windows != nullptr) {
+ std::vector<ANativeWindow*> windowVec(windows, windows + numWindows);
+ for (auto&& window : windowVec) {
+ Surface* surface = static_cast<Surface*>(window);
+ if (Surface::isValid(surface)) {
+ const sp<IBinder>& handle = surface->getSurfaceControlHandle();
+ if (handle != nullptr) {
+ out.push_back(handle);
+ }
+ }
+ }
+ }
+
+ if (controls != nullptr) {
+ std::vector<ASurfaceControl*> controlVec(controls, controls + numSurfaceControls);
+ for (auto&& aSurfaceControl : controlVec) {
+ SurfaceControl* control = reinterpret_cast<SurfaceControl*>(aSurfaceControl);
+ if (control->isValid()) {
+ out.push_back(control->getHandle());
+ }
+ }
+ }
+}
+
// ===================================== FMQ wrapper implementation
bool FMQWrapper::isActive() {
@@ -963,8 +1048,7 @@
hal::SessionTag::APP, true);
}
-APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env,
- jobject sessionObj) {
+APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env, jobject sessionObj) {
VALIDATE_PTR(env)
VALIDATE_PTR(sessionObj)
return APerformanceHintManager::getInstance()->getSessionFromJava(env, sessionObj);
@@ -1065,6 +1149,14 @@
return session->notifyWorkloadReset(cpu, gpu, debugName);
}
+int APerformanceHint_setNativeSurfaces(APerformanceHintSession* session,
+ ANativeWindow** nativeWindows, int nativeWindowsSize,
+ ASurfaceControl** surfaceControls, int surfaceControlsSize) {
+ VALIDATE_PTR(session)
+ return session->setNativeSurfaces(nativeWindows, nativeWindowsSize, surfaceControls,
+ surfaceControlsSize);
+}
+
AWorkDuration* AWorkDuration_create() {
return new AWorkDuration();
}
@@ -1180,6 +1272,11 @@
config->modesToEnable.push_back(hal::SessionMode::GRAPHICS_PIPELINE);
} else {
std::erase(config->modesToEnable, hal::SessionMode::GRAPHICS_PIPELINE);
+
+ // Remove automatic timing modes if we turn off GRAPHICS_PIPELINE,
+ // as it is a strict pre-requisite for these to run
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU);
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU);
}
return 0;
}
@@ -1197,3 +1294,48 @@
void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior) {
kForceNewHintBehavior = newBehavior;
}
+
+int ASessionCreationConfig_setNativeSurfaces(ASessionCreationConfig* config,
+ ANativeWindow** nativeWindows, int nativeWindowsSize,
+ ASurfaceControl** surfaceControls,
+ int surfaceControlsSize) {
+ VALIDATE_PTR(config)
+
+ APerformanceHintManager::layersFromNativeSurfaces<wp<IBinder>>(nativeWindows, nativeWindowsSize,
+ surfaceControls,
+ surfaceControlsSize,
+ config->layers);
+
+ if (config->layers.empty()) {
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+int ASessionCreationConfig_setUseAutoTiming(ASessionCreationConfig* _Nonnull config, bool cpu,
+ bool gpu) {
+ VALIDATE_PTR(config)
+ if ((cpu || gpu) && !config->hasMode(hal::SessionMode::GRAPHICS_PIPELINE)) {
+ ALOGE("Automatic timing is not supported unless graphics pipeline mode is enabled first");
+ return ENOTSUP;
+ }
+
+ if (config->hasMode(hal::SessionMode::AUTO_CPU)) {
+ if (!cpu) {
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU);
+ }
+ } else if (cpu) {
+ config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_CPU));
+ }
+
+ if (config->hasMode(hal::SessionMode::AUTO_GPU)) {
+ if (!gpu) {
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU);
+ }
+ } else if (gpu) {
+ config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_GPU));
+ }
+
+ return 0;
+}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index fc64e9b..6bca145 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -794,28 +794,6 @@
transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
}
-void ASurfaceTransaction_setFrameRateParams(
- ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
- float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
- ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
- CHECK_NOT_NULL(aSurfaceTransaction);
- CHECK_NOT_NULL(aSurfaceControl);
- Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
- sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
-
- if (desiredMaxRate < desiredMinRate) {
- ALOGW("desiredMaxRate must be greater than or equal to desiredMinRate");
- return;
- }
- // TODO(b/362798998): Fix plumbing to send modern params
- int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
- : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
- double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
- ? fixedSourceRate
- : desiredMinRate;
- transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
-}
-
void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl) {
CHECK_NOT_NULL(aSurfaceTransaction);
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index b006580..b8f574f 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -83,6 +83,7 @@
(override));
MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
(override));
+ MOCK_METHOD(ScopedAStatus, passSessionManagerBinder, (const SpAIBinder& sessionManager));
MOCK_METHOD(SpAIBinder, asBinder, (), (override));
MOCK_METHOD(bool, isRemote, (), (override));
};
@@ -99,6 +100,8 @@
MOCK_METHOD(ScopedAStatus, close, (), (override));
MOCK_METHOD(ScopedAStatus, reportActualWorkDuration2,
(const ::std::vector<hal::WorkDuration>& workDurations), (override));
+ MOCK_METHOD(ScopedAStatus, associateToLayers,
+ (const std::vector<::ndk::SpAIBinder>& in_layerTokens), (override));
MOCK_METHOD(SpAIBinder, asBinder, (), (override));
MOCK_METHOD(bool, isRemote, (), (override));
};
diff --git a/nfc/tests/src/android/nfc/NdefRecordTest.java b/nfc/tests/src/android/nfc/NdefRecordTest.java
new file mode 100644
index 0000000..231e939
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NdefRecordTest.java
@@ -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 android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NdefRecordTest {
+
+ @Test
+ public void testNdefRecordConstructor() throws FormatException {
+ NdefRecord applicationRecord = NdefRecord
+ .createApplicationRecord("com.android.test");
+ NdefRecord ndefRecord = new NdefRecord(applicationRecord.toByteArray());
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.toByteArray().length).isGreaterThan(0);
+ assertThat(ndefRecord.getType()).isEqualTo("android.com:pkg".getBytes());
+ assertThat(ndefRecord.getPayload()).isEqualTo("com.android.test".getBytes());
+ }
+
+ @Test
+ public void testCreateExternal() {
+ NdefRecord ndefRecord = NdefRecord.createExternal("test",
+ "android.com:pkg", "com.android.test".getBytes());
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.getType()).isEqualTo("test:android.com:pkg".getBytes());
+ assertThat(ndefRecord.getPayload()).isEqualTo("com.android.test".getBytes());
+ }
+
+ @Test
+ public void testCreateUri() {
+ NdefRecord ndefRecord = NdefRecord.createUri("http://www.example.com");
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_WELL_KNOWN);
+ assertThat(ndefRecord.getType()).isEqualTo(NdefRecord.RTD_URI);
+ }
+
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
index 0cd0b3c..19818e0 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
@@ -22,7 +22,7 @@
android:minWidth="@dimen/settingslib_expressive_space_medium3"
android:minHeight="@dimen/settingslib_expressive_space_medium3"
android:gravity="center"
- android:layout_marginEnd="-4dp"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall6"
android:filterTouchesWhenObscured="false">
<androidx.preference.internal.PreferenceImageView
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index 944bef6..c837ff4 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,7 +21,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingVertical="@dimen/settingslib_expressive_space_small1"
- android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
+ android:paddingStart="@dimen/settingslib_expressive_space_none"
+ android:paddingEnd="@dimen/settingslib_expressive_space_small1"
android:filterTouchesWhenObscured="false">
<TextView
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 9764e64..4428480 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -75,6 +75,7 @@
(holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
setCollapsable(isCollapsable)
setMinLines(minLines)
+ visibility = if (title.isNullOrEmpty()) View.GONE else View.VISIBLE
setText(title.toString())
if (hyperlinkListener != null) {
setHyperlinkListener(hyperlinkListener)
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 89de995..2d13add 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -98,6 +98,7 @@
namespace: "android_settings"
description: "Settings catalyst project migration"
bug: "323791114"
+ is_exported: true
}
flag {
@@ -106,6 +107,7 @@
namespace: "android_settings"
description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop"
bug: "375193223"
+ is_exported: true
}
flag {
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
deleted file mode 100644
index 645b275..0000000
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 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.
--->
-
-<!--
- Default system bookmarks for AOSP.
- Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
-
- Typical shortcuts (not necessarily defined here):
- 'b': Browser
- 'c': Contacts
- 'e': Email
- 'g': GMail
- 'k': Calendar
- 'm': Maps
- 'p': Music
- 's': SMS
- 't': Talk
- 'u': Calculator
- 'y': YouTube
--->
-<bookmarks>
- <!-- TODO(b/358569822): Remove this from Settings DB
- This is legacy implementation to store bookmarks in Settings DB, which is deprecated and
- no longer used -->
- <bookmark
- role="android.app.role.BROWSER"
- shortcut="b" />
- <bookmark
- category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
- <bookmark
- category="android.intent.category.APP_EMAIL"
- shortcut="e" />
- <bookmark
- category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
- <bookmark
- category="android.intent.category.APP_MAPS"
- shortcut="m" />
- <bookmark
- category="android.intent.category.APP_MUSIC"
- shortcut="p" />
- <bookmark
- role="android.app.role.SMS"
- shortcut="s" />
- <bookmark
- category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
-</bookmarks>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index e85ba45..e057682 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -16,14 +16,8 @@
package com.android.providers.settings;
-import android.content.ComponentName;
-import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@@ -46,16 +40,11 @@
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
-import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockscreenCredential;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.File;
-import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -85,7 +74,7 @@
private Context mContext;
private int mUserHandle;
- private static final HashSet<String> mValidTables = new HashSet<String>();
+ private static final HashSet<String> mValidTables = new HashSet<>();
private static final String DATABASE_BACKUP_SUFFIX = "-backup";
@@ -100,7 +89,6 @@
// These are old.
mValidTables.add("bluetooth_devices");
- mValidTables.add("bookmarks");
mValidTables.add("favorites");
mValidTables.add("old_favorites");
mValidTables.add("android_metadata");
@@ -211,21 +199,6 @@
"type INTEGER" +
");");
- db.execSQL("CREATE TABLE bookmarks (" +
- "_id INTEGER PRIMARY KEY," +
- "title TEXT," +
- "folder TEXT," +
- "intent TEXT," +
- "shortcut INTEGER," +
- "ordering INTEGER" +
- ");");
-
- db.execSQL("CREATE INDEX bookmarksIndex1 ON bookmarks (folder);");
- db.execSQL("CREATE INDEX bookmarksIndex2 ON bookmarks (shortcut);");
-
- // Populate bookmarks table with initial bookmarks
- loadBookmarks(db);
-
// Load initial volume levels into DB
loadVolumeLevels(db);
@@ -392,19 +365,6 @@
}
if (upgradeVersion == 30) {
- /*
- * Upgrade 31 clears the title for all quick launch shortcuts so the
- * activities' titles will be resolved at display time. Also, the
- * folder is changed to '@quicklaunch'.
- */
- db.beginTransaction();
- try {
- db.execSQL("UPDATE bookmarks SET folder = '@quicklaunch'");
- db.execSQL("UPDATE bookmarks SET title = ''");
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
upgradeVersion = 31;
}
@@ -1006,8 +966,6 @@
}
if (upgradeVersion == 70) {
- // Update all built-in bookmarks. Some of the package names have changed.
- loadBookmarks(db);
upgradeVersion = 71;
}
@@ -2046,92 +2004,6 @@
}
/**
- * Loads the default set of bookmarked shortcuts from an xml file.
- *
- * @param db The database to write the values into
- */
- private void loadBookmarks(SQLiteDatabase db) {
- ContentValues values = new ContentValues();
-
- PackageManager packageManager = mContext.getPackageManager();
- try {
- XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
- XmlUtils.beginDocument(parser, "bookmarks");
-
- final int depth = parser.getDepth();
- int type;
-
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- String name = parser.getName();
- if (!"bookmark".equals(name)) {
- break;
- }
-
- String pkg = parser.getAttributeValue(null, "package");
- String cls = parser.getAttributeValue(null, "class");
- String shortcutStr = parser.getAttributeValue(null, "shortcut");
- String category = parser.getAttributeValue(null, "category");
-
- int shortcutValue = shortcutStr.charAt(0);
- if (TextUtils.isEmpty(shortcutStr)) {
- Log.w(TAG, "Unable to get shortcut for: " + pkg + "/" + cls);
- continue;
- }
-
- final Intent intent;
- final String title;
- if (pkg != null && cls != null) {
- ActivityInfo info = null;
- ComponentName cn = new ComponentName(pkg, cls);
- try {
- info = packageManager.getActivityInfo(cn, 0);
- } catch (PackageManager.NameNotFoundException e) {
- String[] packages = packageManager.canonicalToCurrentPackageNames(
- new String[] { pkg });
- cn = new ComponentName(packages[0], cls);
- try {
- info = packageManager.getActivityInfo(cn, 0);
- } catch (PackageManager.NameNotFoundException e1) {
- Log.w(TAG, "Unable to add bookmark: " + pkg + "/" + cls, e);
- continue;
- }
- }
-
- intent = new Intent(Intent.ACTION_MAIN, null);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setComponent(cn);
- title = info.loadLabel(packageManager).toString();
- } else if (category != null) {
- intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
- title = "";
- } else {
- Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutStr
- + ": missing package/class or category attributes");
- continue;
- }
-
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- values.put(Settings.Bookmarks.INTENT, intent.toUri(0));
- values.put(Settings.Bookmarks.TITLE, title);
- values.put(Settings.Bookmarks.SHORTCUT, shortcutValue);
- db.delete("bookmarks", "shortcut = ?",
- new String[] { Integer.toString(shortcutValue) });
- db.insert("bookmarks", null, values);
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Got execption parsing bookmarks.", e);
- } catch (IOException e) {
- Log.w(TAG, "Got execption parsing bookmarks.", e);
- }
- }
-
- /**
* Loads the default volume levels. It is actually inserting the index of
* the volume array for each of the volume controls.
*
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
index 0f5e367..ca2b957 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
@@ -16,6 +16,8 @@
package com.android.systemui.animation;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
@@ -39,6 +41,7 @@
import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -90,7 +93,7 @@
() -> {
mStartTransaction = t;
mFinishCallback = finishCallback;
- startAnimationInternal(info);
+ startAnimationInternal(info, /* states= */ null);
});
}
@@ -112,7 +115,13 @@
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishCallback,
WindowAnimationState[] states) {
- logD("takeOverAnimation - " + info);
+ logD("takeOverAnimation - info=" + info + ", states=" + Arrays.toString(states));
+ mHandler.post(
+ () -> {
+ mStartTransaction = t;
+ mFinishCallback = finishCallback;
+ startAnimationInternal(info, states);
+ });
}
@Override
@@ -121,14 +130,19 @@
mHandler.post(this::cancel);
}
- private void startAnimationInternal(TransitionInfo info) {
+ private void startAnimationInternal(
+ TransitionInfo info, @Nullable WindowAnimationState[] states) {
if (!prepareUIs(info)) {
logE("Unable to prepare UI!");
finishAnimation(/* finished= */ false);
return;
}
// Notify player that we are starting.
- mPlayer.onStart(info, mStartTransaction, mOrigin, mOriginTransaction);
+ mPlayer.onStart(info, states, mStartTransaction, mOrigin, mOriginTransaction);
+
+ // Apply the initial transactions in case the player forgot to apply them.
+ mOriginTransaction.commit();
+ mStartTransaction.apply();
// Start the animator.
mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
@@ -205,7 +219,8 @@
.setCornerRadius(leash, windowRadius)
.setWindowCrop(leash, bounds.width(), bounds.height());
}
- } else if (TransitionUtil.isClosingMode(mode)) {
+ } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) {
+ // TRANSIT_CHANGE refers to the closing window in predictive back animation.
closingSurfaces.add(change.getLeash());
// For closing surfaces, starting bounds are base bounds. Apply corner radius if
// it's full screen.
@@ -236,13 +251,8 @@
// Attach origin UIComponent to origin leash.
mOriginTransaction = mOrigin.newTransaction();
- mOriginTransaction
- .attachToTransitionLeash(
- mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height())
- .commit();
-
- // Apply all surface changes.
- mStartTransaction.apply();
+ mOriginTransaction.attachToTransitionLeash(
+ mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height());
return true;
}
@@ -328,6 +338,58 @@
/* baseBounds= */ maxBounds);
}
+ private static void applyWindowAnimationStates(
+ TransitionInfo info,
+ @Nullable WindowAnimationState[] states,
+ UIComponent closingApp,
+ UIComponent openingApp) {
+ if (states == null) {
+ // Nothing to apply.
+ return;
+ }
+ // Calculate bounds.
+ Rect maxClosingBounds = new Rect();
+ Rect maxOpeningBounds = new Rect();
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ Rect bound = getBounds(states[i]);
+ if (bound == null) {
+ continue;
+ }
+ int mode = info.getChanges().get(i).getMode();
+ if (TransitionUtil.isOpeningMode(mode)) {
+ maxOpeningBounds.union(bound);
+ } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) {
+ // TRANSIT_CHANGE refers to the closing window in predictive back animation.
+ maxClosingBounds.union(bound);
+ }
+ }
+
+ // Intentionally use a new transaction instead of reusing the existing transaction since we
+ // want to apply window animation states first without committing any other pending changes
+ // in the existing transaction. The existing transaction is expected to be committed by the
+ // onStart() client callback together with client's custom transformation.
+ UIComponent.Transaction transaction = closingApp.newTransaction();
+ if (!maxClosingBounds.isEmpty()) {
+ logD("Applying closing window bounds: " + maxClosingBounds);
+ transaction.setBounds(closingApp, maxClosingBounds);
+ }
+ if (!maxOpeningBounds.isEmpty()) {
+ logD("Applying opening window bounds: " + maxOpeningBounds);
+ transaction.setBounds(openingApp, maxOpeningBounds);
+ }
+ transaction.commit();
+ }
+
+ @Nullable
+ private static Rect getBounds(@Nullable WindowAnimationState state) {
+ if (state == null || state.bounds == null) {
+ return null;
+ }
+ Rect out = new Rect();
+ state.bounds.roundOut(out);
+ return out;
+ }
+
/**
* An interface that represents an origin transitions.
*
@@ -338,9 +400,14 @@
/**
* Called when an origin transition starts. This method exposes the raw {@link
* TransitionInfo} so that clients can extract more information from it.
+ *
+ * <p>Note: if this transition is taking over a predictive back animation, the {@link
+ * WindowAnimationState} will be passed to this method. The concrete implementation is
+ * expected to apply the {@link WindowAnimationState} before continuing the transition.
*/
default void onStart(
TransitionInfo transitionInfo,
+ @Nullable WindowAnimationState[] states,
SurfaceControl.Transaction sfTransaction,
UIComponent origin,
UIComponent.Transaction uiTransaction) {
@@ -351,12 +418,15 @@
.registerTransactionForClass(
SurfaceUIComponent.class,
new SurfaceUIComponent.Transaction(sfTransaction));
- // Wrap surfaces and start.
- onStart(
- transactions,
- origin,
- wrapSurfaces(transitionInfo, /* isOpening= */ false),
- wrapSurfaces(transitionInfo, /* isOpening= */ true));
+ // Wrap surfaces.
+ UIComponent closingApp = wrapSurfaces(transitionInfo, /* isOpening= */ false);
+ UIComponent openingApp = wrapSurfaces(transitionInfo, /* isOpening= */ true);
+
+ // Restore the pending animation states coming from predictive back transition.
+ applyWindowAnimationStates(transitionInfo, states, closingApp, openingApp);
+
+ // Start.
+ onStart(transactions, origin, closingApp, openingApp);
}
/**
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 9cef43c..cec740a 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -89,7 +89,6 @@
mSurfaceControl =
new SurfaceControl.Builder().setName("ViewUIComponent").setBufferSize(w, h).build();
mSurface = new Surface(mSurfaceControl);
- forceDraw();
// Attach surface to transition leash
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -99,7 +98,13 @@
mView.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
// Make the view invisible AFTER the surface is shown.
- t.addTransactionCommittedListener(mView::post, () -> mView.setVisibility(View.INVISIBLE))
+ t.addTransactionCommittedListener(
+ mView::post,
+ () -> {
+ logD("Surface attached!");
+ forceDraw();
+ mView.setVisibility(View.INVISIBLE);
+ })
.apply();
}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
index 3cbb688..6b26ac5 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
@@ -16,8 +16,10 @@
package com.android.systemui.animation.server;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -26,6 +28,7 @@
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -51,8 +54,8 @@
/** An implementation of the {@link IOriginTransitions}. */
public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
- private static final boolean DEBUG = true;
private static final String TAG = "OriginTransitions";
+ private static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG);
private final Object mLock = new Object();
private final ShellTransitions mShellTransitions;
@@ -149,18 +152,7 @@
if (DEBUG) {
Log.d(TAG, "startAnimation: " + info);
}
- if (!mOnStarting.test(info)) {
- Log.w(TAG, "Skipping cancelled transition " + mTransition);
- t.addTransactionCommittedListener(
- mExecutor,
- () -> {
- try {
- finishCallback.onTransitionFinished(null, null);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to report finish.", e);
- }
- })
- .apply();
+ if (maybeInterceptTransition(info, t, finishCallback)) {
return;
}
mTransition.startAnimation(token, info, t, finishCallback);
@@ -191,6 +183,9 @@
if (DEBUG) {
Log.d(TAG, "takeOverAnimation: " + info);
}
+ if (maybeInterceptTransition(info, t, finishCallback)) {
+ return;
+ }
mTransition.takeOverAnimation(transition, info, t, finishCallback, states);
}
@@ -207,6 +202,27 @@
public String toString() {
return "RemoteTransitionDelegate{transition=" + mTransition + "}";
}
+
+ private boolean maybeInterceptTransition(
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ if (!mOnStarting.test(info)) {
+ Log.w(TAG, "Intercepting cancelled transition " + mTransition);
+ t.addTransactionCommittedListener(
+ mExecutor,
+ () -> {
+ try {
+ finishCallback.onTransitionFinished(null, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to report finish.", e);
+ }
+ })
+ .apply();
+ return true;
+ }
+ return false;
+ }
}
/** A data record containing the origin transition pieces. */
@@ -229,13 +245,25 @@
if (mDestroyed) {
return false;
}
- TransitionFilter filter = createFilterForReverseTransition(info);
+ TransitionFilter filter =
+ createFilterForReverseTransition(
+ info, /* forPredictiveBackTakeover= */ false);
if (filter != null) {
if (DEBUG) {
Log.d(TAG, "Registering filter " + filter);
}
mShellTransitions.registerRemote(filter, mWrappedReturnTransition);
}
+ TransitionFilter takeoverFilter =
+ createFilterForReverseTransition(
+ info, /* forPredictiveBackTakeover= */ true);
+ if (takeoverFilter != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Registering filter for takeover " + takeoverFilter);
+ }
+ mShellTransitions.registerRemoteForTakeover(
+ takeoverFilter, mWrappedReturnTransition);
+ }
return true;
}
}
@@ -331,7 +359,8 @@
}
@Nullable
- private static TransitionFilter createFilterForReverseTransition(TransitionInfo info) {
+ private static TransitionFilter createFilterForReverseTransition(
+ TransitionInfo info, boolean forPredictiveBackTakeover) {
TaskInfo launchingTaskInfo = null;
TaskInfo launchedTaskInfo = null;
ComponentName launchingActivity = null;
@@ -365,7 +394,9 @@
if (DEBUG) {
Log.d(
TAG,
- "createFilterForReverseTransition: launchingTaskInfo="
+ "createFilterForReverseTransition: forPredictiveBackTakeover="
+ + forPredictiveBackTakeover
+ + ", launchingTaskInfo="
+ launchingTaskInfo
+ ", launchedTaskInfo="
+ launchedTaskInfo
@@ -395,8 +426,20 @@
+ " cookie!");
return null;
}
+ if (forPredictiveBackTakeover && launchedTaskInfo == null) {
+ // Predictive back take over currently only support cross-task transition.
+ Log.d(
+ TAG,
+ "createFilterForReverseTransition: skipped - unable to find launched task"
+ + " for predictive back takeover");
+ return null;
+ }
TransitionFilter filter = new TransitionFilter();
- filter.mTypeSet = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ if (forPredictiveBackTakeover) {
+ filter.mTypeSet = new int[] {TRANSIT_PREPARE_BACK_NAVIGATION};
+ } else {
+ filter.mTypeSet = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ }
// The opening activity of the return transition must match the activity we just closed.
TransitionFilter.Requirement req1 = new TransitionFilter.Requirement();
@@ -405,15 +448,18 @@
launchingActivity == null ? launchingTaskInfo.topActivity : launchingActivity;
TransitionFilter.Requirement req2 = new TransitionFilter.Requirement();
- req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ if (forPredictiveBackTakeover) {
+ req2.mModes = new int[] {TRANSIT_CHANGE};
+ } else {
+ req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ }
if (launchedTaskInfo != null) {
// For task transitions, the closing task's cookie must match the task we just
// launched.
req2.mLaunchCookie = launchedTaskInfo.launchCookies.get(0);
} else {
// For activity transitions, the closing activity of the return transition must
- // match
- // the activity we just launched.
+ // match the activity we just launched.
req2.mTopActivity = launchedActivity;
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 38f0998..3eeaf41 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -424,15 +424,16 @@
newKeyguardOccludedState: Boolean?
) {
super.onTransitionAnimationCancelled(newKeyguardOccludedState)
- cleanUp()
+ onDispose()
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
super.onTransitionAnimationEnd(isExpandingFullyAbove)
- cleanUp()
+ onDispose()
}
- private fun cleanUp() {
+ override fun onDispose() {
+ super.onDispose()
cleanUpRunnable?.run()
}
}
@@ -560,6 +561,7 @@
cookie: TransitionCookie? = null,
component: ComponentName? = null,
returnCujType: Int? = null,
+ isEphemeral: Boolean = true,
): Controller? {
// Make sure the View we launch from implements LaunchableView to avoid visibility
// issues.
@@ -587,6 +589,7 @@
cookie,
component,
returnCujType,
+ isEphemeral,
)
}
}
@@ -647,6 +650,9 @@
* appropriately.
*/
fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
+
+ /** The controller will not be used again. Clean up the relevant internal state. */
+ fun onDispose() {}
}
/**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index 3ba9a29..b56a68cb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -39,7 +39,8 @@
launchCujType: Int? = null,
cookie: ActivityTransitionAnimator.TransitionCookie? = null,
component: ComponentName? = null,
- returnCujType: Int? = null
+ returnCujType: Int? = null,
+ isEphemeral: Boolean = true,
): ActivityTransitionAnimator.Controller?
/**
@@ -55,7 +56,8 @@
launchCujType,
cookie = null,
component = null,
- returnCujType = null
+ returnCujType = null,
+ isEphemeral = true,
)
}
@@ -80,14 +82,16 @@
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
return ActivityTransitionAnimator.Controller.fromView(
view,
launchCujType,
cookie,
component,
- returnCujType
+ returnCujType,
+ isEphemeral,
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index e626c04..558c1eba 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -67,6 +67,12 @@
/** The [CujType] associated to this return animation. */
private val returnCujType: Int? = null,
+
+ /**
+ * Whether this controller should be invalidated after its first use, and whenever [ghostedView]
+ * is detached.
+ */
+ private val isEphemeral: Boolean = false,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
) : ActivityTransitionAnimator.Controller {
@@ -119,6 +125,19 @@
returnCujType
}
+ /**
+ * Used to automatically clean up the internal state once [ghostedView] is detached from the
+ * hierarchy.
+ */
+ private val detachListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {}
+
+ override fun onViewDetachedFromWindow(v: View) {
+ onDispose()
+ }
+ }
+
init {
// Make sure the View we launch from implements LaunchableView to avoid visibility issues.
if (ghostedView !is LaunchableView) {
@@ -155,6 +174,16 @@
}
background = findBackground(ghostedView)
+
+ if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+ ghostedView.addOnAttachStateChangeListener(detachListener)
+ }
+ }
+
+ override fun onDispose() {
+ if (TransitionAnimator.returnAnimationsEnabled()) {
+ ghostedView.removeOnAttachStateChangeListener(detachListener)
+ }
}
/**
@@ -164,7 +193,7 @@
protected open fun setBackgroundCornerRadius(
background: Drawable,
topCornerRadius: Float,
- bottomCornerRadius: Float
+ bottomCornerRadius: Float,
) {
// By default, we rely on WrappedDrawable to set/restore the background radii before/after
// each draw.
@@ -195,7 +224,7 @@
val state =
TransitionAnimator.State(
topCornerRadius = getCurrentTopCornerRadius(),
- bottomCornerRadius = getCurrentBottomCornerRadius()
+ bottomCornerRadius = getCurrentBottomCornerRadius(),
)
fillGhostedViewState(state)
return state
@@ -269,7 +298,7 @@
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
val ghostView = this.ghostView ?: return
val backgroundView = this.backgroundView!!
@@ -317,11 +346,11 @@
scale,
scale,
ghostedViewState.centerX - transitionContainerLocation[0],
- ghostedViewState.centerY - transitionContainerLocation[1]
+ ghostedViewState.centerY - transitionContainerLocation[1],
)
ghostViewMatrix.postTranslate(
(leftChange + rightChange) / 2f,
- (topChange + bottomChange) / 2f
+ (topChange + bottomChange) / 2f,
)
ghostView.animationMatrix = ghostViewMatrix
@@ -462,7 +491,7 @@
private fun updateRadii(
radii: FloatArray,
topCornerRadius: Float,
- bottomCornerRadius: Float
+ bottomCornerRadius: Float,
) {
radii[0] = topCornerRadius
radii[1] = topCornerRadius
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index cbe11a3..8a57e8c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -163,7 +164,8 @@
t.show(wallpapers[i].leash);
t.setAlpha(wallpapers[i].leash, 1.f);
}
- if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()) {
+ if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()
+ || ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()) {
resetLauncherAlphaOnDesktopExit(info, launcherTask, leashMap, t);
}
} else {
diff --git a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
index efbdf4d..0abeeb7 100644
--- a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
+++ b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
@@ -20,7 +20,7 @@
import kotlin.coroutines.CoroutineContext
fun newTracingContext(name: String): CoroutineContext {
- return createCoroutineTracingContext(name, walkStackForDefaultNames = true) { className ->
+ return createCoroutineTracingContext(name, walkStackForDefaultNames = false) { className ->
className.startsWith("com.android.systemui.util.kotlin.JavaAdapter") ||
className.startsWith("com.android.systemui.lifecycle.RepeatWhenAttached")
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index a55df2b..103a9b5 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -52,6 +52,9 @@
interface ExpandableController {
/** The [Expandable] controlled by this controller. */
val expandable: Expandable
+
+ /** Called when the [Expandable] stop being included in the composition. */
+ fun onDispose()
}
/**
@@ -88,33 +91,44 @@
// Whether this composable is still composed. We only do the dialog exit animation if this is
// true.
val isComposed = remember { mutableStateOf(true) }
- DisposableEffect(Unit) { onDispose { isComposed.value = false } }
- return remember(
- color,
- contentColor,
- shape,
- borderStroke,
- composeViewRoot,
- density,
- layoutDirection,
- ) {
- ExpandableControllerImpl(
+ val controller =
+ remember(
color,
contentColor,
shape,
borderStroke,
composeViewRoot,
density,
- animatorState,
- isDialogShowing,
- overlay,
- currentComposeViewInOverlay,
- boundsInComposeViewRoot,
layoutDirection,
- isComposed,
- )
+ ) {
+ ExpandableControllerImpl(
+ color,
+ contentColor,
+ shape,
+ borderStroke,
+ composeViewRoot,
+ density,
+ animatorState,
+ isDialogShowing,
+ overlay,
+ currentComposeViewInOverlay,
+ boundsInComposeViewRoot,
+ layoutDirection,
+ isComposed,
+ )
+ }
+
+ DisposableEffect(Unit) {
+ onDispose {
+ isComposed.value = false
+ if (TransitionAnimator.returnAnimationsEnabled()) {
+ controller.onDispose()
+ }
+ }
}
+
+ return controller
}
internal class ExpandableControllerImpl(
@@ -132,19 +146,29 @@
private val layoutDirection: LayoutDirection,
private val isComposed: State<Boolean>,
) : ExpandableController {
+ /** The [ActivityTransitionAnimator.Controller] to be cleaned up [onDispose]. */
+ private var activityControllerForDisposal: ActivityTransitionAnimator.Controller? = null
+
override val expandable: Expandable =
object : Expandable {
override fun activityTransitionController(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
if (!isComposed.value) {
return null
}
- return activityController(launchCujType, cookie, component, returnCujType)
+ val controller = activityController(launchCujType, cookie, component, returnCujType)
+ if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+ activityControllerForDisposal?.onDispose()
+ activityControllerForDisposal = controller
+ }
+
+ return controller
}
override fun dialogTransitionController(
@@ -158,6 +182,11 @@
}
}
+ override fun onDispose() {
+ activityControllerForDisposal?.onDispose()
+ activityControllerForDisposal = null
+ }
+
/**
* Create a [TransitionAnimator.Controller] that is going to be used to drive an activity or
* dialog animation. This controller will:
@@ -181,7 +210,7 @@
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
// We copy state given that it's always the same object that is mutated by
// ActivityTransitionAnimator.
@@ -269,7 +298,7 @@
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
): ActivityTransitionAnimator.Controller {
val delegate = transitionController()
return object :
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 4bccac1..86c5fd8 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
@@ -28,7 +28,6 @@
import androidx.compose.ui.util.fastForEach
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
@@ -528,39 +527,6 @@
transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
}
- /**
- * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
- * to the closest scene.
- *
- * Important: Snapping to the closest scene will instantly finish *all* ongoing transitions,
- * only the progress of the last transition will be checked.
- *
- * @return true if snapped to the closest scene.
- */
- internal fun snapToIdleIfClose(threshold: Float): Boolean {
- val transition = currentTransition ?: return false
- val progress = transition.progress
-
- fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
- fun finishAllTransitions() {
- // Force finish all transitions.
- while (currentTransitions.isNotEmpty()) {
- finishTransition(transitionStates[0] as TransitionState.Transition)
- }
- }
-
- val shouldSnap =
- (isProgressCloseTo(0f) && transition.isFromCurrentContent()) ||
- (isProgressCloseTo(1f) && transition.isToCurrentContent())
- return if (shouldSnap) {
- finishAllTransitions()
- true
- } else {
- false
- }
- }
-
override fun showOverlay(
overlay: OverlayKey,
animationScope: CoroutineScope,
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 79ca891..3b7d661 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
@@ -18,12 +18,9 @@
import android.util.Log
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.TestOverlays.OverlayA
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
@@ -169,130 +166,6 @@
assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
}
- @Test
- fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the initial scene if it is close to 0.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- }
-
- @Test
- fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the initial scene if it is close to 0.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- }
-
- @Test
- fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(from = SceneA, to = SceneB, progress = { 0.8f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- }
-
- @Test
- fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA)))
- }
-
- @Test
- fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-
- val aToB = transition(from = SceneA, to = SceneB, progress = { 0.5f })
- state.startTransitionImmediately(animationScope = backgroundScope, aToB)
- assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
-
- val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f })
- state.startTransitionImmediately(animationScope = backgroundScope, bToC)
- assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- assertThat(state.currentTransitions).isEmpty()
- }
-
- @Test
- fun snapToIdleIfClose_closeButNotCurrentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- var progress by mutableStateOf(0f)
- var currentScene by mutableStateOf(SceneB)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(
- from = SceneA,
- to = SceneB,
- current = { currentScene },
- progress = { progress },
- ),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if we are close to a scene that is not the current scene
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- progress = 1f
- currentScene = SceneA
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
- }
-
private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
progress: () -> Float,
sceneTransitions: SceneTransitions,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index a804879..f924ccb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -62,6 +62,7 @@
import com.android.systemui.complication.ComplicationLayoutEngine
import com.android.systemui.complication.dagger.ComplicationComponent
import com.android.systemui.dreams.complication.HideComplicationTouchHandler
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent
import com.android.systemui.dreams.dagger.DreamOverlayComponent
import com.android.systemui.dreams.touch.CommunalTouchHandler
import com.android.systemui.flags.andSceneContainer
@@ -119,8 +120,7 @@
private val mComplicationComponentFactory = mock<ComplicationComponent.Factory>()
private val mComplicationHostViewController = mock<ComplicationHostViewController>()
private val mComplicationVisibilityController = mock<ComplicationLayoutEngine>()
- private val mDreamComplicationComponentFactory =
- mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory>()
+ private val mDreamComplicationComponentFactory = mock<DreamComplicationComponent.Factory>()
private val mHideComplicationTouchHandler = mock<HideComplicationTouchHandler>()
private val mDreamOverlayComponentFactory = mock<DreamOverlayComponent.Factory>()
private val mCommunalTouchHandler = mock<CommunalTouchHandler>()
@@ -160,8 +160,7 @@
private lateinit var mService: DreamOverlayService
private class EnvironmentComponents(
- val dreamsComplicationComponent:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent,
+ val dreamsComplicationComponent: DreamComplicationComponent,
val dreamOverlayComponent: DreamOverlayComponent,
val complicationComponent: ComplicationComponent,
val ambientTouchComponent: AmbientTouchComponent,
@@ -186,8 +185,7 @@
}
private fun setupComponentFactories(
- dreamComplicationComponentFactory:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory,
+ dreamComplicationComponentFactory: DreamComplicationComponent.Factory,
dreamOverlayComponentFactory: DreamOverlayComponent.Factory,
complicationComponentFactory: ComplicationComponent.Factory,
ambientTouchComponentFactory: AmbientTouchComponent.Factory,
@@ -208,8 +206,7 @@
whenever(complicationComponent.getVisibilityController())
.thenReturn(mComplicationVisibilityController)
- val dreamComplicationComponent =
- mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent>()
+ val dreamComplicationComponent = mock<DreamComplicationComponent>()
whenever(dreamComplicationComponent.getHideComplicationTouchHandler())
.thenReturn(mHideComplicationTouchHandler)
whenever(dreamOverlayComponent.communalTouchHandler).thenReturn(mCommunalTouchHandler)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index 72916a3..d12c045 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -36,14 +36,14 @@
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.ALL_SUPPORTED_MODIFIERS
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutCategory
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
@@ -88,7 +88,7 @@
@Test
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
- fun categories_emitsCorrectlyConvertedShortcutCategories() {
+ fun categories_correctlyConvertsAPIModelsToShortcutHelperModels() {
testScope.runTest {
whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
.thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
@@ -323,4 +323,33 @@
}
}
}
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_isUpdatedAfterCustomShortcutsAreReset() {
+ testScope.runTest {
+ // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper
+ var customInputGestures = listOf(allAppsInputGestureData)
+ whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+ return@then customInputGestures
+ }
+ whenever(
+ inputManager.removeAllCustomInputGestures(
+ /* filter = */ InputGestureData.Filter.KEY
+ )
+ )
+ .then {
+ customInputGestures = emptyList()
+ return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ }
+
+ val categories by collectLastValue(repo.categories)
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+
+ assertThat(categories).containsExactly(allAppsShortcutCategory)
+ repo.resetAllCustomShortcuts()
+ assertThat(categories).isEmpty()
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt
new file mode 100644
index 0000000..60d7089
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.keyboard.shortcut.data.source
+
+import android.content.res.mainResources
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultitaskingShortcutsSourceTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val source = MultitaskingShortcutsSource(kosmos.mainResources, context)
+
+ @Test
+ fun shortcutGroups_doesNotContainCycleThroughRecentAppsShortcuts() {
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts =
+ groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+ val cycleThroughRecentAppsShortcuts =
+ listOf(
+ Triple(
+ context.getString(R.string.group_system_cycle_forward),
+ META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ Triple(
+ context.getString(R.string.group_system_cycle_back),
+ META_SHIFT_ON or META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ )
+
+ assertThat(shortcuts).containsNoneIn(cycleThroughRecentAppsShortcuts)
+ }
+ }
+
+ private companion object {
+ private const val TEST_DEVICE_ID = 1234
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
index 495e98d..b9fb3e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
@@ -26,6 +26,9 @@
import android.view.KeyEvent.KEYCODE_BACK
import android.view.KeyEvent.KEYCODE_HOME
import android.view.KeyEvent.KEYCODE_RECENT_APPS
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
@@ -132,7 +135,33 @@
assertThat(shortcuts).doesNotContain(hardwareShortcut)
}
- companion object {
+ @Test
+ fun shortcutGroups_containsCycleThroughRecentAppsShortcuts() {
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts =
+ groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+ val cycleThroughRecentAppsShortcuts =
+ listOf(
+ Triple(
+ context.getString(R.string.group_system_cycle_forward),
+ META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ Triple(
+ context.getString(R.string.group_system_cycle_back),
+ META_SHIFT_ON or META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ )
+
+ assertThat(shortcuts).containsAtLeastElementsIn(cycleThroughRecentAppsShortcuts)
+ }
+ }
+
+ private companion object {
private const val TEST_DEVICE_ID = 1234
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 7855d42..c287da8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -49,7 +49,6 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shared.model.shortcut
import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.res.R
object TestShortcuts {
@@ -492,17 +491,15 @@
simpleShortcutCategory(AppCategories, "Applications", "Email"),
simpleShortcutCategory(AppCategories, "Applications", "Maps"),
simpleShortcutCategory(AppCategories, "Applications", "SMS"),
- simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
)
- val customInputGestureTypeHome =
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ val customInputGestureTypeHome = simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME)
val allCustomizableInputGesturesWithSimpleShortcutCombinations =
listOf(
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME),
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
),
@@ -626,9 +623,6 @@
}
}
- val expectedStandardDeleteShortcutUiState =
- ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false)
-
val keyDownEventWithoutActionKeyPressed =
androidx.compose.ui.input.key.KeyEvent(
android.view.KeyEvent(
@@ -671,12 +665,4 @@
categoryType = ShortcutCategoryType.System,
subCategoryLabel = "Standard subcategory",
)
-
- val expectedStandardAddShortcutUiState =
- ShortcutCustomizationUiState.AddShortcutDialog(
- shortcutLabel = "Standard shortcut",
- defaultCustomShortcutModifierKey =
- ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
- isDialogShowing = false,
- )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index d3d1a35..2d05ee0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -28,25 +28,26 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardAddShortcutUiState
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardDeleteShortcutUiState
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddShortcutRequest
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -62,8 +63,7 @@
private val mockUserContext: Context = mock()
private val kosmos =
- Kosmos().also {
- it.testCase = this
+ testKosmos().also {
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
private val testScope = kosmos.testScope
@@ -92,7 +92,23 @@
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- assertThat(uiState).isEqualTo(expectedStandardAddShortcutUiState)
+ assertThat(uiState).isEqualTo(
+ AddShortcutDialog(
+ shortcutLabel = "Standard shortcut",
+ defaultCustomShortcutModifierKey =
+ ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ )
+ )
+ }
+ }
+
+ @Test
+ fun uiState_correctlyUpdatedWhenResetShortcutCustomizationIsRequested() {
+ testScope.runTest {
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+ assertThat(uiState).isEqualTo(ResetShortcutDialog())
}
}
@@ -102,7 +118,7 @@
viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- assertThat(uiState).isEqualTo(expectedStandardDeleteShortcutUiState)
+ assertThat(uiState).isEqualTo(DeleteShortcutDialog())
}
}
@@ -113,7 +129,7 @@
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
viewModel.onDialogShown()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).isDialogShowing)
+ assertThat((uiState as AddShortcutDialog).isDialogShowing)
.isTrue()
}
}
@@ -126,13 +142,25 @@
viewModel.onDialogShown()
assertThat(
- (uiState as ShortcutCustomizationUiState.DeleteShortcutDialog).isDialogShowing
+ (uiState as DeleteShortcutDialog).isDialogShowing
)
.isTrue()
}
}
@Test
+ fun uiState_consumedOnResetDialogShown() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ viewModel.onDialogShown()
+
+ assertThat((uiState as ResetShortcutDialog).isDialogShowing)
+ .isTrue()
+ }
+ }
+
+ @Test
fun uiState_inactiveAfterDialogIsDismissed() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
@@ -148,7 +176,7 @@
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.isEmpty()
}
}
@@ -173,7 +201,7 @@
viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
viewModel.onDialogShown()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEmpty()
}
}
@@ -187,7 +215,7 @@
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(
context.getString(
R.string.shortcut_customizer_key_combination_in_use_error_message
@@ -205,7 +233,7 @@
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(
context.getString(
R.string.shortcut_customizer_key_combination_in_use_error_message
@@ -223,7 +251,7 @@
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(context.getString(R.string.shortcut_customizer_generic_error_message))
}
}
@@ -244,6 +272,18 @@
}
@Test
+ fun uiState_becomesInactiveAfterSuccessfullyResettingShortcuts() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.getCustomInputGestures(any())).thenReturn(emptyList())
+
+ openResetShortcutDialogAndResetAllCustomShortcuts()
+
+ assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+ }
+ }
+
+ @Test
fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
testScope.runTest {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
@@ -272,7 +312,7 @@
viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
}
}
@@ -286,13 +326,13 @@
viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
// Close the dialog and show it again
viewModel.onDialogDismissed()
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.isEmpty()
}
}
@@ -313,4 +353,11 @@
viewModel.deleteShortcutCurrentlyBeingCustomized()
}
+
+ private suspend fun openResetShortcutDialogAndResetAllCustomShortcuts() {
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ viewModel.onDialogShown()
+
+ viewModel.resetAllCustomShortcuts()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 534c12c..3a4c993 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -18,7 +18,10 @@
import android.content.Intent
import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.hardware.display.VirtualDisplayConfig
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
import android.testing.TestableLooper
import android.view.View
import android.widget.Spinner
@@ -42,6 +45,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -224,6 +228,34 @@
.notifyProjectionRequestCancelled(TEST_HOST_UID)
}
+ @Test
+ @RequiresFlagsEnabled(
+ com.android.media.projection.flags.Flags
+ .FLAG_MEDIA_PROJECTION_CONNECTED_DISPLAY_NO_VIRTUAL_DEVICE
+ )
+ fun doNotShowVirtualDisplayInDialog() {
+ val displayManager = context.getSystemService(DisplayManager::class.java)!!
+ var virtualDisplay: VirtualDisplay? = null
+ try {
+ virtualDisplay =
+ displayManager.createVirtualDisplay(
+ VirtualDisplayConfig.Builder("virtual display", 1, 1, 160).build()
+ )
+ showDialog()
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ val adapter = spinner.adapter
+ val virtualDisplayAvailable =
+ (0 until adapter.count)
+ .mapNotNull { adapter.getItem(it) as? String }
+ .any { it.contains("virtual display", ignoreCase = true) }
+ assertWithMessage("A Virtual Display was shown in the list of display to record")
+ .that(virtualDisplayAvailable)
+ .isFalse()
+ } finally {
+ virtualDisplay?.release()
+ }
+ }
+
private fun showDialog() {
dialog.show()
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4bf67a1..a0a61c7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3736,10 +3736,6 @@
that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are
for example "Enter split screen". [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_category_multitasking">Multitasking</string>
- <!-- Title of the keyboard shortcut helper category "Recent apps". The helper is a component
- that shows the user which keyboard shortcuts they can use. The "Recent apps" shortcuts are
- for example "Cycle through recent apps". [CHAR LIMIT=NONE] -->
- <string name="shortcutHelper_category_recent_apps">Recent apps</string>
<!-- Title of the keyboard shortcut helper category "Split screen". The helper is a component
that shows the user which keyboard shortcuts they can use. The "Split screen" shortcuts are
for example "Move current app to left split". [CHAR LIMIT=NONE] -->
@@ -3772,6 +3768,11 @@
The helper is a component that shows the user which keyboard shortcuts they can use. Also
allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
<string name="shortcut_customize_mode_remove_shortcut_dialog_title">Remove shortcut?</string>
+ <!-- Title at the top of the keyboard shortcut helper reset shortcut dialog. This dialog allows
+ the user to remove all custom shortcuts the user has set, resetting to default shortcuts only.
+ Shortcut helper is a component that shows the user which keyboard shortcuts they can use. Also
+ allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
+ <string name="shortcut_customize_mode_reset_shortcut_dialog_title">Reset back to default?</string>
<!-- Sub title at the top of the keyboard shortcut helper customization dialog. Explains to the
user what action they need to take in the customization dialog to assign a new custom shortcut.
The shortcut customize dialog allows users to add/remove custom shortcuts
@@ -3782,6 +3783,10 @@
users to add/remove custom shortcuts
[CHAR LIMIT=NONE] -->
<string name="shortcut_customize_mode_remove_shortcut_description">This will delete your custom shortcut permanently.</string>
+ <!-- Sub title at the top of the reset custom shortcut dialog. Explains to the user that the action
+ they're about to take will remove all custom shortcuts they have set, resetting to default shortcuts only.
+ The shortcut customize dialog allows users to add/remove custom shortcuts [CHAR LIMIT=NONE] -->
+ <string name="shortcut_customize_mode_reset_shortcut_description">This will delete all your custom shortcuts permanently.</string>
<!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
hasn't typed in anything in the search box yet. The helper is a component that shows the
user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3849,6 +3854,10 @@
confirm and remove previously added custom shortcut. The helper is a component that
shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_customize_dialog_remove_button_label">Remove</string>
+ <!-- Label on the reset shortcut button in keyboard shortcut helper customize dialog, that allows user to
+ confirm and reset all added custom shortcut. The helper is a component that
+ shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_customize_dialog_reset_button_label">Yes, reset</string>
<!-- Label on the cancel button in keyboard shortcut helper customize dialog, that allows user to
cancel and exit shortcut customization dialog, returning to the main shortcut helper page.
The helper is a component that shows the user which keyboard shortcuts they can use.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 43b7ced..aee3a45 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -65,6 +65,7 @@
import com.android.systemui.communal.shared.model.CommunalTransitionKeys;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
@@ -141,8 +142,7 @@
*/
private boolean mBouncerShowing = false;
- private final com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
- mDreamComplicationComponentFactory;
+ private final DreamComplicationComponent.Factory mDreamComplicationComponentFactory;
private final ComplicationComponent.Factory mComplicationComponentFactory;
private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
private final AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
@@ -376,8 +376,7 @@
@Main DelayableExecutor executor,
ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
ComplicationComponent.Factory complicationComponentFactory,
- com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
- dreamComplicationComponentFactory,
+ DreamComplicationComponent.Factory dreamComplicationComponentFactory,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
AmbientTouchComponent.Factory ambientTouchComponentFactory,
DreamOverlayStateController stateController,
@@ -479,9 +478,9 @@
mLifecycleOwner,
() -> mExecutor.execute(DreamOverlayService.this::requestExit),
new ViewModelStore(), mTouchInsetManager);
- final com.android.systemui.dreams.complication.dagger.ComplicationComponent
- dreamComplicationComponent = mDreamComplicationComponentFactory.create(
- complicationComponent.getVisibilityController(), mTouchInsetManager);
+ final DreamComplicationComponent dreamComplicationComponent =
+ mDreamComplicationComponentFactory.create(
+ complicationComponent.getVisibilityController(), mTouchInsetManager);
final DreamOverlayComponent dreamOverlayComponent = mDreamOverlayComponentFactory.create(
mLifecycleOwner, complicationComponent.getComplicationHostViewController(),
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
index f8ae5c2..ea5fbc6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
@@ -17,8 +17,8 @@
package com.android.systemui.dreams.complication;
import static com.android.systemui.Flags.removeDreamOverlayHideOnTouch;
-import static com.android.systemui.dreams.complication.dagger.ComplicationModule.COMPLICATIONS_FADE_OUT_DELAY;
-import static com.android.systemui.dreams.complication.dagger.ComplicationModule.COMPLICATIONS_RESTORE_TIMEOUT;
+import static com.android.systemui.dreams.complication.dagger.DreamComplicationModule.COMPLICATIONS_FADE_OUT_DELAY;
+import static com.android.systemui.dreams.complication.dagger.DreamComplicationModule.COMPLICATIONS_RESTORE_TIMEOUT;
import android.util.Log;
import android.view.MotionEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
rename to packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt
index 492c502..17d3acd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt
@@ -6,15 +6,15 @@
import dagger.BindsInstance
import dagger.Subcomponent
-@Subcomponent(modules = [ComplicationModule::class])
-interface ComplicationComponent {
- /** Factory for generating [ComplicationComponent]. */
+@Subcomponent(modules = [DreamComplicationModule::class])
+interface DreamComplicationComponent {
+ /** Factory for generating [DreamComplicationComponent]. */
@Subcomponent.Factory
interface Factory {
fun create(
@BindsInstance visibilityController: Complication.VisibilityController,
- @BindsInstance touchInsetManager: TouchInsetManager
- ): ComplicationComponent
+ @BindsInstance touchInsetManager: TouchInsetManager,
+ ): DreamComplicationComponent
}
fun getHideComplicationTouchHandler(): HideComplicationTouchHandler
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt
rename to packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt
index 6fd6f4e..59af22a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt
@@ -1,14 +1,14 @@
package com.android.systemui.dreams.complication.dagger
import android.content.res.Resources
-import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
import javax.inject.Named
@Module
-object ComplicationModule {
+object DreamComplicationModule {
const val COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"
const val COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay"
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 3171bbc..216cb86 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -32,7 +32,7 @@
import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.SystemDialogsCloser;
-import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent;
import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
import com.android.systemui.dreams.homecontrols.dagger.HomeControlsDataSourceModule;
import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent;
@@ -68,7 +68,7 @@
HomeControlsDataSourceModule.class,
},
subcomponents = {
- ComplicationComponent.class,
+ DreamComplicationComponent.class,
DreamOverlayComponent.class,
HomeControlsRemoteServiceComponent.class,
})
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
index 215ceac..0ed4007 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
@@ -78,9 +78,16 @@
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? =
delegate
- .activityTransitionController(launchCujType, cookie, component, returnCujType)
+ .activityTransitionController(
+ launchCujType,
+ cookie,
+ component,
+ returnCujType,
+ isEphemeral,
+ )
?.withStateAwareness(onActivityLaunchTransitionStart, onActivityLaunchTransitionEnd)
override fun dialogTransitionController(
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index b82aa81..1504402 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -284,6 +284,7 @@
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
val delegatedController =
ActivityTransitionAnimator.Controller.fromView(
@@ -292,6 +293,7 @@
cookie,
component,
returnCujType,
+ isEphemeral,
)
return delegatedController?.let { createTransitionControllerDelegate(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
index 9ffdafc..36cd400 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
@@ -57,7 +57,11 @@
_customInputGesture.onStart { refreshCustomInputGestures() }
private fun refreshCustomInputGestures() {
- _customInputGesture.value = retrieveCustomInputGestures()
+ setCustomInputGestures(inputGestures = retrieveCustomInputGestures())
+ }
+
+ private fun setCustomInputGestures(inputGestures: List<InputGestureData>) {
+ _customInputGesture.value = inputGestures
}
fun retrieveCustomInputGestures(): List<InputGestureData> {
@@ -112,6 +116,22 @@
}
}
+ suspend fun resetAllCustomInputGestures(): ShortcutCustomizationRequestResult {
+ return withContext(bgCoroutineContext) {
+ try {
+ inputManager.removeAllCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ setCustomInputGestures(emptyList())
+ SUCCESS
+ } catch (e: Exception) {
+ Log.w(
+ TAG,
+ "Attempted to remove all custom shortcut but ran into a remote error: $e",
+ )
+ ERROR_OTHER
+ }
+ }
+ }
+
private companion object {
private const val TAG = "CustomInputGesturesRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index d1bd51c..4af3786 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -169,7 +169,8 @@
.firstOrNull { it.action.keyGestureType() == keyGestureType }
}
- suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+ suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
+ ShortcutCustomizationRequestResult {
val inputGestureData =
buildInputGestureDataForShortcutBeingCustomized()
?: return ShortcutCustomizationRequestResult.ERROR_OTHER
@@ -184,6 +185,10 @@
return customInputGesturesRepository.deleteCustomInputGesture(inputGestureData)
}
+ suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+ return customInputGesturesRepository.resetAllCustomInputGestures()
+ }
+
private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel()
@@ -297,17 +302,13 @@
return null
}
- private fun fetchGroupLabelByGestureType(
- @KeyGestureType keyGestureType: Int
- ): String? {
+ private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
return context.getString(it)
} ?: return null
}
- private fun fetchShortcutInfoLabelByGestureType(
- @KeyGestureType keyGestureType: Int
- ): String? {
+ private fun fetchShortcutInfoLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
return context.getString(it)
} ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index ecc0761..1c380c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -104,7 +104,6 @@
R.string.shortcut_helper_category_system_apps,
// Multitasking Category
- KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.shortcutHelper_category_recent_apps,
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
R.string.shortcutHelper_category_split_screen,
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index 5ef869e..df6b04e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -26,11 +26,9 @@
import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
import android.view.KeyEvent.KEYCODE_MINUS
import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
-import android.view.KeyEvent.KEYCODE_TAB
import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
-import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -49,13 +47,9 @@
override suspend fun shortcutGroups(deviceId: Int) =
listOf(
KeyboardShortcutGroup(
- resources.getString(R.string.shortcutHelper_category_recent_apps),
- recentsShortcuts(),
- ),
- KeyboardShortcutGroup(
resources.getString(R.string.shortcutHelper_category_split_screen),
splitScreenShortcuts(),
- ),
+ )
)
private fun splitScreenShortcuts() = buildList {
@@ -140,18 +134,4 @@
)
}
}
-
- private fun recentsShortcuts() =
- listOf(
- // Cycle through recent apps (forward):
- // - Alt + Tab
- shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
- command(META_ALT_ON, KEYCODE_TAB)
- },
- // Cycle through recent apps (back):
- // - Shift + Alt + Tab
- shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
- command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
- },
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index a650cd8..687ad95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -21,9 +21,7 @@
import android.hardware.input.KeyGlyphMap
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_BACK
-import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
-import android.view.KeyEvent.KEYCODE_ENTER
import android.view.KeyEvent.KEYCODE_ESCAPE
import android.view.KeyEvent.KEYCODE_H
import android.view.KeyEvent.KEYCODE_HOME
@@ -34,8 +32,10 @@
import android.view.KeyEvent.KEYCODE_S
import android.view.KeyEvent.KEYCODE_SLASH
import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
import com.android.systemui.Flags.shortcutHelperKeyGlyph
@@ -127,29 +127,31 @@
},
// Access home screen:
// - Meta + H
- // - Meta + Enter
shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
command(META_META_ON, KEYCODE_H)
},
- shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
- command(META_META_ON, KEYCODE_ENTER)
- },
// Overview of open apps:
// - Meta + Tab
shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
command(META_META_ON, KEYCODE_TAB)
},
+ // Cycle through recent apps (forward):
+ // - Alt + Tab
+ shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
+ command(META_ALT_ON, KEYCODE_TAB)
+ },
+ // Cycle through recent apps (back):
+ // - Shift + Alt + Tab
+ shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
+ command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
+ },
// Back: go back to previous state (back button)
// - Meta + Escape OR
- // - Meta + Backspace OR
// - Meta + Left arrow
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
command(META_META_ON, KEYCODE_ESCAPE)
},
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
- command(META_META_ON, KEYCODE_DEL)
- },
- shortcutInfo(resources.getString(R.string.group_system_go_back)) {
command(META_META_ON, KEYCODE_DPAD_LEFT)
},
// Take a full screenshot:
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index 7743c53..ef24267 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -46,8 +46,11 @@
return customShortcutRepository.confirmAndSetShortcutCurrentlyBeingCustomized()
}
- suspend fun deleteShortcutCurrentlyBeingCustomized():
- ShortcutCustomizationRequestResult {
+ suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
return customShortcutRepository.deleteShortcutCurrentlyBeingCustomized()
}
+
+ suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+ return customShortcutRepository.resetAllCustomShortcuts()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
index 2d3e7f6..095de41 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
@@ -28,4 +28,6 @@
val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
val subCategoryLabel: String = "",
) : ShortcutCustomizationRequestInfo
+
+ data object Reset : ShortcutCustomizationRequestInfo
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index f28618b..bd0430b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -31,6 +31,7 @@
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -54,7 +55,8 @@
viewModel.shortcutCustomizationUiState.collect { uiState ->
val shouldShowAddDialog = uiState is AddShortcutDialog && !uiState.isDialogShowing
val shouldShowDeleteDialog = uiState is DeleteShortcutDialog && !uiState.isDialogShowing
- if (shouldShowDeleteDialog || shouldShowAddDialog) {
+ val shouldShowResetDialog = uiState is ResetShortcutDialog && !uiState.isDialogShowing
+ if (shouldShowDeleteDialog || shouldShowAddDialog || shouldShowResetDialog) {
dialog = createDialog().also { it.show() }
viewModel.onDialogShown()
} else if (uiState is ShortcutCustomizationUiState.Inactive) {
@@ -83,7 +85,12 @@
onKeyPress = { viewModel.onKeyPressed(it) },
onCancel = { dialog.dismiss() },
onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } },
- onConfirmDeleteShortcut = { coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() } },
+ onConfirmDeleteShortcut = {
+ coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() }
+ },
+ onConfirmResetShortcut = {
+ coroutineScope.launch { viewModel.resetAllCustomShortcuts() }
+ },
)
dialog.setOnDismissListener { viewModel.onDialogDismissed() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index 20040c6..ac6708a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -68,73 +68,133 @@
onCancel: () -> Unit,
onConfirmSetShortcut: () -> Unit,
onConfirmDeleteShortcut: () -> Unit,
+ onConfirmResetShortcut: () -> Unit,
) {
when (uiState) {
is ShortcutCustomizationUiState.AddShortcutDialog -> {
- Column(modifier = modifier) {
- Title(uiState.shortcutLabel)
- Description(
- text =
- stringResource(
- id = R.string.shortcut_customize_mode_add_shortcut_description
- )
- )
- PromptShortcutModifier(
- modifier =
- Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
- .width(131.dp)
- .height(48.dp),
- defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
- )
- SelectedKeyCombinationContainer(
- shouldShowError = uiState.errorMessage.isNotEmpty(),
- onKeyPress = onKeyPress,
- pressedKeys = uiState.pressedKeys,
- )
- ErrorMessageContainer(uiState.errorMessage)
- DialogButtons(
- onCancel,
- isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
- onConfirm = onConfirmSetShortcut,
- confirmButtonText =
- stringResource(
- R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
- ),
- )
- }
+ AddShortcutDialog(modifier, uiState, onKeyPress, onCancel, onConfirmSetShortcut)
}
is ShortcutCustomizationUiState.DeleteShortcutDialog -> {
- Column(modifier) {
- Title(
- title =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
- )
- )
- Description(
- text =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_description
- )
- )
- DialogButtons(
- onCancel = onCancel,
- onConfirm = onConfirmDeleteShortcut,
- confirmButtonText =
- stringResource(
- R.string.shortcut_helper_customize_dialog_remove_button_label
- ),
- )
- }
+ DeleteShortcutDialog(modifier, onCancel, onConfirmDeleteShortcut)
+ }
+ is ShortcutCustomizationUiState.ResetShortcutDialog -> {
+ ResetShortcutDialog(modifier, onCancel, onConfirmResetShortcut)
}
else -> {
- /* No-Op */
+ /* No-op */
}
}
}
@Composable
-fun DialogButtons(
+private fun AddShortcutDialog(
+ modifier: Modifier,
+ uiState: ShortcutCustomizationUiState.AddShortcutDialog,
+ onKeyPress: (KeyEvent) -> Boolean,
+ onCancel: () -> Unit,
+ onConfirmSetShortcut: () -> Unit
+){
+ Column(modifier = modifier) {
+ Title(uiState.shortcutLabel)
+ Description(
+ text =
+ stringResource(
+ id = R.string.shortcut_customize_mode_add_shortcut_description
+ )
+ )
+ PromptShortcutModifier(
+ modifier =
+ Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+ .width(131.dp)
+ .height(48.dp),
+ defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
+ )
+ SelectedKeyCombinationContainer(
+ shouldShowError = uiState.errorMessage.isNotEmpty(),
+ onKeyPress = onKeyPress,
+ pressedKeys = uiState.pressedKeys,
+ )
+ ErrorMessageContainer(uiState.errorMessage)
+ DialogButtons(
+ onCancel,
+ isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
+ onConfirm = onConfirmSetShortcut,
+ confirmButtonText =
+ stringResource(
+ R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
+ ),
+ )
+ }
+}
+
+@Composable
+private fun DeleteShortcutDialog(
+ modifier: Modifier,
+ onCancel: () -> Unit,
+ onConfirmDeleteShortcut: () -> Unit
+){
+ ConfirmationDialog(
+ modifier = modifier,
+ title =
+ stringResource(
+ id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
+ ),
+ description =
+ stringResource(
+ id = R.string.shortcut_customize_mode_remove_shortcut_description
+ ),
+ confirmButtonText =
+ stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
+ onCancel = onCancel,
+ onConfirm = onConfirmDeleteShortcut,
+ )
+}
+
+@Composable
+private fun ResetShortcutDialog(
+ modifier: Modifier,
+ onCancel: () -> Unit,
+ onConfirmResetShortcut: () -> Unit
+){
+ ConfirmationDialog(
+ modifier = modifier,
+ title =
+ stringResource(
+ id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title
+ ),
+ description =
+ stringResource(
+ id = R.string.shortcut_customize_mode_reset_shortcut_description
+ ),
+ confirmButtonText =
+ stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
+ onCancel = onCancel,
+ onConfirm = onConfirmResetShortcut,
+ )
+}
+
+@Composable
+private fun ConfirmationDialog(
+ modifier: Modifier,
+ title: String,
+ description: String,
+ confirmButtonText: String,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Column(modifier) {
+ Title(title = title)
+ Description(text = description)
+ DialogButtons(
+ onCancel = onCancel,
+ onConfirm = onConfirm,
+ confirmButtonText = confirmButtonText,
+ )
+ }
+}
+
+@Composable
+private fun DialogButtons(
onCancel: () -> Unit,
isConfirmButtonEnabled: Boolean = true,
onConfirm: () -> Unit,
@@ -168,7 +228,7 @@
}
@Composable
-fun ErrorMessageContainer(errorMessage: String) {
+private fun ErrorMessageContainer(errorMessage: String) {
if (errorMessage.isNotEmpty()) {
Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) {
Text(
@@ -185,7 +245,7 @@
}
@Composable
-fun SelectedKeyCombinationContainer(
+private fun SelectedKeyCombinationContainer(
shouldShowError: Boolean,
onKeyPress: (KeyEvent) -> Boolean,
pressedKeys: List<ShortcutKey>,
@@ -352,7 +412,7 @@
}
@Composable
-fun ActionKeyText() {
+private fun ActionKeyText() {
Text(
text = "Action",
style = MaterialTheme.typography.titleMedium,
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 1f37c7d..7929307 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
@@ -471,6 +471,9 @@
is ShortcutCustomizationRequestInfo.Delete ->
onCustomizationRequested(requestInfo.copy(categoryType = category.type))
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
}
},
)
@@ -535,6 +538,9 @@
onCustomizationRequested(
requestInfo.copy(subCategoryLabel = subCategory.label)
)
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
}
},
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
index 990257d..bfc9486 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -23,11 +23,17 @@
val shortcutLabel: String,
val errorMessage: String = "",
val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
- val isDialogShowing: Boolean,
+ val isDialogShowing: Boolean = false,
val pressedKeys: List<ShortcutKey> = emptyList(),
) : ShortcutCustomizationUiState
- data class DeleteShortcutDialog(val isDialogShowing: Boolean) : ShortcutCustomizationUiState
+ data class DeleteShortcutDialog(
+ val isDialogShowing: Boolean = false
+ ) : ShortcutCustomizationUiState
+
+ data class ResetShortcutDialog(
+ val isDialogShowing: Boolean = false
+ ) : ShortcutCustomizationUiState
data object Inactive : ShortcutCustomizationUiState
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index b467bb4..76a2e60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -31,6 +31,7 @@
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.res.R
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -82,6 +83,10 @@
_shortcutCustomizationUiState.value = DeleteShortcutDialog(isDialogShowing = false)
shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
+
+ ShortcutCustomizationRequestInfo.Reset -> {
+ _shortcutCustomizationUiState.value = ResetShortcutDialog(isDialogShowing = false)
+ }
}
}
@@ -89,6 +94,7 @@
_shortcutCustomizationUiState.update { uiState ->
(uiState as? AddShortcutDialog)?.copy(isDialogShowing = true)
?: (uiState as? DeleteShortcutDialog)?.copy(isDialogShowing = true)
+ ?: (uiState as? ResetShortcutDialog)?.copy(isDialogShowing = true)
?: uiState
}
}
@@ -134,8 +140,18 @@
}
suspend fun deleteShortcutCurrentlyBeingCustomized() {
- val result =
- shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+ val result = shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+
+ _shortcutCustomizationUiState.update { uiState ->
+ when (result) {
+ ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
+ else -> uiState
+ }
+ }
+ }
+
+ suspend fun resetAllCustomShortcuts() {
+ val result = shortcutCustomizationInteractor.resetAllCustomShortcuts()
_shortcutCustomizationUiState.update { uiState ->
when (result) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index b30e1e9..8cae777 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -48,8 +48,6 @@
object KeyguardClockViewBinder {
private val TAG = KeyguardClockViewBinder::class.simpleName!!
- // When changing to new clock, we need to remove old clock views from burnInLayer
- private var lastClock: ClockController? = null
@JvmStatic
fun bind(
@@ -72,19 +70,33 @@
disposables +=
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ // When changing to new clock, we need to remove old views from burnInLayer
+ var lastClock: ClockController? = null
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.currentClock.collect { currentClock ->
- cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
- addClockViews(currentClock, keyguardRootView)
- updateBurnInLayer(
- keyguardRootView,
- viewModel,
- viewModel.clockSize.value,
- )
- applyConstraints(clockSection, keyguardRootView, true)
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
+ viewModel.currentClock.collect { currentClock ->
+ if (lastClock != currentClock) {
+ cleanupClockViews(
+ lastClock,
+ keyguardRootView,
+ viewModel.burnInLayer,
+ )
+ lastClock = currentClock
+ }
+
+ addClockViews(currentClock, keyguardRootView)
+ updateBurnInLayer(
+ keyguardRootView,
+ viewModel,
+ viewModel.clockSize.value,
+ )
+ applyConstraints(clockSection, keyguardRootView, true)
+ }
}
- }
+ .invokeOnCompletion {
+ cleanupClockViews(lastClock, keyguardRootView, viewModel.burnInLayer)
+ lastClock = null
+ }
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
@@ -185,23 +197,18 @@
viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
}
- private fun cleanupClockViews(
- currentClock: ClockController?,
+ fun cleanupClockViews(
+ lastClock: ClockController?,
rootView: ConstraintLayout,
burnInLayer: Layer?,
) {
- if (lastClock == currentClock) {
- return
- }
-
- lastClock?.let { clock ->
- clock.smallClock.layout.views.forEach {
+ lastClock?.run {
+ smallClock.layout.views.forEach {
burnInLayer?.removeView(it)
rootView.removeView(it)
}
- clock.largeClock.layout.views.forEach { rootView.removeView(it) }
+ largeClock.layout.views.forEach { rootView.removeView(it) }
}
- lastClock = currentClock
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index ac302dd..c0b3d83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -123,6 +123,7 @@
)
}
}
+ lastClock = null
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0a4e8c6..b1719107 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -68,6 +68,7 @@
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.window.BackEvent;
import androidx.annotation.DimenRes;
@@ -585,6 +586,7 @@
mNonLinearFactor = getDimenFloat(res,
com.android.internal.R.dimen.back_progress_non_linear_factor);
updateBackAnimationThresholds();
+ mBackgroundExecutor.execute(this::disableNavBarVirtualKeyHapticFeedback);
}
private float getDimenFloat(Resources res, @DimenRes int resId) {
@@ -1287,6 +1289,15 @@
}
}
+ private void disableNavBarVirtualKeyHapticFeedback() {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .setNavBarVirtualKeyHapticFeedbackEnabled(false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to disable navigation bar button haptics: ", e);
+ }
+ }
+
public void dump(PrintWriter pw) {
pw.println("EdgeBackGestureHandler:");
pw.println(" mIsEnabled=" + mIsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index bdc58c1..eb568f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -280,6 +280,18 @@
private const val DELAY_MS: Long = 3000
private const val INTERVAL_MS: Long = 1000
+ private val RECORDABLE_DISPLAY_TYPES =
+ intArrayOf(
+ Display.TYPE_OVERLAY,
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_INTERNAL,
+ Display.TYPE_WIFI,
+ )
+
+ private val filterDeviceTypeFlag: Boolean =
+ com.android.media.projection.flags.Flags
+ .mediaProjectionConnectedDisplayNoVirtualDevice()
+
private fun createOptionList(displayManager: DisplayManager): List<ScreenShareOption> {
if (!com.android.media.projection.flags.Flags.mediaProjectionConnectedDisplay()) {
return listOf(
@@ -302,6 +314,7 @@
),
)
}
+
return listOf(
ScreenShareOption(
SINGLE_APP,
@@ -322,7 +335,10 @@
),
) +
displayManager.displays
- .filter { it.displayId != Display.DEFAULT_DISPLAY }
+ .filter {
+ it.displayId != Display.DEFAULT_DISPLAY &&
+ (!filterDeviceTypeFlag || it.type in RECORDABLE_DISPLAY_TYPES)
+ }
.map {
ScreenShareOption(
ENTIRE_SCREEN,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index d210e93..c03ba01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -26,7 +26,12 @@
/**
* Source of truth for keyguard state: If locked, occluded, has password, trusted etc.
+ *
+ * @deprecated this class is not supported when KEYGUARD_WM_STATE_REFACTOR is enabled.
+ * Use {@link com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor}
+ * or {@link com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor} instead.
*/
+@Deprecated
public interface KeyguardStateController extends CallbackController<Callback>, Dumpable {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index 772ae77..c0c525b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.dialog.sliders.dagger
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
import dagger.BindsInstance
@@ -34,6 +35,8 @@
fun sliderTouchesViewBinder(): VolumeDialogSliderTouchesViewBinder
+ fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
+
@Subcomponent.Factory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
new file mode 100644
index 0000000..5a7fbc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.volume.dialog.sliders.ui
+
+import android.view.View
+import com.android.systemui.haptics.slider.HapticSlider
+import com.android.systemui.haptics.slider.HapticSliderPlugin
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
+import com.google.android.material.slider.Slider
+import com.google.android.msdl.domain.MSDLPlayer
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@VolumeDialogSliderScope
+class VolumeDialogSliderHapticsViewBinder
+@Inject
+constructor(
+ private val inputEventsViewModel: VolumeDialogSliderInputEventsViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val msdlPlayer: MSDLPlayer,
+ private val systemClock: SystemClock,
+) {
+
+ fun CoroutineScope.bind(view: View) {
+ val sliderView = view.requireViewById<Slider>(R.id.volume_dialog_slider)
+ val hapticSliderPlugin =
+ HapticSliderPlugin(
+ slider = HapticSlider.Slider(sliderView),
+ vibratorHelper = vibratorHelper,
+ msdlPlayer = msdlPlayer,
+ systemClock = systemClock,
+ )
+ hapticSliderPlugin.startInScope(this)
+
+ sliderView.addOnChangeListener { _, value, fromUser ->
+ hapticSliderPlugin.onProgressChanged(value.roundToInt(), fromUser)
+ }
+ sliderView.addOnSliderTouchListener(
+ object : Slider.OnSliderTouchListener {
+
+ override fun onStartTrackingTouch(slider: Slider) {
+ hapticSliderPlugin.onStartTrackingTouch()
+ }
+
+ override fun onStopTrackingTouch(slider: Slider) {
+ hapticSliderPlugin.onStopTrackingTouch()
+ }
+ }
+ )
+
+ inputEventsViewModel.event
+ .onEach {
+ when (it) {
+ is SliderInputEvent.Button -> hapticSliderPlugin.onKeyDown()
+ is SliderInputEvent.Touch -> hapticSliderPlugin.onTouchEvent(it.event)
+ }
+ }
+ .launchIn(this)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
index e033624..4ecac7a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
@@ -20,14 +20,14 @@
import android.view.View
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderTouchesViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
import com.google.android.material.slider.Slider
import javax.inject.Inject
@VolumeDialogSliderScope
class VolumeDialogSliderTouchesViewBinder
@Inject
-constructor(private val viewModel: VolumeDialogSliderTouchesViewModel) {
+constructor(private val viewModel: VolumeDialogSliderInputEventsViewModel) {
@SuppressLint("ClickableViewAccessibility")
fun bind(view: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index f9334df..e52bad9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -57,7 +57,6 @@
}
private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
- slider.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> }
with(slider) {
valueFrom = levelMin.toFloat()
valueTo = levelMax.toFloat()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 242845a..c9b5259 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -62,6 +62,7 @@
) {
with(component.sliderViewBinder()) { bind(sliderContainer) }
with(component.sliderTouchesViewBinder()) { bind(sliderContainer) }
+ with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
index 9126f45..755776ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
@@ -17,14 +17,25 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.stateIn
@VolumeDialogSliderScope
-class VolumeDialogSliderTouchesViewModel
+class VolumeDialogSliderInputEventsViewModel
@Inject
-constructor(private val interactor: VolumeDialogSliderInputEventsInteractor) {
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ private val interactor: VolumeDialogSliderInputEventsInteractor,
+) {
+
+ val event =
+ interactor.event.stateIn(coroutineScope, SharingStarted.Eagerly, null).filterNotNull()
fun onTouchEvent(event: MotionEvent) {
interactor.onTouchEvent(event)
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index de3c5f2..9644a52 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -63,8 +63,6 @@
* - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
*/
public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
- public static final String TAG = "Ravenwood";
-
/** Scope of a hook. */
public enum Scope {
Class,
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 239c806..9eff20a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -45,7 +45,7 @@
import java.util.function.Supplier;
public class RavenwoodContext extends RavenwoodBaseContext {
- private static final String TAG = "Ravenwood";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private final Object mLock = new Object();
private final String mPackageName;
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
index 77275c4..3cb2c67 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
@@ -27,8 +27,6 @@
* Calculates which tests need to be executed on Ravenwood.
*/
public class RavenwoodEnablementChecker {
- private static final String TAG = "RavenwoodDisablementChecker";
-
private RavenwoodEnablementChecker() {
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index 6dfcf4ce..a5d0bfd 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -40,7 +40,7 @@
* All members must be called from the runner's main thread.
*/
public final class RavenwoodRunnerState {
- private static final String TAG = "RavenwoodRunnerState";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private static final String RAVENWOOD_RULE_ERROR =
"RavenwoodRule(s) are not executed in the correct order";
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 3d2bb8e..930914f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -96,7 +96,7 @@
* Responsible for initializing and the environment.
*/
public class RavenwoodRuntimeEnvironmentController {
- private static final String TAG = "RavenwoodRuntimeEnvironmentController";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private RavenwoodRuntimeEnvironmentController() {
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 99b38ed..fac0791 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -34,7 +34,7 @@
* A class to manage the core default system properties of the Ravenwood environment.
*/
public class RavenwoodSystemProperties {
- private static final String TAG = "RavenwoodSystemProperties";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
/** We pull in properties from this file. */
private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 7870585..c8b10d2 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -41,7 +41,7 @@
* `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_latest.csv`.
*/
public class RavenwoodTestStats {
- private static final String TAG = "RavenwoodTestStats";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
private static RavenwoodTestStats sInstance;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
index 31a1416..f3688d6 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
@@ -35,7 +35,7 @@
import org.junit.runners.model.TestClass;
abstract class RavenwoodAwareTestRunnerBase extends Runner implements Filterable, Orderable {
- private static final String TAG = "Ravenwood";
+ public static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
boolean mRealRunnerTakesRunnerBuilder = false;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d4b15c6..d8cde0e 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -41,7 +41,7 @@
*/
@Deprecated
public final class RavenwoodRule implements TestRule {
- private static final String TAG = "RavenwoodRule";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index b4b75178..8d9bcd5 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -31,8 +31,6 @@
* This is only used when a real device-side test has Ravenizer enabled.
*/
public class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
- private static final String TAG = "Ravenwood";
-
private static class NopRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 2a04d44..a967a3f 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -33,7 +33,7 @@
import java.util.function.Supplier;
public class RavenwoodCommonUtils {
- private static final String TAG = "RavenwoodCommonUtils";
+ public static final String TAG = "Ravenwood";
private RavenwoodCommonUtils() {
}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
index 96fa212..7ab9cda 100644
--- a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
+++ b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
@@ -62,8 +62,7 @@
sTagLogLevels.putAll(map);
var def = map.get("*");
- sDefaultLogLevel = def != null ? def
- : RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING ? Log.VERBOSE : Log.INFO;
+ sDefaultLogLevel = def != null ? def : Log.VERBOSE;
}
}
@@ -80,7 +79,7 @@
if (parts.length == 2) {
String tag = parts[0];
try {
- int priority = switch (parts[1]) {
+ int priority = switch (parts[1].toUpperCase(Locale.ROOT)) {
case "V": yield Log.VERBOSE;
case "D": yield Log.DEBUG;
case "I": yield Log.INFO;
diff --git a/ravenwood/scripts/extract-last-soong-commands.py b/ravenwood/scripts/extract-last-soong-commands.py
new file mode 100755
index 0000000..bdc1de0
--- /dev/null
+++ b/ravenwood/scripts/extract-last-soong-commands.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# 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.
+
+
+# This script extracts all the commands executed in the last soong run,
+# and write them into a script file, and print the filename.
+#
+# All the commands are commented out. Uncomment what you want to execute as
+# needed before running it.
+
+import datetime
+import gzip
+import os
+import re
+import shlex
+import sys
+
+re_command = re.compile(r''' ^\[.*?\] \s* (.*) ''', re.X)
+
+HEADER = r'''#!/bin/bash
+
+set -e # Stop on a failed command
+
+cd "${ANDROID_BUILD_TOP:?}"
+
+'''
+
+OUT_SCRIPT_DIR = "/tmp/"
+OUT_SCRIPT_FORMAT = "soong-rerun-%Y-%m-%d_%H-%M-%S.sh"
+
+def main(args):
+ log = os.environ["ANDROID_BUILD_TOP"] + "/out/verbose.log.gz"
+ outdir = "/tmp/"
+ outfile = outdir + datetime.datetime.now().strftime(OUT_SCRIPT_FORMAT)
+
+ with open(outfile, "w") as out:
+ out.write(HEADER)
+
+ with gzip.open(log) as f:
+ for line in f:
+ s = line.decode("utf-8")
+
+ if s.startswith("verbose"):
+ continue
+ if re.match('^\[.*bootstrap blueprint', s):
+ continue
+
+ s = s.rstrip()
+
+ m = re_command.search(s)
+ if m:
+ command = m.groups()[0]
+
+ out.write('#========\n')
+
+ # Show the full command line before executing it.
+ out.write('#echo ' + shlex.quote(command) + '\n')
+ out.write('\n')
+
+ # Execute the command.
+ out.write('#' + command + '\n')
+
+ out.write('\n')
+
+ continue
+
+ if s.startswith("FAILED:"):
+ break
+
+ os.chmod(outfile, 0o755)
+ print(outfile)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
index 7be562c..74c1f3f 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
@@ -41,9 +41,8 @@
assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
- var verboseEnabled = RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
- assertEquals(verboseEnabled, Log.isLoggable("TAG1", Log.DEBUG));
- assertEquals(verboseEnabled, Log.isLoggable("TAG2", Log.VERBOSE));
+ assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+ assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
}
@Test
diff --git a/ravenwood/texts/ravenwood-common-policies.txt b/ravenwood/texts/ravenwood-common-policies.txt
index 08f53977..83c3151 100644
--- a/ravenwood/texts/ravenwood-common-policies.txt
+++ b/ravenwood/texts/ravenwood-common-policies.txt
@@ -14,7 +14,7 @@
# Support APIs not available in standard JRE
class java.io.FileDescriptor keep
- method getInt$ ()I @com.android.ravenwood.RavenwoodJdkPatch.getInt$
- method setInt$ (I)V @com.android.ravenwood.RavenwoodJdkPatch.setInt$
+ method getInt$ @com.android.ravenwood.RavenwoodJdkPatch.getInt$
+ method setInt$ @com.android.ravenwood.RavenwoodJdkPatch.setInt$
class java.util.LinkedHashMap keep
- method eldest ()Ljava/util/Map$Entry; @com.android.ravenwood.RavenwoodJdkPatch.eldest
+ method eldest @com.android.ravenwood.RavenwoodJdkPatch.eldest
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index 59fa464..fc885d6 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -66,6 +66,9 @@
methodName: String,
descriptor: String
) {
+ if (descriptor == "*") {
+ return
+ }
if (classes.findMethod(className, methodName, descriptor) == null) {
log.w("Unknown method $className.$methodName$descriptor")
}
@@ -92,7 +95,8 @@
descriptor: String,
): FilterPolicyWithReason {
return mPolicies[getMethodKey(className, methodName, descriptor)]
- ?: super.getPolicyForMethod(className, methodName, descriptor)
+ ?: mPolicies[getMethodKey(className, methodName, "*")]
+ ?: super.getPolicyForMethod(className, methodName, descriptor)
}
fun setPolicyForMethod(
@@ -107,7 +111,8 @@
override fun getRenameTo(className: String, methodName: String, descriptor: String): String? {
return mRenames[getMethodKey(className, methodName, descriptor)]
- ?: super.getRenameTo(className, methodName, descriptor)
+ ?: mRenames[getMethodKey(className, methodName, "*")]
+ ?: super.getRenameTo(className, methodName, descriptor)
}
fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) {
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index caf80eb..7462a8c 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -303,12 +303,21 @@
}
private fun parseMethod(fields: Array<String>) {
- if (fields.size < 4) {
- throw ParseException("Method ('m') expects 3 fields.")
+ if (fields.size < 3 || fields.size > 4) {
+ throw ParseException("Method ('m') expects 3 or 4 fields.")
}
val name = fields[1]
- val signature = fields[2]
- val policy = parsePolicy(fields[3])
+ val signature: String
+ val policyStr: String
+ if (fields.size <= 3) {
+ signature = "*"
+ policyStr = fields[2]
+ } else {
+ signature = fields[2]
+ policyStr = fields[3]
+ }
+
+ val policy = parsePolicy(policyStr)
if (!policy.isUsableWithMethods) {
throw ParseException("Method can't have policy '$policy'")
@@ -321,7 +330,7 @@
policy.withReason(FILTER_REASON)
)
if (policy == FilterPolicy.Substitute) {
- val fromName = fields[3].substring(1)
+ val fromName = policyStr.substring(1)
if (fromName == name) {
throw ParseException(
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
index d45f414..a3f934c 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
@@ -48,10 +48,11 @@
// Maybe use 'Tri' if we end up having too many replacements.
spec.forEach {
if (className == it.fromClass &&
- methodName == it.fromMethod &&
- descriptor == it.fromDescriptor
+ methodName == it.fromMethod
) {
- return MethodReplaceTarget(it.toClass, it.toMethod)
+ if (it.fromDescriptor == "*" || descriptor == it.fromDescriptor) {
+ return MethodReplaceTarget(it.toClass, it.toMethod)
+ }
}
}
return null
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 3c138d2..2f35d35 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -3,11 +3,11 @@
# field remove remove # Implicitly remove
method <init> ()V keep
method addOne (I)I keep
- method addOneInner (I)I keep
+ method addOneInner keep
method toBeRemoved (Ljava/lang/String;)V remove
method addTwo (I)I @addTwo_host
# method addTwo_host (I)I # used as a substitute
- method nativeAddThree (I)I @addThree_host
+ method nativeAddThree @addThree_host
# method addThree_host (I)I # used as a substitute
method unsupportedMethod ()Ljava/lang/String; throw
method visibleButUsesUnsupportedMethod ()Ljava/lang/String; keep
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a0b989b..ad87cea 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -86,10 +86,17 @@
}
flag {
- name: "enable_hardware_shortcut_disables_warning"
- namespace: "accessibility"
- description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
- bug: "287065325"
+ name: "enable_hardware_shortcut_disables_warning"
+ namespace: "accessibility"
+ description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
+ bug: "287065325"
+}
+
+flag {
+ name: "enable_low_vision_hats"
+ namespace: "accessibility"
+ description: "Use HaTS for low vision feedback."
+ bug: "380346799"
}
flag {
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 5d2ef77..5e1b147 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -23,6 +23,16 @@
}
flag {
+ name: "relayout_fix"
+ namespace: "autofill"
+ description: "Fixing relayout issue. Guarding enabling device config flags"
+ bug: "381226145"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "include_invisible_view_group_in_assist_structure"
namespace: "autofill"
description: "Mitigation for autofill providers miscalculating view visibility"
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 5703633..80e0e5d 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -6,6 +6,7 @@
namespace: "autofill"
description: "Guards against new metrics definitions introduced in W"
bug: "342676602"
+ is_exported: true
}
flag {
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 9326ea8..b4adad2 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -68,6 +68,7 @@
"B&R operations in certain cases."
bug: "376661510"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 7a2106b..4bcfb33 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -240,9 +240,10 @@
boolean matchesMacAddress = Objects.equals(
associationInfo.getDeviceMacAddress(),
restored.getDeviceMacAddress());
- boolean matchesTag = !Flags.associationTag()
- || Objects.equals(associationInfo.getTag(), restored.getTag());
- return matchesMacAddress && matchesTag;
+ boolean matchesDeviceId = !Flags.associationTag()
+ || (associationInfo.getDeviceId() != null
+ && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId()));
+ return matchesMacAddress || matchesDeviceId;
};
AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 7cba9e0..418f3a1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -55,6 +55,7 @@
import android.bluetooth.BluetoothManager;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceId;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
@@ -318,7 +319,6 @@
public List<AssociationInfo> getAssociations(String packageName, int userId) {
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"get associations");
-
return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
}
@@ -694,14 +694,10 @@
}
@Override
- public void setAssociationTag(int associationId, String tag) {
- mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
+ public void setDeviceId(int associationId, DeviceId deviceId) {
+ mAssociationRequestsProcessor.setDeviceId(associationId, deviceId);
}
- @Override
- public void clearAssociationTag(int associationId) {
- setAssociationTag(associationId, null);
- }
@Override
public byte[] getBackupPayload(int userId) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 77b1780..f2d019b 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -38,6 +38,7 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
+import android.companion.DeviceId;
import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Environment;
@@ -131,8 +132,11 @@
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634389553216"
- * system_data_sync_flags="0"/>
- *
+ * system_data_sync_flags="0"
+ * device_icon="device_icon">
+ * <device_id
+ * custom_device_id="1234"/>
+ * </association>
* <association
* id="3"
* profile="android.app.role.COMPANION_DEVICE_WATCH"
@@ -143,7 +147,11 @@
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634641160229"
- * system_data_sync_flags="1"/>
+ * system_data_sync_flags="1"
+ * device_icon="device_icon">
+ * <device_id
+ * custom_device_id="1234"/>
+ * </association>
* </associations>
* </state>
* }</pre>
@@ -160,7 +168,7 @@
private static final String XML_TAG_STATE = "state";
private static final String XML_TAG_ASSOCIATIONS = "associations";
private static final String XML_TAG_ASSOCIATION = "association";
- private static final String XML_TAG_TAG = "tag";
+ private static final String XML_TAG_DEVICE_ID = "device_id";
private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
private static final String XML_ATTR_MAX_ID = "max-id";
@@ -177,6 +185,8 @@
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
private static final String XML_ATTR_DEVICE_ICON = "device_icon";
+ private static final String XML_ATTR_CUSTOM_DEVICE_ID = "custom_device_id";
+ private static final String XML_ATTR_MAC_ADDRESS_DEVICE_ID = "mac_address_device_id";
private static final String LEGACY_XML_ATTR_DEVICE = "device";
@@ -389,16 +399,16 @@
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- final String tag = readStringAttribute(parser, XML_TAG_TAG);
final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
- return new AssociationInfo(associationId, userId, appPackage, tag,
+ return new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile, null,
/* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
- timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, null);
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, /* deviceIcon */ null,
+ /* deviceId */ null);
}
private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -436,7 +446,6 @@
final int associationId = readIntAttribute(parser, XML_ATTR_ID);
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- final String tag = readStringAttribute(parser, XML_TAG_TAG);
final MacAddress macAddress = stringToMacAddress(
readStringAttribute(parser, XML_ATTR_MAC_ADDRESS));
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
@@ -451,10 +460,26 @@
XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
final Icon deviceIcon = byteArrayToIcon(
readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));
+ parser.nextTag();
+ final DeviceId deviceId = readDeviceId(parser);
- return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
+ return new AssociationInfo(associationId, userId, appPackage, macAddress, displayName,
profile, null, selfManaged, notify, revoked, pending, timeApproved,
- lastTimeConnected, systemDataSyncFlags, deviceIcon);
+ lastTimeConnected, systemDataSyncFlags, deviceIcon, deviceId);
+ }
+
+ private static DeviceId readDeviceId(@NonNull TypedXmlPullParser parser)
+ throws XmlPullParserException {
+ if (isStartOfTag(parser, XML_TAG_DEVICE_ID)) {
+ final String customDeviceId = readStringAttribute(
+ parser, XML_ATTR_CUSTOM_DEVICE_ID);
+ final MacAddress macAddress = stringToMacAddress(
+ readStringAttribute(parser, XML_ATTR_MAC_ADDRESS_DEVICE_ID));
+
+ return new DeviceId(customDeviceId, macAddress);
+ }
+
+ return null;
}
private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -475,7 +500,6 @@
writeIntAttribute(serializer, XML_ATTR_ID, a.getId());
writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile());
writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName());
- writeStringAttribute(serializer, XML_TAG_TAG, a.getTag());
writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString());
writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName());
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
@@ -490,14 +514,33 @@
writeByteArrayAttribute(
serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));
+ writeDeviceId(serializer, a);
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
+ private static void writeDeviceId(XmlSerializer parent, @NonNull AssociationInfo a)
+ throws IOException {
+ if (a.getDeviceId() != null) {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID);
+ writeStringAttribute(
+ serializer,
+ XML_ATTR_CUSTOM_DEVICE_ID,
+ a.getDeviceId().getCustomId()
+ );
+ writeStringAttribute(
+ serializer,
+ XML_ATTR_MAC_ADDRESS_DEVICE_ID,
+ a.getDeviceId().getMacAddressAsString()
+ );
+ serializer.endTag(null, XML_TAG_DEVICE_ID);
+ }
+ }
+
private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
throws XmlPullParserException {
if (isStartOfTag(parser, tag)) return;
throw new XmlPullParserException(
- "Should be at the start of \"" + XML_TAG_ASSOCIATIONS + "\" tag");
+ "Should be at the start of \"" + tag + "\" tag");
}
private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index aebd11a..899b302 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -41,6 +41,7 @@
import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceId;
import android.companion.Flags;
import android.companion.IAssociationRequestCallback;
import android.content.ComponentName;
@@ -298,10 +299,10 @@
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- /* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
+ macAddress, displayName, deviceProfile, associatedDevice,
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
/* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
- deviceIcon);
+ deviceIcon, /* deviceId */ null);
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
}
@@ -354,14 +355,14 @@
}
/**
- * Set association tag.
+ * Set Device id for the association.
*/
- public void setAssociationTag(int associationId, String tag) {
- Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]...");
+ public void setDeviceId(int associationId, DeviceId deviceId) {
+ Slog.i(TAG, "Setting DeviceId=[" + deviceId + "] to id=[" + associationId + "]...");
AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+ association = (new AssociationInfo.Builder(association)).setDeviceId(deviceId).build();
mAssociationStore.updateAssociation(association);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c6b80cb..0820615 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -243,6 +243,7 @@
"powerstats_flags_lib",
"locksettings_flags_lib",
"profiling_flags_lib",
+ "android.adpf.sessionmanager_aidl-java",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3976d01..123b7df 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -9,6 +9,7 @@
description: "Allows querying of AOD availability"
bug: "324046664"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -267,6 +268,7 @@
description: "Feature flag for an API to get the highest defined HDR/SDR ratio for a display."
bug: "335181559"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -429,6 +431,7 @@
description: "Flag for an API to get whether display supports ARR or not"
bug: "361433651"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -445,6 +448,7 @@
description: "Flag for an API to get suggested frame rates"
bug: "361433796"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -453,6 +457,7 @@
description: "Feature flag for an API to let the apps subscribe to a specific property change of the Display."
bug: "372700957"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -461,6 +466,7 @@
description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
bug: "365163968"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 2e66fbc..102de73 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -265,6 +265,26 @@
enumLogReason);
}
+ /**
+ * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+ * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+ * @param isEnabled Whether the setting is enabled.
+ * @param enumLogReason The event that triggered the log.
+ * @param manufacturerPnpId Manufacturer PNP ID reported in the EDID.
+ * @param manufacturerYear Manufacture year reported in the EDID.
+ * @param manufacturerWeek Manufacture week reporter in the EDID.
+ */
+ public void powerStateChangeOnActiveSourceLostChanged(boolean isEnabled, int enumLogReason,
+ String manufacturerPnpId, int manufacturerYear, int manufacturerWeek) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.HDMI_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLED,
+ isEnabled,
+ enumLogReason,
+ manufacturerPnpId,
+ manufacturerYear,
+ manufacturerWeek);
+ }
+
private int earcStateToEnum(int earcState) {
switch (earcState) {
case HDMI_EARC_STATUS_IDLE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 1b527da..0b667fc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.hardware.display.DeviceProductInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -31,6 +32,7 @@
import android.os.SystemProperties;
import android.sysprop.HdmiProperties;
import android.util.Slog;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
@@ -82,6 +84,8 @@
// lost.
private Handler mDelayedPopupOnActiveSourceLostHandler;
+ private boolean mIsActiveSourceLostPopupLaunched;
+
// Determines what action should be taken upon receiving Routing Control messages.
@VisibleForTesting
protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -96,6 +100,7 @@
mDelayedStandbyOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
mDelayedPopupOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
+ mIsActiveSourceLostPopupLaunched = false;
}
@Override
@@ -275,6 +280,7 @@
public void run() {
if (!isActiveSource()) {
mService.standby();
+ mIsActiveSourceLostPopupLaunched = false;
}
}
}
@@ -283,6 +289,7 @@
void dismissUiOnActiveSourceStatusRecovered() {
assertRunOnServiceThread();
Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI);
+ mIsActiveSourceLostPopupLaunched = false;
mService.sendBroadcastAsUser(intent);
}
@@ -516,6 +523,7 @@
)));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivityAsUser(intent, context.getUser());
+ mIsActiveSourceLostPopupLaunched = true;
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Unable to start HdmiCecActiveSourceLostActivity");
} finally {
@@ -733,6 +741,14 @@
return Constants.ADDR_TV;
}
+ boolean isActiveSourceLostPopupLaunched() {
+ return mIsActiveSourceLostPopupLaunched;
+ }
+
+ void setIsActiveSourceLostPopupLaunched(boolean isActiveSourceLostPopupLaunched) {
+ mIsActiveSourceLostPopupLaunched = isActiveSourceLostPopupLaunched;
+ }
+
@Override
@ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index f049ef3..35ef18b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -50,6 +50,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
+import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayManager;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
@@ -106,6 +107,7 @@
import android.util.SparseArray;
import android.view.Display;
import android.view.KeyEvent;
+import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -1007,6 +1009,21 @@
}
}, mServiceThreadExecutor);
+ if (isPlaybackDevice()) {
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ boolean goToStandbyOnActiveSourceLost =
+ mHdmiCecConfig.getStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)
+ .equals(HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+ writePowerStateChangeOnActiveSourceLostAtom(goToStandbyOnActiveSourceLost);
+ }
+ }, mServiceThreadExecutor);
+ }
+
mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
new DeviceConfig.OnPropertiesChangedListener() {
@Override
@@ -3189,6 +3206,7 @@
// Cancel an existing timer to send the device to sleep since OTP was triggered.
playback().mDelayedStandbyOnActiveSourceLostHandler
.removeCallbacksAndMessages(null);
+ playback().setIsActiveSourceLostPopupLaunched(false);
}
if (source == null) {
@@ -5227,6 +5245,34 @@
}
/**
+ * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+ * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+ */
+ protected void writePowerStateChangeOnActiveSourceLostAtom(boolean isSettingEnabled) {
+ String manufacturerPnpId = "undefined";
+ int manufactureYear = -1;
+ int manufactureWeek = -1;
+ Display display = getContext().getDisplay();
+ if (display != null) {
+ DeviceProductInfo deviceProductInfo = display.getDeviceProductInfo();
+ manufacturerPnpId = deviceProductInfo.getManufacturerPnpId();
+ manufactureYear = deviceProductInfo.getManufactureYear();
+ }
+ int enumLogReason =
+ HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_UNKNOWN;
+ if (playback() != null) {
+ if (playback().isActiveSourceLostPopupLaunched()) {
+ enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP;
+ } else {
+ enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING;
+ }
+ }
+
+ getAtomWriter().powerStateChangeOnActiveSourceLostChanged(isSettingEnabled, enumLogReason,
+ manufacturerPnpId, manufactureYear, manufactureWeek);
+ }
+
+ /**
* Reads the property that checks if CEC was enabled by the user while in offline mode such that
* it won't be disabled when going to sleep by low energy mode.
*/
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2e167ef..6053557 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -108,6 +108,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssConfiguration;
@@ -147,6 +148,7 @@
import com.android.server.location.provider.StationaryThrottlingLocationProvider;
import com.android.server.location.provider.proxy.ProxyGeocodeProvider;
import com.android.server.location.provider.proxy.ProxyLocationProvider;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
@@ -260,6 +262,11 @@
private volatile @Nullable GnssManagerService mGnssManagerService = null;
private ProxyGeocodeProvider mGeocodeProvider;
+ private @Nullable ProxyPopulationDensityProvider mPopulationDensityProvider = null;
+
+ // A cache for population density lookups. Used if density-based coarse locations are enabled.
+ private @Nullable LocationFudgerCache mLocationFudgerCache = null;
+
private final Object mDeprecatedGnssBatchingLock = new Object();
@GuardedBy("mDeprecatedGnssBatchingLock")
private @Nullable ILocationListener mDeprecatedGnssBatchingListener;
@@ -392,6 +399,25 @@
}
}
+ @VisibleForTesting
+ protected void setProxyPopulationDensityProvider(ProxyPopulationDensityProvider provider) {
+ if (Flags.populationDensityProvider()) {
+ mPopulationDensityProvider = provider;
+ }
+ }
+
+ @VisibleForTesting
+ protected void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudgerCache = cache;
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.setLocationFudgerCache(cache);
+ }
+ }
+
private void removeLocationProviderManager(LocationProviderManager manager) {
synchronized (mProviderManagers) {
boolean removed = mProviderManagers.remove(manager);
@@ -510,6 +536,17 @@
Log.e(TAG, "no geocoder provider found");
}
+ if (Flags.populationDensityProvider()) {
+ setProxyPopulationDensityProvider(
+ ProxyPopulationDensityProvider.createAndRegister(mContext));
+ if (mPopulationDensityProvider == null) {
+ Log.e(TAG, "no population density provider found");
+ }
+ }
+ if (mPopulationDensityProvider != null && Flags.densityBasedCoarseLocations()) {
+ setLocationFudgerCache(new LocationFudgerCache(mPopulationDensityProvider));
+ }
+
// bind to hardware activity recognition
HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy =
HardwareActivityRecognitionProxy.createAndRegister(mContext);
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 88a2697..0da1514 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -16,13 +16,16 @@
package com.android.server.location.fudger;
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.location.geometry.S2CellIdUtils;
import java.security.SecureRandom;
import java.time.Clock;
@@ -83,6 +86,9 @@
@GuardedBy("this")
@Nullable private LocationResult mCachedCoarseLocationResult;
+ @GuardedBy("this")
+ @Nullable private LocationFudgerCache mLocationFudgerCache = null;
+
public LocationFudger(float accuracyM) {
this(accuracyM, SystemClock.elapsedRealtimeClock(), new SecureRandom());
}
@@ -97,6 +103,16 @@
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ synchronized (this) {
+ mLocationFudgerCache = cache;
+ }
+ }
+
+ /**
* Resets the random offsets completely.
*/
public void resetOffsets() {
@@ -162,16 +178,34 @@
longitude += wrapLongitude(metersToDegreesLongitude(mLongitudeOffsetM, latitude));
latitude += wrapLatitude(metersToDegreesLatitude(mLatitudeOffsetM));
- // quantize location by snapping to a grid. this is the primary means of obfuscation. it
- // gives nice consistent results and is very effective at hiding the true location (as long
- // as you are not sitting on a grid boundary, which the random offsets mitigate).
- //
- // note that we quantize the latitude first, since the longitude quantization depends on the
- // latitude value and so leaks information about the latitude
- double latGranularity = metersToDegreesLatitude(mAccuracyM);
- latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
- double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
- longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ // We copy a reference to the cache, so even if mLocationFudgerCache is concurrently set
+ // to null, we can continue executing the condition below.
+ LocationFudgerCache cacheCopy = null;
+ synchronized (this) {
+ cacheCopy = mLocationFudgerCache;
+ }
+
+ // TODO(b/381204398): To ensure a safe rollout, two algorithms co-exist. The first is the
+ // new density-based algorithm, while the second is the traditional coarsening algorithm.
+ // Once rollout is done, clean up the unused algorithm.
+ if (Flags.densityBasedCoarseLocations() && cacheCopy != null
+ && cacheCopy.hasDefaultValue()) {
+ int level = cacheCopy.getCoarseningLevel(latitude, longitude);
+ double[] center = snapToCenterOfS2Cell(latitude, longitude, level);
+ latitude = center[S2CellIdUtils.LAT_INDEX];
+ longitude = center[S2CellIdUtils.LNG_INDEX];
+ } else {
+ // quantize location by snapping to a grid. this is the primary means of obfuscation. it
+ // gives nice consistent results and is very effective at hiding the true location (as
+ // long as you are not sitting on a grid boundary, which the random offsets mitigate).
+ //
+ // note that we quantize the latitude first, since the longitude quantization depends on
+ // the latitude value and so leaks information about the latitude
+ double latGranularity = metersToDegreesLatitude(mAccuracyM);
+ latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
+ double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
+ longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ }
coarse.setLatitude(latitude);
coarse.setLongitude(longitude);
@@ -185,6 +219,15 @@
return coarse;
}
+ @VisibleForTesting
+ protected double[] snapToCenterOfS2Cell(double latDegrees, double lngDegrees, int level) {
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latDegrees, lngDegrees);
+ long coarsenedCell = S2CellIdUtils.getParent(leafCell, level);
+ double[] center = new double[] {0.0, 0.0};
+ S2CellIdUtils.toLatLngDegrees(coarsenedCell, center);
+ return center;
+ }
+
/**
* Update the random offsets over time.
*
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
new file mode 100644
index 0000000..3670c1f
--- /dev/null
+++ b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.fudger;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import java.util.Objects;
+
+/**
+ * A cache for returning the coarsening level to be used. The coarsening level depends on the user
+ * location. If the cache contains the requested latitude/longitude, the s2 level of the cached
+ * cell id is returned. If not, a default value is returned.
+ * This class has a {@link ProxyPopulationDensityProvider} used to refresh the cache.
+ * This cache exists because {@link ProxyPopulationDensityProvider} must be queried asynchronously,
+ * whereas a synchronous answer is needed.
+ * The cache is first-in, first-out, and has a fixed size. Cache entries are valid until evicted by
+ * another value.
+ */
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public class LocationFudgerCache {
+
+ // The maximum number of S2 cell ids stored in the cache.
+ // Each cell id is a long, so the memory requirement is 8*MAX_CACHE_SIZE bytes.
+ protected static final int MAX_CACHE_SIZE = 20;
+
+ private final Object mLock = new Object();
+
+ // mCache is a circular buffer of size MAX_CACHE_SIZE. The next position to be written to is
+ // mPosInCache. Initially, the cache is filled with INVALID_CELL_IDs.
+ @GuardedBy("mLock")
+ private final long[] mCache = new long[MAX_CACHE_SIZE];
+
+ @GuardedBy("mLock")
+ private int mPosInCache = 0;
+
+ @GuardedBy("mLock")
+ private int mCacheSize = 0;
+
+ // The S2 level to coarsen to, if the cache doesn't contain a better answer.
+ // Updated concurrently by callbacks.
+ @GuardedBy("mLock")
+ private Integer mDefaultCoarseningLevel = null;
+
+ // The provider that asynchronously provides what is stored in the cache.
+ private final ProxyPopulationDensityProvider mPopulationDensityProvider;
+
+ private static String sTAG = "LocationFudgerCache";
+
+ public LocationFudgerCache(@NonNull ProxyPopulationDensityProvider provider) {
+ mPopulationDensityProvider = Objects.requireNonNull(provider);
+
+ asyncFetchDefaultCoarseningLevel();
+ }
+
+ /** Returns true if the cache has successfully received a default value from the provider. */
+ public boolean hasDefaultValue() {
+ synchronized (mLock) {
+ return (mDefaultCoarseningLevel != null);
+ }
+ }
+
+ /**
+ * Returns the S2 level to which the provided location should be coarsened.
+ * The answer comes from the cache if available, otherwise the default value is returned.
+ */
+ public int getCoarseningLevel(double latitudeDegrees, double longitudeDegrees) {
+ // If we still haven't received the default level from the provider, try fetching it again.
+ // The answer wouldn't come in time, but it will be used for the following queries.
+ if (!hasDefaultValue()) {
+ asyncFetchDefaultCoarseningLevel();
+ }
+ Long s2CellId = readCacheForLatLng(latitudeDegrees, longitudeDegrees);
+ if (s2CellId == null) {
+ // Asynchronously queries the density from the provider. The answer won't come in time,
+ // but it will update the cache for the following queries.
+ refreshCache(latitudeDegrees, longitudeDegrees);
+
+ return getDefaultCoarseningLevel();
+ }
+ return S2CellIdUtils.getLevel(s2CellId);
+ }
+
+ /**
+ * If the cache contains the current location, returns the corresponding S2 cell id.
+ * Otherwise, returns null.
+ */
+ @Nullable
+ private Long readCacheForLatLng(double latDegrees, double lngDegrees) {
+ synchronized (mLock) {
+ for (int i = 0; i < mCacheSize; i++) {
+ if (S2CellIdUtils.containsLatLngDegrees(mCache[i], latDegrees, lngDegrees)) {
+ return mCache[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Adds the provided s2 cell id to the cache. This might evict other values from the cache. */
+ public void addToCache(long s2CellId) {
+ addToCache(new long[] {s2CellId});
+ }
+
+ /**
+ * Adds the provided s2 cell ids to the cache. This might evict other values from the cache.
+ * If more than MAX_CACHE_SIZE elements are provided, only the first elements are copied.
+ * The first element of the input is added last into the FIFO cache, so it gets evicted last.
+ */
+ public void addToCache(long[] s2CellIds) {
+ synchronized (mLock) {
+ // Only copy up to MAX_CACHE_SIZE elements
+ int end = Math.min(s2CellIds.length, MAX_CACHE_SIZE);
+ mCacheSize = Math.min(mCacheSize + end, MAX_CACHE_SIZE);
+
+ // Add in reverse so the first cell of s2CellIds is the last evicted
+ for (int i = end - 1; i >= 0; i--) {
+ mCache[mPosInCache] = s2CellIds[i];
+ mPosInCache = (mPosInCache + 1) % MAX_CACHE_SIZE;
+ }
+ }
+ }
+
+ /**
+ * Queries the population density provider for the default coarsening level (to be used if the
+ * cache doesn't contain a better answer), and updates mDefaultCoarseningLevel with the answer.
+ */
+ private void asyncFetchDefaultCoarseningLevel() {
+ IS2LevelCallback callback = new IS2LevelCallback.Stub() {
+ @Override
+ public void onResult(int s2level) {
+ synchronized (mLock) {
+ mDefaultCoarseningLevel = Integer.valueOf(s2level);
+ }
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get default population density");
+ }
+ };
+ mPopulationDensityProvider.getDefaultCoarseningLevel(callback);
+ }
+
+ /**
+ * Queries the population density provider and store the result in the cache.
+ */
+ private void refreshCache(double latitude, double longitude) {
+ IS2CellIdsCallback callback = new IS2CellIdsCallback.Stub() {
+ @Override
+ public void onResult(long[] s2CellIds) {
+ addToCache(s2CellIds);
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get population density");
+ }
+ };
+ mPopulationDensityProvider.getCoarsenedS2Cell(latitude, longitude, callback);
+ }
+
+ /**
+ * Returns the default S2 level to coarsen to. This should be used if the cache
+ * does not provide a better answer.
+ */
+ private int getDefaultCoarseningLevel() {
+ synchronized (mLock) {
+ // The minimum valid level is 0.
+ if (mDefaultCoarseningLevel == null) {
+ return 0;
+ }
+ return mDefaultCoarseningLevel;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 4a9bf88..a8c9010 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -48,6 +48,7 @@
import static java.lang.Math.max;
import static java.lang.Math.min;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -105,6 +106,7 @@
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.fudger.LocationFudger;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
@@ -1663,6 +1665,18 @@
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudger.setLocationFudgerCache(cache);
+ }
+
+ /**
* Returns true if this provider is visible to the current caller (whether called from a binder
* thread or not). If a provider isn't visible, then all APIs return the same data they would if
* the provider didn't exist (i.e. the caller can't see or use the provider).
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
new file mode 100644
index 0000000..b0a0f0b
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
@@ -0,0 +1,119 @@
+/*
+ * 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.location.provider.proxy;
+
+import static android.location.provider.PopulationDensityProviderBase.ACTION_POPULATION_DENSITY_PROVIDER;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.provider.IPopulationDensityProvider;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
+
+/**
+ * Proxy for IPopulationDensityProvider implementations.
+ */
+public class ProxyPopulationDensityProvider {
+
+ public static final String TAG = "ProxyPopulationDensityProvider";
+
+ final ServiceWatcher mServiceWatcher;
+
+ /**
+ * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+ * null.
+ */
+ @Nullable
+ public static ProxyPopulationDensityProvider createAndRegister(Context context) {
+ ProxyPopulationDensityProvider proxy = new ProxyPopulationDensityProvider(context);
+ if (proxy.register()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ private ProxyPopulationDensityProvider(Context context) {
+ mServiceWatcher = ServiceWatcher.create(
+ context,
+ "PopulationDensityProxy",
+ CurrentUserServiceSupplier.createFromConfig(
+ context,
+ ACTION_POPULATION_DENSITY_PROVIDER,
+ com.android.internal.R.bool.config_enablePopulationDensityProviderOverlay,
+ com.android.internal.R.string.config_populationDensityProviderPackageName),
+ null);
+ }
+
+ private boolean register() {
+ boolean resolves = mServiceWatcher.checkServiceResolves();
+ if (resolves) {
+ mServiceWatcher.register();
+ }
+ return resolves;
+ }
+
+ /** Gets the default coarsening level. */
+ public void getDefaultCoarseningLevel(IS2LevelCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getDefaultCoarseningLevel(callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying default coarsening level");
+ }
+ }
+ });
+ }
+
+
+ /** Gets the population density at the requested location. */
+ public void getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees,
+ IS2CellIdsCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getCoarsenedS2Cell(latitudeDegrees, longitudeDegrees, callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying coarsened S2 cell");
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1abe526..ab68ed3 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -221,11 +221,6 @@
}
}
- @NonNull
- private SystemMediaRoute2Provider getSystemProviderForUser(@NonNull UserHandler userHandler) {
- return userHandler.mSystemProvider;
- }
-
// Start of methods that implement MediaRouter2 operations.
@NonNull
@@ -251,7 +246,7 @@
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
if (hasSystemRoutingPermissions) {
MediaRoute2ProviderInfo providerInfo =
- getSystemProviderForUser(userRecord.mHandler).getProviderInfo();
+ userRecord.mHandler.getSystemProvider().getProviderInfo();
if (providerInfo != null) {
systemRoutes = providerInfo.getRoutes();
} else {
@@ -264,7 +259,7 @@
} else {
systemRoutes = new ArrayList<>();
systemRoutes.add(
- getSystemProviderForUser(userRecord.mHandler).getDefaultRoute());
+ userRecord.mHandler.getSystemProvider().getDefaultRoute());
}
}
return new ArrayList<>(systemRoutes);
@@ -856,11 +851,10 @@
if (setDeviceRouteSelected) {
// Return a fake system session that shows the device route as selected and
// available bluetooth routes as transferable.
- return getSystemProviderForUser(userRecord.mHandler)
+ return userRecord.mHandler.getSystemProvider()
.generateDeviceRouteSelectedSessionInfo(targetPackageName);
} else {
- sessionInfos = getSystemProviderForUser(userRecord.mHandler)
- .getSessionInfos();
+ sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos();
if (!sessionInfos.isEmpty()) {
// Return a copy of the current system session with no modification,
// except setting the client package name.
@@ -873,8 +867,7 @@
}
} else {
return new RoutingSessionInfo.Builder(
- getSystemProviderForUser(userRecord.mHandler)
- .getDefaultSessionInfo())
+ userRecord.mHandler.getSystemProvider().getDefaultSessionInfo())
.setClientPackageName(targetPackageName)
.build();
}
@@ -1383,7 +1376,7 @@
}
manager.mLastSessionCreationRequest = null;
} else {
- String defaultRouteId = getSystemProviderForUser(userHandler).getDefaultRoute().getId();
+ String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -1471,7 +1464,7 @@
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
UserHandler userHandler = routerRecord.mUserRecord.mHandler;
- String defaultRouteId = getSystemProviderForUser(userHandler).getDefaultRoute().getId();
+ String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -2137,12 +2130,11 @@
notifyRoutesUpdated(routesToReport.values().stream().toList());
List<RoutingSessionInfo> sessionInfos =
- getSystemProviderForUser(mUserRecord.mHandler).getSessionInfos();
+ mUserRecord.mHandler.getSystemProvider().getSessionInfos();
RoutingSessionInfo systemSessionToReport =
newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
? sessionInfos.get(0)
- : getSystemProviderForUser(mUserRecord.mHandler)
- .getDefaultSessionInfo();
+ : mUserRecord.mHandler.getSystemProvider().getDefaultSessionInfo();
notifySessionInfoChanged(systemSessionToReport);
}
}
@@ -2292,7 +2284,7 @@
if (route.isSystemRoute() && !hasSystemRoutingPermission()) {
// The router lacks permission to modify system routing, so we hide system
// route info from them.
- route = getSystemProviderForUser(mUserRecord.mHandler).getDefaultRoute();
+ route = mUserRecord.mHandler.getSystemProvider().getDefaultRoute();
}
mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ceb9314..516b002 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -2329,7 +2329,10 @@
}
}
- mInstallDependencyHelper.notifySessionComplete(session.sessionId, success);
+ if (Flags.sdkDependencyInstaller()) {
+ mInstallDependencyHelper.notifySessionComplete(
+ session.sessionId, success);
+ }
final File appIconFile = buildAppIconFile(session.sessionId);
if (appIconFile.exists()) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index e49dc82..976999c 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -426,6 +426,7 @@
private static final int TRON_COMPILATION_REASON_PREBUILT = 23;
private static final int TRON_COMPILATION_REASON_VDEX = 24;
private static final int TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE = 25;
+ private static final int TRON_COMPILATION_REASON_CLOUD = 26;
// The annotation to add as a suffix to the compilation reason when dexopt was
// performed with dex metadata.
@@ -460,6 +461,8 @@
return TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
case "install-bulk-secondary-downgraded" :
return TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+ case "cloud":
+ return TRON_COMPILATION_REASON_CLOUD;
// These are special markers for dex metadata installation that do not
// have an equivalent as a system property.
case "install" + DEXOPT_REASON_WITH_DEX_METADATA_ANNOTATION :
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index aba15c8..0f6688f 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -23,6 +23,7 @@
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
import static com.android.server.power.hint.Flags.resetOnForkEnabled;
+import android.adpf.ISessionManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -193,6 +194,7 @@
private final Object mCpuHeadroomLock = new Object();
+ private ISessionManager mSessionManager;
// this cache tracks the expiration time of the items and performs cleanup on lookup
private static class HeadroomCache<K, V> {
@@ -818,6 +820,23 @@
for (int i = tokenMap.size() - 1; i >= 0; i--) {
// Will remove the session from tokenMap
ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+ IntArray closedSessionsForSf = new IntArray();
+ // Batch the closure call to SF for all the sessions that die
+ for (int j = sessionSet.size() - 1; j >= 0; j--) {
+ AppHintSession session = sessionSet.valueAt(j);
+ if (session.isTrackedBySf()) {
+ // Mark it as untracked so we don't untrack again on close
+ session.setTrackedBySf(false);
+ closedSessionsForSf.add(session.getSessionId());
+ }
+ }
+ if (mSessionManager != null) {
+ try {
+ mSessionManager.trackedSessionsDied(closedSessionsForSf.toArray());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to communicate with SessionManager");
+ }
+ }
for (int j = sessionSet.size() - 1; j >= 0; j--) {
sessionSet.valueAt(j).close();
}
@@ -1350,9 +1369,9 @@
}
}
- final long sessionId = config.id != -1 ? config.id : halSessionPtr;
+ final long sessionIdForTracing = config.id != -1 ? config.id : halSessionPtr;
logPerformanceHintSessionAtom(
- callingUid, sessionId, durationNanos, tids, tag);
+ callingUid, sessionIdForTracing, durationNanos, tids, tag);
synchronized (mSessionSnapshotMapLock) {
// Update session snapshot upon session creation
@@ -1362,8 +1381,12 @@
}
AppHintSession hs = null;
synchronized (mLock) {
+ Integer configId = null;
+ if (config.id != -1) {
+ configId = new Integer((int) config.id);
+ }
hs = new AppHintSession(callingUid, callingTgid, tag, tids,
- token, halSessionPtr, durationNanos);
+ token, halSessionPtr, durationNanos, configId);
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
mActiveSessions.get(callingUid);
if (tokenMap == null) {
@@ -1390,6 +1413,11 @@
}
}
+ if (creationConfig.layerTokens != null
+ && creationConfig.layerTokens.length > 0) {
+ hs.associateToLayers(creationConfig.layerTokens);
+ }
+
synchronized (mThreadsUsageObject) {
mThreadsUsageMap.computeIfAbsent(callingUid, k -> new ArraySet<>());
ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
@@ -1566,6 +1594,15 @@
}
@Override
+ public void passSessionManagerBinder(IBinder sessionManager) {
+ // Ensure caller is internal
+ if (Process.myUid() != Binder.getCallingUid()) {
+ return;
+ }
+ mSessionManager = ISessionManager.Stub.asInterface(sessionManager);
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
@@ -1688,6 +1725,8 @@
protected boolean mHasBeenPowerEfficient;
protected boolean mHasBeenGraphicsPipeline;
protected boolean mShouldForcePause;
+ protected Integer mSessionId;
+ protected boolean mTrackedBySF;
enum SessionModes {
POWER_EFFICIENCY,
@@ -1696,7 +1735,7 @@
protected AppHintSession(
int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
- long halSessionPtr, long durationNanos) {
+ long halSessionPtr, long durationNanos, Integer sessionId) {
mUid = uid;
mPid = pid;
mTag = sessionTag;
@@ -1710,6 +1749,8 @@
mHasBeenPowerEfficient = false;
mHasBeenGraphicsPipeline = false;
mShouldForcePause = false;
+ mSessionId = sessionId;
+ mTrackedBySF = false;
final boolean allowed = mUidObserver.isUidForeground(mUid);
updateHintAllowedByProcState(allowed);
try {
@@ -1799,6 +1840,19 @@
} catch (NoSuchElementException ignored) {
Slogf.d(TAG, "Death link does not exist for session with UID " + mUid);
}
+ if (mTrackedBySF) {
+ if (mSessionManager != null) {
+ try {
+ mSessionManager.trackedSessionsDied(new int[]{mSessionId});
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Could not communicate with SessionManager", e);
+ }
+ mTrackedBySF = false;
+ } else {
+ Slog.e(TAG, "SessionManager is null but there are tracked sessions");
+ }
+ }
}
synchronized (mLock) {
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
@@ -1875,6 +1929,24 @@
}
}
+ @Override
+ public void associateToLayers(IBinder[] layerTokens) {
+ synchronized (this) {
+ if (mSessionManager != null && mSessionId != null && layerTokens != null) {
+ // Sf only untracks a session when it dies
+ if (layerTokens.length > 0) {
+ mTrackedBySF = true;
+ }
+ try {
+ mSessionManager.associateSessionToLayers(mSessionId, mUid, layerTokens);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Could not communicate with SessionManager", e);
+ }
+ }
+ }
+ }
+
public void setThreads(@NonNull int[] tids) {
setThreadsInternal(tids, true);
}
@@ -2124,10 +2196,27 @@
return mUid;
}
+ public boolean isTrackedBySf() {
+ synchronized (this) {
+ return mTrackedBySF;
+ }
+ }
+
+ public void setTrackedBySf(boolean tracked) {
+ synchronized (this) {
+ mTrackedBySF = tracked;
+ }
+ }
+
+
public int getTag() {
return mTag;
}
+ public Integer getSessionId() {
+ return mSessionId;
+ }
+
public long getTargetDurationNs() {
synchronized (this) {
return mTargetDurationNanos;
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 028ac57..6f18107 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -409,11 +409,10 @@
@Override
public long getWakelockDurationMillis() {
synchronized (BatteryStatsImpl.this) {
- long rawRealtimeUs = mClock.uptimeMillis() * 1000;
- long batteryUptimeUs = getBatteryUptime(rawRealtimeUs);
- long screenOnTimeUs = getScreenOnTime(rawRealtimeUs,
+ long batteryUptimeUs = getBatteryUptime(mClock.uptimeMillis() * 1000);
+ long screenOnTimeUs = getScreenOnTime(mClock.elapsedRealtime() * 1000,
BatteryStats.STATS_SINCE_CHARGED);
- return (batteryUptimeUs - screenOnTimeUs) / 1000;
+ return Math.max(0, (batteryUptimeUs - screenOnTimeUs) / 1000);
}
}
@@ -437,8 +436,9 @@
}
}
- if (wakeLockTimeUs != 0) {
- callback.onUidWakelockDuration(u.getUid(), wakeLockTimeUs / 1000);
+ long wakelockTimeMs = wakeLockTimeUs / 1000;
+ if (wakelockTimeMs != 0) {
+ callback.onUidWakelockDuration(u.getUid(), wakelockTimeMs);
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
index e36c994..e3e4e1b 100644
--- a/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
@@ -108,14 +108,16 @@
mWakelockDurationRetriever.retrieveUidWakelockDuration((uid, durationMs) -> {
if (!mFirstCollection) {
- long[] uidStats = mPowerStats.uidStats.get(uid);
- if (uidStats == null) {
- uidStats = new long[mDescriptor.uidStatsArrayLength];
- mPowerStats.uidStats.put(uid, uidStats);
- }
+ long diffMs = Math.max(0, durationMs - mLastUidWakelockDurations.get(uid));
+ if (diffMs != 0) {
+ long[] uidStats = mPowerStats.uidStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new long[mDescriptor.uidStatsArrayLength];
+ mPowerStats.uidStats.put(uid, uidStats);
+ }
- mStatsLayout.setUidUsageDuration(uidStats,
- Math.max(0, durationMs - mLastUidWakelockDurations.get(uid)));
+ mStatsLayout.setUidUsageDuration(uidStats, diffMs);
+ }
}
mLastUidWakelockDurations.put(uid, durationMs);
});
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index ce6f57f..5e04881 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -29,6 +29,7 @@
namespace: "backstage_power"
description: "Feature flag for streamlined connectivity battery stats"
bug: "323970018"
+ is_exported: true
}
flag {
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 9e75cf2..15c3099 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -86,6 +86,8 @@
@GuardedBy("mLock")
private Status mEndStatusRequest;
@GuardedBy("mLock")
+ private boolean mEndedByVendor;
+ @GuardedBy("mLock")
private long mStartTime; // for debugging
@GuardedBy("mLock")
private long mEndUptime;
@@ -119,14 +121,15 @@
public void finishSession() {
// Do not abort session in HAL, wait for ongoing vibration requests to complete.
// This might take a while to end the session, but it can be aborted by cancelSession.
- requestEndSession(Status.FINISHED, /* shouldAbort= */ false);
+ requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
}
@Override
public void cancelSession() {
// Always abort session in HAL while cancelling it.
// This might be triggered after finishSession was already called.
- requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true);
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
}
@Override
@@ -158,7 +161,7 @@
public DebugInfo getDebugInfo() {
synchronized (mLock) {
return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
- mEndUptime, mEndTime, mVibrations);
+ mEndUptime, mEndTime, mEndedByVendor, mVibrations);
}
}
@@ -172,13 +175,15 @@
@Override
public void onCancel() {
Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
- requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false);
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
}
@Override
public void binderDied() {
Slog.d(TAG, "Binder died, cancelling vibration session...");
- requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
+ /* isVendorRequest= */ false);
}
@Override
@@ -207,7 +212,7 @@
// All requests to end a session should abort it to stop ongoing vibrations, even if
// immediate flag is false. Only the #finishSession API will not abort and wait for
// session vibrations to complete, which might take a long time.
- requestEndSession(status, /* shouldAbort= */ true);
+ requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false);
}
@Override
@@ -224,7 +229,8 @@
public void notifySessionCallback() {
synchronized (mLock) {
// If end was not requested then the HAL has cancelled the session.
- maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
+ maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
+ /* isVendorRequest= */ false);
maybeSetStatusToRequestedLocked();
clearVibrationConductor();
}
@@ -335,10 +341,10 @@
}
}
- private void requestEndSession(Status status, boolean shouldAbort) {
+ private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
boolean shouldTriggerSessionHook = false;
synchronized (mLock) {
- maybeSetEndRequestLocked(status);
+ maybeSetEndRequestLocked(status, isVendorRequest);
if (isStarted()) {
// Always trigger session hook after it has started, in case new request aborts an
// already finishing session. Wait for HAL callback before actually ending here.
@@ -354,12 +360,13 @@
}
@GuardedBy("mLock")
- private void maybeSetEndRequestLocked(Status status) {
+ private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
if (mEndStatusRequest != null) {
// End already requested, keep first requested status and time.
return;
}
mEndStatusRequest = status;
+ mEndedByVendor = isVendorRequest;
mEndTime = System.currentTimeMillis();
mEndUptime = SystemClock.uptimeMillis();
if (mConductor != null) {
@@ -442,15 +449,18 @@
private final long mStartTime;
private final long mEndTime;
private final long mDurationMs;
+ private final boolean mEndedByVendor;
DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
- long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) {
+ long startTime, long endUptime, long endTime, boolean endedByVendor,
+ List<DebugInfo> vibrations) {
mStatus = status;
mCallerInfo = callerInfo;
mCreateUptime = createUptime;
mCreateTime = createTime;
mStartTime = startTime;
mEndTime = endTime;
+ mEndedByVendor = endedByVendor;
mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
}
@@ -478,6 +488,15 @@
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ if (mStartTime > 0) {
+ // Only log sessions that have started.
+ statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
+ statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
+ mVibrations.size());
+ if (!mEndedByVendor) {
+ statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid);
+ }
+ }
for (DebugInfo vibration : mVibrations) {
vibration.logMetrics(statsLogger);
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 27f92b2..2bf4498 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,6 +25,7 @@
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -211,6 +212,11 @@
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
statsLogger.writeVibrationReportedAsync(mStatsInfo);
+ if (Flags.vendorVibrationEffects()) {
+ // Log effect as it was originally requested.
+ statsLogger.logVibrationCountAndSizeIfVendorEffect(mCallerInfo.uid,
+ mOriginalEffect != null ? mOriginalEffect : mPlayedEffect);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index e9c3894..08da43d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -16,8 +16,12 @@
package com.android.server.vibrator;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.Handler;
+import android.os.Parcel;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.util.Slog;
import android.view.HapticFeedbackConstants;
@@ -58,6 +62,16 @@
"vibrator.value_vibration_adaptive_haptic_scale",
new Histogram.UniformOptions(20, 0, 2));
+ // Sizes in [1KB, ~4.5MB) defined by scaled buckets.
+ private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_effect_size",
+ new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f));
+
+ // Session vibration count in [0, ~840) defined by scaled buckets.
+ private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_session_vibrations",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
private final Object mLock = new Object();
private final Handler mHandler;
private final long mVibrationReportedLogIntervalMillis;
@@ -201,4 +215,88 @@
Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
}
}
+
+ /** Logs when a vendor vibration session successfully started. */
+ public void logVibrationVendorSessionStarted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid);
+ }
+
+ /**
+ * Logs when a vendor vibration session is interrupted by the platform.
+ *
+ * <p>A vendor session is interrupted if it has successfully started and its end was not
+ * requested by the vendor. This could be the vibrator service interrupting an ongoing session,
+ * the vibrator HAL triggering the session completed callback early.
+ */
+ public void logVibrationVendorSessionInterrupted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid);
+ }
+
+ /** Logs the number of vibrations requested for a single vendor vibration session. */
+ public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) {
+ sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount);
+ }
+
+ /**
+ * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}.
+ *
+ * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the
+ * {@link VibrationEffect.VendorEffect#getVendorData()} it holds.
+ */
+ public void logVibrationCountAndSizeIfVendorEffect(int uid,
+ @Nullable CombinedVibration vibration) {
+ if (vibration == null) {
+ return;
+ }
+ boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration);
+ if (hasVendorEffects) {
+ // Increment CombinedVibration with one or more vendor effects only once.
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid);
+ }
+ }
+
+ private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) {
+ if (vibration instanceof CombinedVibration.Mono mono) {
+ if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ return true;
+ }
+ return false;
+ }
+ if (vibration instanceof CombinedVibration.Stereo stereo) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < stereo.getEffects().size(); i++) {
+ if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ hasVendorEffects = true;
+ }
+ }
+ return hasVendorEffects;
+ }
+ if (vibration instanceof CombinedVibration.Sequential sequential) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < sequential.getEffects().size(); i++) {
+ hasVendorEffects |= logVibrationSizeOfVendorEffects(uid,
+ sequential.getEffects().get(i));
+ }
+ return hasVendorEffects;
+ }
+ // Unknown combined vibration, skip metrics.
+ return false;
+ }
+
+ private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) {
+ int dataSize;
+ Parcel vendorData = Parcel.obtain();
+ try {
+ // Measure data size as it'll be sent to the HAL via binder, not the serialization size.
+ // PersistableBundle creates an XML representation for the data in writeToStream, so it
+ // might be larger than the actual data that is transferred between processes.
+ effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
+ dataSize = vendorData.dataSize();
+ } finally {
+ vendorData.recycle();
+ }
+ sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize);
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index cc163db..ae726c1 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1197,7 +1197,7 @@
new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
SystemClock.uptimeMillis(), System.currentTimeMillis(),
/* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
- /* vibrations= */ null));
+ /* endedByVendor= */ false, /* vibrations= */ null));
}
private void logAndRecordVibration(DebugInfo info) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 70a8f56..a077a0b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2054,6 +2054,8 @@
break;
}
}
+ long timeRemaining = endTime - System.currentTimeMillis();
+ mWindowManager.mSnapshotController.mTaskSnapshotController.waitFlush(timeRemaining);
// Force checkReadyForSleep to complete.
checkReadyForSleepLocked(false /* allowDelay */);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fc08a91..bccf6b20 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4267,7 +4267,8 @@
return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
}
final int imePolicy = mWmService.mDisplayWindowSettings.getImePolicyLocked(this);
- if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY && forceDesktopMode()) {
+ if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY
+ && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
// If the display has not explicitly requested for the IME to be hidden then it shall
// show the IME locally.
return DISPLAY_IME_POLICY_LOCAL;
@@ -4275,10 +4276,6 @@
return imePolicy;
}
- boolean forceDesktopMode() {
- return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate();
- }
-
/** @see WindowManagerInternal#onToggleImeRequested */
void onShowImeRequested() {
if (mInputMethodWindow == null) {
@@ -4871,7 +4868,7 @@
/** @return {@code true} if there is window to wait before enabling the screen. */
boolean shouldWaitForSystemDecorWindowsOnBoot() {
- if (!isDefaultDisplay && !supportsSystemDecorations()) {
+ if (!isDefaultDisplay && !isSystemDecorationsSupported()) {
// Nothing to wait because the secondary display doesn't support system decorations,
// there is no wallpaper, keyguard (status bar) or application (home) window to show
// during booting.
@@ -5750,22 +5747,48 @@
/**
* @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
*/
- boolean supportsSystemDecorations() {
- boolean forceDesktopModeOnDisplay = forceDesktopMode();
-
- if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
- // System decorations should not be forced on a rear display due to security policies.
- forceDesktopModeOnDisplay =
- forceDesktopModeOnDisplay && ((mDisplay.getFlags() & Display.FLAG_REAR) == 0);
+ boolean isSystemDecorationsSupported() {
+ if (mDisplayId == mWmService.mVr2dDisplayId) {
+ // VR virtual display will be used to run and render 2D app within a VR experience.
+ return false;
}
+ if (!isTrusted()) {
+ // Do not show system decorations on untrusted virtual display.
+ return false;
+ }
+ if (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
+ || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+ // This display is configured to show system decorations.
+ return true;
+ }
+ if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
+ if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
+ // System decorations should not be forced on a rear display due to security
+ // policies.
+ return (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
+ }
+ // If the display is forced to desktop mode, treat it the same as it is configured to
+ // show system decorations.
+ return true;
+ }
+ return false;
+ }
- return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
- || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
- || forceDesktopModeOnDisplay)
- // VR virtual display will be used to run and render 2D app within a VR experience.
- && mDisplayId != mWmService.mVr2dDisplayId
- // Do not show system decorations on untrusted virtual display.
- && isTrusted();
+ /**
+ * This is the development option to force enable desktop mode on all secondary public displays
+ * that are not owned by a virtual device.
+ * When this is enabled, it also force enable system decorations on those displays.
+ *
+ * If we need a per-display config to enable desktop mode for production, that config should
+ * also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy.
+ */
+ boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() {
+ if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) {
+ return false;
+ }
+ // Desktop mode is not supported on virtual devices.
+ int deviceId = mRootWindowContainer.mTaskSupervisor.getDeviceIdForDisplayId(mDisplayId);
+ return deviceId == Context.DEVICE_ID_DEFAULT;
}
/**
@@ -5776,7 +5799,7 @@
*/
boolean isHomeSupported() {
return (mWmService.mDisplayWindowSettings.isHomeSupportedLocked(this) && isTrusted())
- || supportsSystemDecorations();
+ || isSystemDecorationsSupported();
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 76e8a70..659bb67 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -659,7 +659,7 @@
}
} else {
mHasStatusBar = false;
- mHasNavigationBar = mDisplayContent.supportsSystemDecorations();
+ mHasNavigationBar = mDisplayContent.isSystemDecorationsSupported();
}
mRefreshRatePolicy = new RefreshRatePolicy(mService,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index df209ff..f53bc70 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -295,7 +295,7 @@
&& mDeviceStateController
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
- mDefaultDisplayRotationChangedCallback);
+ displayContent.getDisplayId(), mDefaultDisplayRotationChangedCallback);
}
if (isDefaultDisplay) {
@@ -445,7 +445,8 @@
final boolean isTv = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
mDefaultFixedToUserRotation =
- (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()
+ (isCar || isTv || mService.mIsPc
+ || mDisplayContent.isPublicSecondaryDisplayWithDesktopModeForceEnabled()
|| !mDisplayContent.shouldRotateWithContent())
// For debug purposes the next line turns this feature off with:
// $ adb shell setprop config.override_forced_orient true
@@ -1659,7 +1660,8 @@
void removeDefaultDisplayRotationChangedCallback() {
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
- mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback(
+ mDefaultDisplayRotationChangedCallback);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
index ae3787c..01e1b13 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Slog;
import android.view.Display;
import android.view.Surface;
@@ -40,6 +41,7 @@
@Nullable
@VisibleForTesting
Runnable mDefaultDisplayRotationChangedCallback;
+ private int mCallbackDisplayId = Display.INVALID_DISPLAY;
@Surface.Rotation
private int mDefaultDisplayCurrentRotation;
@@ -68,12 +70,15 @@
* Register a callback to be notified when the default display's rotation changes. Clients can
* query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
*/
- void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
- if (mDefaultDisplayRotationChangedCallback != null) {
- throw new UnsupportedOperationException("Multiple clients unsupported");
+ void setDefaultDisplayRotationChangedCallback(int displayId, @NonNull Runnable callback) {
+ if (mDefaultDisplayRotationChangedCallback != null && displayId != mCallbackDisplayId) {
+ throw new UnsupportedOperationException("Multiple clients unsupported"
+ + ". Incoming displayId: " + displayId
+ + ", existing displayId: " + mCallbackDisplayId);
}
mDefaultDisplayRotationChangedCallback = callback;
+ mCallbackDisplayId = displayId;
if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
callback.run();
@@ -82,10 +87,17 @@
/**
* Removes the callback that was added via
- * {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
+ * {@link #setDefaultDisplayRotationChangedCallback(int, Runnable)}.
*/
- void removeDefaultDisplayRotationChangedCallback() {
+ void removeDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
+ if (callback != mDefaultDisplayRotationChangedCallback) {
+ Slog.w(TAG, "Attempted to remove non-matching callback."
+ + " DisplayId: " + mCallbackDisplayId);
+ return;
+ }
+
mDefaultDisplayRotationChangedCallback = null;
+ mCallbackDisplayId = Display.INVALID_DISPLAY;
}
static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index c87b811..f6d05d0 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -143,7 +143,7 @@
}
// No record is present so use default windowing mode policy.
final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
- && (mService.mIsPc || dc.forceDesktopMode());
+ && (mService.mIsPc || dc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
if (forceFreeForm) {
return WindowConfiguration.WINDOWING_MODE_FREEFORM;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c89feb4..46312af 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2853,11 +2853,9 @@
}
void prepareForShutdown() {
+ mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
for (int i = 0; i < getChildCount(); i++) {
- final int displayId = getChildAt(i).mDisplayId;
- mWindowManager.mSnapshotController.mTaskSnapshotController
- .snapshotForShutdown(displayId);
- createSleepToken("shutdown", displayId);
+ createSleepToken("shutdown", getChildAt(i).mDisplayId);
}
}
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index bd8e8f4..8b63ecf7 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -103,12 +103,42 @@
}
/**
- * Write out everything in the queue because of shutdown.
+ * Prepare to enqueue all visible task snapshots because of shutdown.
*/
- void shutdown() {
+ void prepareShutdown() {
synchronized (mLock) {
mShutdown = true;
- mLock.notifyAll();
+ }
+ }
+
+ private boolean isQueueEmpty() {
+ synchronized (mLock) {
+ return mWriteQueue.isEmpty() || mQueueIdling || mPaused;
+ }
+ }
+
+ void waitFlush(long timeout) {
+ if (timeout <= 0) {
+ return;
+ }
+ final long endTime = System.currentTimeMillis() + timeout;
+ while (true) {
+ if (!isQueueEmpty()) {
+ long timeRemaining = endTime - System.currentTimeMillis();
+ if (timeRemaining > 0) {
+ synchronized (mLock) {
+ try {
+ mLock.wait(timeRemaining);
+ } catch (InterruptedException e) {
+ }
+ }
+ } else {
+ Slog.w(TAG, "Snapshot Persist Queue flush timed out");
+ break;
+ }
+ } else {
+ break;
+ }
}
}
@@ -139,7 +169,9 @@
mWriteQueue.addLast(item);
}
item.onQueuedLocked();
- ensureStoreQueueDepthLocked();
+ if (!mShutdown) {
+ ensureStoreQueueDepthLocked();
+ }
if (!mPaused) {
mLock.notifyAll();
}
@@ -213,6 +245,9 @@
if (!writeQueueEmpty && !mPaused) {
continue;
}
+ if (mShutdown && writeQueueEmpty) {
+ mLock.notifyAll();
+ }
try {
mQueueIdling = writeQueueEmpty;
mLock.wait();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 9fe3f756..c130931 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -309,23 +309,31 @@
/**
* Record task snapshots before shutdown.
*/
- void snapshotForShutdown(int displayId) {
+ void prepareShutdown() {
if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
- final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
+ // Make write items run in a batch.
+ mPersister.mSnapshotPersistQueue.setPaused(true);
+ mPersister.mSnapshotPersistQueue.prepareShutdown();
+ for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+ mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
+ if (task.isVisible() && !task.isActivityTypeHome()) {
+ final TaskSnapshot snapshot = captureSnapshot(task);
+ if (snapshot != null) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ }
+ }
+ }, true /* traverseTopToBottom */);
+ }
+ mPersister.mSnapshotPersistQueue.setPaused(false);
+ }
+
+ void waitFlush(long timeout) {
+ if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
- displayContent.forAllLeafTasks(task -> {
- if (task.isVisible() && !task.isActivityTypeHome()) {
- final TaskSnapshot snapshot = captureSnapshot(task);
- if (snapshot != null) {
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- }
- }
- }, true /* traverseTopToBottom */);
- mPersister.mSnapshotPersistQueue.shutdown();
+ mPersister.mSnapshotPersistQueue.waitFlush(timeout);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7a53ccf..9e1509c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7700,7 +7700,7 @@
+ "not exist: %d", displayId);
return false;
}
- return displayContent.supportsSystemDecorations();
+ return displayContent.isSystemDecorationsSupported();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8294737..cebe790 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2757,7 +2757,7 @@
* Expands the given rectangle by the region of window resize handle for freeform window.
* @param inOutRect The rectangle to update.
*/
- private void adjustRegionInFreefromWindowMode(Rect inOutRect) {
+ private void adjustRegionInFreeformWindowMode(Rect inOutRect) {
if (!inFreeformWindowingMode()) {
return;
}
@@ -2808,7 +2808,7 @@
}
}
}
- adjustRegionInFreefromWindowMode(mTmpRect);
+ adjustRegionInFreeformWindowMode(mTmpRect);
outRegion.set(mTmpRect);
cropRegionToRootTaskBoundsIfNeeded(outRegion);
}
@@ -3608,7 +3608,7 @@
}
rootTask.getDimBounds(mTmpRect);
- adjustRegionInFreefromWindowMode(mTmpRect);
+ adjustRegionInFreeformWindowMode(mTmpRect);
region.op(mTmpRect, Region.Op.INTERSECT);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
index 40cf0e9..c8c953d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
@@ -19,6 +19,7 @@
import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.content.pm.PackageManagerInternal;
import android.util.ArraySet;
@@ -64,7 +65,7 @@
/**
* Suspend packages that are requested by a single admin
*
- * @return a list of packages that the admin has requested to suspend but could not be
+ * @return an array of packages that the admin has requested to suspend but could not be
* suspended, due to DPM and PackageManager exemption list.
*
*/
@@ -87,7 +88,7 @@
/**
* Suspend packages considering the exemption list.
*
- * @return the list of packages that couldn't be suspended, either due to the exemption list,
+ * @return the set of packages that couldn't be suspended, either due to the exemption list,
* or due to failures from PackageManagerInternal itself.
*/
private Set<String> suspendWithExemption(Set<String> packages) {
@@ -112,15 +113,15 @@
/**
* Unsuspend packages that are requested by a single admin
*
- * @return a list of packages that the admin has requested to unsuspend but could not be
- * unsuspended, due to other amdin's policy or PackageManager restriction.
+ * @return an array of packages that the admin has requested to unsuspend but could not be
+ * unsuspended, due to other admin's policy or PackageManager restriction.
*
*/
public String[] unsuspend(Set<String> packages) {
- // Unlike suspend(), when unsuspending, call PackageManager with the delta of resolved
- // suspended packages list and not what the admin has requested. This is because some
- // packages might still be subject to another admin's suspension request.
- Set<String> packagesToUnsuspend = new ArraySet<>(mSuspendedPackageBefore);
+ // Unlike suspend(), when unsuspending, take suspension by other admins into account: only
+ // packages not suspended by other admins are passed to PackageManager.
+ Set<String> packagesToUnsuspend = new ArraySet<>(
+ Flags.unsuspendNotSuspended() ? packages : mSuspendedPackageBefore);
packagesToUnsuspend.removeAll(mSuspendedPackageAfter);
// To calculate the result (which packages are not unsuspended), start with packages that
@@ -139,7 +140,7 @@
/**
* Unsuspend packages considering the exemption list.
*
- * @return the list of packages that couldn't be unsuspended, either due to the exemption list,
+ * @return the set of packages that couldn't be unsuspended, either due to the exemption list,
* or due to failures from PackageManagerInternal itself.
*/
private Set<String> unsuspendWithExemption(Set<String> packages) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index a1937ce..9b8a7cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -23,6 +23,8 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -33,20 +35,24 @@
import android.location.ILocationListener;
import android.location.LocationManagerInternal;
import android.location.LocationRequest;
+import android.location.flags.Flags;
import android.location.provider.ProviderRequest;
import android.os.IBinder;
import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.provider.AbstractLocationProvider;
import com.android.server.location.provider.LocationProviderManager;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.google.common.util.concurrent.MoreExecutors;
@@ -54,6 +60,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -75,8 +82,12 @@
private TestInjector mInjector;
private LocationManagerService mLocationManagerService;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Spy private FakeAbstractLocationProvider mProviderWithPermission;
@Spy private FakeAbstractLocationProvider mProviderWithoutPermission;
+ @Mock private ProxyPopulationDensityProvider mPopulationDensityProvider;
@Mock private ILocationListener mLocationListener;
@Mock private IBinder mBinder;
@Mock private Context mContext;
@@ -172,6 +183,32 @@
}
@Test
+ public void testSetLocationFudgerCache_withFeatureFlagDisabled_isNotCalled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager, never()).setLocationFudgerCache(any());
+ }
+
+ @Test
+ public void testSetLocationFudgerCache_withFeatureFlagEnabled_isCalled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager).setLocationFudgerCache(cache);
+ }
+
+ @Test
public void testHasProvider_noPermission() {
assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
new file mode 100644
index 0000000..04b82c4
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.location.fudger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+public class LocationFudgerCacheTest {
+
+ private static final String TAG = "LocationFudgerCacheTest";
+
+ private static final long TIMES_SQUARE_S2_ID =
+ S2CellIdUtils.fromLatLngDegrees(40.758896, -73.985130);
+
+ private static final double[] POINT_IN_TIMES_SQUARE = {40.75889599346095, -73.9851300385147};
+
+ private static final double[] POINT_OUTSIDE_TIMES_SQUARE = {48.858093, 2.294694};
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
+ public void hasDefaultValue_isInitiallyFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_uponQueryError_isStillFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_afterSuccessfulQuery_isTrue()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ assertThat(cache.hasDefaultValue()).isTrue();
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedOutsideOfCache_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ int defaultLevel = 2;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(defaultLevel);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_OUTSIDE_TIMES_SQUARE[0],
+ POINT_OUTSIDE_TIMES_SQUARE[1])).isEqualTo(defaultLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedValueIsCached_returnsCachedValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]))
+ .isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenStarting_queriesDefaultValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifDidntGetDefaultValue_queriesItAgain() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ verify(provider, times(2)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifReceivedDefaultValue_doesNotQueriesIt()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider, times(1)).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ // Verify getDefaultCoarseningLevel did not get called again
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenSuccessfullyQueriesDefaultValue_storesResult()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(level);
+
+ // Query any uncached location
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryingDefaultValueFails_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ // Query any uncached location. The default value is 0
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(0);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsNotCached_queriesProvider() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+ verify(provider).getCoarsenedS2Cell(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenProviderIsQueried_resultIsCached() throws RemoteException {
+ double lat = POINT_IN_TIMES_SQUARE[0];
+ double lng = POINT_IN_TIMES_SQUARE[1];
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ int level = cache.getCoarseningLevel(lat, lng);
+ assertThat(level).isEqualTo(0); // default value
+
+ ArgumentCaptor<IS2CellIdsCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2CellIdsCallback.class);
+ verify(provider).getCoarsenedS2Cell(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), argumentCaptor.capture());
+
+ // Results from the proxy should set the cache
+ int expectedLevel = 4;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(lat, lng);
+ Long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+ IS2CellIdsCallback cb = argumentCaptor.getValue();
+ long[] answer = new long[] {s2CellId};
+ cb.onResult(answer);
+
+ int level2 = cache.getCoarseningLevel(lat, lng);
+ assertThat(level2).isEqualTo(expectedLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsCached_doesNotRefreshIt() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.addToCache(TIMES_SQUARE_S2_ID);
+
+ verify(provider, never()).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
+ @Test
+ public void locationFudgerCache_canContainUpToMaxSizeItems() {
+ // This test has two sequences of arrange-act-assert.
+ // The first checks that the cache correctly store up to MAX_CACHE_SIZE items.
+ // The second checks that any new element replaces the oldest in the cache.
+
+ // Arrange.
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int size = cache.MAX_CACHE_SIZE;
+
+ double[][] latlngs = new double[size][2];
+ long[] cells = new long[size];
+ int[] expectedLevels = new int[size];
+
+ for (int i = 0; i < size; i++) {
+ // Create arbitrary lat/lngs.
+ latlngs[i][0] = 10.0 * i;
+ latlngs[i][1] = 10.0 * i;
+
+ expectedLevels[i] = 10; // we set some arbitrary S2 level for each latlng.
+
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latlngs[i][0], latlngs[i][1]);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevels[i]);
+ cells[i] = s2CellId;
+ }
+
+ // Act.
+ cache.addToCache(cells);
+
+ // Assert: check that the cache contains these latlngs and returns the correct level.
+ for (int i = 0; i < size; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+
+ // Second assertion: A new value evicts the oldest one.
+
+ // Arrange.
+ int expectedLevel = 25;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(-10.0, -180.0);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+
+ // Act.
+ cache.addToCache(s2CellId);
+
+ // Assert: the new point is in the cache.
+ assertThat(cache.getCoarseningLevel(-10.0, -180.0)).isEqualTo(expectedLevel);
+ // Assert: all but the oldest point are still in cache.
+ for (int i = 0; i < size - 1; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+ // Assert: the oldest point has been evicted.
+ assertThat(cache.getCoarseningLevel(latlngs[size - 1][0], latlngs[size - 1][1]))
+ .isEqualTo(0);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index 4e9b6c7..d58e772 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -22,14 +22,23 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.location.Location;
+import android.location.flags.Flags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,6 +64,9 @@
private LocationFudger mFudger;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
long seed = System.currentTimeMillis();
@@ -162,4 +174,64 @@
input.getLongitude() + deltaYM / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR,
0);
}
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsDisabled_cacheIsNotUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledButNotDefault_cacheIsNotUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(false).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledAndDefaultIsSet_cacheIsUsed() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(true).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ Location fine = createLocation("test", mRandom);
+ mFudger.createCoarse(fine);
+
+ // We can't verify that the coordinatese of "fine" are passed to the API due to the addition
+ // of the offset. We must use anyDouble().
+ verify(cache).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_newAlgorithm_snapsToCenterOfS2Cell_testVector() {
+ // NB: a complete test vector is in
+ // frameworks/base/services/tests/mockingservicestests/src/com/android/server/...
+ // location/geometry/S2CellIdUtilsTest.java
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ // Arbitrary location in Times Square, NYC
+ double[] latLng = new double[] {40.758896, -73.985130};
+ int s2Level = 1;
+ // The level-2 S2 cell around this location is "8c", its center is:
+ double[] expected = { 21.037511025421814, -67.38013505195958 };
+
+ double[] center = mFudger.snapToCenterOfS2Cell(latLng[0], latLng[1], s2Level);
+
+ assertThat(center[0]).isEqualTo(expected[0]);
+ assertThat(center[1]).isEqualTo(expected[1]);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 0928264..cd19904 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -40,6 +40,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -72,6 +73,7 @@
import android.location.LocationResult;
import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
+import android.location.provider.IS2LevelCallback;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -98,8 +100,10 @@
import com.android.internal.R;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import org.junit.After;
import org.junit.Before;
@@ -1432,6 +1436,72 @@
PERMISSION_FINE)).isEqualTo(location);
}
+ @Test
+ public void testLocationFudger_withFlagDisabled_cacheIsNotSetAndOldAlgoIsUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabledButNoDefaults_oldAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+
+ // Act: the provider didn't provide a default
+ cb.onError();
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabled_cacheIsSetAndNewAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int defaultLevel = 2;
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+ cb.onResult(defaultLevel);
+
+ Location test = new Location("any-provider");
+ test.setLatitude(10.0);
+ test.setLongitude(20.0);
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ // We can't test that 10.0, 20.0 was passed due to the offset. We only test that a call
+ // happened.
+ verify(provider).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
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 727d1b5..32578a7 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
@@ -2076,10 +2076,10 @@
private AssociationInfo createAssociationInfo(int associationId, String deviceProfile,
CharSequence displayName) {
return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
- /* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
+ MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
/* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
/* timeApprovedMs= */0, /* lastTimeConnectedMs= */0,
- /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null);
+ /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null, /* deviceId= */ null);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index dd4101e..30dac9f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -433,4 +433,21 @@
.dsmStatusChanged(anyBoolean(), anyBoolean(),
eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
}
+
+ @Test
+ public void testPowerStateChangeOnActiveSourceLostToggled_writesAtom_logReasonSetting() {
+ mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.writePowerStateChangeOnActiveSourceLostAtom(true);
+ mTestLooper.dispatchAll();
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .powerStateChangeOnActiveSourceLostChanged(eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING), anyString(), anyInt(), anyInt());
+ verify(mHdmiCecAtomWriterSpy, never())
+ .powerStateChangeOnActiveSourceLostChanged(eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP), anyString(), anyInt(), anyInt());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 077bb03..861e72d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -95,9 +95,6 @@
private boolean mActiveMediaSessionsPaused;
private FakePowerManagerInternalWrapper mPowerManagerInternal =
new FakePowerManagerInternalWrapper();
-
- private boolean mIsOnActiveSourceLostPopupActive;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -165,12 +162,12 @@
mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
@Override
void startHdmiCecActiveSourceLostActivity() {
- mIsOnActiveSourceLostPopupActive = true;
+ setIsActiveSourceLostPopupLaunched(true);
}
@Override
void dismissUiOnActiveSourceStatusRecovered() {
- mIsOnActiveSourceLostPopupActive = false;
+ setIsActiveSourceLostPopupLaunched(false);
}
};
mHdmiCecLocalDevicePlayback.init();
@@ -2389,7 +2386,7 @@
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
}
@Test
@@ -2430,7 +2427,7 @@
// Pop-up is not shown, playback device asserts active source since TV doesn't answer the
// request.
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2480,7 +2477,7 @@
mTestLooper.dispatchAll();
// Pop-up is not shown since playback device is active source.
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2532,7 +2529,7 @@
// Pop-up is shown, playback device doesn't assert active source since active path is
// switched to a non-CEC device.
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(ADDR_INVALID);
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
@@ -2569,7 +2566,7 @@
});
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mPowerManager.isInteractive()).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
@@ -2609,13 +2606,13 @@
mNativeWrapper.onCecMessage(activeSourceFromTv);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2664,13 +2661,13 @@
// Pop-up is triggered.
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(routingChangeToPlayback))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2711,7 +2708,7 @@
mNativeWrapper.onCecMessage(activeSourceFromTv);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
mHdmiControlService.oneTouchPlay(new IHdmiControlCallback() {
@Override
public void onComplete(int result) throws RemoteException {
@@ -2724,7 +2721,7 @@
});
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2897,11 +2894,11 @@
} else {
mTestLooper.moveTimeForward(TIMEOUT_MS);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
return;
}
}
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
mPowerManagerInternal.setIdleDuration(idleDuration);
mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index e328419..194d48a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2796,6 +2796,12 @@
stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onStarted(any(IVibrationSession.class));
verify(callback, never()).onFinishing();
verify(callback, never()).onFinished(anyInt());
@@ -2817,6 +2823,12 @@
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onFinishing();
verify(callback)
.onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2838,6 +2850,12 @@
assertThat(session).isNull();
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -2860,6 +2878,12 @@
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onFinishing();
verify(callback, times(2))
.onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2886,6 +2910,12 @@
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onStarted(any(IVibrationSession.class));
verify(callback, never()).onFinishing();
verify(callback)
@@ -2923,6 +2953,12 @@
assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -2949,6 +2985,12 @@
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -3038,6 +3080,11 @@
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -3340,6 +3387,10 @@
assertThat(service.isVibrating(2)).isFalse();
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(1));
}
@Test
@@ -3391,6 +3442,10 @@
assertThat(service.isVibrating(2)).isFalse();
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(2));
}
@Test
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
index 6dba967..a9a1f93 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -76,7 +76,7 @@
var apiconfig = new SoundTrigger.RecognitionConfig.Builder()
.setCaptureRequested(true)
- .setAllowMultipleTriggers(false) // must be false
+ .setMultipleTriggersAllowed(false) // must be false
.setKeyphrases(keyphrases)
.setData(data)
.setAudioCapabilities(flags)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9408f90..9cbea2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -601,12 +601,12 @@
TYPE_WALLPAPER, TYPE_APPLICATION);
// Verify not waiting for display without system decorations.
- doReturn(false).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(false).when(secondaryDisplay).isSystemDecorationsSupported();
assertFalse(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify waiting for non-drawn windows on display with system decorations.
reset(secondaryDisplay);
- doReturn(true).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(true).when(secondaryDisplay).isSystemDecorationsSupported();
assertTrue(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify not waiting for drawn windows on display with system decorations.
@@ -1865,7 +1865,6 @@
mRootWindowContainer.getDisplayRotationCoordinator();
final DisplayContent defaultDisplayContent = mDisplayContent;
final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -1922,7 +1921,6 @@
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -2263,25 +2261,25 @@
}
@Test
- public void testForceDesktopMode() {
+ public void testIsPublicSecondaryDisplayWithDesktopModeForceEnabled() {
mWm.mForceDesktopModeOnExternalDisplays = true;
// Not applicable for default display
- assertFalse(mDefaultDisplay.forceDesktopMode());
+ assertFalse(mDefaultDisplay.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Not applicable for private secondary display.
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.flags = FLAG_PRIVATE;
final DisplayContent privateDc = createNewDisplay(displayInfo);
- assertFalse(privateDc.forceDesktopMode());
+ assertFalse(privateDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Applicable for public secondary display.
final DisplayContent publicDc = createNewDisplay();
- assertTrue(publicDc.forceDesktopMode());
+ assertTrue(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Make sure forceDesktopMode() is false when the force config is disabled.
mWm.mForceDesktopModeOnExternalDisplays = false;
- assertFalse(publicDc.forceDesktopMode());
+ assertFalse(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
index 4557df0..266ffff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
@@ -40,6 +40,9 @@
@Presubmit
public class DisplayRotationCoordinatorTests {
+ private static final int FIRST_DISPLAY_ID = 1;
+ private static final int SECOND_DISPLAY_ID = 2;
+
@NonNull
private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
@@ -50,22 +53,45 @@
}
@Test (expected = UnsupportedOperationException.class)
- public void testSecondRegistrationWithoutRemovingFirst() {
+ public void testSecondRegistrationWithoutRemovingFirstWhenDifferentDisplay() {
Runnable callback1 = mock(Runnable.class);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
}
@Test
+ public void testSecondRegistrationWithoutRemovingFirstWhenSameDisplay() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
+ public void testRemoveIncorrectRegistration() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback2);
+ assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
+
+ // FIRST_DISPLAY_ID is still able to register another callback because the previous
+ // removal should not have succeeded.
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
public void testSecondRegistrationAfterRemovingFirst() {
Runnable callback1 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback1);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
verify(callback2).run();
@@ -75,7 +101,7 @@
@Test
public void testRegisterThenDefaultDisplayRotationChanged() {
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
verify(callback, never()).run();
@@ -88,7 +114,7 @@
public void testDefaultDisplayRotationChangedThenRegister() {
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
verify(callback).run();
assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index b595383..f795d93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -605,7 +605,7 @@
@Test
public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
DisplayContent display = createNewDisplay();
- doReturn(true).when(display).supportsSystemDecorations();
+ doReturn(true).when(display).isSystemDecorationsSupported();
// Remove the current home root task if it exists so a new one can be created below.
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
@@ -622,7 +622,7 @@
public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
DisplayContent display = createNewDisplay();
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
- doReturn(false).when(display).supportsSystemDecorations();
+ doReturn(false).when(display).isSystemDecorationsSupported();
assertNull(taskDisplayArea.getRootHomeTask());
assertNull(taskDisplayArea.getOrCreateRootHomeTask());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 546b1ba..ccce57a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -204,13 +204,13 @@
final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy();
spyOn(displayPolicy);
if (mSystemDecorations) {
- doReturn(true).when(newDisplay).supportsSystemDecorations();
+ doReturn(true).when(newDisplay).isSystemDecorationsSupported();
doReturn(true).when(displayPolicy).hasNavigationBar();
doReturn(true).when(displayPolicy).hasBottomNavigationBar();
} else {
doReturn(false).when(displayPolicy).hasNavigationBar();
doReturn(false).when(displayPolicy).hasStatusBar();
- doReturn(false).when(newDisplay).supportsSystemDecorations();
+ doReturn(false).when(newDisplay).isSystemDecorationsSupported();
}
// Update the display policy to make the screen fully turned on so animation is allowed
displayPolicy.screenTurningOn(null /* screenOnListener */);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index fb031bd..01ff674 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -842,7 +842,7 @@
return;
}
- model.setRequested(config.isAllowMultipleTriggers());
+ model.setRequested(config.isMultipleTriggersAllowed());
// TODO: Remove this block if the lower layer supports multiple triggers.
if (model.isRequested()) {
updateRecognitionLocked(model, true);
@@ -964,7 +964,7 @@
RecognitionConfig config = modelData.getRecognitionConfig();
if (config != null) {
// Whether we should continue by starting this again.
- modelData.setRequested(config.isAllowMultipleTriggers());
+ modelData.setRequested(config.isMultipleTriggersAllowed());
}
// TODO: Remove this block if the lower layer supports multiple triggers.
if (modelData.isRequested()) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 19a6ddc..e0cdbdd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1445,7 +1445,7 @@
runOrAddOperation(new Operation(
// always execute:
() -> {
- if (!mRecognitionConfig.isAllowMultipleTriggers()) {
+ if (!mRecognitionConfig.isMultipleTriggersAllowed()) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ad5d42a..024d7f5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5251,6 +5251,36 @@
}
/**
+ * Returns the Group Identifier Level 2 in hexadecimal format.
+ * @return the Group Identifier Level 2 for the SIM card.
+ * Return null if it is unavailable.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_GET_GROUP_ID_LEVEL2)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @SystemApi
+ @Nullable
+ public String getGroupIdLevel2() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ return null;
+ }
+ return info.getGroupIdLevel2ForSubscriber(getSubId(), mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ // This could happen before phone restarts due to crashing
+ return null;
+ }
+ }
+
+ /**
* Returns the phone number string for line 1, for example, the MSISDN
* for a GSM phone for a particular subscription. Return null if it is unavailable.
* <p>
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 974cc14..71327dc 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -83,6 +83,12 @@
String getGroupIdLevel1ForSubscriber(int subId, String callingPackage,
String callingFeatureId);
+ /**
+ * Retrieves the Group Identifier Level1 for GSM phones of a subId.
+ */
+ String getGroupIdLevel2ForSubscriber(int subId, String callingPackage,
+ String callingFeatureId);
+
/** @deprecared Use {@link getIccSerialNumberWithFeature(String, String)} instead */
@UnsupportedAppUsage
String getIccSerialNumber(String callingPackage);
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
new file mode 100644
index 0000000..6573c2c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.testapp.ActivityOptions
+
+class BottomHalfPipAppHelper(
+ instrumentation: Instrumentation,
+ private val useLaunchingActivity: Boolean = false,
+) : PipAppHelper(
+ instrumentation,
+ appName = ActivityOptions.BottomHalfPip.LABEL,
+ componentNameMatcher = ActivityOptions.BottomHalfPip.COMPONENT
+ .toFlickerComponent()
+) {
+ override val openAppIntent: Intent
+ get() = super.openAppIntent.apply {
+ component = if (useLaunchingActivity) {
+ ActivityOptions.BottomHalfPip.LAUNCHING_APP_COMPONENT
+ } else {
+ ActivityOptions.BottomHalfPip.COMPONENT
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 9ce8e80..7c24a4a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -347,6 +347,27 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".BottomHalfPipLaunchingActivity"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="BottomHalfPipLaunchingActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".BottomHalfPipActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+ android:theme="@style/TranslucentTheme"
+ android:label="BottomHalfPipActivity"
+ android:exported="true">
+ </activity>
<activity android:name=".SplitScreenActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 47d1137..837d050 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -62,6 +62,12 @@
<item name="android:backgroundDimEnabled">false</item>
</style>
+ <style name="TranslucentTheme" parent="@style/OptOutEdgeToEdge">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
<item name="android:windowDisablePreview">true</item>
</style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 73625da..0c1ac99 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -241,6 +241,21 @@
FLICKER_APP_PACKAGE + ".PipActivity");
}
+ public static class BottomHalfPip {
+ public static final String LAUNCHING_APP_LABEL = "BottomHalfPipLaunchingActivity";
+ // Test App > Bottom Half PIP Activity
+ public static final String LABEL = "BottomHalfPipActivity";
+
+ // Use the bottom half layout for PIP Activity
+ public static final String EXTRA_BOTTOM_HALF_LAYOUT = "bottom_half";
+
+ public static final ComponentName LAUNCHING_APP_COMPONENT = new ComponentName(
+ FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".BottomHalfPipLaunchingActivity");
+
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".BottomHalfPipActivity");
+ }
+
public static class SplitScreen {
public static class Primary {
public static final String LABEL = "SplitScreenPrimaryActivity";
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
new file mode 100644
index 0000000..3d48655
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.flicker.testapp;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+public class BottomHalfPipActivity extends PipActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTheme(R.style.TranslucentTheme);
+ updateLayout();
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateLayout();
+ }
+
+ /**
+ * Sets to match parent layout if the activity is
+ * {@link Activity#isInPictureInPictureMode()}. Otherwise, set to bottom half
+ * layout.
+ *
+ * @see #setToBottomHalfMode(boolean)
+ */
+ private void updateLayout() {
+ setToBottomHalfMode(!isInPictureInPictureMode());
+ }
+
+ /**
+ * Sets `useBottomHalfLayout` to `true` to use the bottom half layout. Use the
+ * [LayoutParams.MATCH_PARENT] layout.
+ */
+ private void setToBottomHalfMode(boolean useBottomHalfLayout) {
+ final WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ if (useBottomHalfLayout) {
+ final int taskHeight = getWindowManager().getCurrentWindowMetrics().getBounds()
+ .height();
+ attrs.y = taskHeight / 2;
+ attrs.height = taskHeight / 2;
+ } else {
+ attrs.y = 0;
+ attrs.height = LayoutParams.MATCH_PARENT;
+ }
+ getWindow().setAttributes(attrs);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
new file mode 100644
index 0000000..d9d4361
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.flicker.testapp;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+public class BottomHalfPipLaunchingActivity extends SimpleActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = new Intent(this, BottomHalfPipActivity.class);
+ startActivity(intent);
+ }
+}