Merge "Add query API for hotword flag support"
This commit is contained in:
@@ -404,7 +404,7 @@ java_defaults {
|
||||
"modules-utils-uieventlogger-interface",
|
||||
"framework-permission-aidl-java",
|
||||
"spatializer-aidl-java",
|
||||
"audiopolicy-types-aidl-java",
|
||||
"audiopolicy-aidl-java",
|
||||
"sounddose-aidl-java",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -6635,6 +6635,7 @@ package android.media {
|
||||
method public boolean isAudioServerRunning();
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled();
|
||||
method public boolean isHdmiSystemAudioSupported();
|
||||
method @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public boolean isHotwordStreamSupported(boolean);
|
||||
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
|
||||
method @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public boolean isUltrasoundSupported();
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
|
||||
|
||||
@@ -7526,6 +7526,27 @@ public class AudioManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Indicates whether the platform supports capturing content from the hotword recognition
|
||||
* pipeline. To capture content of this type, create an AudioRecord with
|
||||
* {@link AudioRecord.Builder.setRequestHotwordStream(boolean, boolean)}.
|
||||
* @param lookbackAudio Query if the hotword stream additionally supports providing buffered
|
||||
* audio prior to stream open.
|
||||
* @return True if the platform supports capturing hotword content, and if lookbackAudio
|
||||
* is true, if it additionally supports capturing buffered hotword content prior to stream
|
||||
* open. False otherwise.
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
|
||||
public boolean isHotwordStreamSupported(boolean lookbackAudio) {
|
||||
try {
|
||||
return getService().isHotwordStreamSupported(lookbackAudio);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Introspection API to retrieve audio product strategies.
|
||||
|
||||
@@ -165,6 +165,9 @@ interface IAudioService {
|
||||
@EnforcePermission("ACCESS_ULTRASOUND")
|
||||
boolean isUltrasoundSupported();
|
||||
|
||||
@EnforcePermission("CAPTURE_AUDIO_HOTWORD")
|
||||
boolean isHotwordStreamSupported(boolean lookbackAudio);
|
||||
|
||||
void setMicrophoneMute(boolean on, String callingPackage, int userId, in String attributionTag);
|
||||
|
||||
oneway void setMicrophoneMuteFromSwitch(boolean on);
|
||||
|
||||
@@ -173,7 +173,6 @@ java_library_static {
|
||||
"android.hardware.power.stats-V1-java",
|
||||
"android.hardware.power-V4-java",
|
||||
"android.hidl.manager-V1.2-java",
|
||||
"capture_state_listener-aidl-java",
|
||||
"icu4j_calendar_astronomer",
|
||||
"netd-client",
|
||||
"overlayable_policy_aidl-java",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 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;
|
||||
|
||||
|
||||
/**
|
||||
* Facade to IAudioPolicyService which fulfills AudioService dependencies.
|
||||
* See @link{IAudioPolicyService.aidl}
|
||||
*/
|
||||
public interface AudioPolicyFacade {
|
||||
|
||||
public boolean isHotwordStreamSupported(boolean lookbackAudio);
|
||||
}
|
||||
@@ -146,6 +146,7 @@ import android.os.HwBinder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PermissionEnforcer;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
@@ -249,6 +250,7 @@ public class AudioService extends IAudioService.Stub
|
||||
private final AudioSystemAdapter mAudioSystem;
|
||||
private final SystemServerAdapter mSystemServer;
|
||||
private final SettingsAdapter mSettings;
|
||||
private final AudioPolicyFacade mAudioPolicy;
|
||||
|
||||
/** Debug audio mode */
|
||||
protected static final boolean DEBUG_MODE = false;
|
||||
@@ -923,7 +925,13 @@ public class AudioService extends IAudioService.Stub
|
||||
|
||||
public Lifecycle(Context context) {
|
||||
super(context);
|
||||
mService = new AudioService(context);
|
||||
mService = new AudioService(context,
|
||||
AudioSystemAdapter.getDefaultAdapter(),
|
||||
SystemServerAdapter.getDefaultAdapter(context),
|
||||
SettingsAdapter.getDefaultAdapter(),
|
||||
new DefaultAudioPolicyFacade(),
|
||||
null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -976,14 +984,6 @@ public class AudioService extends IAudioService.Stub
|
||||
// Construction
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** @hide */
|
||||
public AudioService(Context context) {
|
||||
this(context,
|
||||
AudioSystemAdapter.getDefaultAdapter(),
|
||||
SystemServerAdapter.getDefaultAdapter(context),
|
||||
SettingsAdapter.getDefaultAdapter(),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
@@ -994,9 +994,11 @@ public class AudioService extends IAudioService.Stub
|
||||
* {@link AudioSystemThread} is created as the messaging thread instead.
|
||||
*/
|
||||
public AudioService(Context context, AudioSystemAdapter audioSystem,
|
||||
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper) {
|
||||
this (context, audioSystem, systemServer, settings, looper,
|
||||
context.getSystemService(AppOpsManager.class));
|
||||
SystemServerAdapter systemServer, SettingsAdapter settings,
|
||||
AudioPolicyFacade audioPolicy, @Nullable Looper looper) {
|
||||
this (context, audioSystem, systemServer, settings, audioPolicy, looper,
|
||||
context.getSystemService(AppOpsManager.class),
|
||||
PermissionEnforcer.fromContext(context));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1009,8 +1011,10 @@ public class AudioService extends IAudioService.Stub
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
|
||||
public AudioService(Context context, AudioSystemAdapter audioSystem,
|
||||
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
|
||||
AppOpsManager appOps) {
|
||||
SystemServerAdapter systemServer, SettingsAdapter settings,
|
||||
AudioPolicyFacade audioPolicy, @Nullable Looper looper, AppOpsManager appOps,
|
||||
@NonNull PermissionEnforcer enforcer) {
|
||||
super(enforcer);
|
||||
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
|
||||
mContext = context;
|
||||
mContentResolver = context.getContentResolver();
|
||||
@@ -1019,7 +1023,7 @@ public class AudioService extends IAudioService.Stub
|
||||
mAudioSystem = audioSystem;
|
||||
mSystemServer = systemServer;
|
||||
mSettings = settings;
|
||||
|
||||
mAudioPolicy = audioPolicy;
|
||||
mPlatformType = AudioSystem.getPlatformType(context);
|
||||
|
||||
mIsSingleVolume = AudioSystem.isSingleVolume(context);
|
||||
@@ -3955,6 +3959,20 @@ public class AudioService extends IAudioService.Stub
|
||||
return AudioSystem.isUltrasoundSupported();
|
||||
}
|
||||
|
||||
/** @see AudioManager#isHotwordStreamSupported() */
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
|
||||
public boolean isHotwordStreamSupported(boolean lookbackAudio) {
|
||||
super.isHotwordStreamSupported_enforcePermission();
|
||||
try {
|
||||
return mAudioPolicy.isHotwordStreamSupported(lookbackAudio);
|
||||
} catch (IllegalStateException e) {
|
||||
// Suppress connection failure to APM, since the method is purely informative
|
||||
Log.e(TAG, "Suppressing exception calling into AudioPolicy", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean canChangeAccessibilityVolume() {
|
||||
synchronized (mAccessibilityServiceUidsLock) {
|
||||
if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2022 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 android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.media.IAudioPolicyService;
|
||||
import android.media.permission.ClearCallingIdentityContext;
|
||||
import android.media.permission.SafeCloseable;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
/**
|
||||
* Default implementation of a facade to IAudioPolicyManager which fulfills AudioService
|
||||
* dependencies. This forwards calls as-is to IAudioPolicyManager.
|
||||
* Public methods throw IllegalStateException if AudioPolicy is not initialized/available
|
||||
*/
|
||||
public class DefaultAudioPolicyFacade implements AudioPolicyFacade, IBinder.DeathRecipient {
|
||||
|
||||
private static final String TAG = "DefaultAudioPolicyFacade";
|
||||
private static final String AUDIO_POLICY_SERVICE_NAME = "media.audio_policy";
|
||||
|
||||
private final Object mServiceLock = new Object();
|
||||
@GuardedBy("mServiceLock")
|
||||
private IAudioPolicyService mAudioPolicy;
|
||||
|
||||
public DefaultAudioPolicyFacade() {
|
||||
try {
|
||||
getAudioPolicyOrInit();
|
||||
} catch (IllegalStateException e) {
|
||||
// Log and suppress this exception, we may be able to connect later
|
||||
Log.e(TAG, "Failed to initialize APM connection", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHotwordStreamSupported(boolean lookbackAudio) {
|
||||
IAudioPolicyService ap = getAudioPolicyOrInit();
|
||||
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
|
||||
return ap.isHotwordStreamSupported(lookbackAudio);
|
||||
} catch (RemoteException e) {
|
||||
resetServiceConnection(ap.asBinder());
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Log.wtf(TAG, "Unexpected binderDied without IBinder object");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied(@NonNull IBinder who) {
|
||||
resetServiceConnection(who);
|
||||
}
|
||||
|
||||
private void resetServiceConnection(@Nullable IBinder deadAudioPolicy) {
|
||||
synchronized (mServiceLock) {
|
||||
if (mAudioPolicy != null && mAudioPolicy.asBinder().equals(deadAudioPolicy)) {
|
||||
mAudioPolicy.asBinder().unlinkToDeath(this, 0);
|
||||
mAudioPolicy = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable IAudioPolicyService getAudioPolicy() {
|
||||
synchronized (mServiceLock) {
|
||||
return mAudioPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Does not block.
|
||||
* @throws IllegalStateException for any failed connection
|
||||
*/
|
||||
private @NonNull IAudioPolicyService getAudioPolicyOrInit() {
|
||||
synchronized (mServiceLock) {
|
||||
if (mAudioPolicy != null) {
|
||||
return mAudioPolicy;
|
||||
}
|
||||
// Do not block while attempting to connect to APM. Defer to caller.
|
||||
IAudioPolicyService ap = IAudioPolicyService.Stub.asInterface(
|
||||
ServiceManager.checkService(AUDIO_POLICY_SERVICE_NAME));
|
||||
if (ap == null) {
|
||||
throw new IllegalStateException(TAG + ": Unable to connect to AudioPolicy");
|
||||
}
|
||||
try {
|
||||
ap.asBinder().linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
throw new IllegalStateException(
|
||||
TAG + ": Unable to link deathListener to AudioPolicy", e);
|
||||
}
|
||||
mAudioPolicy = ap;
|
||||
return mAudioPolicy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,8 @@ public class AbsoluteVolumeBehaviorTest {
|
||||
private IAudioDeviceVolumeDispatcher.Stub mMockDispatcher =
|
||||
mock(IAudioDeviceVolumeDispatcher.Stub.class);
|
||||
|
||||
private AudioPolicyFacade mMockAudioPolicy = mock(AudioPolicyFacade.class);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mTestLooper = new TestLooper();
|
||||
@@ -74,7 +76,7 @@ public class AbsoluteVolumeBehaviorTest {
|
||||
mSystemServer = new NoOpSystemServerAdapter();
|
||||
mSettingsAdapter = new NoOpSettingsAdapter();
|
||||
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
|
||||
mSettingsAdapter, mTestLooper.getLooper()) {
|
||||
mSettingsAdapter, mMockAudioPolicy, mTestLooper.getLooper()) {
|
||||
@Override
|
||||
public int getDeviceForStream(int stream) {
|
||||
return AudioSystem.DEVICE_OUT_SPEAKER;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.server.audio;
|
||||
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -45,6 +46,7 @@ public class AudioDeviceVolumeManagerTest {
|
||||
private SystemServerAdapter mSystemServer;
|
||||
private SettingsAdapter mSettingsAdapter;
|
||||
private TestLooper mTestLooper;
|
||||
private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class);
|
||||
|
||||
private AudioService mAudioService;
|
||||
|
||||
@@ -59,7 +61,7 @@ public class AudioDeviceVolumeManagerTest {
|
||||
mSystemServer = new NoOpSystemServerAdapter();
|
||||
mSettingsAdapter = new NoOpSettingsAdapter();
|
||||
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
|
||||
mSettingsAdapter, mTestLooper.getLooper()) {
|
||||
mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper()) {
|
||||
@Override
|
||||
public int getDeviceForStream(int stream) {
|
||||
return AudioSystem.DEVICE_OUT_SPEAKER;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.server.audio;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -28,6 +29,7 @@ import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.media.AudioSystem;
|
||||
import android.os.Looper;
|
||||
import android.os.PermissionEnforcer;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -37,8 +39,11 @@ import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
|
||||
@@ -49,11 +54,18 @@ public class AudioServiceTest {
|
||||
|
||||
private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100;
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
private Context mContext;
|
||||
private AudioSystemAdapter mAudioSystem;
|
||||
@Spy private SystemServerAdapter mSpySystemServer;
|
||||
private SettingsAdapter mSettingsAdapter;
|
||||
|
||||
@Spy private NoOpSystemServerAdapter mSpySystemServer;
|
||||
@Mock private AppOpsManager mMockAppOpsManager;
|
||||
@Mock private AudioPolicyFacade mMockAudioPolicy;
|
||||
@Mock private PermissionEnforcer mMockPermissionEnforcer;
|
||||
|
||||
// the class being unit-tested here
|
||||
private AudioService mAudioService;
|
||||
|
||||
@@ -67,13 +79,12 @@ public class AudioServiceTest {
|
||||
}
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
mAudioSystem = new NoOpAudioSystemAdapter();
|
||||
mSpySystemServer = spy(new NoOpSystemServerAdapter());
|
||||
mSettingsAdapter = new NoOpSettingsAdapter();
|
||||
mMockAppOpsManager = mock(AppOpsManager.class);
|
||||
when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(AppOpsManager.MODE_ALLOWED);
|
||||
mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer,
|
||||
mSettingsAdapter, null, mMockAppOpsManager);
|
||||
mSettingsAdapter, mMockAudioPolicy, null, mMockAppOpsManager,
|
||||
mMockPermissionEnforcer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,4 +164,15 @@ public class AudioServiceTest {
|
||||
Assert.assertEquals(ringMaxVol, mAudioService.getStreamVolume(
|
||||
AudioSystem.STREAM_NOTIFICATION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudioPolicyException() throws Exception {
|
||||
Log.i(TAG, "running testAudioPolicyException");
|
||||
Assert.assertNotNull(mAudioService);
|
||||
// Ensure that AudioPolicy inavailability doesn't bring down SystemServer
|
||||
when(mMockAudioPolicy.isHotwordStreamSupported(anyBoolean())).thenThrow(
|
||||
new IllegalStateException(), new IllegalStateException());
|
||||
Assert.assertEquals(false, mAudioService.isHotwordStreamSupported(false));
|
||||
Assert.assertEquals(false, mAudioService.isHotwordStreamSupported(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.audio;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
@@ -47,6 +48,7 @@ public class DeviceVolumeBehaviorTest {
|
||||
private SystemServerAdapter mSystemServer;
|
||||
private SettingsAdapter mSettingsAdapter;
|
||||
private TestLooper mTestLooper;
|
||||
private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class);
|
||||
|
||||
private AudioService mAudioService;
|
||||
|
||||
@@ -67,7 +69,7 @@ public class DeviceVolumeBehaviorTest {
|
||||
mSystemServer = new NoOpSystemServerAdapter();
|
||||
mSettingsAdapter = new NoOpSettingsAdapter();
|
||||
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
|
||||
mSettingsAdapter, mTestLooper.getLooper());
|
||||
mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper());
|
||||
mTestLooper.dispatchAll();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user