Adding CarAudioPowerListener
Listener handles initializing and registering with
CarPowerManagementService, and then enables and
disables CarAudioService accordingly.
Bug: 176258537
Test: atest CarAudioPowerListenerTest
Test: adb shell cmd car_service apply-power-policy
system_power_policy_suspend_to_ram and check dumpsys
for enabled status
Change-Id: I25c776a7d47ca384a756007ab304d4ba88a02240
diff --git a/service/src/com/android/car/audio/CarAudioPowerListener.java b/service/src/com/android/car/audio/CarAudioPowerListener.java
new file mode 100644
index 0000000..b946745
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioPowerListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.car.audio;
+
+import static android.car.hardware.power.PowerComponent.AUDIO;
+
+import android.annotation.NonNull;
+import android.car.hardware.power.CarPowerPolicy;
+import android.car.hardware.power.CarPowerPolicyFilter;
+import android.car.hardware.power.ICarPowerPolicyListener;
+import android.util.Slog;
+
+import com.android.car.CarLocalServices;
+import com.android.car.CarLog;
+import com.android.car.power.CarPowerManagementService;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+class CarAudioPowerListener {
+ private static final String TAG = CarLog.tagFor(CarAudioPowerListener.class);
+
+ private final Object mLock = new Object();
+ private final CarAudioService mCarAudioService;
+ private final CarPowerManagementService mCarPowerManagementService;
+
+ private final ICarPowerPolicyListener mChangeListener =
+ new ICarPowerPolicyListener.Stub() {
+ @Override
+ public void onPolicyChanged(CarPowerPolicy policy,
+ CarPowerPolicy accumulatedPolicy) {
+ synchronized (mLock) {
+ if (mIsAudioEnabled != accumulatedPolicy.isComponentEnabled(AUDIO)) {
+ updateAudioPowerStateLocked(accumulatedPolicy);
+ }
+ }
+ }
+ };
+
+ @GuardedBy("mLock")
+ private boolean mIsAudioEnabled;
+
+ static CarAudioPowerListener newCarAudioPowerListener(
+ @NonNull CarAudioService carAudioService) {
+ CarPowerManagementService carPowerService = CarLocalServices.getService(
+ CarPowerManagementService.class);
+ return new CarAudioPowerListener(carAudioService, carPowerService);
+ }
+
+ @VisibleForTesting
+ CarAudioPowerListener(@NonNull CarAudioService carAudioService,
+ CarPowerManagementService carPowerManagementService) {
+ mCarAudioService = Objects.requireNonNull(carAudioService);
+ mCarPowerManagementService = carPowerManagementService;
+ }
+
+ boolean isAudioEnabled() {
+ synchronized (mLock) {
+ return mIsAudioEnabled;
+ }
+ }
+
+ void startListeningForPolicyChanges() {
+ if (mCarPowerManagementService == null) {
+ Slog.w(TAG, "Cannot find CarPowerManagementService");
+ mCarAudioService.enableAudio();
+ return;
+ }
+
+ CarPowerPolicyFilter filter = new CarPowerPolicyFilter.Builder()
+ .setComponents(new int[]{AUDIO}).build();
+ mCarPowerManagementService.addPowerPolicyListener(filter, mChangeListener);
+ initializePowerState();
+ }
+
+ void stopListeningForPolicyChanges() {
+ if (mCarPowerManagementService == null) {
+ return;
+ }
+ mCarPowerManagementService.removePowerPolicyListener(mChangeListener);
+ }
+
+ private void initializePowerState() {
+ CarPowerPolicy policy = mCarPowerManagementService.getCurrentPowerPolicy();
+
+ if (policy == null) {
+ Slog.w(TAG, "Policy is null. Defaulting to enabled");
+ mCarAudioService.enableAudio();
+ return;
+ }
+
+ synchronized (mLock) {
+ updateAudioPowerStateLocked(policy);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void updateAudioPowerStateLocked(CarPowerPolicy policy) {
+ mIsAudioEnabled = policy.isComponentEnabled(AUDIO);
+
+ if (mIsAudioEnabled) {
+ mCarAudioService.enableAudio();
+ } else {
+ mCarAudioService.disableAudio();
+ }
+ }
+}
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 29e0885..b6291a4 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -189,6 +189,7 @@
private OccupantZoneConfigChangeListener
mOccupantZoneConfigChangeListener = new CarAudioOccupantConfigChangeListener();
private CarAudioPlaybackCallback mCarAudioPlaybackCallback;
+ private CarAudioPowerListener mCarAudioPowerListener;
public CarAudioService(Context context) {
mContext = context;
@@ -229,6 +230,7 @@
setupDynamicRoutingLocked();
setupHalAudioFocusListenerLocked();
setupAudioConfigurationCallbackLocked();
+ setupPowerPolicyListener();
} else {
Slog.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
setupLegacyVolumeChangedListener();
@@ -240,6 +242,11 @@
restoreMasterMuteState();
}
+ private void setupPowerPolicyListener() {
+ mCarAudioPowerListener = CarAudioPowerListener.newCarAudioPowerListener(this);
+ mCarAudioPowerListener.startListeningForPolicyChanges();
+ }
+
private void restoreMasterMuteState() {
if (mUseCarVolumeGroupMuting) {
return;
@@ -275,6 +282,10 @@
mAudioControlWrapper.unlinkToDeath();
mAudioControlWrapper = null;
}
+
+ if (mCarAudioPowerListener != null) {
+ mCarAudioPowerListener.stopListeningForPolicyChanges();
+ }
}
}
@@ -282,9 +293,11 @@
public void dump(IndentingPrintWriter writer) {
writer.println("*CarAudioService*");
writer.increaseIndent();
+
+ writer.println("Configurations:");
+ writer.increaseIndent();
writer.printf("Run in legacy mode? %b\n", !mUseDynamicRouting);
writer.printf("Persist master mute state? %b\n", mPersistMasterMuteState);
- writer.printf("Master muted? %b\n", mAudioManager.isMasterMute());
writer.printf("Use hal ducking signals %b\n", mUseHalDuckingSignals);
writer.printf("Volume context priority list version: %d\n",
mAudioVolumeAdjustmentContextsVersion);
@@ -292,8 +305,18 @@
if (mCarAudioConfigurationPath != null) {
writer.printf("Car audio configuration path: %s\n", mCarAudioConfigurationPath);
}
- // Empty line for comfortable reading
+ writer.decreaseIndent();
writer.println();
+
+ writer.println("Current State:");
+ writer.increaseIndent();
+ writer.printf("Master muted? %b\n", mAudioManager.isMasterMute());
+ if (mCarAudioPowerListener != null) {
+ writer.printf("Audio enabled? %b\n", mCarAudioPowerListener.isAudioEnabled());
+ }
+ writer.decreaseIndent();
+ writer.println();
+
if (mUseDynamicRouting) {
writer.printf("Volume Group Mute Enabled? %b\n", mUseCarVolumeGroupMuting);
synchronized (mImplLock) {
@@ -344,6 +367,7 @@
writer.println("No HalAudioFocus instance\n");
}
if (mCarDucking != null) {
+ writer.println();
mCarDucking.dump(writer);
}
if (mCarVolumeGroupMuting != null) {
@@ -1209,6 +1233,20 @@
return getCarAudioZone(zoneId).getInputAudioDevices();
}
+ void disableAudio() {
+ // Todo (b/176258537) abandon focus and mute everything
+ if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
+ Slog.d(CarLog.TAG_AUDIO, "Disabling audio");
+ }
+ }
+
+ void enableAudio() {
+ // Todo (b/176258537) resume focus and unmute appropriate things
+ if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
+ Slog.d(CarLog.TAG_AUDIO, "Enabling audio");
+ }
+ }
+
private void enforcePermission(String permissionName) {
if (mContext.checkCallingOrSelfPermission(permissionName)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
new file mode 100644
index 0000000..7c2c580
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2021 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.car.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.car.hardware.power.CarPowerPolicy;
+import android.car.hardware.power.CarPowerPolicyFilter;
+import android.car.hardware.power.ICarPowerPolicyListener;
+import android.car.hardware.power.PowerComponent;
+
+import com.android.car.power.CarPowerManagementService;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarAudioPowerListenerTest {
+ private static final String POLICY_ID = "POLICY_ID";
+ private static final int[] EMPTY_COMPONENTS = new int[0];
+ private static final int[] COMPONENTS_WITH_AUDIO = {PowerComponent.AUDIO};
+ private static final CarPowerPolicy ENABLED_POLICY = new CarPowerPolicy(POLICY_ID,
+ COMPONENTS_WITH_AUDIO, EMPTY_COMPONENTS);
+ private static final CarPowerPolicy DISABLED_POLICY = new CarPowerPolicy(POLICY_ID,
+ EMPTY_COMPONENTS, COMPONENTS_WITH_AUDIO);
+ private static final CarPowerPolicy EMPTY_POLICY = new CarPowerPolicy(POLICY_ID,
+ EMPTY_COMPONENTS, EMPTY_COMPONENTS);
+
+ @Mock
+ CarAudioService mMockCarAudioService;
+
+ @Mock
+ CarPowerManagementService mMockCarPowerService;
+
+ @Test
+ public void constructor_nullCarAudioService_throws() {
+ assertThrows(NullPointerException.class,
+ () -> new CarAudioPowerListener(null, mMockCarPowerService));
+ }
+
+ @Test
+ public void startListeningForPolicyChanges_withoutPowerService_enablesAudio() {
+ CarAudioPowerListener listener = new CarAudioPowerListener(mMockCarAudioService, null);
+
+ listener.startListeningForPolicyChanges();
+
+ verify(mMockCarAudioService).enableAudio();
+ }
+
+ @Test
+ public void startListeningForPolicyChanges_addsPowerPolicyListener() {
+ CarAudioPowerListener listener = new CarAudioPowerListener(mMockCarAudioService,
+ mMockCarPowerService);
+
+ listener.startListeningForPolicyChanges();
+
+ ArgumentCaptor<CarPowerPolicyFilter> captor = ArgumentCaptor.forClass(
+ CarPowerPolicyFilter.class);
+ verify(mMockCarPowerService).addPowerPolicyListener(
+ captor.capture(), any(ICarPowerPolicyListener.class));
+ assertThat(captor.getValue().components).asList().containsExactly(PowerComponent.AUDIO);
+
+ }
+
+ @Test
+ public void startListeningForPolicyChanges_withNullPolicy_enablesAudio() {
+ when(mMockCarPowerService.getCurrentPowerPolicy()).thenReturn(null);
+ CarAudioPowerListener listener = new CarAudioPowerListener(mMockCarAudioService,
+ mMockCarPowerService);
+
+ listener.startListeningForPolicyChanges();
+
+ verify(mMockCarAudioService).enableAudio();
+ }
+
+ @Test
+ public void startListeningForPolicyChanges_withPowerEnabled_enablesAudio() {
+ withAudioInitiallyEnabled();
+ CarAudioPowerListener listener = new CarAudioPowerListener(mMockCarAudioService,
+ mMockCarPowerService);
+
+ listener.startListeningForPolicyChanges();
+
+ verify(mMockCarAudioService).enableAudio();
+ }
+
+ @Test
+ public void startListeningForPolicyChanges_withPowerDisabled_disablesAudio() {
+ withAudioInitiallyDisabled();
+ CarAudioPowerListener listener = new CarAudioPowerListener(mMockCarAudioService,
+ mMockCarPowerService);
+
+ listener.startListeningForPolicyChanges();
+
+ verify(mMockCarAudioService).disableAudio();
+ }
+
+ @Test
+ public void onPolicyChange_withPowerSwitchingToEnabled_enablesAudio() throws Exception {
+ withAudioInitiallyDisabled();
+ ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
+ verify(mMockCarAudioService, never()).enableAudio();
+
+ changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
+
+ verify(mMockCarAudioService).enableAudio();
+ }
+
+ @Test
+ public void onPolicyChange_withPowerRemainingEnabled_doesNothing() throws Exception {
+ withAudioInitiallyEnabled();
+ ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
+ verify(mMockCarAudioService).enableAudio();
+
+ changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
+
+ verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService, never()).disableAudio();
+ }
+
+ @Test
+ public void onPolicyChange_withPowerSwitchingToDisabled_disablesAudio() throws Exception {
+ withAudioInitiallyEnabled();
+ ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
+ verify(mMockCarAudioService, never()).disableAudio();
+
+ changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
+
+ verify(mMockCarAudioService).disableAudio();
+ }
+
+ @Test
+ public void onPolicyChange_withPowerStayingDisabled_doesNothing() throws Exception {
+ withAudioInitiallyDisabled();
+ ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
+ verify(mMockCarAudioService).disableAudio();
+
+ changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
+
+ verify(mMockCarAudioService).disableAudio();
+ verify(mMockCarAudioService, never()).enableAudio();
+ }
+
+ private void withAudioInitiallyEnabled() {
+ when(mMockCarPowerService.getCurrentPowerPolicy()).thenReturn(ENABLED_POLICY);
+ }
+
+ private void withAudioInitiallyDisabled() {
+ when(mMockCarPowerService.getCurrentPowerPolicy()).thenReturn(DISABLED_POLICY);
+ }
+
+ private ICarPowerPolicyListener registerAndGetChangeListener() {
+ CarAudioPowerListener listener = new CarAudioPowerListener(mMockCarAudioService,
+ mMockCarPowerService);
+ listener.startListeningForPolicyChanges();
+ ArgumentCaptor<ICarPowerPolicyListener> captor = ArgumentCaptor.forClass(
+ ICarPowerPolicyListener.class);
+ verify(mMockCarPowerService).addPowerPolicyListener(
+ any(), captor.capture());
+
+ return captor.getValue();
+ }
+}