diff options
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); + } +} |