Merge "Tweak transition logs to make them a bit easier to follow" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 605ce22..6d74a84 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -16,6 +16,7 @@
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
+ ":android.content.res.flags-aconfig-java{.generated_srcjars}",
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
@@ -306,6 +307,19 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Resources
+aconfig_declarations {
+ name: "android.content.res.flags-aconfig",
+ package: "android.content.res",
+ srcs: ["core/java/android/content/res/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.content.res.flags-aconfig-java",
+ aconfig_declarations: "android.content.res.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media BetterTogether
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 7142eb5..e162100 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -131,13 +131,7 @@
defaults: ["framework-doc-stubs-sources-default"],
args: metalava_framework_docs_args +
" --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
- api_levels_sdk_type: "system",
- extensions_info_file: ":sdk-extensions-info",
+ api_levels_module: "api_versions_system",
}
/////////////////////////////////////////////////////////////////////
diff --git a/core/api/current.txt b/core/api/current.txt
index f6564ec..66aeb0f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10571,7 +10571,7 @@
public final class ContextParams {
method @Nullable public String getAttributionTag();
method @Nullable public android.content.AttributionSource getNextAttributionSource();
- method @NonNull public boolean shouldRegisterAttributionSource();
+ method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource();
}
public static final class ContextParams.Builder {
@@ -10580,7 +10580,7 @@
method @NonNull public android.content.ContextParams build();
method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource);
- method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
+ method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
}
public class ContextWrapper extends android.content.Context {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c72d09d..5d1f6dc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -312,7 +312,7 @@
field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
- field @FlaggedApi("backstage_power.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
+ field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -16741,6 +16741,23 @@
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
}
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable {
+ ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getLevel();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.NtnSignalStrength> CREATOR;
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback {
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength);
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees();
@@ -16776,6 +16793,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
@@ -16786,6 +16804,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -16794,6 +16813,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index d1f9067..f401a76 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -2,7 +2,7 @@
flag {
name: "user_interaction_type_api"
- namespace: "power_optimization"
+ namespace: "backstage_power"
description: "Feature flag for user interaction event report/query API"
bug: "296061232"
}
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index 988a9c0..b844d35 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -16,7 +16,10 @@
package android.content;
+import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE;
+
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -102,6 +105,7 @@
* registered.
*/
@NonNull
+ @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
public boolean shouldRegisterAttributionSource() {
return mShouldRegisterAttributionSource;
}
@@ -179,6 +183,7 @@
* created should be registered.
*/
@NonNull
+ @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {
mShouldRegisterAttributionSource = shouldRegister;
return this;
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
index 7ab7ed1..74953ff 100644
--- a/core/java/android/content/pm/ArchivedActivityParcel.aidl
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -16,9 +16,12 @@
package android.content.pm;
+import android.content.ComponentName;
+
/** @hide */
parcelable ArchivedActivityParcel {
String title;
+ ComponentName originalComponentName;
// PNG compressed bitmaps.
byte[] iconBitmap;
byte[] monochromeIconBitmap;
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
new file mode 100644
index 0000000..0c2c0f4
--- /dev/null
+++ b/core/java/android/content/res/flags.aconfig
@@ -0,0 +1,10 @@
+package: "android.content.res"
+
+flag {
+ name: "default_locale"
+ namespace: "resource_manager"
+ description: "Feature flag for default locale in LocaleConfig"
+ bug: "117306409"
+ # fixed_read_only or device wont boot because of permission issues accessing flags during boot
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index f033f97..bcf447b 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -16,6 +16,7 @@
package android.hardware;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -27,6 +28,8 @@
import android.util.Log;
import android.util.SparseArray;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -1809,6 +1812,41 @@
protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
Sensor sensor, boolean disable);
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION})
+ public @interface DataInjectionMode {}
+ /**
+ * This mode is only used for testing purposes. Not all HALs support this mode. In this mode,
+ * the HAL ignores the sensor data provided by physical sensors and accepts the data that is
+ * injected from the SensorService as if it were the real sensor data. This mode is primarily
+ * used for testing various algorithms like vendor provided SensorFusion, Step Counter and
+ * Step Detector etc. Typically, in this mode, there is a client app which injects
+ * sensor data into the HAL. Normal apps can register and unregister for any sensor
+ * that supports injection. Registering to sensors that do not support injection will
+ * give an error.
+ * This is the default data injection mode.
+ * @hide
+ */
+ public static final int DATA_INJECTION = 1;
+ /**
+ * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is
+ * delivered to all requesting apps rather than just the package allowed to inject data.
+ * This mode is only allowed to be used on development builds.
+ * @hide
+ */
+ public static final int REPLAY_DATA_INJECTION = 3;
+ /**
+ * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+ * buffer in the platform and played back to all requesting apps.
+ * This is useful for playing back sensor data to test platform components without
+ * relying on the HAL to support data injection.
+ * @hide
+ */
+ public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
+
/**
* For testing purposes only. Not for third party applications.
@@ -1833,13 +1871,47 @@
*/
@SystemApi
public boolean initDataInjection(boolean enable) {
- return initDataInjectionImpl(enable);
+ return initDataInjectionImpl(enable, DATA_INJECTION);
+ }
+
+ /**
+ * For testing purposes only. Not for third party applications.
+ *
+ * Initialize data injection mode and create a client for data injection. SensorService should
+ * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or
+ * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in
+ * a Data Injection mode, use one of:
+ *
+ * <ul>
+ * <li>adb shell dumpsys sensorservice data_injection</li>
+ * <li>adb shell dumpsys sensorservice replay_data_injection package_name</li>
+ * <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li>
+ * </ul>
+ *
+ * Typically this is done using a host side test. This mode is expected to be used
+ * only for testing purposes. See {@link DataInjectionMode} for details of each data injection
+ * mode. Once this method succeeds, the test can call
+ * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL.
+ * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable"
+ *
+ * @param enable True to initialize a client in a data injection mode.
+ * False to clean up the native resources.
+ *
+ * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION.
+ * See {@link DataInjectionMode} for details.
+ *
+ * @return true if the HAL supports data injection and false
+ * otherwise.
+ * @hide
+ */
+ public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) {
+ return initDataInjectionImpl(enable, mode);
}
/**
* @hide
*/
- protected abstract boolean initDataInjectionImpl(boolean enable);
+ protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode);
/**
* For testing purposes only. Not for third party applications.
@@ -1871,9 +1943,6 @@
if (sensor == null) {
throw new IllegalArgumentException("sensor cannot be null");
}
- if (!sensor.isDataInjectionSupported()) {
- throw new IllegalArgumentException("sensor does not support data injection");
- }
if (values == null) {
throw new IllegalArgumentException("sensor data cannot be null");
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index dfd3802..40e03db 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -90,6 +90,8 @@
private static native void nativeGetRuntimeSensors(
long nativeInstance, int deviceId, List<Sensor> list);
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
+ private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance);
+ private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance);
private static native int nativeCreateDirectChannel(
long nativeInstance, int deviceId, long size, int channelType, int fd,
@@ -384,20 +386,41 @@
}
}
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
synchronized (sLock) {
+ boolean isDataInjectionModeEnabled = false;
if (enable) {
- boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+ switch (mode) {
+ case DATA_INJECTION:
+ isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+ break;
+ case REPLAY_DATA_INJECTION:
+ isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled(
+ mNativeInstance);
+ break;
+ case HAL_BYPASS_REPLAY_DATA_INJECTION:
+ isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled(
+ mNativeInstance);
+ break;
+ default:
+ break;
+ }
// The HAL does not support injection OR SensorService hasn't been set in DI mode.
if (!isDataInjectionModeEnabled) {
- Log.e(TAG, "Data Injection mode not enabled");
+ Log.e(TAG, "The correct Data Injection mode has not been enabled");
return false;
}
+ if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) {
+ // The inject event queue has been initialized for a different type of DI
+ // close it and create a new one
+ sInjectEventQueue.dispose();
+ sInjectEventQueue = null;
+ }
// Initialize a client for data_injection.
if (sInjectEventQueue == null) {
try {
sInjectEventQueue = new InjectEventQueue(
- mMainLooper, this, mContext.getPackageName());
+ mMainLooper, this, mode, mContext.getPackageName());
} catch (RuntimeException e) {
Log.e(TAG, "Cannot create InjectEventQueue: " + e);
}
@@ -421,6 +444,12 @@
Log.e(TAG, "Data injection mode not activated before calling injectSensorData");
return false;
}
+ if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION
+ && !sensor.isDataInjectionSupported()) {
+ // DI mode and Replay DI mode require support from the sensor HAL
+ // HAL Bypass mode doesn't require this.
+ throw new IllegalArgumentException("sensor does not support data injection");
+ }
int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy,
timestamp);
// If there are any errors in data injection clean up the native resources.
@@ -825,6 +854,8 @@
protected static final int OPERATING_MODE_NORMAL = 0;
protected static final int OPERATING_MODE_DATA_INJECTION = 1;
+ protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3;
+ protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
if (packageName == null) packageName = "";
@@ -1134,8 +1165,12 @@
}
final class InjectEventQueue extends BaseEventQueue {
- public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) {
- super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
+
+ private int mMode;
+ public InjectEventQueue(Looper looper, SystemSensorManager manager,
+ @DataInjectionMode int mode, String packageName) {
+ super(looper, manager, mode, packageName);
+ mMode = mode;
}
int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
@@ -1161,6 +1196,10 @@
protected void removeSensorEvent(Sensor sensor) {
}
+
+ int getDataInjectionMode() {
+ return mMode;
+ }
}
protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0221296..02304b5 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -1074,6 +1074,14 @@
*/
public interface FaceDetectionCallback {
void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+ /**
+ * An error has occurred with face detection.
+ *
+ * This callback signifies that this operation has been completed, and
+ * no more callbacks should be expected.
+ */
+ default void onDetectionError(int errorMsgId) {}
}
/**
@@ -1373,6 +1381,9 @@
} else if (mRemovalCallback != null) {
mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
getErrorString(mContext, errMsgId, vendorCode));
+ } else if (mFaceDetectionCallback != null) {
+ mFaceDetectionCallback.onDetectionError(errMsgId);
+ mFaceDetectionCallback = null;
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5bfda70..935157a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -462,6 +462,14 @@
* Invoked when a fingerprint has been detected.
*/
void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+ /**
+ * An error has occurred with fingerprint detection.
+ *
+ * This callback signifies that this operation has been completed, and
+ * no more callbacks should be expected.
+ */
+ default void onDetectionError(int errorMsgId) {}
}
/**
@@ -1484,6 +1492,9 @@
? mRemoveTracker.mSingleFingerprint : null;
mRemovalCallback.onRemovalError(fp, clientErrMsgId,
getErrorString(mContext, errMsgId, vendorCode));
+ } else if (mFingerprintDetectionCallback != null) {
+ mFingerprintDetectionCallback.onDetectionError(errMsgId);
+ mFingerprintDetectionCallback = null;
}
}
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index aa55e54..05024ea 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -644,7 +644,7 @@
}
@Override
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, int mode) {
return false;
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 7fceda4..b7f2e06 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -16,7 +16,7 @@
flag {
name: "remove_app_profiler_pss_collection"
- namespace: "power_optimization"
+ namespace: "backstage_power"
description: "Replaces background PSS collection in AppProfiler with RSS"
bug: "297542292"
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d60d4c6..3f06a91 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -28,3 +28,10 @@
description: "enable AttributionSource.setNextAttributionSource"
bug: "304478648"
}
+
+flag {
+ name: "should_register_attribution_source"
+ namespace: "permissions"
+ description: "enable the shouldRegisterAttributionSource API"
+ bug: "305057691"
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 3f41c56..d280621 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -520,7 +520,7 @@
@NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+ // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -546,6 +546,10 @@
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale,
+ // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the
+ // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -612,6 +616,11 @@
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+ // AlwaysOnHotwordDetector.Callback)} and replace with the permission
+ // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
+
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
/* modulProperties */ null, /* executor= */ null, callback);
@@ -663,7 +672,11 @@
@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+ // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+ // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission
+ // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -690,6 +703,10 @@
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle,
+ // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+ // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2906d86..766e924 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -200,6 +200,12 @@
public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
"settings_remote_device_credential_validation";
+ /**
+ * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine".
+ * @hide
+ */
+ public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE =
+ "settings_treat_pause_as_quarantine";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -247,6 +253,7 @@
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
// TODO: b/298454866 Replace with Trunk Stable Feature Flag
DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
+ DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
@@ -264,6 +271,7 @@
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
+ PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);
}
/**
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index a89f795..dc41b70 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -52,6 +52,12 @@
*/
public static final int RECORD_CONTENT_TASK = 1;
+ /** Full screen sharing (app is not selected). */
+ public static final int TARGET_UID_FULL_SCREEN = -1;
+
+ /** Can't report (e.g. side loaded app). */
+ public static final int TARGET_UID_UNKNOWN = -2;
+
/**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
@@ -89,27 +95,36 @@
*/
private boolean mWaitingForConsent = false;
+ /** UID of the package that is captured if selected. */
+ private int mTargetUid = TARGET_UID_UNKNOWN;
+
/**
* Default instance, with recording the display.
*/
private ContentRecordingSession() {
}
- /**
- * Returns an instance initialized for recording the indicated display.
- */
+ /** Returns an instance initialized for recording the indicated display. */
public static ContentRecordingSession createDisplaySession(int displayToMirror) {
- return new ContentRecordingSession().setDisplayToRecord(displayToMirror)
- .setContentToRecord(RECORD_CONTENT_DISPLAY);
+ return new ContentRecordingSession()
+ .setDisplayToRecord(displayToMirror)
+ .setContentToRecord(RECORD_CONTENT_DISPLAY)
+ .setTargetUid(TARGET_UID_FULL_SCREEN);
}
- /**
- * Returns an instance initialized for task recording.
- */
+ /** Returns an instance initialized for task recording. */
public static ContentRecordingSession createTaskSession(
@NonNull IBinder taskWindowContainerToken) {
- return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK)
- .setTokenToRecord(taskWindowContainerToken);
+ return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN);
+ }
+
+ /** Returns an instance initialized for task recording. */
+ public static ContentRecordingSession createTaskSession(
+ @NonNull IBinder taskWindowContainerToken, int targetUid) {
+ return new ContentRecordingSession()
+ .setContentToRecord(RECORD_CONTENT_TASK)
+ .setTokenToRecord(taskWindowContainerToken)
+ .setTargetUid(targetUid);
}
/**
@@ -175,13 +190,33 @@
}
}
+ @IntDef(prefix = "TARGET_UID_", value = {
+ TARGET_UID_FULL_SCREEN,
+ TARGET_UID_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TargetUid {}
+
+ @DataClass.Generated.Member
+ public static String targetUidToString(@TargetUid int value) {
+ switch (value) {
+ case TARGET_UID_FULL_SCREEN:
+ return "TARGET_UID_FULL_SCREEN";
+ case TARGET_UID_UNKNOWN:
+ return "TARGET_UID_UNKNOWN";
+ default: return Integer.toHexString(value);
+ }
+ }
+
@DataClass.Generated.Member
/* package-private */ ContentRecordingSession(
int virtualDisplayId,
@RecordContent int contentToRecord,
int displayToRecord,
@Nullable IBinder tokenToRecord,
- boolean waitingForConsent) {
+ boolean waitingForConsent,
+ int targetUid) {
this.mVirtualDisplayId = virtualDisplayId;
this.mContentToRecord = contentToRecord;
@@ -196,6 +231,7 @@
this.mDisplayToRecord = displayToRecord;
this.mTokenToRecord = tokenToRecord;
this.mWaitingForConsent = waitingForConsent;
+ this.mTargetUid = targetUid;
// onConstructed(); // You can define this method to get a callback
}
@@ -251,6 +287,14 @@
}
/**
+ * UID of the package that is captured if selected.
+ */
+ @DataClass.Generated.Member
+ public int getTargetUid() {
+ return mTargetUid;
+ }
+
+ /**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@@ -314,6 +358,15 @@
return this;
}
+ /**
+ * UID of the package that is captured if selected.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ContentRecordingSession setTargetUid( int value) {
+ mTargetUid = value;
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -325,7 +378,8 @@
"contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
"displayToRecord = " + mDisplayToRecord + ", " +
"tokenToRecord = " + mTokenToRecord + ", " +
- "waitingForConsent = " + mWaitingForConsent +
+ "waitingForConsent = " + mWaitingForConsent + ", " +
+ "targetUid = " + mTargetUid +
" }";
}
@@ -346,7 +400,8 @@
&& mContentToRecord == that.mContentToRecord
&& mDisplayToRecord == that.mDisplayToRecord
&& java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord)
- && mWaitingForConsent == that.mWaitingForConsent;
+ && mWaitingForConsent == that.mWaitingForConsent
+ && mTargetUid == that.mTargetUid;
}
@Override
@@ -361,6 +416,7 @@
_hash = 31 * _hash + mDisplayToRecord;
_hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
_hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent);
+ _hash = 31 * _hash + mTargetUid;
return _hash;
}
@@ -378,6 +434,7 @@
dest.writeInt(mContentToRecord);
dest.writeInt(mDisplayToRecord);
if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord);
+ dest.writeInt(mTargetUid);
}
@Override
@@ -397,6 +454,7 @@
int contentToRecord = in.readInt();
int displayToRecord = in.readInt();
IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder();
+ int targetUid = in.readInt();
this.mVirtualDisplayId = virtualDisplayId;
this.mContentToRecord = contentToRecord;
@@ -412,6 +470,7 @@
this.mDisplayToRecord = displayToRecord;
this.mTokenToRecord = tokenToRecord;
this.mWaitingForConsent = waitingForConsent;
+ this.mTargetUid = targetUid;
// onConstructed(); // You can define this method to get a callback
}
@@ -442,6 +501,7 @@
private int mDisplayToRecord;
private @Nullable IBinder mTokenToRecord;
private boolean mWaitingForConsent;
+ private int mTargetUid;
private long mBuilderFieldsSet = 0L;
@@ -513,10 +573,21 @@
return this;
}
+ /**
+ * UID of the package that is captured if selected.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTargetUid(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mTargetUid = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ContentRecordingSession build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x20; // Mark builder used
+ mBuilderFieldsSet |= 0x40; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mVirtualDisplayId = INVALID_DISPLAY;
@@ -533,17 +604,21 @@
if ((mBuilderFieldsSet & 0x10) == 0) {
mWaitingForConsent = false;
}
+ if ((mBuilderFieldsSet & 0x20) == 0) {
+ mTargetUid = TARGET_UID_UNKNOWN;
+ }
ContentRecordingSession o = new ContentRecordingSession(
mVirtualDisplayId,
mContentToRecord,
mDisplayToRecord,
mTokenToRecord,
- mWaitingForConsent);
+ mWaitingForConsent,
+ mTargetUid);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x20) != 0) {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -551,10 +626,10 @@
}
@DataClass.Generated(
- time = 1683628463074L,
+ time = 1697456140720L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
- inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 67ac811..0f35048 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -78,6 +78,11 @@
for (int i = 0; i < size; i++) {
final Map.Entry<SurfaceControl, Long> entry = entries.get(i);
final SurfaceControl sc = entry.getKey();
+ if (sc == null) {
+ // Just skip if the key has since been removed from the weak hash map
+ continue;
+ }
+
final long timeRegistered = entry.getValue();
pw.print(" ");
pw.print(sc.getName());
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e9d0e4c..49b16c7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19,6 +19,8 @@
import static android.content.res.Resources.ID_NULL;
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
+import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -27,6 +29,7 @@
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH;
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.toolkitSetFrameRate;
import static android.view.flags.Flags.viewVelocityApi;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
@@ -114,6 +117,7 @@
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.FloatProperty;
import android.util.LayoutDirection;
import android.util.Log;
@@ -5509,6 +5513,11 @@
private ViewTranslationResponse mViewTranslationResponse;
/**
+ * A threshold value to determine the frame rate category of the View based on the size.
+ */
+ private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -20183,6 +20192,9 @@
return;
}
+ // For VRR to vote the preferred frame rate
+ votePreferredFrameRate();
+
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
mContentCaptureSessionCached = false;
@@ -20285,6 +20297,8 @@
*/
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
+ // For VRR to vote the preferred frame rate
+ votePreferredFrameRate();
mParent.onDescendantInvalidated(this, this);
}
}
@@ -32981,6 +32995,40 @@
return null;
}
+ private float getSizePercentage() {
+ if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) {
+ return 0;
+ }
+
+ DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
+ int screenSize = displayMetrics.widthPixels
+ * displayMetrics.heightPixels;
+ int viewSize = getWidth() * getHeight();
+
+ if (screenSize == 0 || viewSize == 0) {
+ return 0f;
+ }
+ return (float) viewSize / screenSize;
+ }
+
+ private int calculateFrameRateCategory() {
+ float sizePercentage = getSizePercentage();
+
+ if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+ return FRAME_RATE_CATEGORY_LOW;
+ } else {
+ return FRAME_RATE_CATEGORY_NORMAL;
+ }
+ }
+
+ private void votePreferredFrameRate() {
+ // use toolkitSetFrameRate flag to gate the change
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) {
+ viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory());
+ }
+ }
+
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9392783..9d15c78 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -24,6 +24,8 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -74,7 +76,10 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -87,6 +92,7 @@
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
+import static android.view.flags.Flags.toolkitSetFrameRate;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -732,6 +738,7 @@
private SurfaceControl mBoundsLayer;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final Transaction mTransaction = new Transaction();
+ private final Transaction mFrameRateTransaction = new Transaction();
@UnsupportedAppUsage
boolean mAdded;
@@ -955,6 +962,34 @@
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
+ /*
+ * for Variable Refresh Rate project
+ */
+
+ // The preferred frame rate category of the view that
+ // could be updated on a frame-by-frame basis.
+ private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ // The preferred frame rate category of the last frame that
+ // could be used to lower frame rate after touch boost
+ private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ // The preferred frame rate of the view that is mainly used for
+ // touch boosting, view velocity handling, and TextureView.
+ private float mPreferredFrameRate = 0;
+ // Used to check if there were any view invalidations in
+ // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
+ private boolean mHasInvalidation = false;
+ // Used to check if it is in the touch boosting period.
+ private boolean mIsFrameRateBoosting = false;
+ // Used to check if there is a message in the message queue
+ // for idleness handling.
+ private boolean mHasIdledMessage = false;
+ // time for touch boost period.
+ private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500;
+ // time for checking idle status periodically.
+ private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
+ // time for revaluating the idle status before lowering the frame rate.
+ private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -3918,6 +3953,12 @@
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}
+
+ // For the variable refresh rate project.
+ setPreferredFrameRate(mPreferredFrameRate);
+ setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ mLastPreferredFrameRateCategory = mPreferredFrameRateCategory;
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
}
private void createSyncIfNeeded() {
@@ -5970,6 +6011,8 @@
private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;
private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
+ private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
+ private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
final class ViewRootHandler extends Handler {
@Override
@@ -6265,6 +6308,32 @@
mNumPausedForSync = 0;
scheduleTraversals();
break;
+ case MSG_TOUCH_BOOST_TIMEOUT:
+ /**
+ * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
+ */
+ mIsFrameRateBoosting = false;
+ setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
+ mLastPreferredFrameRateCategory));
+ break;
+ case MSG_CHECK_INVALIDATION_IDLE:
+ if (!mHasInvalidation && !mIsFrameRateBoosting) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ mHasIdledMessage = false;
+ } else {
+ /**
+ * If there is no invalidation within a certain period,
+ * we consider the display is idled.
+ * We then set the frame rate catetogry to NO_PREFERENCE.
+ * Note that SurfaceFlinger also has a mechanism to lower the refresh rate
+ * if there is no updates of the buffer.
+ */
+ mHasInvalidation = false;
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
+ FRAME_RATE_IDLENESS_REEVALUATE_TIME);
+ }
+ break;
}
}
}
@@ -7207,6 +7276,7 @@
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
+ final int action = event.getAction();
boolean handled = mHandwritingInitiator.onTouchEvent(event);
if (handled) {
// If handwriting is started, toolkit doesn't receive ACTION_UP.
@@ -7227,6 +7297,22 @@
scheduleConsumeBatchedInputImmediately();
}
}
+
+ // For the variable refresh rate project
+ if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
+ // set the frame rate to the maximum value.
+ mIsFrameRateBoosting = true;
+ setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ }
+ /**
+ * We want to lower the refresh rate when MotionEvent.ACTION_UP,
+ * MotionEvent.ACTION_CANCEL is detected.
+ * Not using ACTION_MOVE to avoid checking and sending messages too frequently.
+ */
+ if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL)) {
+ sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME);
+ }
return handled ? FINISH_HANDLED : FORWARD;
}
@@ -11810,4 +11896,94 @@
@NonNull Consumer<Boolean> listener) {
t.clearTrustedPresentationCallback(getSurfaceControl());
}
+
+ private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
+ if (!shouldSetFrameRateCategory()) {
+ return;
+ }
+
+ int frameRateCategory = mIsFrameRateBoosting
+ ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+
+ try {
+ mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
+ frameRateCategory, false).apply();
+ } catch (Exception e) {
+ Log.e(mTag, "Unable to set frame rate category", e);
+ }
+
+ if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) {
+ // Check where the display is idled periodically.
+ // If so, set the frame rate category to NO_PREFERENCE
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
+ FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS);
+ mHasIdledMessage = true;
+ }
+ }
+
+ private void setPreferredFrameRate(float preferredFrameRate) {
+ if (!shouldSetFrameRate()) {
+ return;
+ }
+
+ try {
+ mFrameRateTransaction.setFrameRate(mSurfaceControl,
+ preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply();
+ } catch (Exception e) {
+ Log.e(mTag, "Unable to set frame rate", e);
+ }
+ }
+
+ private void sendDelayedEmptyMessage(int message, int delayedTime) {
+ mHandler.removeMessages(message);
+
+ mHandler.sendEmptyMessageDelayed(message, delayedTime);
+ }
+
+ private boolean shouldSetFrameRateCategory() {
+ // use toolkitSetFrameRate flag to gate the change
+ return mSurface.isValid() && toolkitSetFrameRate();
+ }
+
+ private boolean shouldSetFrameRate() {
+ // use toolkitSetFrameRate flag to gate the change
+ return mPreferredFrameRate > 0 && toolkitSetFrameRate();
+ }
+
+ private boolean shouldTouchBoost(int motionEventAction, int windowType) {
+ boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
+ || motionEventAction == MotionEvent.ACTION_MOVE
+ || motionEventAction == MotionEvent.ACTION_UP;
+ boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION
+ || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION;
+ // use toolkitSetFrameRate flag to gate the change
+ return desiredAction && desiredType && toolkitSetFrameRate();
+ }
+
+ /**
+ * Allow Views to vote for the preferred frame rate category
+ *
+ * @param frameRateCategory the preferred frame rate category of a View
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public void votePreferredFrameRateCategory(int frameRateCategory) {
+ mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory);
+ mHasInvalidation = true;
+ }
+
+ /**
+ * Get the value of mPreferredFrameRateCategory
+ */
+ @VisibleForTesting
+ public int getPreferredFrameRateCategory() {
+ return mPreferredFrameRateCategory;
+ }
+
+ /**
+ * Get the value of mPreferredFrameRate
+ */
+ @VisibleForTesting
+ public float getPreferredFrameRate() {
+ return mPreferredFrameRate;
+ }
}
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index eb9d62b..5d14698 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -263,7 +263,6 @@
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady");
}
synchronized (mLock) {
- toggleTimeout(false);
if (mHasWMSync) {
try {
WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken);
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 392aa1b..6c025a4 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -20,4 +20,11 @@
description: "Refactor dim to fix flickers"
bug: "281632483,295291019"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "transit_ready_tracking"
+ namespace: "windowing_frontend"
+ description: "Enable accurate transition readiness tracking"
+ bug: "294925498"
+}
diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
index 6fd5018..504928c 100644
--- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
+++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
@@ -74,16 +74,15 @@
return windowSwipeToDismiss;
}
- private boolean isPointerIndexValid(MotionEvent ev) {
+ private int getIndexForValidPointer(MotionEvent ev) {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
if (DEBUG) {
Log.e(TAG, "Invalid pointer index: ignoring.");
}
mDiscardIntercept = true;
- return false;
}
- return true;
+ return pointerIndex;
}
private void updateSwiping(MotionEvent ev) {
@@ -98,7 +97,7 @@
}
}
- private void updateDiscardIntercept(MotionEvent ev) {
+ private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) {
if (!mSwiping) {
// Don't look at canScroll until we have passed the touch slop
return;
@@ -107,8 +106,8 @@
return;
}
final boolean checkLeft = mDownX < ev.getRawX();
- final float x = ev.getX(mActivePointerId);
- final float y = ev.getY(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {
mDiscardIntercept = true;
}
@@ -152,11 +151,12 @@
if (mDiscardIntercept) {
break;
}
- if (!isPointerIndexValid(ev)) {
+ final int pointerIndex = getIndexForValidPointer(ev);
+ if (pointerIndex == -1) {
break;
}
updateSwiping(ev);
- updateDiscardIntercept(ev);
+ updateDiscardIntercept(ev, pointerIndex);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index deb138f..9c883d1 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -265,6 +265,18 @@
return mgr->isDataInjectionEnabled();
}
+static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+ jlong sensorManager) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+ return mgr->isReplayDataInjectionEnabled();
+}
+
+static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+ jlong sensorManager) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+ return mgr->isHalBypassReplayDataInjectionEnabled();
+}
+
static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
jint deviceId, jlong size, jint channelType, jint fd,
jobject hardwareBufferObj) {
@@ -533,6 +545,11 @@
{"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
+ {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled},
+
+ {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z",
+ (void *)nativeIsHalBypassReplayDataInjectionEnabled},
+
{"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",
(void *)nativeCreateDirectChannel},
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index eb14db0..068f4dd 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -115,6 +115,9 @@
// Only set if the app defined a monochrome icon.
optional string monochrome_icon_bitmap_path = 3;
+
+ // The component name of the original activity (pre-archival).
+ optional string original_component_name = 4;
}
/** Information about main activities. */
diff --git a/core/res/Android.bp b/core/res/Android.bp
index b71995f..4e686b7 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -104,6 +104,7 @@
android_app {
name: "framework-res",
+ use_resource_processor: false,
sdk_version: "core_platform",
certificate: "platform",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 88b578b..cffbaa7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6123,7 +6123,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<!-- @SystemApi @hide
- @FlaggedApi("backstage_power.report_usage_stats_permission")
+ @FlaggedApi("android.app.usage.report_usage_stats_permission")
Allows trusted system components to report events to UsageStatsManager -->
<permission android:name="android.permission.REPORT_USAGE_STATS"
android:protectionLevel="signature|module" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 2993a0e..445ddf5 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -65,6 +65,7 @@
"device-time-shell-utils",
"testables",
"com.android.text.flags-aconfig-java",
+ "flag-junit",
],
libs: [
@@ -75,6 +76,7 @@
"framework",
"ext",
"framework-res",
+ "android.view.flags-aconfig-java",
],
jni_libs: [
"libpowermanagertest_jni",
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 07dec5d..b843ad7 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -79,6 +79,8 @@
private FaceManager.AuthenticationCallback mAuthCallback;
@Mock
private FaceManager.EnrollmentCallback mEnrollmentCallback;
+ @Mock
+ private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@Captor
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor;
@@ -191,6 +193,23 @@
any(), anyString(), any(), any(), anyBoolean());
}
+ @Test
+ public void detectClient_onError() throws RemoteException {
+ ArgumentCaptor<IFaceServiceReceiver> argumentCaptor =
+ ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback,
+ new FaceAuthenticateOptions.Builder().build());
+
+ verify(mService).detectFace(any(), argumentCaptor.capture(), any());
+
+ argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(mFaceDetectionCallback).onDetectionError(anyInt());
+ }
+
private void initializeProperties() throws RemoteException {
verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index 625e2e3..70313b8 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -74,6 +74,8 @@
private FingerprintManager.AuthenticationCallback mAuthCallback;
@Mock
private FingerprintManager.EnrollmentCallback mEnrollCallback;
+ @Mock
+ private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor;
@@ -166,4 +168,21 @@
anyString());
verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());
}
+
+ @Test
+ public void detectClient_onError() throws RemoteException {
+ ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor =
+ ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+ mFingerprintManager.detectFingerprint(new CancellationSignal(),
+ mFingerprintDetectionCallback,
+ new FingerprintAuthenticateOptions.Builder().build());
+
+ verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any());
+
+ argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 6a9fc04..1a38dec 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,6 +17,11 @@
package android.view;
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
+import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -48,8 +53,12 @@
import android.os.Binder;
import android.os.SystemProperties;
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 android.provider.Settings;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
@@ -97,6 +106,10 @@
// state after the test completes.
private static boolean sOriginalTouchMode;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@BeforeClass
public static void setUpClass() {
sContext = sInstrumentation.getTargetContext();
@@ -427,6 +440,129 @@
assertThat(result).isFalse();
}
+ /**
+ * Test the default values are properly set
+ */
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_getDefaultValues() {
+ ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
+ sContext.getDisplayNoVerify());
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the visibility of a view
+ * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+ * Visible: FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.INVISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ });
+
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.VISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * <7%: FRAME_RATE_CATEGORY_LOW
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * >=7% : FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test how values of the frame rate cateogry are aggregated.
+ * It should take the max value among all of the voted categories per frame.
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ sInstrumentation.runOnMainSync(() -> {
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 86f26e5..df212eb 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -17,7 +17,9 @@
package android.widget;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.util.PollingCheck;
@@ -32,6 +34,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
@Presubmit
@@ -49,23 +54,43 @@
}
@Test
- public void testScrollAfterFlingTop() {
- mHorizontalScrollView.scrollTo(100, 0);
- mHorizontalScrollView.fling(-10000);
- PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
- PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+ public void testScrollAfterFlingLeft() throws Throwable {
+ WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+ mHorizontalScrollView.mEdgeGlowLeft = edgeEffect;
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0));
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000));
+ assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+ mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame
+ PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f);
assertEquals(0, mHorizontalScrollView.getScrollX());
}
@Test
- public void testScrollAfterFlingBottom() {
+ public void testScrollAfterFlingRight() throws Throwable {
+ WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+ mHorizontalScrollView.mEdgeGlowRight = edgeEffect;
int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
int maxScroll = childWidth - mHorizontalScrollView.getWidth();
- mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
- mHorizontalScrollView.fling(10000);
- PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0));
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000));
+ assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+ mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame
PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
}
+
+ static class WatchedEdgeEffect extends EdgeEffect {
+ public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
+
+ WatchedEdgeEffect(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onAbsorb(int velocity) {
+ super.onAbsorb(velocity);
+ onAbsorbLatch.countDown();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
new file mode 100644
index 0000000..74a29dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
@@ -0,0 +1 @@
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8a64037..1898ea7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,19 +17,28 @@
package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
+import android.content.Context;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
* the successor of its sibling {@link Pip1Module}.
@@ -42,8 +51,26 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm) {
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ Optional<PipController> pipController) {
return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
pipBoundsAlgorithm);
}
+
+ @WMSingleton
+ @Provides
+ static Optional<PipController> providePipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellController, displayController, displayInsetsController,
+ pipDisplayLayoutState));
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 0000000..186cb61
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final String TAG = PipController.class.getSimpleName();
+
+ private Context mContext;
+ private ShellController mShellController;
+ private DisplayController mDisplayController;
+ private DisplayInsetsController mDisplayInsetsController;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
+ private PipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ mContext = context;
+ mShellController = shellController;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ // Ensure that we have the display info in case we get calls to update the bounds before the
+ // listener calls back
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+ DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+
+ mShellController.addConfigurationChangeListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+ new DisplayInsetsController.OnInsetsChangedListener() {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ onDisplayChanged(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+ }
+ });
+ }
+
+ /**
+ * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+ */
+ public static PipController create(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Device doesn't support Pip feature", TAG);
+ return null;
+ }
+ return new PipController(context, shellInit, shellController, displayController,
+ displayInsetsController, pipDisplayLayoutState);
+ }
+
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ mPipDisplayLayoutState.onConfigurationChanged();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ private void onDisplayChanged(DisplayLayout layout) {
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d31476c..d277eef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -925,19 +925,8 @@
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome
- // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
- // live tile (pausing app). If the gesture is "cancelled" we need to return to
- // 3p launcher instead of "task-switching" away from it.
- && (!mWillFinishToHome || mPausingSeparateHome)
- && mPausingTasks != null && mState == STATE_NORMAL) {
- if (mPausingSeparateHome) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to 3p home");
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to app");
- }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
// The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
@@ -969,6 +958,15 @@
wct.restoreTransientOrder(mRecentsTask);
}
} else {
+ if (mPausingSeparateHome) {
+ if (mOpeningTasks.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " recents occluded 3p home");
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " switch task by recents on 3p home");
+ }
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish");
// The general case: committing to recents, going home, or switching tasks.
for (int i = 0; i < mOpeningTasks.size(); ++i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 0548a8e..d0e647b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -297,7 +297,7 @@
}
// Task surface itself
- float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+ float shadowRadius;
final Point taskPosition = mTaskInfo.positionInParent;
if (isFullscreen) {
// Setting the task crop to the width/height stops input events from being sent to
@@ -308,9 +308,12 @@
// drag-resized by the window decoration.
startT.setWindowCrop(mTaskSurface, null);
finishT.setWindowCrop(mTaskSurface, null);
+ // Shadow is not needed for fullscreen tasks
+ shadowRadius = 0;
} else {
startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ shadowRadius = loadDimension(resources, params.mShadowRadiusId);
}
startT.setShadowRadius(mTaskSurface, shadowRadius)
.show(mTaskSurface);
diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java
index b761afa..3c54d4a 100644
--- a/media/java/android/media/RingtoneV1.java
+++ b/media/java/android/media/RingtoneV1.java
@@ -16,14 +16,15 @@
package android.media;
-import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources.NotFoundException;
import android.media.audiofx.HapticGenerator;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.RemoteException;
import android.os.Trace;
import android.os.VibrationEffect;
@@ -61,7 +62,6 @@
private final Context mContext;
private final AudioManager mAudioManager;
- private final Ringtone.Injectables mInjectables;
private VolumeShaper.Configuration mVolumeShaperConfig;
private VolumeShaper mVolumeShaper;
@@ -74,10 +74,12 @@
private final IRingtonePlayer mRemotePlayer;
private final Binder mRemoteToken;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private MediaPlayer mLocalPlayer;
private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
private HapticGenerator mHapticGenerator;
+ @UnsupportedAppUsage
private Uri mUri;
private String mTitle;
@@ -92,15 +94,10 @@
private boolean mHapticGeneratorEnabled = false;
private final Object mPlaybackSettingsLock = new Object();
- /** @hide */
+ /** {@hide} */
+ @UnsupportedAppUsage
public RingtoneV1(Context context, boolean allowRemote) {
- this(context, new Ringtone.Injectables(), allowRemote);
- }
-
- /** @hide */
- RingtoneV1(Context context, @NonNull Ringtone.Injectables injectables, boolean allowRemote) {
mContext = context;
- mInjectables = injectables;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAllowRemote = allowRemote;
mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
@@ -203,7 +200,7 @@
}
destroyLocalPlayer();
// try opening uri locally before delegating to remote player
- mLocalPlayer = mInjectables.newMediaPlayer();
+ mLocalPlayer = new MediaPlayer();
try {
mLocalPlayer.setDataSource(mContext, mUri);
mLocalPlayer.setAudioAttributes(mAudioAttributes);
@@ -243,7 +240,19 @@
*/
public boolean hasHapticChannels() {
// FIXME: support remote player, or internalize haptic channels support and remove entirely.
- return mInjectables.hasHapticChannels(mLocalPlayer);
+ try {
+ android.os.Trace.beginSection("Ringtone.hasHapticChannels");
+ if (mLocalPlayer != null) {
+ for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
+ if (trackInfo.hasHapticChannels()) {
+ return true;
+ }
+ }
+ }
+ } finally {
+ android.os.Trace.endSection();
+ }
+ return false;
}
/**
@@ -325,7 +334,7 @@
* @see android.media.audiofx.HapticGenerator#isAvailable()
*/
public boolean setHapticGeneratorEnabled(boolean enabled) {
- if (!mInjectables.isHapticGeneratorAvailable()) {
+ if (!HapticGenerator.isAvailable()) {
return false;
}
synchronized (mPlaybackSettingsLock) {
@@ -353,7 +362,7 @@
mLocalPlayer.setVolume(mVolume);
mLocalPlayer.setLooping(mIsLooping);
if (mHapticGenerator == null && mHapticGeneratorEnabled) {
- mHapticGenerator = mInjectables.createHapticGenerator(mLocalPlayer);
+ mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
}
if (mHapticGenerator != null) {
mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
@@ -388,6 +397,7 @@
*
* @hide
*/
+ @UnsupportedAppUsage
public void setUri(Uri uri) {
setUri(uri, null);
}
@@ -415,6 +425,7 @@
}
/** {@hide} */
+ @UnsupportedAppUsage
public Uri getUri() {
return mUri;
}
@@ -545,7 +556,7 @@
Log.e(TAG, "Could not load fallback ringtone");
return false;
}
- mLocalPlayer = mInjectables.newMediaPlayer();
+ mLocalPlayer = new MediaPlayer();
if (afd.getDeclaredLength() < 0) {
mLocalPlayer.setDataSource(afd.getFileDescriptor());
} else {
@@ -583,12 +594,12 @@
}
public boolean isLocalOnly() {
- return !mAllowRemote;
+ return mAllowRemote;
}
public boolean isUsingRemotePlayer() {
// V2 testing api, but this is the v1 approximation.
- return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null) && (mUri != null);
+ return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null);
}
class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 80e2247..31e65eb 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -175,5 +175,5 @@
@EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
+ oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
}
diff --git a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
similarity index 69%
rename from media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
rename to media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
index 2c8daba..3c0c684 100644
--- a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
@@ -14,22 +14,20 @@
* limitations under the License.
*/
-package com.android.media;
+package com.android.mediaframeworktest.unit;
import static android.media.Ringtone.MEDIA_SOUND;
import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
import static android.media.Ringtone.MEDIA_VIBRATION;
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerFallbackSetup;
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerSetup;
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStarted;
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStopped;
-
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -55,29 +53,34 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.testing.TestableContext;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.framework.base.media.ringtone.tests.R;
-import com.android.media.testing.RingtoneInjectablesTrackingTestRule;
+import com.android.mediaframeworktest.R;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.FileNotFoundException;
+import java.util.ArrayDeque;
+import java.util.Map;
+import java.util.Queue;
-/**
- * Test behavior of {@link Ringtone} when it's created via {@link Ringtone.Builder}.
- */
@RunWith(AndroidJUnit4.class)
-public class RingtoneBuilderTest {
+public class RingtoneTest {
private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
@@ -90,8 +93,11 @@
private static final VibrationEffect VIBRATION_EFFECT =
VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
+ private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
+ VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
- @Rule public final RingtoneInjectablesTrackingTestRule
+ @Rule
+ public final RingtoneInjectablesTrackingTestRule
mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
@Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
@@ -116,7 +122,6 @@
mContext = spy(testContext);
}
-
@Test
public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
@@ -137,14 +142,14 @@
assertThat(ringtone.isLocalOnly()).isFalse();
// Prepare
- verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
+ verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyPlayerStarted(mockMediaPlayer);
+ verifyLocalPlay(mockMediaPlayer);
// Verify dynamic controls.
ringtone.setVolume(0.8f);
@@ -160,7 +165,7 @@
// Release
ringtone.stop();
- verifyPlayerStopped(mockMediaPlayer);
+ verifyLocalStop(mockMediaPlayer);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
// playback case. This shouldn't be necessary in other tests that have the same basic
@@ -194,16 +199,16 @@
assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
// Prepare
- verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, audioAttributes);
+ verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyPlayerStarted(mockMediaPlayer);
+ verifyLocalPlay(mockMediaPlayer);
// Release
ringtone.stop();
- verifyPlayerStopped(mockMediaPlayer);
+ verifyLocalStop(mockMediaPlayer);
verifyZeroInteractions(mMockRemotePlayer);
verifyZeroInteractions(mMockVibrator);
@@ -215,8 +220,8 @@
setupFileNotFound(mockMediaPlayer, SOUND_URI);
Ringtone ringtone =
newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .build();
+ .setUri(SOUND_URI)
+ .build();
assertThat(ringtone).isNotNull();
assertThat(ringtone.isUsingRemotePlayer()).isTrue();
@@ -279,7 +284,7 @@
// Prepare
// Uses attributes with haptic channels enabled, but will use the effect when there aren't
// any present.
- verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -287,7 +292,7 @@
// Play
ringtone.play();
- verifyPlayerStarted(mockMediaPlayer);
+ verifyLocalPlay(mockMediaPlayer);
verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
// Verify dynamic controls.
@@ -305,7 +310,7 @@
// Release
ringtone.stop();
- verifyPlayerStopped(mockMediaPlayer);
+ verifyLocalStop(mockMediaPlayer);
verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
@@ -383,7 +388,7 @@
// Prepare
// Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
// knows there aren't any.
- verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -438,7 +443,7 @@
// Prepare
// Uses attributes with haptic channels enabled, but will use the effect when there aren't
// any present.
- verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -446,7 +451,7 @@
// Play
ringtone.play();
// Vibrator.vibrate isn't called because the vibration comes from the sound.
- verifyPlayerStarted(mockMediaPlayer);
+ verifyLocalPlay(mockMediaPlayer);
// Verify dynamic controls (no-op without sound)
ringtone.setVolume(0.8f);
@@ -461,7 +466,7 @@
// Release
ringtone.stop();
- verifyPlayerStopped(mockMediaPlayer);
+ verifyLocalStop(mockMediaPlayer);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
// playback case. This shouldn't be necessary in other tests that have the same basic
@@ -491,17 +496,17 @@
// Prepare
// The attributes here have haptic channels enabled (unlike above)
- verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
when(mockMediaPlayer.isPlaying()).thenReturn(true);
- verifyPlayerStarted(mockMediaPlayer);
+ verifyLocalPlay(mockMediaPlayer);
// Release
ringtone.stop();
- verifyPlayerStopped(mockMediaPlayer);
+ verifyLocalStop(mockMediaPlayer);
verifyZeroInteractions(mMockRemotePlayer);
// Nothing after the initial hasVibrator - it uses audio-coupled.
@@ -531,7 +536,7 @@
// Prepare
// The attributes here have haptic channels enabled (unlike above)
- verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).prepare();
// Play
@@ -554,7 +559,7 @@
@Test
public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
AssetFileDescriptor testResourceFd =
- mContext.getResources().openRawResourceFd(R.raw.test_sound_file);
+ mContext.getResources().openRawResourceFd(R.raw.shortmp3);
// Ensure it will flow as expected.
assertThat(testResourceFd).isNotNull();
assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
@@ -570,18 +575,18 @@
// Delegates straight to fallback in local player.
// Prepare
- verifyPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
+ verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyPlayerStarted(mockMediaPlayer);
+ verifyLocalPlay(mockMediaPlayer);
// Release
ringtone.stop();
- verifyPlayerStopped(mockMediaPlayer);
+ verifyLocalStop(mockMediaPlayer);
verifyNoMoreInteractions(mockMediaPlayer);
verifyNoMoreInteractions(mMockRemotePlayer);
@@ -610,10 +615,24 @@
verifyNoMoreInteractions(mMockRemotePlayer);
}
+ @Test
+ public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
+ mContext.getOrCreateTestableResources()
+ .addOverride(com.android.internal.R.raw.fallbackring, null);
+ Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
+ .setUri(null)
+ .setLocalOnly()
+ .build();
+ // Local player fallback fails as the resource isn't found (no media player creation is
+ // attempted), and since there is no local player, the ringtone ends up having nothing to
+ // do.
+ assertThat(ringtone).isNull();
+ }
+
private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
AudioAttributes audioAttributes) {
return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
- .setInjectables(mMediaPlayerRule.getRingtoneTestInjectables());
+ .setInjectables(mMediaPlayerRule.injectables);
}
private static AudioAttributes audioAttributes(int audioUsage) {
@@ -628,4 +647,194 @@
doThrow(new FileNotFoundException("Fake file not found"))
.when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
}
+
+ private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
+ AudioAttributes expectedAudioAttributes) throws Exception {
+ verify(mockPlayer).setDataSource(mContext, expectedUri);
+ verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+ verify(mockPlayer).setPreferredDevice(null);
+ verify(mockPlayer).prepare();
+ }
+
+ private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
+ AudioAttributes expectedAudioAttributes) throws Exception {
+ // This is very specific but it's a simple way to test that the test resource matches.
+ if (afd.getDeclaredLength() < 0) {
+ verify(mockPlayer).setDataSource(afd.getFileDescriptor());
+ } else {
+ verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
+ afd.getStartOffset(),
+ afd.getDeclaredLength());
+ }
+ verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+ verify(mockPlayer).setPreferredDevice(null);
+ verify(mockPlayer).prepare();
+ }
+
+ private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
+ verify(mockMediaPlayer).setOnCompletionListener(any());
+ verify(mockMediaPlayer).start();
+ }
+
+ private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
+ verify(mockMediaPlayer).stop();
+ verify(mockMediaPlayer).setOnCompletionListener(isNull());
+ verify(mockMediaPlayer).reset();
+ verify(mockMediaPlayer).release();
+ }
+
+ /**
+ * This rule ensures that all expected media player creations from the factory do actually
+ * occur. The reason for this level of control is that creating a media player is fairly
+ * expensive and blocking, so we do want unit tests of this class to "declare" interactions
+ * of all created media players.
+ *
+ * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
+ * failed (and media player assertions may just be a distracting side effect). Otherwise, the
+ * teardown failures hide the real test ones.
+ */
+ public static class RingtoneInjectablesTrackingTestRule implements TestRule {
+ public Ringtone.Injectables injectables = new TestInjectables();
+ public boolean hapticGeneratorAvailable = true;
+
+ // Queue of (local) media players, in order of expected creation. Enqueue using
+ // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
+ // This queue is asserted to be empty at the end of the test.
+ private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
+
+ // Similar to media players, but for haptic generator, which also needs releasing.
+ private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
+
+ // Media players with haptic channels.
+ private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ // Only assert if the test didn't fail (base.evaluate() would throw).
+ assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
+ .that(mMockMediaPlayerQueue).isEmpty();
+ // Only assert if the test didn't fail (base.evaluate() would throw).
+ assertWithMessage(
+ "Test setup an expectLocalHapticGenerator but it wasn't consumed")
+ .that(mMockHapticGeneratorMap).isEmpty();
+ }
+ };
+ }
+
+ private TestMediaPlayer expectLocalMediaPlayer() {
+ TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
+ // Delegate to simulated methods. This means they can be verified but also reflect
+ // realistic transitions from the TestMediaPlayer.
+ doCallRealMethod().when(mockMediaPlayer).start();
+ doCallRealMethod().when(mockMediaPlayer).stop();
+ doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
+ when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+ when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+ mMockMediaPlayerQueue.add(mockMediaPlayer);
+ return mockMediaPlayer;
+ }
+
+ private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
+ HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
+ // A test should never want this.
+ assertWithMessage("Can't expect a second haptic generator created "
+ + "for one media player")
+ .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
+ .isNull();
+ return mockHapticGenerator;
+ }
+
+ private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
+ if (hasHapticChannels) {
+ mHapticChannels.add(mp);
+ } else {
+ mHapticChannels.remove(mp);
+ }
+ }
+
+ private class TestInjectables extends Ringtone.Injectables {
+ @Override
+ public MediaPlayer newMediaPlayer() {
+ assertWithMessage(
+ "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
+ .that(mMockMediaPlayerQueue)
+ .isNotEmpty();
+ return mMockMediaPlayerQueue.remove();
+ }
+
+ @Override
+ public boolean isHapticGeneratorAvailable() {
+ return hapticGeneratorAvailable;
+ }
+
+ @Override
+ public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
+ HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
+ assertWithMessage("Unexpected HapticGenerator creation. "
+ + "Bug or need expectHapticGenerator")
+ .that(mockHapticGenerator)
+ .isNotNull();
+ return mockHapticGenerator;
+ }
+
+ @Override
+ public boolean isHapticPlaybackSupported() {
+ return true;
+ }
+
+ @Override
+ public boolean hasHapticChannels(MediaPlayer mp) {
+ return mHapticChannels.contains(mp);
+ }
+ }
+ }
+
+ /**
+ * MediaPlayer relies on a native backend and so its necessary to intercept calls from
+ * fake usage hitting them.
+ *
+ * Mocks don't work directly on native calls, but if they're overridden then it does work.
+ * Some basic state faking is also done to make the mocks more realistic.
+ */
+ private static class TestMediaPlayer extends MediaPlayer {
+ private boolean mIsPlaying = false;
+ private boolean mIsLooping = false;
+
+ @Override
+ public void start() {
+ mIsPlaying = true;
+ }
+
+ @Override
+ public void stop() {
+ mIsPlaying = false;
+ }
+
+ @Override
+ public void setLooping(boolean value) {
+ mIsLooping = value;
+ }
+
+ @Override
+ public boolean isLooping() {
+ return mIsLooping;
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return mIsPlaying;
+ }
+
+ void simulatePlayingFinished() {
+ if (!mIsPlaying) {
+ throw new IllegalStateException(
+ "Attempted to pretend playing finished when not playing");
+ }
+ mIsPlaying = false;
+ }
+ }
}
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
index 8d1e5e3..55b98c4 100644
--- a/media/tests/ringtone/Android.bp
+++ b/media/tests/ringtone/Android.bp
@@ -9,24 +9,15 @@
srcs: ["src/**/*.java"],
libs: [
- "android.test.base",
- "android.test.mock",
"android.test.runner",
+ "android.test.base",
],
static_libs: [
- "androidx.test.ext.junit",
- "androidx.test.ext.truth",
"androidx.test.rules",
- "frameworks-base-testutils",
- "mockito-target-inline-minus-junit4",
- "testables",
"testng",
- ],
-
- jni_libs: [
- "libdexmakerjvmtiagent",
- "libstaticjvmtiagent",
+ "androidx.test.ext.truth",
+ "frameworks-base-testutils",
],
test_suites: [
diff --git a/media/tests/ringtone/OWNERS b/media/tests/ringtone/OWNERS
deleted file mode 100644
index 93b44f4..0000000
--- a/media/tests/ringtone/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 345036
-
-include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
deleted file mode 100644
index e97e117..0000000
--- a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2023 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.media.testing;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.MediaPlayer;
-import android.net.Uri;
-
-/**
- * Helper class with assertion methods on mock {@link MediaPlayer} instances.
- */
-public final class MediaPlayerTestHelper {
-
- /** Verify this local media player mock instance was started. */
- public static void verifyPlayerStarted(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).setOnCompletionListener(any());
- verify(mockMediaPlayer).start();
- }
-
- /** Verify this local media player mock instance was stopped and released. */
- public static void verifyPlayerStopped(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).stop();
- verify(mockMediaPlayer).setOnCompletionListener(isNull());
- verify(mockMediaPlayer).reset();
- verify(mockMediaPlayer).release();
- }
-
- /** Verify this local media player mock instance was setup with given attributes. */
- public static void verifyPlayerSetup(Context context, MediaPlayer mockPlayer,
- Uri expectedUri, AudioAttributes expectedAudioAttributes) throws Exception {
- verify(mockPlayer).setDataSource(context, expectedUri);
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- /** Verify this local media player mock instance was setup with given fallback attributes. */
- public static void verifyPlayerFallbackSetup(MediaPlayer mockPlayer,
- AssetFileDescriptor afd, AudioAttributes expectedAudioAttributes) throws Exception {
- // This is very specific but it's a simple way to test that the test resource matches.
- if (afd.getDeclaredLength() < 0) {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor());
- } else {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength());
- }
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- private MediaPlayerTestHelper() {
- }
-}
diff --git a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
deleted file mode 100644
index 25752ce..0000000
--- a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright 2023 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.media.testing;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.when;
-
-import android.media.MediaPlayer;
-import android.media.Ringtone;
-import android.media.audiofx.HapticGenerator;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.mockito.Mockito;
-
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
-
-/**
- * This rule ensures that all expected media player creations from the factory do actually
- * occur. The reason for this level of control is that creating a media player is fairly
- * expensive and blocking, so we do want unit tests of this class to "declare" interactions
- * of all created media players.
- * <p>
- * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
- * failed (and media player assertions may just be a distracting side effect). Otherwise, the
- * teardown failures hide the real test ones.
- */
-public class RingtoneInjectablesTrackingTestRule implements TestRule {
-
- private final Ringtone.Injectables mRingtoneTestInjectables = new TestInjectables();
-
- // Queue of (local) media players, in order of expected creation. Enqueue using
- // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
- // This queue is asserted to be empty at the end of the test.
- private final Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
- // Similar to media players, but for haptic generator, which also needs releasing.
- private final Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
- // Media players with haptic channels.
- private final ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
- private boolean mHapticGeneratorAvailable = true;
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- base.evaluate();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
- .that(mMockMediaPlayerQueue).isEmpty();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage(
- "Test setup an expectLocalHapticGenerator but it wasn't consumed")
- .that(mMockHapticGeneratorMap).isEmpty();
- }
- };
- }
-
- /** The {@link Ringtone.Injectables} to be used for creating a testable {@link Ringtone}. */
- public Ringtone.Injectables getRingtoneTestInjectables() {
- return mRingtoneTestInjectables;
- }
-
- /**
- * Create a test {@link MediaPlayer} that will be provided to the {@link Ringtone} instance
- * created with {@link #getRingtoneTestInjectables()}.
- *
- * <p>If a media player is not created during the test execution after this method is called
- * then the test will fail. It will also fail if the ringtone attempts to create one without
- * this method being called first.
- */
- public TestMediaPlayer expectLocalMediaPlayer() {
- TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
- // Delegate to simulated methods. This means they can be verified but also reflect
- // realistic transitions from the TestMediaPlayer.
- doCallRealMethod().when(mockMediaPlayer).start();
- doCallRealMethod().when(mockMediaPlayer).stop();
- doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
- when(mockMediaPlayer.isLooping()).thenCallRealMethod();
- mMockMediaPlayerQueue.add(mockMediaPlayer);
- return mockMediaPlayer;
- }
-
- /**
- * Create a test {@link HapticGenerator} that will be provided to the {@link Ringtone} instance
- * created with {@link #getRingtoneTestInjectables()}.
- *
- * <p>If a haptic generator is not created during the test execution after this method is called
- * then the test will fail. It will also fail if the ringtone attempts to create one without
- * this method being called first.
- */
- public HapticGenerator expectHapticGenerator(MediaPlayer mediaPlayer) {
- HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
- // A test should never want this.
- assertWithMessage("Can't expect a second haptic generator created "
- + "for one media player")
- .that(mMockHapticGeneratorMap.put(mediaPlayer, mockHapticGenerator))
- .isNull();
- return mockHapticGenerator;
- }
-
- /**
- * Configures the {@link MediaPlayer} to always return given flag when
- * {@link Ringtone.Injectables#hasHapticChannels(MediaPlayer)} is called.
- */
- public void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
- if (hasHapticChannels) {
- mHapticChannels.add(mp);
- } else {
- mHapticChannels.remove(mp);
- }
- }
-
- /** Test implementation of {@link Ringtone.Injectables} that uses the test rule setup. */
- private class TestInjectables extends Ringtone.Injectables {
- @Override
- public MediaPlayer newMediaPlayer() {
- assertWithMessage(
- "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
- .that(mMockMediaPlayerQueue)
- .isNotEmpty();
- return mMockMediaPlayerQueue.remove();
- }
-
- @Override
- public boolean isHapticGeneratorAvailable() {
- return mHapticGeneratorAvailable;
- }
-
- @Override
- public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
- HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
- assertWithMessage("Unexpected HapticGenerator creation. "
- + "Bug or need expectHapticGenerator")
- .that(mockHapticGenerator)
- .isNotNull();
- return mockHapticGenerator;
- }
-
- @Override
- public boolean isHapticPlaybackSupported() {
- return true;
- }
-
- @Override
- public boolean hasHapticChannels(MediaPlayer mp) {
- return mHapticChannels.contains(mp);
- }
- }
-
- /**
- * MediaPlayer relies on a native backend and so its necessary to intercept calls from
- * fake usage hitting them.
- * <p>
- * Mocks don't work directly on native calls, but if they're overridden then it does work.
- * Some basic state faking is also done to make the mocks more realistic.
- */
- public static class TestMediaPlayer extends MediaPlayer {
- private boolean mIsPlaying = false;
- private boolean mIsLooping = false;
-
- @Override
- public void start() {
- mIsPlaying = true;
- }
-
- @Override
- public void stop() {
- mIsPlaying = false;
- }
-
- @Override
- public void setLooping(boolean value) {
- mIsLooping = value;
- }
-
- @Override
- public boolean isLooping() {
- return mIsLooping;
- }
-
- @Override
- public boolean isPlaying() {
- return mIsPlaying;
- }
-
- /**
- * Updates {@link #isPlaying()} result to false, if it's set to true.
- *
- * @throws IllegalStateException is {@link #isPlaying()} is already false
- */
- public void simulatePlayingFinished() {
- if (!mIsPlaying) {
- throw new IllegalStateException(
- "Attempted to pretend playing finished when not playing");
- }
- mIsPlaying = false;
- }
- }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index ce96bbf..abc62c4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.util.lerp
@@ -196,29 +197,44 @@
state.fromScene == state.toScene ||
!layoutImpl.isTransitionReady(state) ||
state.fromScene !in element.sceneValues ||
- state.toScene !in element.sceneValues ||
- !isSharedElementEnabled(layoutImpl, state, element.key)
+ state.toScene !in element.sceneValues
) {
return true
}
- val otherScene =
- layoutImpl.scenes.getValue(
- if (scene.key == state.fromScene) {
- state.toScene
- } else {
- state.fromScene
- }
- )
-
- // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
- // it is usually drawn below everything else.
- val isHighestScene = scene.zIndex > otherScene.zIndex
- return if (element.key.isBackground) {
- !isHighestScene
- } else {
- isHighestScene
+ val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
+ if (sharedTransformation?.enabled == false) {
+ return true
}
+
+ return shouldDrawOrComposeSharedElement(
+ layoutImpl,
+ state,
+ scene.key,
+ element.key,
+ sharedTransformation,
+ )
+}
+
+internal fun shouldDrawOrComposeSharedElement(
+ layoutImpl: SceneTransitionLayoutImpl,
+ transition: TransitionState.Transition,
+ scene: SceneKey,
+ element: ElementKey,
+ sharedTransformation: SharedElementTransformation?
+): Boolean {
+ val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker
+ val fromScene = transition.fromScene
+ val toScene = transition.toScene
+
+ return scenePicker.sceneDuringTransition(
+ element = element,
+ fromScene = fromScene,
+ toScene = toScene,
+ progress = transition::progress,
+ fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
+ toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
+ ) == scene
}
private fun isSharedElementEnabled(
@@ -226,6 +242,14 @@
transition: TransitionState.Transition,
element: ElementKey,
): Boolean {
+ return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
+}
+
+internal fun sharedElementTransformation(
+ layoutImpl: SceneTransitionLayoutImpl,
+ transition: TransitionState.Transition,
+ element: ElementKey,
+): SharedElementTransformation? {
val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
val sharedInToScene = spec.transformations(element, transition.toScene).shared
@@ -238,7 +262,7 @@
)
}
- return sharedInFromScene?.enabled ?: true
+ return sharedInFromScene
}
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 6dbeb69..fa385d0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -22,6 +22,8 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.Modifier
@@ -60,7 +62,16 @@
// which case we still need to draw it.
val picture = remember { Picture() }
- if (shouldComposeMovableElement(layoutImpl, scene.key, element)) {
+ // Whether we should compose the movable element here. The scene picker logic to know in
+ // which scene we should compose/draw a movable element might depend on the current
+ // transition progress, so we put this in a derivedStateOf to prevent many recompositions
+ // during the transition.
+ val shouldComposeMovableElement by
+ remember(layoutImpl, scene.key, element) {
+ derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
+ }
+
+ if (shouldComposeMovableElement) {
Box(
Modifier.drawWithCache {
val width = size.width.toInt()
@@ -172,14 +183,13 @@
return scene == fromScene
}
- // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless
- // it is a background) given that this is the one that is going to be drawn.
- val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex
- return if (element.key.isBackground) {
- !isHighestScene
- } else {
- isHighestScene
- }
+ return shouldDrawOrComposeSharedElement(
+ layoutImpl,
+ transitionState,
+ scene,
+ element.key,
+ sharedElementTransformation(layoutImpl, transitionState, element.key),
+ )
}
private class MovableElementScopeImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 4966977..7b7ddfa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -120,8 +120,14 @@
*
* @param enabled whether the matched element(s) should actually be shared in this transition.
* Defaults to true.
+ * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we
+ * should draw or compose this shared element.
*/
- fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
+ fun sharedElement(
+ matcher: ElementMatcher,
+ enabled: Boolean = true,
+ scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker,
+ )
/**
* Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
@@ -144,6 +150,44 @@
fun reversed(builder: TransitionBuilder.() -> Unit)
}
+interface SharedElementScenePicker {
+ /**
+ * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
+ * composed (when using `MovableElement(key)`) during the transition from [fromScene] to
+ * [toScene].
+ */
+ fun sceneDuringTransition(
+ element: ElementKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ progress: () -> Float,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float,
+ ): SceneKey
+}
+
+object DefaultSharedElementScenePicker : SharedElementScenePicker {
+ override fun sceneDuringTransition(
+ element: ElementKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ progress: () -> Float,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float
+ ): SceneKey {
+ // By default shared elements are drawn in the highest scene possible, unless it is a
+ // background.
+ return if (
+ (fromSceneZIndex > toSceneZIndex && !element.isBackground) ||
+ (fromSceneZIndex < toSceneZIndex && element.isBackground)
+ ) {
+ fromScene
+ } else {
+ toScene
+ }
+ }
+}
+
@TransitionDsl
interface PropertyTransformationBuilder {
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index f1c2717..d2bfd91 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -111,8 +111,12 @@
range = null
}
- override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
- transformations.add(SharedElementTransformation(matcher, enabled))
+ override fun sharedElement(
+ matcher: ElementMatcher,
+ enabled: Boolean,
+ scenePicker: SharedElementScenePicker,
+ ) {
+ transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))
}
override fun timestampRange(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 2ef8d56..0db8469 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -21,6 +21,7 @@
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SharedElementScenePicker
import com.android.compose.animation.scene.TransitionState
/** A transformation applied to one or more elements during a transition. */
@@ -48,6 +49,7 @@
internal class SharedElementTransformation(
override val matcher: ElementMatcher,
internal val enabled: Boolean,
+ internal val scenePicker: SharedElementScenePicker,
) : Transformation
/** A transformation that is applied on the element during the whole transition. */
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 4204cd5..83af630 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -144,7 +144,36 @@
rule.testTransition(
fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },
toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) },
- transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+ transition = {
+ spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+ sharedElement(
+ TestElements.Foo,
+ scenePicker =
+ object : SharedElementScenePicker {
+ override fun sceneDuringTransition(
+ element: ElementKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ progress: () -> Float,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float
+ ): SceneKey {
+ assertThat(fromScene).isEqualTo(TestScenes.SceneA)
+ assertThat(toScene).isEqualTo(TestScenes.SceneB)
+ assertThat(fromSceneZIndex).isEqualTo(0)
+ assertThat(toSceneZIndex).isEqualTo(1)
+
+ // Compose Foo in Scene A if progress < 0.65f, otherwise compose it
+ // in Scene B.
+ return if (progress() < 0.65f) {
+ TestScenes.SceneA
+ } else {
+ TestScenes.SceneB
+ }
+ }
+ }
+ )
+ },
fromScene = TestScenes.SceneA,
toScene = TestScenes.SceneB,
) {
@@ -170,9 +199,12 @@
at(32) {
// During the transition, there is a single counter that is moved, with the current
- // value.
+ // value. Given that progress = 0.5f, it is currently composed in SceneA.
rule
- .onNode(hasText("count: 3"))
+ .onNode(
+ hasText("count: 3") and
+ hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+ )
.assertIsDisplayed()
.assertSizeIsEqualTo(75.dp, 75.dp)
@@ -186,6 +218,26 @@
.isEqualTo(1)
}
+ at(48) {
+ // During the transition, there is a single counter that is moved, with the current
+ // value. Given that progress = 0.75f, it is currently composed in SceneB.
+ rule
+ .onNode(
+ hasText("count: 3") and
+ hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+ )
+ .assertIsDisplayed()
+
+ // There are no other counters.
+ assertThat(
+ rule
+ .onAllNodesWithText("count: ", substring = true)
+ .fetchSemanticsNodes()
+ .size
+ )
+ .isEqualTo(1)
+ }
+
after {
// At the end of the transition, the counter still has the current value.
rule
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 959cf6f..01fc035 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -51,7 +51,6 @@
public class KeyguardPasswordViewController
extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
- private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final DevicePostureController mPostureController;
private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -164,13 +163,6 @@
// If there's more than one IME, enable the IME switcher button
updateSwitchImeButton();
-
- // When we the current user is switching, InputMethodManagerService sometimes has not
- // switched internal state yet here. As a quick workaround, we check the keyboard state
- // again.
- // TODO: Remove this workaround by ensuring such a race condition never happens.
- mMainExecutor.executeDelayed(
- this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index e7f835f..c3aaef7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,5 +1,6 @@
package com.android.systemui.deviceentry
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import dagger.Module
@@ -7,6 +8,7 @@
includes =
[
DeviceEntryRepositoryModule::class,
+ DeviceEntryHapticsRepositoryModule::class,
],
)
object DeviceEntryModule
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
new file mode 100644
index 0000000..1458404
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Interface for classes that can access device-entry haptics application state. */
+interface DeviceEntryHapticsRepository {
+ /**
+ * Whether a successful biometric haptic has been requested. Has not yet been handled if true.
+ */
+ val successHapticRequest: Flow<Boolean>
+
+ /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */
+ val errorHapticRequest: Flow<Boolean>
+
+ fun requestSuccessHaptic()
+ fun handleSuccessHaptic()
+ fun requestErrorHaptic()
+ fun handleErrorHaptic()
+}
+
+/** Encapsulates application state for device entry haptics. */
+@SysUISingleton
+class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository {
+ private val _successHapticRequest = MutableStateFlow(false)
+ override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
+
+ private val _errorHapticRequest = MutableStateFlow(false)
+ override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
+
+ override fun requestSuccessHaptic() {
+ _successHapticRequest.value = true
+ }
+
+ override fun handleSuccessHaptic() {
+ _successHapticRequest.value = false
+ }
+
+ override fun requestErrorHaptic() {
+ _errorHapticRequest.value = true
+ }
+
+ override fun handleErrorHaptic() {
+ _errorHapticRequest.value = false
+ }
+}
+
+@Module
+interface DeviceEntryHapticsRepositoryModule {
+ @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
new file mode 100644
index 0000000..53d6f73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Business logic for device entry haptic events. Determines whether the haptic should play. In
+ * particular, there are extra guards for whether device entry error and successes hatpics should
+ * play when the physical fingerprint sensor is located on the power button.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryHapticsInteractor
+@Inject
+constructor(
+ private val repository: DeviceEntryHapticsRepository,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+ biometricSettingsRepository: BiometricSettingsRepository,
+ keyEventInteractor: KeyEventInteractor,
+ powerInteractor: PowerInteractor,
+ private val systemClock: SystemClock,
+ private val logger: BiometricUnlockLogger,
+) {
+ private val powerButtonSideFpsEnrolled =
+ combineTransform(
+ fingerprintPropertyRepository.sensorType,
+ biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+ ) { sensorType, enrolledAndEnabled ->
+ if (sensorType == FingerprintSensorType.POWER_BUTTON) {
+ emit(enrolledAndEnabled)
+ } else {
+ emit(false)
+ }
+ }
+ .distinctUntilChanged()
+ private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown
+ private val lastPowerButtonWakeup: Flow<Long> =
+ powerInteractor.detailedWakefulness
+ .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) }
+ .map { systemClock.uptimeMillis() }
+ .onStart {
+ // If the power button hasn't been pressed, we still want this to evaluate to true:
+ // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs`
+ emit(recentPowerButtonPressThresholdMs * -1L - 1L)
+ }
+
+ val playSuccessHaptic: Flow<Boolean> =
+ repository.successHapticRequest
+ .filter { it }
+ .sample(
+ combine(
+ powerButtonSideFpsEnrolled,
+ powerButtonDown,
+ lastPowerButtonWakeup,
+ ::Triple
+ )
+ )
+ .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+ val sideFpsAllowsHaptic =
+ !powerButtonDown &&
+ systemClock.uptimeMillis() - lastPowerButtonWakeup >
+ recentPowerButtonPressThresholdMs
+ val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
+ if (!allowHaptic) {
+ logger.d("Skip success haptic. Recent power button press or button is down.")
+ handleSuccessHaptic() // immediately handle, don't vibrate
+ }
+ allowHaptic
+ }
+ val playErrorHaptic: Flow<Boolean> =
+ repository.errorHapticRequest
+ .filter { it }
+ .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
+ .map { (sideFpsEnrolled, powerButtonDown) ->
+ val allowHaptic = !sideFpsEnrolled || !powerButtonDown
+ if (!allowHaptic) {
+ logger.d("Skip error haptic. Power button is down.")
+ handleErrorHaptic() // immediately handle, don't vibrate
+ }
+ allowHaptic
+ }
+
+ fun vibrateSuccess() {
+ repository.requestSuccessHaptic()
+ }
+
+ fun vibrateError() {
+ repository.requestErrorHaptic()
+ }
+
+ fun handleSuccessHaptic() {
+ repository.handleSuccessHaptic()
+ }
+
+ fun handleErrorHaptic() {
+ repository.handleErrorHaptic()
+ }
+
+ private val recentPowerButtonPressThresholdMs = 400L
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 081618e..472cc24 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -259,7 +259,7 @@
// TODO(b/290652751): Tracking bug.
@JvmField
val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
- unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true)
+ releasedFlag("migrate_split_keyguard_bottom_area")
// TODO(b/297037052): Tracking bug.
@JvmField
@@ -274,7 +274,7 @@
/** Migrate the lock icon view to the new keyguard root view. */
// TODO(b/286552209): Tracking bug.
- @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true)
+ @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon")
// TODO(b/288276738): Tracking bug.
@JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
@@ -419,7 +419,7 @@
releasedFlag("incompatible_charging_battery_icon")
// TODO(b/293585143): Tracking Bug
- val INSTANT_TETHER = unreleasedFlag("instant_tether")
+ val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)
// TODO(b/294588085): Tracking Bug
val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index a511713..119ade4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -28,6 +28,7 @@
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -44,6 +45,7 @@
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import javax.inject.Inject
@@ -72,7 +74,9 @@
private val keyguardIndicationController: KeyguardIndicationController,
private val lockIconViewController: LockIconViewController,
private val shadeInteractor: ShadeInteractor,
- private val interactionJankMonitor: InteractionJankMonitor
+ private val interactionJankMonitor: InteractionJankMonitor,
+ private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
+ private val vibratorHelper: VibratorHelper,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -143,6 +147,8 @@
shadeInteractor,
{ keyguardStatusViewController!!.getClockController() },
interactionJankMonitor,
+ deviceEntryHapticsInteractor,
+ vibratorHelper,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index c72e6ce..4d5c503 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.binder
import android.annotation.DrawableRes
+import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
@@ -29,6 +30,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -38,6 +40,7 @@
import com.android.systemui.plugins.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -45,6 +48,7 @@
import javax.inject.Provider
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@@ -62,6 +66,8 @@
shadeInteractor: ShadeInteractor,
clockControllerProvider: Provider<ClockController>?,
interactionJankMonitor: InteractionJankMonitor?,
+ deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
+ vibratorHelper: VibratorHelper?,
): DisposableHandle {
var onLayoutChangeListener: OnLayoutChange? = null
val childViews = mutableMapOf<Int, View?>()
@@ -177,6 +183,44 @@
}
}
}
+
+ if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
+ launch {
+ deviceEntryHapticsInteractor.playSuccessHaptic
+ .filter { it }
+ .collect {
+ if (
+ featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+ ) {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
+ } else {
+ vibratorHelper.vibrateAuthSuccess("device-entry::success")
+ }
+ deviceEntryHapticsInteractor.handleSuccessHaptic()
+ }
+ }
+
+ launch {
+ deviceEntryHapticsInteractor.playErrorHaptic
+ .filter { it }
+ .collect {
+ if (
+ featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+ ) {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.REJECT,
+ )
+ } else {
+ vibratorHelper.vibrateAuthSuccess("device-entry::error")
+ }
+ deviceEntryHapticsInteractor.handleErrorHaptic()
+ }
+ }
+ }
}
}
viewModel.clockControllerProvider = clockControllerProvider
@@ -189,7 +233,7 @@
view.setOnHierarchyChangeListener(
object : OnHierarchyChangeListener {
override fun onChildViewAdded(parent: View, child: View) {
- childViews.put(child.id, view)
+ childViews.put(child.id, child)
}
override fun onChildViewRemoved(parent: View, child: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 5a4bbef..692984a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -46,6 +46,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
@@ -114,6 +115,7 @@
private val chipbarCoordinator: ChipbarCoordinator,
private val keyguardStateController: KeyguardStateController,
private val shadeInteractor: ShadeInteractor,
+ private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -339,6 +341,8 @@
shadeInteractor,
null, // clock provider only needed for burn in
null, // jank monitor not required for preview mode
+ null, // device entry haptics not required for preview mode
+ null, // device entry haptics not required for preview mode
)
)
rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
index 9371d4e..342a440 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -50,11 +50,8 @@
override fun addViews(constraintLayout: ConstraintLayout) {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- val view =
- LayoutInflater.from(constraintLayout.context)
- .inflate(R.layout.ambient_indication, constraintLayout, false)
-
- constraintLayout.addView(view)
+ LayoutInflater.from(constraintLayout.context)
+ .inflate(R.layout.ambient_indication, constraintLayout, true)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index 8634b09..a53f0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -19,7 +19,11 @@
import android.os.Process
import android.os.RemoteException
import android.util.Log
-import com.android.internal.util.FrameworkStatsLog
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -36,21 +40,23 @@
*
* @param sessionCreationSource The entry point requesting permission to capture.
*/
- fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) {
- // TODO check that state & SessionCreationSource matches expected values
- notifyToServer(state, sessionCreationSource)
+ fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) {
+ notifyToServer(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+ sessionCreationSource
+ )
}
/**
* Request to log that the permission request moved to the given state.
*
- * Should not be used for the initialization state, since that
+ * Should not be used for the initialization state, since that should use {@link
+ * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the
+ * sessionCreationSource.
*/
fun notifyPermissionProgress(state: Int) {
// TODO validate state is valid
- notifyToServer(
- state,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN)
+ notifyToServer(state, SessionCreationSource.UNKNOWN)
}
/**
@@ -64,16 +70,21 @@
* Indicates the entry point for requesting the permission. Must be a valid state defined in
* the SessionCreationSource enum.
*/
- private fun notifyToServer(state: Int, sessionCreationSource: Int) {
+ private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) {
Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
try {
service.notifyPermissionRequestStateChange(
- Process.myUid(), state, sessionCreationSource)
+ Process.myUid(),
+ state,
+ sessionCreationSource.toMetricsConstant()
+ )
} catch (e: RemoteException) {
Log.e(
TAG,
- "Error notifying server of permission flow state $state from source $sessionCreationSource",
- e)
+ "Error notifying server of permission flow state $state from source " +
+ "$sessionCreationSource",
+ e
+ )
}
}
@@ -81,3 +92,18 @@
const val TAG = "MediaProjectionMetricsLogger"
}
}
+
+enum class SessionCreationSource {
+ APP,
+ CAST,
+ SYSTEM_UI_SCREEN_RECORDER,
+ UNKNOWN;
+
+ fun toMetricsConstant(): Int =
+ when (this) {
+ APP -> METRICS_CREATION_SOURCE_APP
+ CAST -> METRICS_CREATION_SOURCE_CAST
+ SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+ UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index b5d3e91..0bbcfd9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -87,14 +87,15 @@
override fun getLayoutResource() = R.layout.media_projection_app_selector
- public override fun onCreate(bundle: Bundle?) {
+ public override fun onCreate(savedInstanceState: Bundle?) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
component =
componentFactory.create(
hostUserHandle = hostUserHandle,
callingPackage = callingPackage,
view = this,
- resultHandler = this
+ resultHandler = this,
+ isFirstStart = savedInstanceState == null
)
component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
@@ -113,7 +114,7 @@
reviewGrantedConsentRequired =
intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
- super.onCreate(bundle)
+ super.onCreate(savedInstanceState)
controller.init()
// we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
// our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 2217509..8c6f307 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -146,6 +146,9 @@
@BindsInstance @MediaProjectionAppSelector callingPackage: String?,
@BindsInstance view: MediaProjectionAppSelectorView,
@BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+ // Whether the app selector is starting for the first time. False when it is re-starting
+ // due to a config change.
+ @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean,
): MediaProjectionAppSelectorComponent
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index fced117..69132d3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -18,6 +18,8 @@
import android.content.ComponentName
import android.os.UserHandle
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -43,9 +45,17 @@
@MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
@MediaProjectionAppSelector private val callerPackageName: String?,
private val thumbnailLoader: RecentTaskThumbnailLoader,
+ @MediaProjectionAppSelector private val isFirstStart: Boolean,
+ private val logger: MediaProjectionMetricsLogger,
) {
fun init() {
+ // Only log during the first start of the app selector.
+ // Don't log when the app selector restarts due to a config change.
+ if (isFirstStart) {
+ logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
scope.launch {
val recentTasks = recentTaskListProvider.loadRecentTasks()
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index a9e6c53..e9b4582 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -22,6 +22,7 @@
data class RecentTask(
val taskId: Int,
+ val displayId: Int,
@UserIdInt val userId: Int,
val topActivityComponent: ComponentName?,
val baseIntentComponent: ComponentName?,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index aa4c4e5..730aa62 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -60,6 +60,7 @@
.map {
RecentTask(
it.taskId,
+ it.displayId,
it.userId,
it.topActivity,
it.baseIntent?.component,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index fd1a683..ba837db 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -130,10 +130,10 @@
view.width,
view.height
)
- activityOptions.setPendingIntentBackgroundActivityStartMode(
+ activityOptions.pendingIntentBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
activityOptions.launchCookie = launchCookie
+ activityOptions.launchDisplayId = task.displayId
activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
resultHandler.returnSelectedApp(launchCookie)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index d08d040..fa418fc 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -53,7 +53,9 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
@@ -74,6 +76,7 @@
private final FeatureFlags mFeatureFlags;
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
private final StatusBarManager mStatusBarManager;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private String mPackageName;
private int mUid;
@@ -90,15 +93,17 @@
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
- StatusBarManager statusBarManager) {
+ StatusBarManager statusBarManager,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
}
@Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
final Intent launchingIntent = getIntent();
mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
@@ -133,6 +138,10 @@
try {
if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ SessionCreationSource.APP);
+ }
final IMediaProjection projection =
MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
mReviewGrantedConsentRequired);
@@ -231,6 +240,13 @@
mDialog = dialogBuilder.create();
}
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ appName == null
+ ? SessionCreationSource.CAST
+ : SessionCreationSource.APP);
+ }
+
setUpDialog(mDialog);
mDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 7a0c087..f469c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -38,6 +38,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
@@ -45,13 +47,13 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.CallbackController;
+import dagger.Lazy;
+
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* Helper class to initiate a screen recording
*/
@@ -71,6 +73,7 @@
private final FeatureFlags mFlags;
private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
protected static final String INTENT_UPDATE_STATE =
"com.android.systemui.screenrecord.UPDATE_STATE";
@@ -115,7 +118,8 @@
FeatureFlags flags,
UserContextProvider userContextProvider,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
mMainExecutor = mainExecutor;
mContext = context;
mFlags = flags;
@@ -123,6 +127,7 @@
mBroadcastDispatcher = broadcastDispatcher;
mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
@@ -149,6 +154,9 @@
return new ScreenCaptureDisabledDialog(mContext);
}
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this,
activityStarter, mUserContextProvider, onStartRecordingClicked)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6783afa..1ecb127 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,7 @@
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
import android.app.Activity;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.Gravity;
@@ -35,8 +36,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -74,21 +75,26 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setWindowAttributes();
+ setContentView(R.layout.brightness_mirror_container);
+ setBrightnessDialogViewAttributes();
+ }
+ private void setWindowAttributes() {
final Window window = getWindow();
- window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ window.setGravity(Gravity.TOP | Gravity.LEFT);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
// Calling this creates the decor View, so setLayout takes proper effect
// (see Dialog#onWindowAttributesChanged)
window.getDecorView();
- window.setLayout(
- WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
+ }
- setContentView(R.layout.brightness_mirror_container);
+ void setBrightnessDialogViewAttributes() {
FrameLayout frame = findViewById(R.id.brightness_mirror_container);
// The brightness mirror container is INVISIBLE by default.
frame.setVisibility(View.VISIBLE);
@@ -97,6 +103,14 @@
getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
lp.leftMargin = horizontalMargin;
lp.rightMargin = horizontalMargin;
+
+ int verticalMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.notification_guts_option_vertical_padding);
+
+ lp.topMargin = verticalMargin;
+ lp.bottomMargin = verticalMargin;
+
frame.setLayoutParams(lp);
Rect bounds = new Rect();
frame.addOnLayoutChangeListener(
@@ -113,6 +127,20 @@
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
mBrightnessController = mBrightnessControllerFactory.create(controller);
+
+ Configuration configuration = getResources().getConfiguration();
+ int orientation = configuration.orientation;
+
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2
+ - lp.leftMargin * 2;
+ } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ lp.width = getWindowManager().getDefaultDisplay().getWidth()
+ - lp.leftMargin * 2;
+ }
+
+ frame.setLayoutParams(lp);
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index fb45a67..8129b83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
import android.annotation.IntDef;
import android.content.res.Resources;
@@ -30,7 +28,6 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.Trace;
-import android.view.HapticFeedbackConstants;
import androidx.annotation.Nullable;
@@ -47,16 +44,17 @@
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -73,12 +71,14 @@
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Controller which coordinates all the biometric unlocking actions with the UI.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
- private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -175,6 +175,7 @@
private final BiometricUnlockLogger mLogger;
private final SystemClock mSystemClock;
private final boolean mOrderUnlockAndWake;
+ private final DeviceEntryHapticsInteractor mHapticsInteractor;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -284,7 +285,8 @@
ScreenOffAnimationController screenOffAnimationController,
VibratorHelper vibrator,
SystemClock systemClock,
- FeatureFlags featureFlags
+ FeatureFlags featureFlags,
+ DeviceEntryHapticsInteractor hapticsInteractor
) {
mPowerManager = powerManager;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -314,6 +316,7 @@
mFeatureFlags = featureFlags;
mOrderUnlockAndWake = resources.getBoolean(
com.android.internal.R.bool.config_orderUnlockAndWake);
+ mHapticsInteractor = hapticsInteractor;
dumpManager.registerDumpable(this);
}
@@ -434,7 +437,7 @@
if (mode == MODE_WAKE_AND_UNLOCK
|| mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
|| mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
- vibrateSuccess(biometricSourceType);
+ mHapticsInteractor.vibrateSuccess();
onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
}
startWakeAndUnlock(mode);
@@ -722,7 +725,7 @@
&& !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
KeyguardUpdateMonitor.getCurrentUser()))
|| (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
- vibrateError(biometricSourceType);
+ mHapticsInteractor.vibrateError();
}
cleanup();
@@ -749,45 +752,6 @@
cleanup();
}
- // these haptics are for device-entry only
- private void vibrateSuccess(BiometricSourceType type) {
- if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
- && lastWakeupFromPowerButtonWithinHapticThreshold()) {
- mLogger.d("Skip auth success haptic. Power button was recently pressed.");
- return;
- }
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mVibratorHelper.performHapticFeedback(
- mKeyguardViewController.getViewRootImpl().getView(),
- HapticFeedbackConstants.CONFIRM
- );
- } else {
- mVibratorHelper.vibrateAuthSuccess(
- getClass().getSimpleName() + ", type =" + type + "device-entry::success");
- }
- }
-
- private boolean lastWakeupFromPowerButtonWithinHapticThreshold() {
- final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason()
- == PowerManager.WAKE_REASON_POWER_BUTTON;
- return lastWakeupFromPowerButton
- && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME
- && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime()
- < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS;
- }
-
- private void vibrateError(BiometricSourceType type) {
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mVibratorHelper.performHapticFeedback(
- mKeyguardViewController.getViewRootImpl().getView(),
- HapticFeedbackConstants.REJECT
- );
- } else {
- mVibratorHelper.vibrateAuthError(
- getClass().getSimpleName() + ", type =" + type + "device-entry::error");
- }
- }
-
private void cleanup() {
releaseBiometricWakeLock();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index e9e52a2..1670dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -104,7 +104,7 @@
val callback =
object : WifiPickerTracker.WifiPickerTrackerCallback {
override fun onWifiEntriesChanged() {
- val connectedEntry = wifiPickerTracker?.connectedWifiEntry
+ val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
logOnWifiEntriesChanged(connectedEntry)
val secondaryNetworks =
@@ -217,6 +217,21 @@
.stateIn(scope, SharingStarted.Eagerly, emptyList())
/**
+ * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+ * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+ * if it exists, falling back on the connected entry if null
+ */
+ private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+ get() {
+ val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+ return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+ mergedEntry
+ } else {
+ this?.connectedWifiEntry
+ }
+ }
+
+ /**
* Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
* primary network. Returns an inactive network if it's not primary.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 9b06a37..d566725 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -191,7 +191,7 @@
}
@Override
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
throw new UnsupportedOperationException("not implemented");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
new file mode 100644
index 0000000..9b8e581
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
+
+ private lateinit var repository: DeviceEntryHapticsRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var keyEventRepository: FakeKeyEventRepository
+ private lateinit var powerRepository: FakePowerRepository
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var underTest: DeviceEntryHapticsInteractor
+
+ @Before
+ fun setUp() {
+ repository = DeviceEntryHapticsRepositoryImpl()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ keyEventRepository = FakeKeyEventRepository()
+ powerRepository = FakePowerRepository()
+ systemClock = FakeSystemClock()
+ underTest =
+ DeviceEntryHapticsInteractor(
+ repository = repository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ biometricSettingsRepository = biometricSettingsRepository,
+ keyEventInteractor = KeyEventInteractor(keyEventRepository),
+ powerInteractor =
+ PowerInteractor(
+ powerRepository,
+ mock(FalsingCollector::class.java),
+ mock(ScreenOffAnimationController::class.java),
+ mock(StatusBarStateController::class.java),
+ ),
+ systemClock = systemClock,
+ logger = mock(BiometricUnlockLogger::class.java),
+ )
+ }
+
+ @Test
+ fun nonPowerButtonFPS_vibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_vibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(false)
+
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isFalse()
+ }
+
+ @Test
+ fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(false)
+
+ // It's only been 50ms since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50)
+
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isFalse()
+ }
+
+ @Test
+ fun nonPowerButtonFPS_vibrateError() = runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ underTest.vibrateError()
+ assertThat(playErrorHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_vibrateError() = runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ underTest.vibrateError()
+ assertThat(playErrorHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_powerDown_doNotVibrateError() = runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(true)
+ underTest.vibrateError()
+ assertThat(playErrorHaptic).isFalse()
+ }
+
+ private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
+ fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = fingerprintSensorType,
+ sensorLocations = mapOf(),
+ )
+ }
+
+ private fun setPowerButtonFingerprintProperty() {
+ setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
+ }
+
+ private fun setFingerprintEnrolled() {
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ }
+
+ private fun setAwakeFromPowerButton() {
+ powerRepository.updateWakefulness(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.POWER_BUTTON,
+ WakeSleepReason.POWER_BUTTON,
+ powerButtonLaunchGestureTriggered = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 34360d2..6cdf4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -4,7 +4,9 @@
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -20,6 +22,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@RunWith(AndroidTestingRunner::class)
@@ -37,10 +40,11 @@
private val view: MediaProjectionAppSelectorView = mock()
private val policyResolver: ScreenCaptureDevicePolicyResolver = mock()
+ private val logger = mock<MediaProjectionMetricsLogger>()
private val thumbnailLoader = FakeThumbnailLoader()
- private val controller =
+ private fun createController(isFirstStart: Boolean = true) =
MediaProjectionAppSelectorController(
taskListProvider,
view,
@@ -50,6 +54,8 @@
appSelectorComponentName,
callerPackageName,
thumbnailLoader,
+ isFirstStart,
+ logger
)
@Before
@@ -61,7 +67,7 @@
fun initNoRecentTasks_bindsEmptyList() {
taskListProvider.tasks = emptyList()
- controller.init()
+ createController().init()
verify(view).bind(emptyList())
}
@@ -70,7 +76,7 @@
fun initOneRecentTask_bindsList() {
taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
- controller.init()
+ createController().init()
verify(view).bind(listOf(createRecentTask(taskId = 1)))
}
@@ -86,7 +92,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3)
}
@@ -101,7 +107,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -124,7 +130,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -147,7 +153,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -172,7 +178,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -199,11 +205,29 @@
taskListProvider.tasks = tasks
givenCaptureAllowed(isAllow = false)
- controller.init()
+ createController().init()
verify(view).bind(emptyList())
}
+ @Test
+ fun init_firstStart_logsAppSelectorDisplayed() {
+ val controller = createController(isFirstStart = true)
+
+ controller.init()
+
+ verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
+ @Test
+ fun init_notFirstStart_doesNotLogAppSelectorDisplayed() {
+ val controller = createController(isFirstStart = false)
+
+ controller.init()
+
+ verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
private fun givenCaptureAllowed(isAllow: Boolean) {
whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow)
}
@@ -216,6 +240,7 @@
): RecentTask {
return RecentTask(
taskId = taskId,
+ displayId = 0,
topActivityComponent = topActivityComponent,
baseIntentComponent = ComponentName("com", "Test"),
userId = userId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 2c7ee56..d75553f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -128,6 +128,7 @@
private fun createRecentTask(taskId: Int): RecentTask =
RecentTask(
taskId = taskId,
+ displayId = 0,
userId = 0,
topActivityComponent = null,
baseIntentComponent = null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6e6833d..90d2e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,8 +17,10 @@
package com.android.systemui.screenrecord;
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -37,6 +39,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
@@ -76,6 +80,8 @@
private ActivityStarter mActivityStarter;
@Mock
private UserTracker mUserTracker;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
@@ -86,8 +92,15 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mFeatureFlags = new FakeFeatureFlags();
- mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
- mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
+ mController = new RecordingController(
+ mMainExecutor,
+ mBroadcastDispatcher,
+ mContext,
+ mFeatureFlags,
+ mUserContextProvider,
+ () -> mDevicePolicyResolver,
+ mUserTracker,
+ mMediaProjectionMetricsLogger);
mController.addCallback(mCallback);
}
@@ -269,4 +282,21 @@
assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
}
+
+ @Test
+ public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+ mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ verify(mMediaProjectionMetricsLogger)
+ .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 700de53..8344cd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -18,7 +18,9 @@
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -38,7 +40,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
-import android.view.HapticFeedbackConstants;
import android.view.ViewRootImpl;
import com.android.internal.logging.MetricsLogger;
@@ -47,6 +48,7 @@
import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -122,6 +124,8 @@
private BiometricUnlockLogger mLogger;
@Mock
private ViewRootImpl mViewRootImpl;
+ @Mock
+ private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
private final FakeSystemClock mSystemClock = new FakeSystemClock();
private FakeFeatureFlags mFeatureFlags;
private BiometricUnlockController mBiometricUnlockController;
@@ -158,7 +162,8 @@
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
mSystemClock,
- mFeatureFlags
+ mFeatureFlags,
+ mDeviceEntryHapticsInteractor
);
biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
biometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -462,145 +467,23 @@
}
@Test
- public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() {
- // GIVEN side fingerprint enrolled, last wake reason was power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
- // GIVEN last wake time just occurred
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+ public void onFingerprintSuccess_requestSuccessHaptic() {
// WHEN biometric fingerprint succeeds
givenFingerprintModeUnlockCollapsing();
mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
true);
- // THEN DO NOT vibrate the device
- verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString());
+ // THEN always vibrate the device
+ verify(mDeviceEntryHapticsInteractor).vibrateSuccess();
}
@Test
- public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
- // GIVEN side fingerprint enrolled, last wake reason was power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
- // GIVEN last wake time was 500ms ago
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
- mSystemClock.advanceTime(500);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).vibrateAuthSuccess(anyString());
- }
-
- @Test
- public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() {
- // GIVEN oneway haptics is enabled
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- // GIVEN side fingerprint enrolled, last wake reason was power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
- // GIVEN last wake time was 500ms ago
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
- mSystemClock.advanceTime(500);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.CONFIRM)
- );
- }
-
- @Test
- public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() {
- // GIVEN side fingerprint enrolled, wakeup just happened
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
- // GIVEN last wake reason was from a gesture
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).vibrateAuthSuccess(anyString());
- }
-
- @Test
- public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() {
- //GIVEN oneway haptics is enabled
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- // GIVEN side fingerprint enrolled, wakeup just happened
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
- // GIVEN last wake reason was from a gesture
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.CONFIRM)
- );
- }
-
- @Test
- public void onSideFingerprintFail_alwaysPlaysHaptic() {
- // GIVEN side fingerprint enrolled, last wake reason was recent power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+ public void onFingerprintFail_requestErrorHaptic() {
// WHEN biometric fingerprint fails
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
// THEN always vibrate the device
- verify(mVibratorHelper).vibrateAuthError(anyString());
- }
-
- @Test
- public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() {
- // GIVEN oneway haptics is enabled
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- // GIVEN side fingerprint enrolled, last wake reason was recent power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
- // WHEN biometric fingerprint fails
- mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
- // THEN always vibrate the device
- verify(mVibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.REJECT)
- );
+ verify(mDeviceEntryHapticsInteractor).vibrateError();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index c2f5665..3126362 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -201,11 +201,11 @@
testScope.runTest {
val latest by collectLastValue(underTest.isWifiDefault)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest).isTrue()
@@ -229,11 +229,11 @@
testScope.runTest {
val latest by collectLastValue(underTest.isWifiDefault)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isDefaultNetwork).thenReturn(false)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest).isFalse()
@@ -526,13 +526,14 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(3)
whenever(this.subscriptionId).thenReturn(567)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -546,11 +547,12 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
whenever(wifiManager.maxSignalLevel).thenReturn(5)
getCallback().onWifiEntriesChanged()
@@ -566,12 +568,13 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
@@ -628,11 +631,12 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(false)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
getCallback().onWifiEntriesChanged()
assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
@@ -717,12 +721,14 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(3)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -730,6 +736,7 @@
// WHEN we lose our current network
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
getCallback().onWifiEntriesChanged()
// THEN we update to no network
@@ -767,6 +774,56 @@
}
@Test
+ fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val mergedEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ }
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(1)
+ whenever(this.title).thenReturn(TITLE)
+ }
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+ }
+
+ @Test
+ fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val mergedEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ whenever(this.isDefaultNetwork).thenReturn(false)
+ }
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(1)
+ whenever(this.title).thenReturn(TITLE)
+ }
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ }
+
+ @Test
fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
testScope.runTest {
featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 197873f..288dcfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -186,7 +186,7 @@
}
@Override
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
return false;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index e5225f6..1d02e4c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -191,7 +191,7 @@
"ImmutabilityAnnotation",
"securebox",
"apache-commons-math",
- "power_optimization_flags_lib",
+ "backstage_power_flags_lib",
"notification_flags_lib",
"camera_platform_flags_core_java_lib",
"biometrics_flags_lib",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 8df5456..638abdb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1387,11 +1387,12 @@
@UserIdInt int userId);
/**
- * Tells PackageManager when a component (except BroadcastReceivers) of the package is used
+ * Tells PackageManager when a component of the package is used
* and the package should get out of stopped state and be enabled.
*/
public abstract void notifyComponentUsed(@NonNull String packageName,
- @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo);
+ @UserIdInt int userId, @Nullable String recentCallingPackage,
+ @NonNull String debugInfo);
/** @deprecated For legacy shell command only. */
@Deprecated
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 127c5b3..3c56752 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1440,10 +1440,9 @@
r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
}
- // Broadcast is being executed, its package can't be stopped.
try {
- mService.mPackageManagerInt.setPackageStoppedState(
- r.curComponent.getPackageName(), false, r.userId);
+ mService.mPackageManagerInt.notifyComponentUsed(
+ r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ r.curComponent.getPackageName() + ": " + e);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d19eae5..b481697 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1982,8 +1982,8 @@
mService.notifyPackageUse(receiverPackageName,
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- mService.mPackageManagerInt.setPackageStoppedState(
- receiverPackageName, false, r.userId);
+ mService.mPackageManagerInt.notifyComponentUsed(
+ receiverPackageName, r.userId, r.callerPackage, r.toString());
}
private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 16e3fdf2..2d231b3 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -152,6 +152,7 @@
"preload_safety",
"responsible_apis",
"rust",
+ "safety_center",
"system_performance",
"test_suites",
"text",
@@ -159,7 +160,14 @@
"tv_system_ui",
"vibrator",
"virtual_devices",
+ "wear_calling_messaging",
+ "wear_connectivity",
+ "wear_esim_carriers",
"wear_frameworks",
+ "wear_health_services",
+ "wear_media",
+ "wear_offload",
+ "wear_security",
"wear_system_health",
"wear_systems",
"window_surfaces",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 26d99d8..bb9ea28 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -2,7 +2,7 @@
flag {
name: "oomadjuster_correctness_rewrite"
- namespace: "android_platform_power_optimization"
+ namespace: "backstage_power"
description: "Utilize new OomAdjuster implementation"
bug: "298055811"
is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 6c0fef5..5f4e4c3 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -157,7 +157,8 @@
Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
// Once the uid is no longer running, close all remain audio session(s) for this UID
if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
- final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid));
+ final List<Integer> sessions =
+ new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));
Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
for (Integer session : sessions) {
Intent intent = new Intent(
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 35a4f47..5b87eea 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -758,7 +758,8 @@
mContext.registerReceiver(mIdleModeReceiver, filter);
- mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
+ mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled())
+ ? SmallAreaDetectionController.create(mContext) : null;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
index adaa539..bf384b0 100644
--- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.ArrayMap;
@@ -30,15 +31,14 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import java.io.PrintWriter;
-import java.util.Arrays;
import java.util.Map;
final class SmallAreaDetectionController {
- private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
- private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+ private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds);
+ private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold);
// TODO(b/281720315): Move this to DeviceConfig once server side ready.
private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
@@ -47,12 +47,8 @@
private final Object mLock = new Object();
private final Context mContext;
private final PackageManagerInternal mPackageManager;
- private final UserManagerInternal mUserManager;
@GuardedBy("mLock")
private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
- // TODO(b/298722189): Update allowlist when user changes
- @GuardedBy("mLock")
- private int[] mUserIds;
static SmallAreaDetectionController create(@NonNull Context context) {
final SmallAreaDetectionController controller =
@@ -67,7 +63,6 @@
SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
mContext = context;
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
- mUserManager = LocalServices.getService(UserManagerInternal.class);
deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
BackgroundThread.getExecutor(),
new SmallAreaDetectionController.OnPropertiesChangedListener());
@@ -76,6 +71,7 @@
@VisibleForTesting
void updateAllowlist(@Nullable String property) {
+ final Map<String, Float> allowPkgMap = new ArrayMap<>();
synchronized (mLock) {
mAllowPkgMap.clear();
if (property != null) {
@@ -86,8 +82,11 @@
.getStringArray(R.array.config_smallAreaDetectionAllowlist);
for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
}
- updateSmallAreaDetection();
+
+ if (mAllowPkgMap.isEmpty()) return;
+ allowPkgMap.putAll(mAllowPkgMap);
}
+ updateSmallAreaDetection(allowPkgMap);
}
@GuardedBy("mLock")
@@ -105,43 +104,32 @@
}
}
- @GuardedBy("mLock")
- private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
- for (int i = 0; i < mUserIds.length; i++) {
- final int userId = mUserIds[i];
- final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
- if (uid > 0) list.put(uid, threshold);
- }
- }
-
- @GuardedBy("mLock")
- private void updateSmallAreaDetection() {
- if (mAllowPkgMap.isEmpty()) return;
-
- mUserIds = mUserManager.getUserIds();
-
- final SparseArray<Float> uidThresholdList = new SparseArray<>();
- for (String pkg : mAllowPkgMap.keySet()) {
- final float threshold = mAllowPkgMap.get(pkg);
- updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+ private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) {
+ final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size());
+ for (String pkg : allowPkgMap.keySet()) {
+ final float threshold = allowPkgMap.get(pkg);
+ final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg);
+ if (stage != null) {
+ appIdThresholdList.put(stage.getAppId(), threshold);
+ }
}
- final int[] uids = new int[uidThresholdList.size()];
- final float[] thresholds = new float[uidThresholdList.size()];
- for (int i = 0; i < uidThresholdList.size(); i++) {
- uids[i] = uidThresholdList.keyAt(i);
- thresholds[i] = uidThresholdList.valueAt(i);
+ final int[] appIds = new int[appIdThresholdList.size()];
+ final float[] thresholds = new float[appIdThresholdList.size()];
+ for (int i = 0; i < appIdThresholdList.size(); i++) {
+ appIds[i] = appIdThresholdList.keyAt(i);
+ thresholds[i] = appIdThresholdList.valueAt(i);
}
- updateSmallAreaDetection(uids, thresholds);
+ updateSmallAreaDetection(appIds, thresholds);
}
@VisibleForTesting
- void updateSmallAreaDetection(int[] uids, float[] thresholds) {
- nativeUpdateSmallAreaDetection(uids, thresholds);
+ void updateSmallAreaDetection(int[] appIds, float[] thresholds) {
+ nativeUpdateSmallAreaDetection(appIds, thresholds);
}
- void setSmallAreaDetectionThreshold(int uid, float threshold) {
- nativeSetSmallAreaDetectionThreshold(uid, threshold);
+ void setSmallAreaDetectionThreshold(int appId, float threshold) {
+ nativeSetSmallAreaDetectionThreshold(appId, threshold);
}
void dump(PrintWriter pw) {
@@ -151,7 +139,6 @@
for (String pkg : mAllowPkgMap.keySet()) {
pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
}
- pw.println(" mUserIds=" + Arrays.toString(mUserIds));
}
}
@@ -167,11 +154,15 @@
private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
@Override
public void onPackageAdded(@NonNull String packageName, int uid) {
+ float threshold = 0.0f;
synchronized (mLock) {
if (mAllowPkgMap.containsKey(packageName)) {
- setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+ threshold = mAllowPkgMap.get(packageName);
}
}
+ if (threshold > 0.0f) {
+ setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index fae8383..d953e8e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -71,6 +71,10 @@
Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
Flags::enablePowerThrottlingClamper);
+ private final FlagState mSmallAreaDetectionFlagState = new FlagState(
+ Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
+ Flags::enableSmallAreaDetection);
+
/** Returns whether connected display management is enabled or not. */
public boolean isConnectedDisplayManagementEnabled() {
return mConnectedDisplayManagementFlagState.isEnabled();
@@ -147,6 +151,10 @@
return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
}
+ public boolean isSmallAreaDetectionEnabled() {
+ return mSmallAreaDetectionFlagState.isEnabled();
+ }
+
private static class FlagState {
private final String mName;
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 9ab9c9d..9141814 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
@@ -104,3 +104,12 @@
bug: "211737588"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_small_area_detection"
+ namespace: "display_manager"
+ description: "Feature flag for SmallAreaDetection"
+ bug: "298722189"
+ is_fixed_read_only: true
+}
+
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index ca23844..d023913 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -723,7 +723,9 @@
if (mode.getPhysicalWidth() > maxAllowedWidth
|| mode.getPhysicalHeight() > maxAllowedHeight
|| mode.getPhysicalWidth() < outSummary.minWidth
- || mode.getPhysicalHeight() < outSummary.minHeight) {
+ || mode.getPhysicalHeight() < outSummary.minHeight
+ || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate
+ || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) {
continue;
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
new file mode 100644
index 0000000..ff70cb3
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 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.media.projection;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+
+public class MediaProjectionSessionIdGenerator {
+
+ private static final String PREFERENCES_FILE_NAME = "media_projection_session_id";
+ private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key";
+ private static final int SESSION_ID_DEFAULT_VALUE = 0;
+
+ private static final Object sInstanceLock = new Object();
+
+ @GuardedBy("sInstanceLock")
+ private static MediaProjectionSessionIdGenerator sInstance;
+
+ private final Object mSessionIdLock = new Object();
+
+ @GuardedBy("mSessionIdLock")
+ private final SharedPreferences mSharedPreferences;
+
+ /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */
+ public static MediaProjectionSessionIdGenerator getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ File preferencesFile =
+ new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+ SharedPreferences preferences =
+ context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+ sInstance = new MediaProjectionSessionIdGenerator(preferences);
+ }
+ return sInstance;
+ }
+ }
+
+ @VisibleForTesting
+ public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) {
+ this.mSharedPreferences = sharedPreferences;
+ }
+
+ /** Returns the current session ID. This value is persisted across reboots. */
+ public int getCurrentSessionId() {
+ synchronized (mSessionIdLock) {
+ return getCurrentSessionIdInternal();
+ }
+ }
+
+ /**
+ * Creates and returns a new session ID. This value will be persisted as the new current session
+ * ID, and will be persisted across reboots.
+ */
+ public int createAndGetNewSessionId() {
+ synchronized (mSessionIdLock) {
+ int newSessionId = getCurrentSessionId() + 1;
+ setSessionIdInternal(newSessionId);
+ return newSessionId;
+ }
+ }
+
+ @GuardedBy("mSessionIdLock")
+ private void setSessionIdInternal(int value) {
+ mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply();
+ }
+
+ @GuardedBy("mSessionIdLock")
+ private int getCurrentSessionIdInternal() {
+ return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index f59188e..0fb1f7a 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -196,8 +196,12 @@
for (int i = 0, size = mainActivities.length; i < size; ++i) {
var mainActivity = mainActivities[i];
Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
- ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
- mainActivity.title, iconPath, null);
+ ArchiveActivityInfo activityInfo =
+ new ArchiveActivityInfo(
+ mainActivity.title,
+ mainActivity.originalComponentName,
+ iconPath,
+ null);
archiveActivityInfos.add(activityInfo);
}
@@ -215,8 +219,12 @@
for (int i = 0, size = mainActivities.size(); i < size; i++) {
LauncherActivityInfo mainActivity = mainActivities.get(i);
Path iconPath = storeIcon(packageName, mainActivity, userId, i);
- ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
- mainActivity.getLabel().toString(), iconPath, null);
+ ArchiveActivityInfo activityInfo =
+ new ArchiveActivityInfo(
+ mainActivity.getLabel().toString(),
+ mainActivity.getComponentName(),
+ iconPath,
+ null);
archiveActivityInfos.add(activityInfo);
}
@@ -593,6 +601,7 @@
}
var archivedActivity = new ArchivedActivityParcel();
archivedActivity.title = info.getTitle();
+ archivedActivity.originalComponentName = info.getOriginalComponentName();
archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
info.getMonochromeIconBitmap());
@@ -624,6 +633,7 @@
}
var archivedActivity = new ArchivedActivityParcel();
archivedActivity.title = info.getLabel().toString();
+ archivedActivity.originalComponentName = info.getComponentName();
archivedActivity.iconBitmap =
info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
drawableToBitmap(info.getIcon(/* density= */ 0)));
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 651845e..e749968 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -747,7 +747,7 @@
@Override
public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId,
- @NonNull String recentCallingPackage, @NonNull String debugInfo) {
+ @Nullable String recentCallingPackage, @NonNull String debugInfo) {
mService.notifyComponentUsed(snapshot(), packageName, userId,
recentCallingPackage, debugInfo);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index abeabc9..839b699 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
@@ -164,6 +165,7 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.ExceptionUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -4576,7 +4578,7 @@
}
void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName,
- @UserIdInt int userId, @NonNull String recentCallingPackage,
+ @UserIdInt int userId, @Nullable String recentCallingPackage,
@NonNull String debugInfo) {
synchronized (mLock) {
final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
@@ -6133,8 +6135,16 @@
final Computer snapshot = snapshotComputer();
enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,
"setPackagesSuspendedAsUser");
- boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
- && Flags.quarantinedEnabled();
+ boolean quarantined = false;
+ if (Flags.quarantinedEnabled()) {
+ if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
+ quarantined = true;
+ } else if (FeatureFlagUtils.isEnabled(mContext,
+ SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
+ final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
+ quarantined = callingPackage.equals(wellbeingPkg);
+ }
+ }
return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
false /* forQuietMode */, quarantined);
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9f4e86d..3ca933a 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1228,6 +1228,9 @@
long activityInfoToken = proto.start(
PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle());
+ proto.write(
+ ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME,
+ activityInfo.getOriginalComponentName().flattenToString());
if (activityInfo.getIconBitmap() != null) {
proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,
activityInfo.getIconBitmap().toAbsolutePath().toString());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 397a841..e726d91 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -368,6 +368,7 @@
private static final String ATTR_VALUE = "value";
private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+ private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name";
private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
@@ -2079,6 +2080,8 @@
if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
String title = parser.getAttributeValue(null,
ATTR_ARCHIVE_ACTIVITY_TITLE);
+ String originalComponentName =
+ parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME);
String iconAttribute = parser.getAttributeValue(null,
ATTR_ARCHIVE_ICON_PATH);
Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute);
@@ -2087,17 +2090,27 @@
Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(
monochromeAttribute);
- if (title == null || iconPath == null) {
- Slog.wtf(TAG,
- TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
- TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+ if (title == null || originalComponentName == null || iconPath == null) {
+ Slog.wtf(
+ TAG,
+ TextUtils.formatSimple(
+ "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s",
+ TAG_ARCHIVE_ACTIVITY_INFO,
+ ATTR_ARCHIVE_ACTIVITY_TITLE,
+ title,
+ ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+ originalComponentName,
ATTR_ARCHIVE_ICON_PATH,
iconPath));
continue;
}
activityInfos.add(
- new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+ new ArchiveState.ArchiveActivityInfo(
+ title,
+ ComponentName.unflattenFromString(originalComponentName),
+ iconPath,
+ monochromeIconPath));
}
}
return activityInfos;
@@ -2469,6 +2482,10 @@
for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+ serializer.attribute(
+ null,
+ ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+ activityInfo.getOriginalComponentName().flattenToString());
if (activityInfo.getIconBitmap() != null) {
serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
activityInfo.getIconBitmap().toAbsolutePath().toString());
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
index 4916a4a..1e40d44 100644
--- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -16,9 +16,11 @@
package com.android.server.pm.pkg;
+import android.content.ComponentName;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.internal.util.AnnotationValidations;
import com.android.internal.util.DataClass;
import java.nio.file.Path;
@@ -56,6 +58,10 @@
@NonNull
private final String mTitle;
+ /** The component name of the original activity (pre-archival). */
+ @NonNull
+ private final ComponentName mOriginalComponentName;
+
/**
* The path to the stored icon of the activity in the app's locale. Null if the app does
* not define any icon (default icon would be shown on the launcher).
@@ -96,11 +102,13 @@
@DataClass.Generated.Member
public ArchiveActivityInfo(
@NonNull String title,
+ @NonNull ComponentName originalComponentName,
@Nullable Path iconBitmap,
@Nullable Path monochromeIconBitmap) {
this.mTitle = title;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTitle);
+ this.mOriginalComponentName = originalComponentName;
+ AnnotationValidations.validate(NonNull.class, null, mTitle);
+ AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName);
this.mIconBitmap = iconBitmap;
this.mMonochromeIconBitmap = monochromeIconBitmap;
@@ -116,6 +124,14 @@
}
/**
+ * The component name of the original activity (pre-archival).
+ */
+ @DataClass.Generated.Member
+ public @NonNull ComponentName getOriginalComponentName() {
+ return mOriginalComponentName;
+ }
+
+ /**
* The path to the stored icon of the activity in the app's locale. Null if the app does
* not define any icon (default icon would be shown on the launcher).
*/
@@ -140,6 +156,7 @@
return "ArchiveActivityInfo { " +
"title = " + mTitle + ", " +
+ "originalComponentName = " + mOriginalComponentName + ", " +
"iconBitmap = " + mIconBitmap + ", " +
"monochromeIconBitmap = " + mMonochromeIconBitmap +
" }";
@@ -159,6 +176,7 @@
//noinspection PointlessBooleanExpression
return true
&& java.util.Objects.equals(mTitle, that.mTitle)
+ && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName)
&& java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
&& java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
}
@@ -171,6 +189,7 @@
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+ _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName);
_hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
_hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
return _hash;
@@ -180,7 +199,8 @@
time = 1693590309015L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ inputSignatures =
+ "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
@Deprecated
private void __metadata() {}
@@ -224,11 +244,9 @@
@NonNull List<ArchiveActivityInfo> activityInfos,
@NonNull String installerTitle) {
this.mActivityInfos = activityInfos;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mActivityInfos);
+ AnnotationValidations.validate(NonNull.class, null, mActivityInfos);
this.mInstallerTitle = installerTitle;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mInstallerTitle);
+ AnnotationValidations.validate(NonNull.class, null, mInstallerTitle);
// onConstructed(); // You can define this method to get a callback
}
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 1da9dd7..607d435 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -1,5 +1,5 @@
aconfig_declarations {
- name: "power_optimization_flags",
+ name: "backstage_power_flags",
package: "com.android.server.power.optimization",
srcs: [
"stats/*.aconfig",
@@ -7,6 +7,6 @@
}
java_aconfig_library {
- name: "power_optimization_flags_lib",
- aconfig_declarations: "power_optimization_flags",
+ name: "backstage_power_flags_lib",
+ aconfig_declarations: "backstage_power_flags",
}
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 add806f..0f13571 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -2,14 +2,14 @@
flag {
name: "power_monitor_api"
- namespace: "power_optimization"
+ namespace: "backstage_power"
description: "Feature flag for ODPM API"
bug: "295027807"
}
flag {
name: "streamlined_battery_stats"
- namespace: "power_optimization"
+ namespace: "backstage_power"
description: "Feature flag for streamlined battery stats"
bug: "285646152"
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 0718f2f..4200fbf 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3982,7 +3982,9 @@
if (wallpaper == null) {
// common case, this is the first lookup post-boot of the system or
// unified lock, so we bring up the saved state lazily now and recheck.
- int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM;
+ // if we're loading the system wallpaper for the first time, also load the lock
+ // wallpaper to determine if the system wallpaper is system+lock or system only.
+ int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK;
loadSettingsLocked(userId, false, whichLoad);
wallpaper = whichSet.get(userId);
if (wallpaper == null) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4237668..6d59b29 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -276,6 +276,10 @@
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
removedWindowContainer = window;
+ } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
+ // skip if current activity is translucent
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ removedWindowContainer = window;
} else if (prevActivity != null) {
if (!isOccluded || prevActivity.canShowWhenLocked()) {
// We have another Activity in the same currentTask to go to
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1d5d279..4922e90 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5635,6 +5635,8 @@
if (noAnimation) {
mDisplayContent.prepareAppTransition(TRANSIT_NONE);
mTaskSupervisor.mNoAnimActivities.add(top);
+ mTransitionController.collect(top);
+ mTransitionController.setNoAnimation(top);
ActivityOptions.abort(options);
} else {
updateTransitLocked(TRANSIT_TO_FRONT, options);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9663f3a..88f72f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8461,16 +8461,18 @@
return true;
}
// For a task session, find the activity identified by the launch cookie.
- final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+ final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie(
incomingSession.getTokenToRecord());
- if (wct == null) {
+ if (wci == null) {
Slog.w(TAG, "Handling a new recording session; unable to find the "
+ "WindowContainerToken");
return false;
}
// Replace the launch cookie in the session details with the task's
// WindowContainerToken.
- incomingSession.setTokenToRecord(wct.asBinder());
+ incomingSession.setTokenToRecord(wci.getToken().asBinder());
+ // Also replace the UNKNOWN target UID with the actual UID.
+ incomingSession.setTargetUid(wci.getUid());
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
return true;
@@ -8798,21 +8800,41 @@
mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
}
+ @VisibleForTesting
+ static class WindowContainerInfo {
+ private final int mUid;
+ @NonNull private final WindowContainerToken mToken;
+
+ private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) {
+ this.mUid = uid;
+ this.mToken = token;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ @NonNull
+ public WindowContainerToken getToken() {
+ return mToken;
+ }
+ }
+
/**
- * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
- * with the given launch cookie.
+ * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with
+ * the given launch cookie.
*
* @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
- * activity
+ * activity
* @return a token representing the task containing the activity started with the given launch
- * cookie, or {@code null} if the token couldn't be found.
+ * cookie, or {@code null} if the token couldn't be found.
*/
@VisibleForTesting
@Nullable
- WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+ WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) {
// Find the activity identified by the launch cookie.
- final ActivityRecord targetActivity = mRoot.getActivity(
- activity -> activity.mLaunchCookie == launchCookie);
+ final ActivityRecord targetActivity =
+ mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);
if (targetActivity == null) {
Slog.w(TAG, "Unable to find the activity for this launch cookie");
return null;
@@ -8827,7 +8849,7 @@
Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
return null;
}
- return taskWindowContainerToken;
+ return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken);
}
/**
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 709d5e3..24ee163 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -189,7 +189,7 @@
"android.hardware.thermal@1.0",
"android.hardware.thermal-V1-ndk",
"android.hardware.tv.input@1.0",
- "android.hardware.tv.input-V1-ndk",
+ "android.hardware.tv.input-V2-ndk",
"android.hardware.vibrator-V2-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
@@ -244,5 +244,5 @@
filegroup {
name: "lib_oomConnection_native",
- srcs: ["com_android_server_am_OomConnection.cpp",],
+ srcs: ["com_android_server_am_OomConnection.cpp"],
}
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
index b256f16..1844d30 100644
--- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -24,33 +24,33 @@
#include "utils/Log.h"
namespace android {
-static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds,
jfloatArray jthresholds) {
- if (juids == nullptr || jthresholds == nullptr) return;
+ if (jappIds == nullptr || jthresholds == nullptr) return;
- ScopedIntArrayRO uids(env, juids);
+ ScopedIntArrayRO appIds(env, jappIds);
ScopedFloatArrayRO thresholds(env, jthresholds);
- if (uids.size() != thresholds.size()) {
- ALOGE("uids size exceeds thresholds size!");
+ if (appIds.size() != thresholds.size()) {
+ ALOGE("appIds size exceeds thresholds size!");
return;
}
- std::vector<int32_t> uidVector;
+ std::vector<int32_t> appIdVector;
std::vector<float> thresholdVector;
- size_t size = uids.size();
- uidVector.reserve(size);
+ size_t size = appIds.size();
+ appIdVector.reserve(size);
thresholdVector.reserve(size);
for (int i = 0; i < size; i++) {
- uidVector.push_back(static_cast<int32_t>(uids[i]));
+ appIdVector.push_back(static_cast<int32_t>(appIds[i]));
thresholdVector.push_back(static_cast<float>(thresholds[i]));
}
- SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+ SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector);
}
-static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId,
jfloat threshold) {
- SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+ SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold);
}
static const JNINativeMethod gMethods[] = {
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index c736617..dc05462 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -368,12 +368,20 @@
}
JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper(
- const AidlTvMessageEvent& aidlTvMessageEvent) {
+ const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) {
+ auto messageList = aidlTvMessageEvent.messages;
TvMessageEventWrapper event;
- event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1,
- std::end(aidlTvMessageEvent.messages));
+ // Handle backwards compatibility for V1
+ if (isLegacyMessage) {
+ event.deviceId = messageList[0].groupId;
+ event.messages.insert(event.messages.begin(), std::begin(messageList) + 1,
+ std::end(messageList));
+ } else {
+ event.deviceId = aidlTvMessageEvent.deviceId;
+ event.messages.insert(event.messages.begin(), std::begin(messageList),
+ std::end(messageList));
+ }
event.streamId = aidlTvMessageEvent.streamId;
- event.deviceId = aidlTvMessageEvent.messages[0].groupId;
event.type = aidlTvMessageEvent.type;
return event;
}
@@ -449,15 +457,30 @@
::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(
const AidlTvMessageEvent& event) {
const std::string DEVICE_ID_SUBTYPE = "device_id";
- if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) {
- mHal->mLooper
- ->sendMessage(new NotifyTvMessageHandler(mHal,
- TvMessageEventWrapper::createEventWrapper(
- event)),
- static_cast<int>(event.type));
+ ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok();
+ int32_t aidlVersion = 0;
+ if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) {
+ bool validLegacyMessage = aidlVersion == 1 &&
+ event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1;
+ bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0;
+ if (validLegacyMessage || validTvMessage) {
+ mHal->mLooper->sendMessage(
+ new NotifyTvMessageHandler(mHal,
+ TvMessageEventWrapper::
+ createEventWrapper(event,
+ validLegacyMessage)),
+ static_cast<int>(event.type));
+ } else {
+ status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion);
+ }
+ } else {
+ status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not "
+ "be inferred.",
+ aidlVersion);
}
-
- return ::ndk::ScopedAStatus::ok();
+ return status;
}
JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
@@ -521,4 +544,12 @@
}
}
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) {
+ if (mIsHidl) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ } else {
+ return mAidlTvInput->getInterfaceVersion(_aidl_return);
+ }
+}
+
} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index 1d8d162..6026a10 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -138,7 +138,7 @@
TvMessageEventWrapper() {}
static TvMessageEventWrapper createEventWrapper(
- const AidlTvMessageEvent& aidlTvMessageEvent);
+ const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage);
int streamId;
int deviceId;
@@ -195,6 +195,7 @@
::ndk::ScopedAStatus getTvMessageQueueDesc(
MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,
int32_t in_streamId);
+ ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return);
private:
::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c26aee8..924e2f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -338,6 +338,8 @@
"com.android.clockwork.modes.ModeManagerService";
private static final String WEAR_DISPLAY_SERVICE_CLASS =
"com.android.clockwork.display.WearDisplayService";
+ private static final String WEAR_DEBUG_SERVICE_CLASS =
+ "com.android.clockwork.debug.WearDebugService";
private static final String WEAR_TIME_SERVICE_CLASS =
"com.android.clockwork.time.WearTimeService";
private static final String WEAR_SETTINGS_SERVICE_CLASS =
@@ -2636,6 +2638,12 @@
mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
t.traceEnd();
+ if (Build.IS_DEBUGGABLE) {
+ t.traceBegin("StartWearDebugService");
+ mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS);
+ t.traceEnd();
+ }
+
t.traceBegin("StartWearTimeService");
mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
t.traceEnd();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 952cfc4..cbedcaf 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -43,6 +43,7 @@
import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
+import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SuspendDialogInfo;
@@ -873,12 +874,20 @@
.setUid(packageSetting.getAppId())
.hideAsFinal());
- ArchiveState archiveState = new ArchiveState(
- List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
- Path.of("/monochromePath1")),
- new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
- Path.of("/monochromePath2"))),
- "installerTitle");
+ ArchiveState archiveState =
+ new ArchiveState(
+ List.of(
+ new ArchiveState.ArchiveActivityInfo(
+ "title1",
+ new ComponentName("pkg1", "class1"),
+ Path.of("/path1"),
+ Path.of("/monochromePath1")),
+ new ArchiveState.ArchiveActivityInfo(
+ "title2",
+ new ComponentName("pkg2", "class2"),
+ Path.of("/path2"),
+ Path.of("/monochromePath2"))),
+ "installerTitle");
packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
archiveState);
settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 58ae740..87a297b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.overlay.OverlayPaths;
@@ -192,8 +193,8 @@
return new SuspendParams(dialogInfo, appExtras, launcherExtras);
}
- private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey,
- String sValue, String dKey, double dValue) {
+ private static PersistableBundle createPersistableBundle(
+ String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) {
final PersistableBundle result = new PersistableBundle(3);
if (lKey != null) {
result.putLong("com.unit_test." + lKey, lValue);
@@ -320,6 +321,7 @@
assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]);
}
}
+
private static void assertLastPackageUsageSet(
PackageStateUnserialized state, int reason, long value) throws Exception {
for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) {
@@ -330,6 +332,7 @@
}
}
}
+
@Test
public void testPackageUseReasons() throws Exception {
PackageSetting packageSetting = Mockito.mock(PackageSetting.class);
@@ -377,6 +380,7 @@
assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build()));
assertFalse(testState.setOverlayPaths(null));
}
+
@Test
public void testSharedLibOverlayPaths() {
final PackageUserStateImpl testState = new PackageUserStateImpl();
@@ -401,8 +405,12 @@
@Test
public void archiveState() {
PackageUserStateImpl packageUserState = new PackageUserStateImpl();
- ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
- "appTitle", Path.of("/path1"), Path.of("/path2"));
+ ArchiveState.ArchiveActivityInfo archiveActivityInfo =
+ new ArchiveState.ArchiveActivityInfo(
+ "appTitle",
+ new ComponentName("pkg", "class"),
+ Path.of("/path1"),
+ Path.of("/path2"));
ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
"installerTitle");
packageUserState.setArchiveState(archiveState);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d021f1d..16d72e4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -117,7 +117,6 @@
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.input.InputManagerInternal;
import com.android.server.lights.LightsManager;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -312,7 +311,6 @@
@Mock SensorManager mSensorManager;
@Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
@Mock PackageManagerInternal mMockPackageManagerInternal;
- @Mock UserManagerInternal mMockUserManagerInternal;
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@@ -336,8 +334,6 @@
VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
// TODO: b/287945043
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResources = Mockito.spy(mContext.getResources());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index c4f72b3..6a95d5c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -102,6 +102,9 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.testutils.FakeDeviceConfigInterface;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -121,26 +124,28 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
@SmallTest
@RunWith(JUnitParamsRunner.class)
public class DisplayModeDirectorTest {
public static Collection<Object[]> getAppRequestedSizeTestCases() {
var appRequestedSizeTestCases = Arrays.asList(new Object[][] {
- {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY,
- DEFAULT_MODE_75.getRefreshRate(), Map.of()},
- {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY,
- APP_MODE_HIGH_90.getRefreshRate(),
- Map.of(
+ {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+ /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of()},
+ {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(),
+ /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
APP_MODE_HIGH_90.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))},
- {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
- Map.of(
+ {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+ /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
APP_MODE_HIGH_90.getPhysicalHeight()),
@@ -149,9 +154,10 @@
Vote.PRIORITY_LOW_POWER_MODE,
Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight()))},
- {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
- LIMIT_MODE_70.getRefreshRate(),
- Map.of(
+ {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+ /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_65.getPhysicalWidth(),
APP_MODE_65.getPhysicalHeight()),
@@ -160,9 +166,10 @@
Vote.PRIORITY_LOW_POWER_MODE,
Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight()))},
- {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
- LIMIT_MODE_70.getRefreshRate(),
- Map.of(
+ {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+ /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_65.getPhysicalWidth(),
APP_MODE_65.getPhysicalHeight()),
@@ -173,10 +180,12 @@
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight(),
- 0, Float.POSITIVE_INFINITY)), false},
- {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(),
- APP_MODE_65.getRefreshRate(),
- Map.of(
+ 0, Float.POSITIVE_INFINITY)),
+ /*displayResolutionRangeVotingEnabled*/ false},
+ {/*expectedBaseModeId*/ APP_MODE_65.getModeId(),
+ /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_65.getPhysicalWidth(),
APP_MODE_65.getPhysicalHeight()),
@@ -187,7 +196,40 @@
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight(),
- 0, Float.POSITIVE_INFINITY)), true}});
+ 0, Float.POSITIVE_INFINITY)),
+ /*displayResolutionRangeVotingEnabled*/ true},
+ {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+ /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
+ Vote.PRIORITY_APP_REQUEST_SIZE,
+ Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+ APP_MODE_HIGH_90.getPhysicalHeight()),
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+ Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+ Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.forSizeAndPhysicalRefreshRatesRange(
+ 0, 0,
+ LIMIT_MODE_70.getPhysicalWidth(),
+ LIMIT_MODE_70.getPhysicalHeight(),
+ 0, APP_MODE_65.getRefreshRate())),
+ /*displayResolutionRangeVotingEnabled*/ false},
+ {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65
+ /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
+ Vote.PRIORITY_APP_REQUEST_SIZE,
+ Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+ APP_MODE_HIGH_90.getPhysicalHeight()),
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+ Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+ Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.forSizeAndPhysicalRefreshRatesRange(
+ 0, 0,
+ LIMIT_MODE_70.getPhysicalWidth(),
+ LIMIT_MODE_70.getPhysicalHeight(),
+ 0, APP_MODE_65.getRefreshRate())),
+ /*displayResolutionRangeVotingEnabled*/ true}});
final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2);
@@ -218,6 +260,8 @@
private static final boolean DEBUG = false;
private static final float FLOAT_TOLERANCE = 0.01f;
+ private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode(
+ /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60);
private static final Display.Mode APP_MODE_65 = new Display.Mode(
/*modeId=*/65, /*width=*/1900, /*height=*/1900, 65);
private static final Display.Mode LIMIT_MODE_70 = new Display.Mode(
@@ -227,8 +271,7 @@
private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode(
/*modeId=*/90, /*width=*/3000, /*height=*/3000, 90);
private static final Display.Mode[] TEST_MODES = new Display.Mode[] {
- new Display.Mode(
- /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60),
+ DEFAULT_MODE_60,
APP_MODE_65,
LIMIT_MODE_70,
DEFAULT_MODE_75,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 596a3f3..b3605cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -280,7 +280,9 @@
0, 0);
// Sleep until timeout should have triggered
- SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+ if (wedge) {
+ SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+ }
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 410ae35..367e14b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -228,7 +228,7 @@
LocalServices.removeServiceForTest(AlarmManagerInternal.class);
LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
- doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
+ doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());
doAnswer((invocation) -> {
return getUidForPackage(invocation.getArgument(0));
}).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
@@ -1014,8 +1014,9 @@
eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
// Confirm that we unstopped manifest receivers
- verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
- eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
+ verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed(
+ eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM),
+ eq(callerApp.info.packageName), any());
}
// Confirm that we've reported expected usage events
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
index 1ce79a5..05ac5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -16,8 +16,6 @@
package com.android.server.display;
-import static android.os.Process.INVALID_UID;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -35,7 +33,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import org.junit.Before;
import org.junit.Rule;
@@ -55,7 +53,10 @@
@Mock
private PackageManagerInternal mMockPackageManagerInternal;
@Mock
- private UserManagerInternal mMockUserManagerInternal;
+ private PackageStateInternal mMockPkgStateA;
+ @Mock
+ private PackageStateInternal mMockPkgStateB;
+
private SmallAreaDetectionController mSmallAreaDetectionController;
@@ -64,29 +65,18 @@
private static final String PKG_NOT_INSTALLED = "com.not.installed";
private static final float THRESHOLD_A = 0.05f;
private static final float THRESHOLD_B = 0.07f;
- private static final int USER_1 = 110;
- private static final int USER_2 = 111;
- private static final int UID_A_1 = 11011111;
- private static final int UID_A_2 = 11111111;
- private static final int UID_B_1 = 11022222;
- private static final int UID_B_2 = 11122222;
+ private static final int APP_ID_A = 11111;
+ private static final int APP_ID_B = 22222;
@Before
public void setup() {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
- when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
- when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
- when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
- when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
- when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
- when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
- INVALID_UID);
- when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
- INVALID_UID);
+ when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA);
+ when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB);
+ when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A);
+ when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B);
mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
new ContextWrapper(ApplicationProvider.getApplicationContext()),
@@ -99,9 +89,9 @@
final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
- final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_A, APP_ID_B};
+ final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
@@ -110,9 +100,9 @@
final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_B_1, UID_B_2};
- final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_B};
+ final float[] resultThresholdArray = {THRESHOLD_B};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
@@ -122,9 +112,9 @@
PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_A_1, UID_A_2};
- final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_A};
+ final float[] resultThresholdArray = {THRESHOLD_A};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index eb50556..610ea90 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -427,6 +428,7 @@
for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
mainActivity.getLabel().toString(),
+ mainActivity.getComponentName(),
ICON_PATH, null);
activityInfos.add(activityInfo);
}
@@ -437,9 +439,11 @@
ActivityInfo activityInfo = mock(ActivityInfo.class);
LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
when(activity1.getLabel()).thenReturn("activity1");
+ when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1"));
when(activity1.getActivityInfo()).thenReturn(activityInfo);
LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
when(activity2.getLabel()).thenReturn("activity2");
+ when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2"));
when(activity2.getActivityInfo()).thenReturn(activityInfo);
return List.of(activity1, activity2);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e8cbcf9..a3d415e 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -337,11 +337,7 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
- mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY);
- mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
- mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME);
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM);
+ mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
new file mode 100644
index 0000000..07cdf4d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Tests for the {@link MediaProjectionSessionIdGenerator} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionSessionIdGeneratorTest {
+
+ private static final String TEST_PREFS_FILE = "media-projection-session-id-test";
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+ private final SharedPreferences mSharedPreferences = createSharePreferences();
+ private final MediaProjectionSessionIdGenerator mGenerator =
+ createGenerator(mSharedPreferences);
+
+ @Before
+ public void setUp() {
+ mSharedPreferences.edit().clear().commit();
+ }
+
+ @After
+ public void tearDown() {
+ mSharedPreferences.edit().clear().commit();
+ mSharedPreferencesFile.delete();
+ }
+
+ @Test
+ public void getCurrentSessionId_byDefault_returns0() {
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ }
+
+ @Test
+ public void getCurrentSessionId_multipleTimes_returnsSameValue() {
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ }
+
+ @Test
+ public void createAndGetNewSessionId_returnsIncrementedId() {
+ int previousValue = mGenerator.getCurrentSessionId();
+
+ int newValue = mGenerator.createAndGetNewSessionId();
+
+ assertThat(newValue).isEqualTo(previousValue + 1);
+ }
+
+ @Test
+ public void createAndGetNewSessionId_persistsNewValue() {
+ int newValue = mGenerator.createAndGetNewSessionId();
+
+ MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences());
+
+ assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue);
+ }
+
+ private SharedPreferences createSharePreferences() {
+ return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+ }
+
+ private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) {
+ return new MediaProjectionSessionIdGenerator(sharedPreferences);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 8e7ba70..dd7dec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -130,12 +130,22 @@
// verify if back animation would start.
assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
- // reset drawing status
+ // reset drawing status to test translucent activity
backNavigationInfo.onBackNavigationFinished(false);
mBackNavigationController.clearBackAnimations();
- topTask.forAllWindows(w -> {
- makeWindowVisibleAndDrawn(w);
- }, true);
+ final ActivityRecord topActivity = topTask.getTopMostActivity();
+ makeWindowVisibleAndDrawn(topActivity.findMainWindow());
+ // simulate translucent
+ topActivity.setOccludesParent(false);
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+ // reset drawing status to test keyguard occludes
+ topActivity.setOccludesParent(true);
+ backNavigationInfo.onBackNavigationFinished(false);
+ mBackNavigationController.clearBackAnimations();
+ makeWindowVisibleAndDrawn(topActivity.findMainWindow());
setupKeyguardOccluded();
backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -201,9 +211,7 @@
// reset drawing status
backNavigationInfo.onBackNavigationFinished(false);
mBackNavigationController.clearBackAnimations();
- testCase.recordFront.forAllWindows(w -> {
- makeWindowVisibleAndDrawn(w);
- }, true);
+ makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow());
setupKeyguardOccluded();
backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index e86fc36..eaeb804 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -100,6 +100,9 @@
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerService.WindowContainerInfo;
+
+import com.google.common.truth.Expect;
import org.junit.Rule;
import org.junit.Test;
@@ -125,6 +128,9 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation(),
ADD_TRUSTED_DISPLAY);
+ @Rule
+ public Expect mExpect = Expect.create();
+
@Test
public void testIsRequestedOrientationMapped() {
mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -674,64 +680,68 @@
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
- assertThat(wct).isNull();
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null);
+ assertThat(wci).isNull();
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
Binder cookie = new Binder("test cookie");
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
- assertThat(wct).isNull();
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ assertThat(wci).isNull();
final ActivityRecord testActivity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.build();
- wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
- assertThat(wct).isNull();
+ wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ assertThat(wci).isNull();
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
final Binder cookie = new Binder("ginger cookie");
final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
- setupActivityWithLaunchCookie(cookie, launchRootTask);
+ final int uid = 123;
+ setupActivityWithLaunchCookie(cookie, launchRootTask, uid);
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
- assertThat(wct).isEqualTo(launchRootTask);
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ mExpect.that(wci.getToken()).isEqualTo(launchRootTask);
+ mExpect.that(wci.getUid()).isEqualTo(uid);
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
final Binder cookie1 = new Binder("ginger cookie");
final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
- setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+ final int uid1 = 123;
+ setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1);
setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 456);
setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 789);
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
- assertThat(wct).isEqualTo(launchRootTask1);
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1);
+ mExpect.that(wci.getToken()).isEqualTo(launchRootTask1);
+ mExpect.that(wci.getUid()).isEqualTo(uid1);
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
setupActivityWithLaunchCookie(new Binder("ginger cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 123);
setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 456);
setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 789);
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(
new Binder("some other cookie"));
- assertThat(wct).isNull();
+ assertThat(wci).isNull();
}
@Test
@@ -778,17 +788,18 @@
}
@Test
- public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+ public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() {
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
Task task = createTask(mDefaultDisplay);
ActivityRecord activityRecord = createActivityRecord(task);
- ContentRecordingSession session = ContentRecordingSession.createTaskSession(
- activityRecord.mLaunchCookie);
+ ContentRecordingSession session =
+ ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie);
wmInternal.setContentRecordingSession(session);
- assertThat(session.getTokenToRecord()).isEqualTo(
- task.mRemoteToken.toWindowContainerToken().asBinder());
+ mExpect.that(session.getTokenToRecord())
+ .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder());
+ mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid());
}
@Test
@@ -1010,12 +1021,12 @@
}
}
- private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+ private void setupActivityWithLaunchCookie(
+ IBinder launchCookie, WindowContainerToken wct, int uid) {
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
when(remoteToken.toWindowContainerToken()).thenReturn(wct);
- final ActivityRecord testActivity = new ActivityBuilder(mAtm)
- .setCreateTask(true)
- .build();
+ final ActivityRecord testActivity =
+ new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build();
testActivity.mLaunchCookie = launchCookie;
testActivity.getTask().mRemoteToken = remoteToken;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 138e575..1c689d0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -58,6 +58,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
@@ -67,6 +68,7 @@
import android.os.ShellCallback;
import android.os.Trace;
import android.os.UserHandle;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -1286,6 +1288,17 @@
}
}
+ // Enforce permissions that are flag controlled. The flag value decides if the permission
+ // should be enforced.
+ private void initAndVerifyDetector_enforcePermissionWithFlags() {
+ PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class);
+ if (Flags.voiceActivationPermissionApis()) {
+ enforcer.enforcePermission(
+ android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO,
+ getCallingPid(), getCallingUid());
+ }
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
@Override
public void initAndVerifyDetector(
@@ -1295,7 +1308,13 @@
@NonNull IBinder token,
IHotwordRecognitionStatusCallback callback,
int detectorType) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #initAndVerifyDetector(Identity, PersistableBundle, ShareMemory, IBinder,
+ // IHotwordRecognitionStatusCallback, int)}
+ // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully
+ // launched.
super.initAndVerifyDetector_enforcePermission();
+ initAndVerifyDetector_enforcePermissionWithFlags();
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
new file mode 100644
index 0000000..54cab48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 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.telephony.satellite;
+
+import android.telephony.satellite.NtnSignalStrength;
+
+/**
+ * Interface for non-terrestrial signal strength notify callback.
+ * @hide
+ */
+oneway interface INtnSignalStrengthCallback {
+ /**
+ * Called when NTN signal strength changes.
+ * @param ntnSignalStrength The new NTN signal strength.
+ */
+ void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
new file mode 100644
index 0000000..a79cb69
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, 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.telephony.satellite;
+
+parcelable NtnSignalStrength;
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
new file mode 100644
index 0000000..16d7654
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * NTN signal strength related information.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public final class NtnSignalStrength implements Parcelable {
+ /** Non-terrestrial network signal strength is not available. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_NONE = 0;
+ /** Non-terrestrial network signal strength is poor. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_POOR = 1;
+ /** Non-terrestrial network signal strength is moderate. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2;
+ /** Non-terrestrial network signal strength is good. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_GOOD = 3;
+ /** Non-terrestrial network signal strength is great. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_GREAT = 4;
+ @NtnSignalStrengthLevel private int mLevel;
+
+ /** @hide */
+ @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = {
+ NTN_SIGNAL_STRENGTH_NONE,
+ NTN_SIGNAL_STRENGTH_POOR,
+ NTN_SIGNAL_STRENGTH_MODERATE,
+ NTN_SIGNAL_STRENGTH_GOOD,
+ NTN_SIGNAL_STRENGTH_GREAT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NtnSignalStrengthLevel {}
+
+ /**
+ * Create a parcelable object to inform the current non-terrestrial signal strength
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public NtnSignalStrength(@NtnSignalStrengthLevel int level) {
+ this.mLevel = level;
+ }
+
+ /**
+ * This constructor is used to create a copy of an existing NtnSignalStrength object.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public NtnSignalStrength(@Nullable NtnSignalStrength source) {
+ this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel();
+ }
+
+ private NtnSignalStrength(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NtnSignalStrengthLevel public int getLevel() {
+ return mLevel;
+ }
+
+ /**
+ * @return 0
+ */
+ @Override
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @param out The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mLevel);
+ }
+
+ private void readFromParcel(Parcel in) {
+ mLevel = in.readInt();
+ }
+
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull public static final Creator<NtnSignalStrength> CREATOR =
+ new Creator<NtnSignalStrength>() {
+ @Override public NtnSignalStrength createFromParcel(Parcel in) {
+ return new NtnSignalStrength(in);
+ }
+
+ @Override public NtnSignalStrength[] newArray(int size) {
+ return new NtnSignalStrength[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return mLevel;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+
+ NtnSignalStrength that = (NtnSignalStrength) obj;
+ return mLevel == that.mLevel;
+ }
+
+ @Override public String toString() {
+ return "NtnSignalStrength{"
+ + "mLevel=" + mLevel
+ + '}';
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
new file mode 100644
index 0000000..4b79590
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for notifying satellite signal strength change.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface NtnSignalStrengthCallback {
+ /**
+ * Called when non-terrestrial network signal strength changes.
+ * @param ntnSignalStrength The new non-terrestrial network signal strength.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7322aeb..21d93bd 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -35,7 +35,9 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.ITelephony;
@@ -77,6 +79,8 @@
private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback>
+ sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>();
private final int mSubId;
@@ -192,6 +196,14 @@
public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
/**
+ * Bundle key to get the response from
+ * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+
+ public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength";
+
+ /**
* The request was successfully processed.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1866,6 +1878,165 @@
return new HashSet<>();
}
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * <p>
+ * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+ * For satellite connectivity enabled using carrier roaming, please refer to
+ * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+ * </p>
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered. If the request is
+ * successful, {@link OutcomeReceiver#onResult(Object)} will return an instance of
+ * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel}
+ * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
+ * signal strength data available.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
+ * {@link SatelliteException} with the {@link SatelliteResult}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available or
+ * satellite is not supported.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull
+ public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) {
+ NtnSignalStrength ntnSignalStrength =
+ resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH,
+ NtnSignalStrength.class);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(ntnSignalStrength)));
+ } else {
+ loge("KEY_NTN_SIGNAL_STRENGTH does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestNtnSignalStrength(mSubId, receiver);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("requestNtnSignalStrength() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers for NTN signal strength changed from satellite modem.
+ *
+ * <p>
+ * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+ * For satellite connectivity enabled using carrier roaming, please refer to
+ * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+ * </p>
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ *
+ * @return The {@link SatelliteResult} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SatelliteResult public int registerForNtnSignalStrengthChanged(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull NtnSignalStrengthCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ INtnSignalStrengthCallback internalCallback =
+ new INtnSignalStrengthCallback.Stub() {
+ @Override
+ public void onNtnSignalStrengthChanged(
+ NtnSignalStrength ntnSignalStrength) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onNtnSignalStrengthChanged(
+ ntnSignalStrength)));
+ }
+ };
+ sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
+ return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_RESULT_REQUEST_FAILED;
+ }
+
+ /**
+ * Unregisters for NTN signal strength changed from satellite modem.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * <p>
+ * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+ * For satellite connectivity enabled using carrier roaming, please refer to
+ * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}..
+ * </p>
+ *
+ * @param callback The callback that was passed to
+ * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) {
+ Objects.requireNonNull(callback);
+ INtnSignalStrengthCallback internalCallback =
+ sNtnSignalStrengthCallbackMap.remove(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (internalCallback != null) {
+ telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
+ } else {
+ loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+
+ }
+
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
new file mode 100644
index 0000000..b7712bd
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.telephony.satellite.stub;
+
+import android.telephony.satellite.NtnSignalStrength;
+
+/**
+ * Consumer pattern for a request that receives the signal strength of non-terrestrial network from
+ * the SatelliteService.
+ * @hide
+ */
+oneway interface INtnSignalStrengthConsumer {
+ void accept(in NtnSignalStrength result);
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 7fda550..6b47db1 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -16,6 +16,7 @@
package android.telephony.satellite.stub;
+import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
import android.telephony.satellite.stub.ISatelliteListener;
import android.telephony.satellite.stub.SatelliteDatagram;
@@ -454,4 +455,44 @@
*/
void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
in IBooleanConsumer callback);
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ */
+ void requestSignalStrength(in IIntegerConsumer resultCallback,
+ in INtnSignalStrengthConsumer callback);
+
+ /**
+ * The satellite service should report the NTN signal strength via
+ * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
+ *
+ * @param resultCallback The callback to receive the error code result of the operation.
+ *
+ * Valid result codes returned:
+ * SatelliteResult:SATELLITE_RESULT_SUCCESS
+ * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+ * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+ * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+ * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+ */
+ void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
+
+ /**
+ * The satellite service should stop reporting NTN signal strength to the framework. This will
+ * be called when device is screen off to save power by not letting signal strength updates to
+ * wake up application processor.
+ *
+ * @param resultCallback The callback to receive the error code result of the operation.
+ *
+ * Valid result codes returned:
+ * SatelliteResult:SATELLITE_RESULT_SUCCESS
+ * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+ * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+ * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+ * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+ */
+ void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d687162..d44ddfa 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -16,6 +16,7 @@
package android.telephony.satellite.stub;
+import android.telephony.satellite.stub.NtnSignalStrength;
import android.telephony.satellite.stub.NTRadioTechnology;
import android.telephony.satellite.stub.PointingInfo;
import android.telephony.satellite.stub.SatelliteDatagram;
@@ -58,4 +59,10 @@
* @param state The current satellite modem state.
*/
void onSatelliteModemStateChanged(in SatelliteModemState state);
+
+ /**
+ * Called when NTN signal strength changes.
+ * @param ntnSignalStrength The new NTN signal strength.
+ */
+ void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
}
diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
new file mode 100644
index 0000000..f489005
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.NtnSignalStrengthLevel;
+
+/**
+ * @hide
+ */
+parcelable NtnSignalStrength {
+ /**
+ * Non-terrestrial signal strength. The value represents the level of signal strength which can
+ * be translated to the number of signal bars.
+ */
+ NtnSignalStrengthLevel signalStrengthLevel;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
new file mode 100644
index 0000000..53b1373
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.telephony.satellite.stub;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum NtnSignalStrengthLevel {
+ NTN_SIGNAL_STRENGTH_NONE = 0,
+ NTN_SIGNAL_STRENGTH_POOR = 1,
+ NTN_SIGNAL_STRENGTH_MODERATE = 2,
+ NTN_SIGNAL_STRENGTH_GOOD = 3,
+ NTN_SIGNAL_STRENGTH_GREAT = 4
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 4cee01e..a636a61 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -241,6 +241,30 @@
"requestIsSatelliteEnabledForCarrier");
}
+ @Override
+ public void requestSignalStrength(IIntegerConsumer resultCallback,
+ INtnSignalStrengthConsumer callback) throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback),
+ "requestSignalStrength");
+ }
+
+ @Override
+ public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+ throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback),
+ "startSendingNtnSignalStrength");
+ }
+
+ @Override
+ public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+ throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback),
+ "stopSendingNtnSignalStrength");
+ }
+
// Call the methods with a clean calling identity on the executor and wait indefinitely for
// the future to return.
private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -728,4 +752,35 @@
@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
// stub implementation
}
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ */
+ public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback,
+ INtnSignalStrengthConsumer callback) {
+ // stub implementation
+ }
+
+ /**
+ * Requests to deliver signal strength changed events through the
+ * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)}
+ * callback.
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ */
+ public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) {
+ // stub implementation
+ }
+
+ /**
+ * Requests to stop signal strength changed events
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ */
+ public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){
+ // stub implementation
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3aa5a5a..58e7026 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,10 +67,12 @@
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.INtnSignalStrengthCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
import com.android.ims.internal.IImsServiceFeatureCallback;
@@ -2837,7 +2839,6 @@
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
-
/**
* Registers for provision state changed from satellite modem.
*
@@ -3071,4 +3072,40 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param receiver Result receiver to get the error code of the request and the current signal
+ * strength of the satellite connection.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
+
+ /**
+ * Registers for NTN signal strength changed from satellite modem.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ *
+ * @return The {@link SatelliteResult} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+
+ /**
+ * Unregisters for NTN signal strength changed from satellite modem.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param subId The subId of the subscription to unregister for provision state changed.
+ * @param callback The callback that was passed to
+ * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void unregisterForNtnSignalStrengthChanged(int subId,
+ in INtnSignalStrengthCallback callback);
}
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
index bfd3508..c901efa 100644
--- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
+++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
@@ -26,4 +26,5 @@
android_test {
name: "AaptAutoVersionTest",
sdk_version: "current",
+ use_resource_processor: false,
}
diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp
index 7db9d26..d0649ea 100644
--- a/tools/aapt2/integration-tests/BasicTest/Android.bp
+++ b/tools/aapt2/integration-tests/BasicTest/Android.bp
@@ -26,4 +26,5 @@
android_test {
name: "AaptBasicTest",
sdk_version: "current",
+ use_resource_processor: false,
}
diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
index 80404ee..ebb4e9f 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
@@ -24,9 +24,9 @@
}
android_test {
-
name: "AaptTestStaticLib_App",
sdk_version: "current",
+ use_resource_processor: false,
srcs: ["src/**/*.java"],
asset_dirs: [
"assets",
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
index a84da43..ee12a929 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
@@ -26,6 +26,7 @@
android_library {
name: "AaptTestStaticLib_LibOne",
sdk_version: "current",
+ use_resource_processor: false,
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
}
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
index d386c3a..83b23624 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
@@ -26,6 +26,7 @@
android_library {
name: "AaptTestStaticLib_LibTwo",
sdk_version: "current",
+ use_resource_processor: false,
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
libs: ["AaptTestStaticLib_LibOne"],
diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
index 1e8cf86..15a6a20 100644
--- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp
+++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
@@ -26,4 +26,5 @@
android_test {
name: "AaptSymlinkTest",
sdk_version: "current",
+ use_resource_processor: false,
}