summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt5
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java21
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java58
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java25
-rw-r--r--core/java/android/hardware/camera2/extension/RequestProcessor.java2
-rw-r--r--core/java/android/os/Process.java2
-rw-r--r--core/java/android/os/flags.aconfig8
-rw-r--r--core/jni/android_util_Process.cpp4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt3
-rw-r--r--services/core/java/com/android/server/BatteryService.java30
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java5
-rw-r--r--services/core/java/com/android/server/audio/MusicFxHelper.java366
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java7
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterImpl.java11
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterUtils.java9
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java5
-rw-r--r--services/core/java/com/android/server/utils/WatchedSparseSetArray.java14
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java16
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java17
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java642
27 files changed, 1150 insertions, 187 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index a67c6e120b0e..61c7ec6411c6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18812,6 +18812,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AWB_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AWB_LOCK_AVAILABLE;
+ field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB;
@@ -19100,6 +19101,7 @@ package android.hardware.camera2 {
field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2; // 0x2
field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; // 0x4
field public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; // 0x5
+ field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6; // 0x6
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2; // 0x2
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; // 0x0
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; // 0x1
@@ -19165,6 +19167,8 @@ package android.hardware.camera2 {
field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS = 2; // 0x2
field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE = 1; // 0x1
field public static final int CONTROL_EXTENDED_SCENE_MODE_DISABLED = 0; // 0x0
+ field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1; // 0x1
+ field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0; // 0x0
field public static final int CONTROL_MODE_AUTO = 1; // 0x1
field public static final int CONTROL_MODE_OFF = 0; // 0x0
field public static final int CONTROL_MODE_OFF_KEEP_STATE = 3; // 0x3
@@ -19485,6 +19489,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EXTENDED_SCENE_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_LOW_LIGHT_BOOST_STATE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c64f2304db4e..aeac7bf0a51c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4540,6 +4540,7 @@ package android.hardware.camera2.extension {
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request {
ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters();
}
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dec5b9c7352c..e3a552023e4b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1355,6 +1355,27 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<Boolean>("android.control.autoframingAvailable", boolean.class);
/**
+ * <p>The operating luminance range of low light boost measured in lux (lx).</p>
+ * <p><b>Range of valid values:</b><br></p>
+ * <p>The lower bound indicates the lowest scene luminance value the AE mode
+ * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' can operate within. Scenes of lower luminance
+ * than this may receive less brightening, increased noise, or artifacts.</p>
+ * <p>The upper bound indicates the luminance threshold at the point when the mode is enabled.
+ * For example, 'Range[0.3, 30.0]' defines 0.3 lux being the lowest scene luminance the
+ * mode can reliably support. 30.0 lux represents the threshold when this mode is
+ * activated. Scenes measured at less than or equal to 30 lux will activate low light
+ * boost.</p>
+ * <p>If this key is defined, then the AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' will
+ * also be present.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+ public static final Key<android.util.Range<Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE =
+ new Key<android.util.Range<Float>>("android.control.lowLightBoostInfoLuminanceRange", new TypeReference<android.util.Range<Float>>() {{ }});
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 003718e6b54e..765a8f7c842c 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2333,6 +2333,46 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5;
+ /**
+ * <p>Like 'ON' but applies additional brightness boost in low light scenes.</p>
+ * <p>When the scene lighting conditions are within the range defined by
+ * {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange} this mode will apply additional
+ * brightness boost.</p>
+ * <p>This mode will automatically adjust the intensity of low light boost applied
+ * according to the scene lighting conditions. A darker scene will receive more boost
+ * while a brighter scene will receive less boost.</p>
+ * <p>This mode can ignore the set target frame rate to allow more light to be captured
+ * which can result in choppier motion. The frame rate can extend to lower than the
+ * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges} but will not go below 10 FPS. This mode
+ * can also increase the sensor sensitivity gain which can result in increased luma
+ * and chroma noise. The sensor sensitivity gain can extend to higher values beyond
+ * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}. This mode may also apply additional
+ * processing to recover details in dark and bright areas of the image,and noise
+ * reduction at high sensitivity gain settings to manage the trade-off between light
+ * sensitivity and capture noise.</p>
+ * <p>This mode is restricted to two output surfaces. One output surface type can either
+ * be SurfaceView or TextureView. Another output surface type can either be MediaCodec
+ * or MediaRecorder. This mode cannot be used with a target FPS range higher than 30
+ * FPS.</p>
+ * <p>If the session configuration is not supported, the AE mode reported in the
+ * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p>
+ * <p>The application can observe the CapturerResult field
+ * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or
+ * 'INACTIVE'.</p>
+ * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the
+ * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.
+ * This mode will be 'INACTIVE' once the scene lighting condition is greater than the
+ * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
+ * @see CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE
+ * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+ * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
+ * @see CaptureRequest#CONTROL_AE_MODE
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+ public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6;
+
//
// Enumeration values for CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
//
@@ -4074,6 +4114,24 @@ public abstract class CameraMetadata<TKey> {
public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2;
//
+ // Enumeration values for CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+ //
+
+ /**
+ * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled but not applied.</p>
+ * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+ public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0;
+
+ /**
+ * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled and applied.</p>
+ * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+ public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1;
+
+ //
// Enumeration values for CaptureResult#FLASH_STATE
//
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 35f295a36d87..ab4406c37c8e 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2814,6 +2814,31 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
new Key<Integer>("android.control.autoframingState", int.class);
/**
+ * <p>Current state of the low light boost AE mode.</p>
+ * <p>When low light boost is enabled by setting the AE mode to
+ * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light
+ * boost when the light level threshold is exceeded.</p>
+ * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
+ * indicate when it is not being applied by returning 'INACTIVE'.</p>
+ * <p>This key will be absent from the CaptureResult if AE mode is not set to
+ * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE INACTIVE}</li>
+ * <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE ACTIVE}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @see #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE
+ * @see #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+ public static final Key<Integer> CONTROL_LOW_LIGHT_BOOST_STATE =
+ new Key<Integer>("android.control.lowLightBoostState", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java
index 7c099d67e6e9..bf5ea12df358 100644
--- a/core/java/android/hardware/camera2/extension/RequestProcessor.java
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -250,7 +250,7 @@ public final class RequestProcessor {
*/
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
@NonNull
- List<Pair<CaptureRequest.Key, Object>> getParameters() {
+ public List<Pair<CaptureRequest.Key, Object>> getParameters() {
return mParameters;
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523ee23dd..7e07e1f2e499 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1599,7 +1599,7 @@ public class Process {
* fully removed, otherwise system resources may leak.
* @hide
*/
- public static final native boolean sendSignalToProcessGroup(int uid, int pid, int signal);
+ public static final native int sendSignalToProcessGroup(int uid, int pid, int signal);
/**
* Freeze the cgroup for the given UID.
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 980c13c5c2d6..145981c92283 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -76,3 +76,11 @@ flag {
description: "Guards the ADPF GPU APIs."
bug: "284324521"
}
+
+flag {
+ name: "battery_service_support_current_adb_command"
+ namespace: "backstage_power"
+ description: "Whether or not BatteryService supports adb commands for Current values."
+ is_fixed_read_only: true
+ bug: "315037695"
+}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 43e0c34ab0f3..91dfc6023e42 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1238,7 +1238,7 @@ jint android_os_Process_killProcessGroup(JNIEnv* env, jobject clazz, jint uid, j
return killProcessGroup(uid, pid, SIGKILL);
}
-jboolean android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
+jint android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
jint signal) {
return sendSignalToProcessGroup(uid, pid, signal);
}
@@ -1310,7 +1310,7 @@ static const JNINativeMethod methods[] = {
//{"setApplicationObject", "(Landroid/os/IBinder;)V",
//(void*)android_os_Process_setApplicationObject},
{"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
- {"sendSignalToProcessGroup", "(III)Z", (void*)android_os_Process_sendSignalToProcessGroup},
+ {"sendSignalToProcessGroup", "(III)I", (void*)android_os_Process_sendSignalToProcessGroup},
{"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
{"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
{"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
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 0f168c7b4ce6..0367ba160605 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
@@ -255,6 +255,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private ArrayList<TaskState> mOpeningTasks = null;
private WindowContainerToken mPipTask = null;
+ private int mPipTaskId = -1;
private WindowContainerToken mRecentsTask = null;
private int mRecentsTaskId = -1;
private TransitionInfo mInfo = null;
@@ -904,12 +905,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
@Override
public void setFinishTaskTransaction(int taskId,
PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "[%d] RecentsController.setFinishTaskTransaction: taskId=%d",
- mInstanceId, taskId);
mExecutor.execute(() -> {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.setFinishTaskTransaction: taskId=%d,"
+ + " [mFinishCB is non-null]=%b",
+ mInstanceId, taskId, mFinishCB != null);
if (mFinishCB == null) return;
mPipTransaction = finishTransaction;
+ mPipTaskId = taskId;
});
}
@@ -1003,10 +1006,35 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
// the leaf-tasks are not "independent" so aren't hidden by normal setup).
t.hide(mPausingTasks.get(i).mTaskSurface);
}
- if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
- t.show(mInfo.getChange(mPipTask).getLeash());
- PictureInPictureSurfaceTransaction.apply(mPipTransaction,
- mInfo.getChange(mPipTask).getLeash(), t);
+ if (mPipTransaction != null && sendUserLeaveHint) {
+ SurfaceControl pipLeash = null;
+ if (mPipTask != null) {
+ pipLeash = mInfo.getChange(mPipTask).getLeash();
+ } else if (mPipTaskId != -1) {
+ // find a task with taskId from #setFinishTaskTransaction()
+ for (TransitionInfo.Change change : mInfo.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().taskId == mPipTaskId) {
+ pipLeash = change.getLeash();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.finishInner:"
+ + " found a change with taskId=%d", mPipTaskId);
+ }
+ }
+ }
+ if (pipLeash == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.finishInner: no valid PiP leash;"
+ + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
+ mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+ } else {
+ t.show(pipLeash);
+ PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.finishInner: PiP transaction %s merged",
+ mPipTransaction);
+ }
+ mPipTaskId = -1;
mPipTask = null;
mPipTransaction = null;
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index d06cf775ca60..e272958d78f8 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -105,7 +105,8 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
)
locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true)
mockLocationEnabled = true
- mainHandler.post(updateLocation)
+ // postpone first location update to make sure GPS is set as test provider
+ mainHandler.postDelayed(updateLocation, 200)
// normal app open through the Launcher All Apps
// var mapsAddressOption = "Golden Gate Bridge"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 3819a1075adb..e0dd4e17ce38 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -34,6 +34,7 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme
fun SettingsOutlinedTextField(
value: String,
label: String,
+ errorMessage: String? = null,
singleLine: Boolean = true,
enabled: Boolean = true,
onTextChange: (String) -> Unit,
@@ -49,6 +50,12 @@ fun SettingsOutlinedTextField(
},
singleLine = singleLine,
enabled = enabled,
+ isError = errorMessage != null,
+ supportingText = {
+ if (errorMessage != null) {
+ Text(text = errorMessage)
+ }
+ }
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index 00572d3163eb..7f7490d9ecc3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
+
import android.os.UserHandle
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -22,7 +24,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor
import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
import com.android.systemui.utils.leaks.FakeFlashlightController
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
index f819f53838b8..28d43b369dd4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -14,12 +14,13 @@
* limitations under the License.
*/
+package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
+
import android.app.ActivityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
-import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
import com.android.systemui.statusbar.policy.FlashlightController
import com.android.systemui.util.mockito.mock
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b9a3c0c41841..33726d17da62 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.os.Flags.stateOfHealthPublic;
+import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copyV1Battery;
@@ -927,9 +928,12 @@ public final class BatteryService extends SystemService {
pw.println("Battery service (battery) commands:");
pw.println(" help");
pw.println(" Print this help text.");
- pw.println(" get [-f] [ac|usb|wireless|dock|status|level|temp|present|counter|invalid]");
- pw.println(" set [-f] "
- + "[ac|usb|wireless|dock|status|level|temp|present|counter|invalid] <value>");
+ String getSetOptions = "ac|usb|wireless|dock|status|level|temp|present|counter|invalid";
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ getSetOptions += "|current_now|current_average";
+ }
+ pw.println(" get [-f] [" + getSetOptions + "]");
+ pw.println(" set [-f] [" + getSetOptions + "] <value>");
pw.println(" Force a battery property value, freezing battery state.");
pw.println(" -f: force a battery change broadcast be sent, prints new sequence.");
pw.println(" unplug [-f]");
@@ -1001,6 +1005,16 @@ public final class BatteryService extends SystemService {
case "counter":
pw.println(mHealthInfo.batteryChargeCounterUah);
break;
+ case "current_now":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ pw.println(mHealthInfo.batteryCurrentMicroamps);
+ }
+ break;
+ case "current_average":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ pw.println(mHealthInfo.batteryCurrentAverageMicroamps);
+ }
+ break;
case "temp":
pw.println(mHealthInfo.batteryTemperatureTenthsCelsius);
break;
@@ -1058,6 +1072,16 @@ public final class BatteryService extends SystemService {
case "counter":
mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value);
break;
+ case "current_now":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ mHealthInfo.batteryCurrentMicroamps = Integer.parseInt(value);
+ }
+ break;
+ case "current_average":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ mHealthInfo.batteryCurrentAverageMicroamps =
+ Integer.parseInt(value);
+ }
case "temp":
mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value);
break;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f7b7aaa60a35..44cb1367928d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -13779,6 +13779,11 @@ public class AudioService extends IAudioService.Stub
return mDeviceBroker.getDeviceAddresses(device);
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ MusicFxHelper getMusicFxHelper() {
+ return mMusicFxHelper;
+ }
+
//======================
// misc
//======================
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 5f4e4c3bc4e0..85b3b49ecf78 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -17,9 +17,11 @@
package com.android.server.audio;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+
import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.IUidObserver;
import android.app.UidObserver;
@@ -48,7 +50,6 @@ import java.util.List;
/**
* MusicFx management.
- * .
*/
public class MusicFxHelper {
private static final String TAG = "AS.MusicFxHelper";
@@ -60,14 +61,113 @@ public class MusicFxHelper {
// Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver.
private final Object mClientUidMapLock = new Object();
+ private final String mPackageName = this.getClass().getPackage().getName();
+
+ private final String mMusicFxPackageName = "com.android.musicfx";
+
+ /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;
+
// The binder token identifying the UidObserver registration.
private IBinder mUidObserverToken = null;
+ // Package name and list of open audio sessions for this package
+ private static class PackageSessions {
+ String mPackageName;
+ List<Integer> mSessions;
+ }
+
+ /*
+ * Override of SparseArray class to add bind/unbind and UID observer in the put/remove methods.
+ *
+ * put:
+ * - the first key/value set put into MySparseArray will trigger a procState bump (bindService)
+ * - if no valid observer token exist, will call registerUidObserver for put
+ * - for each new uid put into array, it will be added to uid observer list
+ *
+ * remove:
+ * - for each uid removed from array, it will be removed from uid observer list as well
+ * - if it's the last uid in array, no more MusicFx procState bump (unbindService), uid
+ * observer will also be removed, and observer token reset to null
+ */
+ private class MySparseArray extends SparseArray<PackageSessions> {
+ private final String mMusicFxPackageName = "com.android.musicfx";
+
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES
+ })
+ @Override
+ public void put(int uid, PackageSessions pkgSessions) {
+ if (size() == 0) {
+ int procState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ try {
+ procState = ActivityManager.getService().getPackageProcessState(
+ mMusicFxPackageName, mPackageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException with getPackageProcessState: " + e);
+ }
+ if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ Intent bindIntent = new Intent().setClassName(mMusicFxPackageName,
+ "com.android.musicfx.KeepAliveService");
+ mContext.bindServiceAsUser(
+ bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.of(getCurrentUserId()));
+ Log.i(TAG, "bindService to " + mMusicFxPackageName);
+ }
+
+ Log.i(TAG, mMusicFxPackageName + " procState " + procState);
+ }
+ try {
+ if (mUidObserverToken == null) {
+ mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
+ mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, mPackageName,
+ new int[]{uid});
+ Log.i(TAG, "registered to observer with UID " + uid);
+ } else if (get(uid) == null) { // addUidToObserver if this is a new UID
+ ActivityManager.getService().addUidToObserver(mUidObserverToken, mPackageName,
+ uid);
+ Log.i(TAG, " UID " + uid + " add to observer");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException with UID observer add/register: " + e);
+ }
+
+ super.put(uid, pkgSessions);
+ }
+
+ @Override
+ public void remove(int uid) {
+ if (get(uid) != null) {
+ try {
+ ActivityManager.getService().removeUidFromObserver(mUidObserverToken,
+ mPackageName, uid);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException with removeUidFromObserver: " + e);
+ }
+ }
+
+ super.remove(uid);
+
+ // stop foreground service delegate and unregister UID observers with the last UID
+ if (size() == 0) {
+ try {
+ ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException with unregisterUidObserver: " + e);
+ }
+ mUidObserverToken = null;
+ mContext.unbindService(mMusicFxBindConnection);
+ Log.i(TAG, "last session closed, unregister UID observer, and unbind "
+ + mMusicFxPackageName);
+ }
+ }
+ }
+
// Hashmap of UID and list of open sessions for this UID.
@GuardedBy("mClientUidMapLock")
- private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>();
-
- /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;
+ private MySparseArray mClientUidSessionMap = new MySparseArray();
// UID observer for effect MusicFx clients
private final IUidObserver mEffectUidObserver = new UidObserver() {
@@ -102,23 +202,27 @@ public class MusicFxHelper {
* Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
* {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
*
+ * Only intents without target application package {@link android.content.Intent#getPackage}
+ * will be handled by the MusicFxHelper, all intents handled and forwarded by MusicFxHelper
+ * will have the target application package.
+ *
* If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}:
- * - If the MusicFx process is not running, call bindService with AUTO_CREATE to create.
- * - If this is the first audio session in MusicFx, call set foreground service delegate.
+ * - If the MusicFx process is not running, call bindServiceAsUser with AUTO_CREATE to create.
+ * - If this is the first audio session of MusicFx, call set foreground service delegate.
* - If this is the first audio session for a given UID, add the UID into observer.
*
- * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}:
- * - MusicFx will not be foreground delegated anymore.
- * - The KeepAliveService of MusicFx will be unbound.
- * - The UidObserver will be removed.
+ * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}
+ * - The KeepAliveService of MusicFx will be unbound, and MusicFx will not be foreground
+ * delegated anymore if the last session of the last package was closed.
+ * - The Uid Observer will be removed when the last session of a package was closed.
*/
+ @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
public void handleAudioEffectBroadcast(Context context, Intent intent) {
String target = intent.getPackage();
if (target != null) {
Log.w(TAG, "effect broadcast already targeted to " + target);
return;
}
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
final PackageManager pm = context.getPackageManager();
// TODO this should target a user-selected panel
List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */);
@@ -126,14 +230,14 @@ public class MusicFxHelper {
ResolveInfo ri = ril.get(0);
final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
try {
- final int senderUid = pm.getPackageUidAsUser(senderPackageName,
- PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
+ final int senderUid = pm.getPackageUidAsUser(senderPackageName,
+ PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.setPackage(ri.activityInfo.packageName);
- synchronized (mClientUidMapLock) {
- setMusicFxServiceWithObserver(context, intent, senderUid);
+ if (setMusicFxServiceWithObserver(intent, senderUid, senderPackageName)) {
+ context.sendBroadcastAsUser(intent, UserHandle.ALL);
}
- context.sendBroadcastAsUser(intent, UserHandle.ALL);
return;
}
} catch (PackageManager.NameNotFoundException e) {
@@ -144,127 +248,105 @@ public class MusicFxHelper {
Log.w(TAG, "couldn't find receiver package for effect intent");
}
- /**
- * Handle the UidObserver onUidGone callback of MusicFx clients.
- * All open audio sessions of this UID will be closed.
- * If this is the last UID for MusicFx:
- * - MusicFx will not be foreground delegated anymore.
- * - The KeepAliveService of MusicFx will be unbound.
- * - The UidObserver will be removed.
- */
- public void handleEffectClientUidGone(int uid) {
- synchronized (mClientUidMapLock) {
- 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 =
- 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(
- AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
- intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session);
- setMusicFxServiceWithObserver(mContext, intent, uid);
- Log.i(TAG, "Close session " + session + " of UID " + uid);
- }
- mClientUidSessionMap.remove(Integer.valueOf(uid));
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES
+ })
+ @GuardedBy("mClientUidMapLock")
+ private boolean handleAudioEffectSessionOpen(
+ int senderUid, String senderPackageName, int sessionId) {
+ Log.d(TAG, senderPackageName + " UID " + senderUid + " open MusicFx session " + sessionId);
+
+ PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+ if (pkgSessions != null && pkgSessions.mSessions != null) {
+ if (pkgSessions.mSessions.contains(sessionId)) {
+ Log.e(TAG, "Audio session " + sessionId + " already open for UID: "
+ + senderUid + ", package: " + senderPackageName + ", abort");
+ return false;
+ }
+ if (pkgSessions.mPackageName != senderPackageName) {
+ Log.w(TAG, "Inconsistency package names for UID open: " + senderUid + " prev: "
+ + pkgSessions.mPackageName + ", now: " + senderPackageName);
+ return false;
}
+ } else {
+ // first session for this UID, create a new Package/Sessions pair
+ pkgSessions = new PackageSessions();
+ pkgSessions.mSessions = new ArrayList();
+ pkgSessions.mPackageName = senderPackageName;
}
+
+ pkgSessions.mSessions.add(Integer.valueOf(sessionId));
+ mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions);
+ return true;
}
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES
+ })
@GuardedBy("mClientUidMapLock")
- private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) {
- PackageManager pm = context.getPackageManager();
- try {
- final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
- AudioManager.AUDIO_SESSION_ID_GENERATE);
- if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) {
- Log.e(TAG, "Intent missing audio session: " + audioSession);
- return;
+ private boolean handleAudioEffectSessionClose(
+ int senderUid, String senderPackageName, int sessionId) {
+ Log.d(TAG, senderPackageName + " UID " + senderUid + " close MusicFx session " + sessionId);
+
+ PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+ if (pkgSessions == null) {
+ Log.e(TAG, senderPackageName + " UID " + senderUid + " does not exist in map, abort");
+ return false;
+ }
+ if (pkgSessions.mPackageName != senderPackageName) {
+ Log.w(TAG, "Inconsistency package names for UID " + senderUid + " close, prev: "
+ + pkgSessions.mPackageName + ", now: " + senderPackageName);
+ return false;
+ }
+
+ if (pkgSessions.mSessions != null && pkgSessions.mSessions.size() != 0) {
+ if (!pkgSessions.mSessions.contains(sessionId)) {
+ Log.e(TAG, senderPackageName + " UID " + senderUid + " session " + sessionId
+ + " does not exist in map, abort");
+ return false;
}
- // only apply to com.android.musicfx and KeepAliveService for now
- final String musicFxPackageName = "com.android.musicfx";
- final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService";
- final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName,
- PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+ pkgSessions.mSessions.remove(Integer.valueOf(sessionId));
+ }
- if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
- List<Integer> sessions = new ArrayList<>();
- Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession);
- // start foreground service delegate and register UID observer with the first
- // session of first UID open
- if (0 == mClientUidSessionMap.size()) {
- final int procState = ActivityManager.getService().getPackageProcessState(
- musicFxPackageName, this.getClass().getPackage().getName());
- // if musicfx process not in binding state, call bindService with AUTO_CREATE
- if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- Intent bindIntent = new Intent().setClassName(musicFxPackageName,
- musicFxKeepAliveService);
- context.bindServiceAsUser(
- bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
- UserHandle.of(getCurrentUserId()));
- Log.i(TAG, "bindService to " + musicFxPackageName);
- }
+ if (pkgSessions.mSessions == null || pkgSessions.mSessions.size() == 0) {
+ // remove UID from map as well as the UID observer with the last session close
+ mClientUidSessionMap.remove(Integer.valueOf(senderUid));
+ } else {
+ mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions);
+ }
- Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid
- + " procState " + procState);
- } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) {
- sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
- if (sessions.contains(audioSession)) {
- Log.e(TAG, "Audio session " + audioSession + " already exist for UID "
- + senderUid + ", abort");
- return;
- }
- }
- // first session of this UID
- if (sessions.size() == 0) {
- // call registerUidObserverForUids with the first UID and first session
- if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) {
- mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
- mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
- ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid});
- Log.i(TAG, "UID " + senderUid + " registered to observer");
- } else {
- // add UID to observer for each new UID
- ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG,
- senderUid);
- Log.i(TAG, "UID " + senderUid + " addeded to observer");
- }
- }
+ return true;
+ }
- sessions.add(Integer.valueOf(audioSession));
- mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
- } else {
- if (mClientUidSessionMap.get(senderUid) != null) {
- Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession);
- List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
- sessions.remove(Integer.valueOf(audioSession));
- if (0 == sessions.size()) {
- mClientUidSessionMap.remove(Integer.valueOf(senderUid));
- } else {
- mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
- }
+ /**
+ * @return true if the intent is validated and handled successfully, false with any error
+ * (invalid sender/intent for example).
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES
+ })
+ private boolean setMusicFxServiceWithObserver(
+ Intent intent, int senderUid, String packageName) {
+ final int session = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
+ AudioManager.AUDIO_SESSION_ID_GENERATE);
+ if (AudioManager.AUDIO_SESSION_ID_GENERATE == session) {
+ Log.e(TAG, packageName + " intent have no invalid audio session");
+ return false;
+ }
- // stop foreground service delegate and unregister UID observer with the
- // last session of last UID close
- if (0 == mClientUidSessionMap.size()) {
- ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
- mClientUidSessionMap.clear();
- context.unbindService(mMusicFxBindConnection);
- Log.i(TAG, " remove all sessions, unregister UID observer, and unbind "
- + musicFxPackageName);
- }
- } else {
- // if the audio session already closed, print an error
- Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession
- + " which does not exist");
- }
+ synchronized (mClientUidMapLock) {
+ if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
+ return handleAudioEffectSessionOpen(senderUid, packageName, session);
+ } else {
+ return handleAudioEffectSessionClose(senderUid, packageName, session);
}
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Not able to find UID from package: " + e);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException " + e + " with handling intent");
}
}
@@ -281,6 +363,39 @@ public class MusicFxHelper {
return UserHandle.USER_SYSTEM;
}
+
+ /**
+ * Handle the UidObserver onUidGone callback of MusicFx clients.
+ * Send close intent for all open audio sessions of this UID. The mClientUidSessionMap will be
+ * updated with the handling of close intent in setMusicFxServiceWithObserver.
+ */
+ @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
+ private void handleEffectClientUidGone(int uid) {
+ synchronized (mClientUidMapLock) {
+ Log.d(TAG, "handle MSG_EFFECT_CLIENT_GONE uid: " + uid + " mapSize: "
+ + mClientUidSessionMap.size());
+ // Once the uid is no longer running, close all remain audio session(s) for this UID
+ final PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(uid));
+ if (pkgSessions != null) {
+ Log.i(TAG, "UID " + uid + " gone, closing all sessions");
+
+ // send close intent for each open session of the gone UID
+ for (Integer sessionId : pkgSessions.mSessions) {
+ Intent closeIntent =
+ new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+ closeIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, pkgSessions.mPackageName);
+ closeIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
+ closeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ // set broadcast target
+ closeIntent.setPackage(mMusicFxPackageName);
+ mContext.sendBroadcastAsUser(closeIntent, UserHandle.ALL);
+ }
+ mClientUidSessionMap.remove(Integer.valueOf(uid));
+ }
+ }
+ }
+
+ @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
/*package*/ void handleMessage(Message msg) {
switch (msg.what) {
case MSG_EFFECT_CLIENT_GONE:
@@ -292,5 +407,4 @@ public class MusicFxHelper {
break;
}
}
-
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index d359280c2a6f..bab3cbea108e 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -424,6 +424,7 @@ public abstract class IContextHubWrapper {
// 9a17008d-6bf1-445a-9011-6d21bd985b6c
private static final byte[] UUID = {-102, 23, 0, -115, 107, -15, 68, 90,
-112, 17, 109, 33, -67, -104, 91, 108};
+ private static final String NAME = "ContextHubService";
ContextHubAidlCallback(int contextHubId, ICallback callback) {
mContextHubId = contextHubId;
@@ -466,10 +467,14 @@ public abstract class IContextHubWrapper {
// TODO(271471342): Implement
}
- public byte[] getUuid() throws RemoteException {
+ public byte[] getUuid() {
return UUID;
}
+ public String getName() {
+ return NAME;
+ }
+
@Override
public String getInterfaceHash() {
return android.hardware.contexthub.IContextHubCallback.HASH;
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index bdcec3a33221..82622d9a4ea8 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -54,6 +54,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.util.SparseSetArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -1030,14 +1031,18 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
private void recomputeComponentVisibility(
ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
final WatchedArraySet<String> protectedBroadcasts;
+ final WatchedArraySet<Integer> forceQueryable;
synchronized (mProtectedBroadcastsLock) {
protectedBroadcasts = mProtectedBroadcasts.snapshot();
}
+ synchronized (mForceQueryableLock) {
+ forceQueryable = mForceQueryable.snapshot();
+ }
final ParallelComputeComponentVisibility computer = new ParallelComputeComponentVisibility(
- existingSettings, mForceQueryable, protectedBroadcasts);
+ existingSettings, forceQueryable, protectedBroadcasts);
+ SparseSetArray<Integer> queriesViaComponent = computer.execute();
synchronized (mQueriesViaComponentLock) {
- mQueriesViaComponent.clear();
- computer.execute(mQueriesViaComponent);
+ mQueriesViaComponent.copyFrom(queriesViaComponent);
}
mQueriesViaComponentRequireRecompute.set(false);
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index f3f64c5010ee..200734b37269 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -26,6 +26,7 @@ import android.content.IntentFilter;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
+import android.util.SparseSetArray;
import com.android.internal.pm.pkg.component.ParsedComponent;
import com.android.internal.pm.pkg.component.ParsedIntentInfo;
@@ -37,7 +38,6 @@ import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.utils.WatchedArraySet;
-import com.android.server.utils.WatchedSparseSetArray;
import java.util.ArrayList;
import java.util.List;
@@ -213,7 +213,9 @@ final class AppsFilterUtils {
/**
* Computes component visibility of all packages in parallel from a thread pool.
*/
- void execute(@NonNull WatchedSparseSetArray<Integer> outQueriesViaComponent) {
+ @NonNull
+ SparseSetArray<Integer> execute() {
+ final SparseSetArray<Integer> queriesViaComponent = new SparseSetArray<>();
final ExecutorService pool = ConcurrentUtils.newFixedThreadPool(
MAX_THREADS, ParallelComputeComponentVisibility.class.getSimpleName(),
THREAD_PRIORITY_DEFAULT);
@@ -239,7 +241,7 @@ final class AppsFilterUtils {
try {
final ArraySet<Integer> visibleList = future.get();
if (visibleList.size() != 0) {
- outQueriesViaComponent.addAll(appId, visibleList);
+ queriesViaComponent.addAll(appId, visibleList);
}
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException(e);
@@ -248,6 +250,7 @@ final class AppsFilterUtils {
} finally {
pool.shutdownNow();
}
+ return queriesViaComponent;
}
/**
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 4442845f83b2..1af127175f80 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -78,7 +78,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
private boolean mIsPerUidTimeInStateSupported;
private PowerStatsInternal mPowerStatsInternal;
- private int[] mCpuEnergyConsumerIds;
+ private int[] mCpuEnergyConsumerIds = new int[0];
private PowerStats.Descriptor mPowerStatsDescriptor;
// Reusable instance
private PowerStats mCpuPowerStats;
@@ -286,8 +286,6 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
if (mPowerStatsInternal != null) {
readCpuEnergyConsumerIds();
- } else {
- mCpuEnergyConsumerIds = new int[0];
}
int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount();
@@ -320,7 +318,6 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
private void readCpuEnergyConsumerIds() {
EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo();
if (energyConsumerInfo == null) {
- mCpuEnergyConsumerIds = new int[0];
return;
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
index 0386e66aaf3d..b8850afdf3ad 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
@@ -142,6 +142,20 @@ public class WatchedSparseSetArray<T> extends WatchableImpl implements Snappable
return (T) mStorage.valueAt(intIndex, valueIndex);
}
+ /**
+ * Copy from another SparseSetArray.
+ */
+ public void copyFrom(@NonNull SparseSetArray<T> c) {
+ clear();
+ final int end = c.size();
+ for (int i = 0; i < end; i++) {
+ final int key = c.keyAt(i);
+ final ArraySet<T> set = c.get(key);
+ mStorage.addAll(key, set);
+ }
+ onChanged();
+ }
+
@NonNull
@Override
public Object snapshot() {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e5794a1f7efd..dfb2a5fcf9fa 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1121,6 +1121,7 @@ class ActivityStarter {
callerApp,
request.originatingPendingIntent,
request.forcedBalByPiSender,
+ resultRecord,
intent,
checkedOptions);
request.logMessage.append(" (").append(balVerdict).append(")");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a04513f407b8..73edb4bed6ca 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -103,6 +103,7 @@ import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORD
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -118,6 +119,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
@@ -2242,7 +2244,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
final BackgroundActivityStartController balController =
mTaskSupervisor.getBackgroundActivityLaunchController();
- if (balController.shouldAbortBackgroundActivityStart(
+ final BalVerdict balVerdict = balController.checkBackgroundActivityStart(
callingUid,
callingPid,
callingPackage,
@@ -2252,10 +2254,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
null,
BackgroundStartPrivileges.NONE,
null,
- null)) {
- if (!isBackgroundActivityStartsEnabled()) {
- return;
- }
+ null,
+ null);
+ if (balVerdict.blocks() && !isBackgroundActivityStartsEnabled()) {
+ Slog.w(TAG, "moveTaskToFront blocked: " + balVerdict);
+ return;
+ }
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "moveTaskToFront allowed: " + balVerdict);
}
try {
final Task task = mRootWindowContainer.anyTaskForId(taskId);
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 761b0a8f0b39..50de0b08f3b0 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
+import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
@@ -31,6 +33,7 @@ import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Slog;
/**
* An implementation of IAppTask, that allows an app to manage its own tasks via
@@ -123,7 +126,7 @@ class AppTaskImpl extends IAppTask.Stub {
}
final BackgroundActivityStartController balController =
mService.mTaskSupervisor.getBackgroundActivityLaunchController();
- if (balController.shouldAbortBackgroundActivityStart(
+ BalVerdict balVerdict = balController.checkBackgroundActivityStart(
callingUid,
callingPid,
callingPackage,
@@ -133,10 +136,14 @@ class AppTaskImpl extends IAppTask.Stub {
null,
BackgroundStartPrivileges.NONE,
null,
- null)) {
- if (!mService.isBackgroundActivityStartsEnabled()) {
- return;
- }
+ null,
+ null);
+ if (balVerdict.blocks() && !mService.isBackgroundActivityStartsEnabled()) {
+ Slog.w(TAG, "moveTaskToFront blocked: : " + balVerdict);
+ return;
+ }
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "moveTaskToFront allowed: " + balVerdict);
}
}
mService.mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 07dac5481b79..92665af1075b 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -205,27 +205,6 @@ public class BackgroundActivityStartController {
return activity != null && packageName.equals(activity.getPackageName());
}
- /**
- * @see #checkBackgroundActivityStart(int, int, String, int, int, WindowProcessController,
- * PendingIntentRecord, BackgroundStartPrivileges, Intent, ActivityOptions)
- */
- boolean shouldAbortBackgroundActivityStart(
- int callingUid,
- int callingPid,
- final String callingPackage,
- int realCallingUid,
- int realCallingPid,
- WindowProcessController callerApp,
- PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender,
- Intent intent,
- ActivityOptions checkedOptions) {
- return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
- realCallingUid, realCallingPid,
- callerApp, originatingPendingIntent,
- forcedBalByPiSender, intent, checkedOptions).blocks();
- }
-
private class BalState {
private final String mCallingPackage;
@@ -255,6 +234,7 @@ public class BackgroundActivityStartController {
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
BackgroundStartPrivileges forcedBalByPiSender,
+ ActivityRecord resultRecord,
Intent intent,
ActivityOptions checkedOptions) {
this.mCallingPackage = callingPackage;
@@ -267,7 +247,9 @@ public class BackgroundActivityStartController {
mOriginatingPendingIntent = originatingPendingIntent;
mIntent = intent;
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
- if (originatingPendingIntent == null) {
+ if (originatingPendingIntent == null // not a PendingIntent
+ || resultRecord != null // sent for result
+ ) {
// grant BAL privileges unless explicitly opted out
mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
@@ -535,6 +517,7 @@ public class BackgroundActivityStartController {
* @param forcedBalByPiSender If set to allow, the
* PendingIntent's sender will try to force allow background activity starts.
* This is only possible if the sender of the PendingIntent is a system process.
+ * @param resultRecord If not null, this indicates that the caller expects a result.
* @param intent Intent that should be started.
* @param checkedOptions ActivityOptions to allow specific opt-ins/opt outs.
*
@@ -550,6 +533,7 @@ public class BackgroundActivityStartController {
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
BackgroundStartPrivileges forcedBalByPiSender,
+ ActivityRecord resultRecord,
Intent intent,
ActivityOptions checkedOptions) {
@@ -560,7 +544,7 @@ public class BackgroundActivityStartController {
BalState state = new BalState(callingUid, callingPid, callingPackage,
realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
- forcedBalByPiSender, intent, checkedOptions);
+ forcedBalByPiSender, resultRecord, intent, checkedOptions);
// In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
// visible window.
diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
new file mode 100644
index 000000000000..472a82c02937
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
@@ -0,0 +1,642 @@
+/*
+ * 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.server.audio;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.audiofx.AudioEffect;
+import android.os.Message;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MusicFxHelperTest {
+ private static final String TAG = "MusicFxHelperTest";
+
+ @Mock private AudioService mMockAudioService;
+ @Mock private Context mMockContext;
+ @Mock private PackageManager mMockPackageManager;
+
+ private ResolveInfo mResolveInfo1 = new ResolveInfo();
+ private ResolveInfo mResolveInfo2 = new ResolveInfo();
+ private final String mTestPkg1 = "testPkg1", mTestPkg2 = "testPkg2", mTestPkg3 = "testPkg3";
+ private final String mMusicFxPkgName = "com.android.musicfx";
+ private final int mTestUid1 = 1, mTestUid2 = 2, mTestUid3 = 3, mMusicFxUid = 78;
+ private final int mTestSession1 = 11, mTestSession2 = 22, mTestSession3 = 33;
+
+ private List<ResolveInfo> mEmptyList = new ArrayList<>();
+ private List<ResolveInfo> mSingleList = new ArrayList<>();
+ private List<ResolveInfo> mDoubleList = new ArrayList<>();
+
+ // the class being unit-tested here
+ @InjectMocks private MusicFxHelper mMusicFxHelper;
+
+ @Before
+ @SuppressWarnings("DirectInvocationOnMock")
+ public void setUp() throws Exception {
+ mMockAudioService = mock(AudioService.class);
+ mMusicFxHelper = mMockAudioService.getMusicFxHelper();
+ MockitoAnnotations.initMocks(this);
+
+ mResolveInfo1.activityInfo = new ActivityInfo();
+ mResolveInfo1.activityInfo.packageName = mTestPkg1;
+ mResolveInfo2.activityInfo = new ActivityInfo();
+ mResolveInfo2.activityInfo.packageName = mTestPkg2;
+
+ mSingleList.add(mResolveInfo1);
+ mDoubleList.add(mResolveInfo1);
+ mDoubleList.add(mResolveInfo2);
+
+ Assert.assertNotNull(mMusicFxHelper);
+ }
+
+ private Intent newIntent(String action, String packageName, int sessionId) {
+ Intent intent = new Intent(action);
+ if (packageName != null) {
+ intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName);
+ }
+ intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
+ return intent;
+ }
+
+ /**
+ * Helper function to send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION intent with verification.
+ *
+ * @throws NameNotFoundException if no such package is available to the caller.
+ */
+ private void openSessionWithResList(
+ List<ResolveInfo> list, int bind, int broadcast, String packageName, int audioSession,
+ int uid) {
+ doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+ doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt());
+ if (list != null && list.size() != 0) {
+ try {
+ doReturn(uid).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(packageName), anyObject(), anyInt());
+ doReturn(mMusicFxUid).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "NameNotFoundException: " + e);
+ }
+ }
+
+ Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION,
+ packageName, audioSession);
+ mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+ verify(mMockContext, times(bind))
+ .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
+ verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject());
+ }
+
+ /**
+ * Helper function to send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent with verification.
+ *
+ * @throws NameNotFoundException if no such package is available to the caller.
+ */
+ private void closeSessionWithResList(
+ List<ResolveInfo> list, int unBind, int broadcast, String packageName,
+ int audioSession, int uid) {
+ doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+ doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt());
+ if (list != null && list.size() != 0) {
+ try {
+ doReturn(uid).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(packageName), anyObject(), anyInt());
+ doReturn(mMusicFxUid).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "NameNotFoundException: " + e);
+ }
+ }
+
+ Intent intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION,
+ packageName, audioSession);
+ mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+ verify(mMockContext, times(unBind)).unbindService(anyObject());
+ verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject());
+ }
+
+ /**
+ * Helper function to send MSG_EFFECT_CLIENT_GONE message with verification.
+ */
+ private void sendMessage(int msgId, int uid, int unBinds, int broadcasts) {
+ mMusicFxHelper.handleMessage(Message.obtain(null, msgId, uid /* arg1 */, 0 /* arg2 */));
+ verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(anyObject(), anyObject());
+ verify(mMockContext, times(unBinds)).unbindService(anyObject());
+ }
+
+ /**
+ * Send invalid message to MusicFxHelper.
+ */
+ @Test
+ public void testInvalidMessage() {
+ Log.i(TAG, "running testInvalidMessage");
+
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE - 1, 0, 0, 0);
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE + 1, 0, 0, 0);
+ }
+
+ /**
+ * Send client gone message to MusicFxHelper when no client exist.
+ */
+ @Test
+ public void testGoneMessageWhenNoClient() {
+ Log.i(TAG, "running testGoneMessageWhenNoClient");
+
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, 0, 0, 0);
+ }
+
+ /**
+ * Send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent to MusicFxHelper when no session exist.
+ */
+ @Test
+ public void testCloseBroadcastIntent() {
+ Log.i(TAG, "running testCloseBroadcastIntent");
+
+ closeSessionWithResList(null, 0, 0, null, mTestSession1, mTestUid1);
+ }
+
+ /**
+ * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION intent when target application package was set.
+ * When the target application package was set for an intent, it means this intent is limited
+ * to a specific target application, as a result MusicFxHelper will not handle this intent.
+ */
+ @Test
+ public void testBroadcastIntentWithPackage() {
+ Log.i(TAG, "running testBroadcastIntentWithPackage");
+
+ Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION, null, 1);
+ intent.setPackage(mTestPkg1);
+ mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+ verify(mMockContext, times(0))
+ .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
+ verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject());
+
+ intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, null, 1);
+ intent.setPackage(mTestPkg2);
+ mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+ verify(mMockContext, times(0))
+ .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
+ verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject());
+ }
+
+ /**
+ * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with no broadcast receiver.
+ */
+ @Test
+ public void testBroadcastIntentWithNoPackageAndNoBroadcastReceiver() {
+ Log.i(TAG, "running testBroadcastIntentWithNoPackageAndNoBroadcastReceiver");
+
+ openSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
+ closeSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
+ }
+
+ /**
+ * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with one broadcast receiver.
+ */
+ @Test
+ public void testBroadcastIntentWithNoPackageAndOneBroadcastReceiver() {
+ Log.i(TAG, "running testBroadcastIntentWithNoPackageAndOneBroadcastReceiver");
+
+ int broadcasts = 1, bind = 1, unbind = 1;
+ openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid1);
+
+ // repeat with different session ID
+ broadcasts = broadcasts + 1;
+ bind = bind + 1;
+ unbind = unbind + 1;
+ openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession2, mTestUid1);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession2, mTestUid1);
+
+ // repeat with different UID
+ broadcasts = broadcasts + 1;
+ bind = bind + 1;
+ unbind = unbind + 1;
+ openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid2);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid2);
+ }
+
+ /**
+ * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with two broadcast receivers.
+ */
+ @Test
+ public void testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers() {
+ Log.i(TAG, "running testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers");
+
+ openSessionWithResList(mDoubleList, 1, 1, null, mTestSession1, mTestUid1);
+ closeSessionWithResList(mDoubleList, 1, 2, null, mTestSession1, mTestUid1);
+ }
+
+ /**
+ * Open/close session UID not matching.
+ * No broadcast for mismatching sessionID/UID/packageName.
+ */
+ @Test
+ public void testBroadcastBadIntents() {
+ Log.i(TAG, "running testBroadcastBadIntents");
+
+ int broadcasts = 1;
+ openSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ // mismatch UID
+ closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid2);
+ // mismatch AudioSession
+ closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+ // mismatch packageName
+ closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg2, mTestSession1, mTestUid1);
+
+ // cleanup with correct UID and session ID
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ }
+
+ /**
+ * Open/close sessions with one UID, some with correct intents some with illegal intents.
+ * No broadcast for mismatching sessionID/UID/packageName.
+ */
+ @Test
+ public void testBroadcastGoodAndBadIntents() {
+ Log.i(TAG, "running testBroadcastGoodAndBadIntents");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ // mismatch packageName, session ID and UID
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+ mTestUid2);
+ // mismatch session ID and mismatch UID
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+ mTestUid2);
+ // mismatch packageName and mismatch UID
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession1,
+ mTestUid2);
+ // mismatch packageName and sessionID
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+ mTestUid1);
+ // inconsistency package name for same UID
+ openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid1);
+ // open session2 with good intent
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+
+ // cleanup with correct UID and session ID
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+ broadcasts = broadcasts + 1;
+ unbind = unbind + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+ mTestUid1);
+ }
+
+ /**
+ * Send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION when there is no listener.
+ */
+ @Test
+ public void testBroadcastOpenSessionWithValidPackageNameAndNoListener() {
+ Log.i(TAG, "running testBroadcastOpenSessionWithValidPackageNameAndNoListener");
+
+ // null listener list should not trigger any action
+ openSessionWithResList(null, 0, 0, mTestPkg1, mTestSession1, mTestUid1);
+ // empty listener list should not trigger any action
+ openSessionWithResList(mEmptyList, 0, 0, mTestPkg1, mTestSession1, mTestUid1);
+ }
+
+ /**
+ * One MusicFx client, open session and close.
+ */
+ @Test
+ public void testOpenCloseAudioSession() {
+ Log.i(TAG, "running testOpenCloseAudioSession");
+
+ int broadcasts = 1;
+ openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ }
+
+ /**
+ * One MusicFx client, open session and close, then gone.
+ */
+ @Test
+ public void testOpenCloseAudioSessionAndGone() {
+ Log.i(TAG, "running testOpenCloseAudioSessionAndGone");
+
+ int broadcasts = 1;
+ openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+ broadcasts = broadcasts + 1; // 1 open session left
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts);
+ }
+
+ /**
+ * One MusicFx client, open session, then UID gone without close.
+ */
+ @Test
+ public void testOpenOneSessionAndGo() {
+ Log.i(TAG, "running testOpenOneSessionAndGo");
+
+ int broadcasts = 1;
+ openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+ broadcasts = broadcasts + 1;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts);
+ }
+
+ /**
+ * Two MusicFx clients open and close sessions.
+ */
+ @Test
+ public void testOpenTwoSessionsAndClose() {
+ Log.i(TAG, "running testOpenTwoSessionsAndClose");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+ mTestUid2);
+ broadcasts = broadcasts + 1;
+ unbind = unbind + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+
+ broadcasts = broadcasts + 1;
+ bind = bind + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+ broadcasts = broadcasts + 1;
+ unbind = unbind + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+ mTestUid2);
+ }
+
+ /**
+ * Two MusicFx clients open sessions, then both UID gone without close.
+ */
+ @Test
+ public void testOpenTwoSessionsAndGo() {
+ Log.i(TAG, "running testOpenTwoSessionsAndGo");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+ broadcasts = broadcasts + 1;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts);
+
+ broadcasts = broadcasts + 1;
+ unbind = unbind + 1;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+ }
+
+ /**
+ * Two MusicFx clients open sessions, one close but not gone, the other one gone without close.
+ */
+ @Test
+ public void testTwoSessionsOpenOneCloseOneGo() {
+ Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+
+ broadcasts = broadcasts + 1;
+ unbind = unbind + 1;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+ }
+
+ /**
+ * One MusicFx client, open multiple audio sessions, and close all sessions.
+ */
+ @Test
+ public void testTwoSessionsInSameUidOpenClose() {
+ Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+ broadcasts = broadcasts + 1;
+ unbind = unbind + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+ mTestUid1);
+ }
+
+ /**
+ * Three MusicFx clients, each with multiple audio sessions, and close all sessions.
+ */
+ @Test
+ public void testThreeSessionsInThreeUidOpenClose() {
+ Log.i(TAG, "running testThreeSessionsInThreeUidOpenClose");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ //client1
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+ // client2
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+ // client3
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3);
+
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession3,
+ mTestUid3);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+ mTestUid2);
+ // all sessions of client1 closed
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+ mTestUid1);
+ // all sessions of client3 closed
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession1,
+ mTestUid3);
+ // all sessions of client2 closed
+ broadcasts = broadcasts + 1;
+ // now expect unbind to happen
+ unbind = unbind + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession3,
+ mTestUid2);
+ }
+
+ /**
+ * Two MusicFx clients, with multiple audio sessions, one close all sessions, and other gone.
+ */
+ @Test
+ public void testTwoUidOneCloseOneGo() {
+ Log.i(TAG, "running testTwoUidOneCloseOneGo");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ //client1
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+ // client2
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession1, mTestUid2);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+ // client2 gone
+ broadcasts = broadcasts + 2;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+ // client 1 close all sessions
+ broadcasts = broadcasts + 1;
+ unbind = unbind + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+ mTestUid1);
+ }
+
+ /**
+ * Three MusicFx clients, with multiple audio sessions, all UID gone.
+ */
+ @Test
+ public void testThreeUidAllGo() {
+ Log.i(TAG, "running testThreeUidAllGo");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ //client1
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+ // client2
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2);
+ // client3
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3);
+
+ // client2 gone
+ broadcasts = broadcasts + 2;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+ // client3 gone
+ broadcasts = broadcasts + 2;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts);
+ // client 1 gone
+ broadcasts = broadcasts + 2;
+ // now expect unbindService to happen
+ unbind = unbind + 1;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts);
+ }
+
+ /**
+ * Three MusicFx clients, multiple audio sessions, open and UID gone in difference sequence.
+ */
+ @Test
+ public void testThreeUidDiffSequence() {
+ Log.i(TAG, "running testThreeUidDiffSequence");
+
+ int broadcasts = 1, bind = 1, unbind = 0;
+ //client1
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+ // client2
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+ // client1 close one session
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+ // client2 open another session
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2);
+ // client3 open one session
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3);
+ // client2 gone
+ broadcasts = broadcasts + 2;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+ // client3 open another session
+ broadcasts = broadcasts + 1;
+ openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3);
+ // client1 close another session, and gone
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+ mTestUid1);
+ // last UID client3 gone, unbind
+ broadcasts = broadcasts + 2;
+ unbind = unbind + 1;
+ sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts);
+ }
+}