Merge "m3: Refine AlertDialog style" 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 1a34781..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
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 52d13d1..612a48a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5254,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 {
@@ -7192,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;
}
@@ -7201,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 {
@@ -16271,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/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/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/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 d318b62..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 {
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 cb6db6c..3133020 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
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;
@@ -1209,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;
@@ -1338,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) {
@@ -7112,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();
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/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index cbc20dc..26ff430 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -543,7 +543,7 @@
mRunningTrackers.put(cuj, tracker);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerAdded(cuj);
+ mDebugOverlay.onTrackerAdded(cuj, tracker.mTracker.hashCode());
}
return tracker;
@@ -578,7 +578,7 @@
running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
mRunningTrackers.remove(cuj);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerRemoved(cuj, reason);
+ mDebugOverlay.onTrackerRemoved(cuj, reason, tracker.hashCode());
}
return false;
}
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index 009837b..97f8879 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -41,13 +41,14 @@
import android.os.Trace;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseIntArray;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import com.android.internal.jank.FrameTracker.Reasons;
+import java.util.ArrayList;
+
/**
* An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
* associated with one of the CUJs being tracked. There's no guarantee which window it will
@@ -68,13 +69,14 @@
class InteractionMonitorDebugOverlay {
private static final String TAG = "InteractionMonitorDebug";
private static final int REASON_STILL_RUNNING = -1000;
+ private static final long HIDE_OVERLAY_DELAY = 2000L;
// Sparse array where the key in the CUJ and the value is the session status, or null if
// it's currently running
private final Application mCurrentApplication;
private final Handler mUiThread;
private final DebugOverlayView mDebugOverlayView;
private final WindowManager mWindowManager;
- private final SparseIntArray mRunningCujs = new SparseIntArray();
+ private final ArrayList<TrackerState> mRunningCujs = new ArrayList<>();
InteractionMonitorDebugOverlay(@NonNull Application currentApplication,
@NonNull @UiThread Handler uiThread, @ColorInt int bgColor, double yOffset) {
@@ -94,8 +96,7 @@
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
- PixelFormat.TRANSLUCENT);
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -116,30 +117,53 @@
mWindowManager.addView(mDebugOverlayView, lp);
}
+ private final Runnable mHideOverlayRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mRunningCujs.clear();
+ mDebugOverlayView.setVisibility(INVISIBLE);
+ }
+ };
+
@AnyThread
- void onTrackerAdded(@Cuj.CujType int addedCuj) {
+ void onTrackerAdded(@Cuj.CujType int addedCuj, int cookie) {
+ mUiThread.removeCallbacks(mHideOverlayRunnable);
mUiThread.post(() -> {
String cujName = Cuj.getNameOfCuj(addedCuj);
- Log.i(TAG, cujName + " started");
- // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
- // is still running
- mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
+ Log.i(TAG, cujName + " started (cookie=" + cookie + ")");
+ mRunningCujs.add(new TrackerState(addedCuj, cookie));
mDebugOverlayView.setVisibility(VISIBLE);
mDebugOverlayView.invalidate();
});
}
@AnyThread
- void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason) {
+ void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason, int cookie) {
mUiThread.post(() -> {
- mRunningCujs.put(removedCuj, reason);
+ TrackerState foundTracker = null;
+ boolean allTrackersEnded = true;
+ for (int i = 0; i < mRunningCujs.size(); i++) {
+ TrackerState tracker = mRunningCujs.get(i);
+ if (tracker.mCuj == removedCuj && tracker.mCookie == cookie) {
+ foundTracker = tracker;
+ } else {
+ // If none of the trackers have REASON_STILL_RUNNING status, then
+ // all CUJs have ended
+ allTrackersEnded = allTrackersEnded && tracker.mState != REASON_STILL_RUNNING;
+ }
+ }
+
+ if (foundTracker != null) {
+ foundTracker.mState = reason;
+ }
+
String cujName = Cuj.getNameOfCuj(removedCuj);
- Log.i(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
- // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
- if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
+ Log.i(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled")
+ + " (cookie=" + cookie + ")");
+
+ if (allTrackersEnded) {
Log.i(TAG, "All CUJs ended");
- mRunningCujs.clear();
- mDebugOverlayView.setVisibility(INVISIBLE);
+ mUiThread.postDelayed(mHideOverlayRunnable, HIDE_OVERLAY_DELAY);
}
mDebugOverlayView.invalidate();
});
@@ -152,6 +176,21 @@
});
}
+ @AnyThread
+ private static class TrackerState {
+ final int mCookie;
+ final int mCuj;
+ int mState;
+
+ private TrackerState(int cuj, int cookie) {
+ mCuj = cuj;
+ mCookie = cookie;
+ // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
+ // is still running
+ mState = REASON_STILL_RUNNING;
+ }
+ }
+
@UiThread
private class DebugOverlayView extends View {
private static final String TRACK_NAME = "InteractionJankMonitor";
@@ -164,7 +203,15 @@
private final float mDensity;
private final Paint mDebugPaint;
private final Paint.FontMetrics mDebugFontMetrics;
- private final String mPackageName;
+ private final String mPackageNameText;
+
+ final int mPadding;
+ final int mPackageNameFontSize;
+ final int mCujFontSize;
+ final float mCujNameTextHeight;
+ final float mCujStatusWidth;
+ final float mPackageNameTextHeight;
+ final float mPackageNameWidth;
private DebugOverlayView(Context context, @ColorInt int bgColor, double yOffset) {
super(context);
@@ -176,7 +223,14 @@
mDebugPaint = new Paint();
mDebugPaint.setAntiAlias(false);
mDebugFontMetrics = new Paint.FontMetrics();
- mPackageName = mCurrentApplication.getPackageName();
+ mPackageNameText = "package:" + mCurrentApplication.getPackageName();
+ mPadding = dipToPx(5);
+ mPackageNameFontSize = dipToPx(12);
+ mCujFontSize = dipToPx(18);
+ mCujNameTextHeight = getTextHeight(mCujFontSize);
+ mCujStatusWidth = mCujNameTextHeight * 1.2f;
+ mPackageNameTextHeight = getTextHeight(mPackageNameFontSize);
+ mPackageNameWidth = getWidthOfText(mPackageNameText, mPackageNameFontSize);
}
private int dipToPx(int dip) {
@@ -189,11 +243,16 @@
return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
}
+ private float getWidthOfText(String text, int fontSize) {
+ mDebugPaint.setTextSize(fontSize);
+ return mDebugPaint.measureText(text);
+ }
+
private float getWidthOfLongestCujName(int cujFontSize) {
mDebugPaint.setTextSize(cujFontSize);
float maxLength = 0;
for (int i = 0; i < mRunningCujs.size(); i++) {
- String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
+ String cujName = Cuj.getNameOfCuj(mRunningCujs.get(i).mCuj);
float textLength = mDebugPaint.measureText(cujName);
if (textLength > maxLength) {
maxLength = textLength;
@@ -211,47 +270,52 @@
// performance analysis.
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
- final int padding = dipToPx(5);
final int h = getHeight();
final int w = getWidth();
final int dy = (int) (h * mYOffset);
- int packageNameFontSize = dipToPx(12);
- int cujFontSize = dipToPx(18);
- final float cujNameTextHeight = getTextHeight(cujFontSize);
- final float packageNameTextHeight = getTextHeight(packageNameFontSize);
- float maxLength = getWidthOfLongestCujName(cujFontSize);
+
+ float maxLength = Math.max(mPackageNameWidth, getWidthOfLongestCujName(mCujFontSize))
+ + mCujStatusWidth;
final int dx = (int) ((w - maxLength) / 2f);
canvas.translate(dx, dy);
// Draw background rectangle for displaying the text showing the CUJ name
mDebugPaint.setColor(mBgColor);
- canvas.drawRect(-padding * 2, // more padding on top so we can draw the package name
- -padding, padding * 2 + maxLength,
- padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
- mDebugPaint);
- mDebugPaint.setTextSize(packageNameFontSize);
+ canvas.drawRect(-mPadding * 2, // more padding on top so we can draw the package name
+ -mPadding, mPadding * 2 + maxLength, mPadding * 2 + mPackageNameTextHeight
+ + mCujNameTextHeight * mRunningCujs.size(), mDebugPaint);
+ mDebugPaint.setTextSize(mPackageNameFontSize);
mDebugPaint.setColor(Color.BLACK);
mDebugPaint.setStrikeThruText(false);
- canvas.translate(0, packageNameTextHeight);
- canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
- mDebugPaint.setTextSize(cujFontSize);
+ canvas.translate(0, mPackageNameTextHeight);
+ canvas.drawText(mPackageNameText, 0, 0, mDebugPaint);
+ mDebugPaint.setTextSize(mCujFontSize);
// Draw text for CUJ names
for (int i = 0; i < mRunningCujs.size(); i++) {
- int status = mRunningCujs.valueAt(i);
- if (status == REASON_STILL_RUNNING) {
- mDebugPaint.setColor(Color.BLACK);
- mDebugPaint.setStrikeThruText(false);
- } else if (status == REASON_END_NORMAL) {
- mDebugPaint.setColor(Color.GRAY);
- mDebugPaint.setStrikeThruText(false);
- } else {
- // Cancelled, or otherwise ended for a bad reason
- mDebugPaint.setColor(Color.RED);
- mDebugPaint.setStrikeThruText(true);
- }
- String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
- canvas.translate(0, cujNameTextHeight);
- canvas.drawText(cujName, 0, 0, mDebugPaint);
+ TrackerState tracker = mRunningCujs.get(i);
+ int status = tracker.mState;
+ String statusText = switch (status) {
+ case REASON_STILL_RUNNING -> {
+ mDebugPaint.setColor(Color.BLACK);
+ mDebugPaint.setStrikeThruText(false);
+ yield "☐"; // BALLOT BOX
+ }
+ case REASON_END_NORMAL -> {
+ mDebugPaint.setColor(Color.GRAY);
+ mDebugPaint.setStrikeThruText(false);
+ yield "✅"; // WHITE HEAVY CHECK MARK
+ }
+ default -> {
+ // Cancelled, or otherwise ended for a bad reason
+ mDebugPaint.setColor(Color.RED);
+ mDebugPaint.setStrikeThruText(true);
+ yield "❌"; // CROSS MARK
+ }
+ };
+ String cujName = Cuj.getNameOfCuj(tracker.mCuj);
+ canvas.translate(0, mCujNameTextHeight);
+ canvas.drawText(statusText, 0, 0, mDebugPaint);
+ canvas.drawText(cujName, mCujStatusWidth, 0, mDebugPaint);
}
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 324e84c..c953d88 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -25,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Flags;
+import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Process;
import android.util.ArrayMap;
@@ -246,12 +247,13 @@
negated = true;
featureFlag = featureFlag.substring(1).strip();
}
- Boolean flagValue = getFlagValue(featureFlag);
- if (flagValue == null) {
- flagValue = false;
- }
+ final Boolean flagValue = getFlagValue(featureFlag);
boolean shouldSkip = false;
- if (flagValue == negated) {
+ if (flagValue == null) {
+ Slog.w(LOG_TAG, "Skipping element " + parser.getName()
+ + " due to unknown feature flag " + featureFlag);
+ shouldSkip = true;
+ } else if (flagValue == negated) {
// Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
Slog.i(LOG_TAG, "Skipping element " + parser.getName()
+ " behind feature flag " + featureFlag + " = " + flagValue);
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 9f77999..bc2daf2 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 01c2e9c..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>
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 0701128..778d9f9 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -157,6 +157,30 @@
</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">
@@ -208,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/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2c46860..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;
@@ -854,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/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/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/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 077d7d3..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
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/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/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/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/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/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/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/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/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/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/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);
+ }
+}