diff options
author | TreeHugger Robot <treehugger-gerrit@google.com> | 2020-04-15 01:49:04 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-04-15 01:49:04 +0000 |
commit | f34492a57fbe61ee0dee75c66a9a176dd9272b66 (patch) | |
tree | 6aa37f48e1794b26dcdd92e08e861db1733657e3 | |
parent | 136c1428949b2c60619ba6454845e3d8246920dc (diff) | |
parent | 25b0e6bbe92318d609b0cb4fad78d3e68f79f93b (diff) |
Merge "AudioService: test mic muting API behavior with errors" into rvc-dev
6 files changed, 313 insertions, 17 deletions
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 387a2be41d3c..f840f2d359d5 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -185,6 +185,9 @@ public class AudioService extends IAudioService.Stub private static final String TAG = "AS.AudioService"; + private final AudioSystemAdapter mAudioSystem; + private final SystemServerAdapter mSystemServer; + /** Debug audio mode */ protected static final boolean DEBUG_MODE = false; @@ -649,10 +652,19 @@ public class AudioService extends IAudioService.Stub /** @hide */ public AudioService(Context context) { + this(context, AudioSystemAdapter.getDefaultAdapter(), + SystemServerAdapter.getDefaultAdapter(context)); + } + + public AudioService(Context context, AudioSystemAdapter audioSystem, + SystemServerAdapter systemServer) { mContext = context; mContentResolver = context.getContentResolver(); mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + mAudioSystem = audioSystem; + mSystemServer = systemServer; + mPlatformType = AudioSystem.getPlatformType(context); mIsSingleVolume = AudioSystem.isSingleVolume(context); @@ -842,11 +854,13 @@ public class AudioService extends IAudioService.Stub context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); - LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); + if (mSystemServer.isPrivileged()) { + LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); - mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); + mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); - mRecordMonitor.initMonitor(); + mRecordMonitor.initMonitor(); + } final float[] preScale = new float[3]; preScale[0] = mContext.getResources().getFraction( @@ -935,7 +949,7 @@ public class AudioService extends IAudioService.Stub onIndicateSystemReady(); - mMicMuteFromSystemCached = AudioSystem.isMicrophoneMuted(); + mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted(); setMicMuteFromSwitchInput(); } @@ -1636,12 +1650,15 @@ public class AudioService extends IAudioService.Stub } if (currentImeUid != mCurrentImeUid || forceUpdate) { - AudioSystem.setCurrentImeUid(currentImeUid); + mAudioSystem.setCurrentImeUid(currentImeUid); mCurrentImeUid = currentImeUid; } } private void readPersistedSettings() { + if (!mSystemServer.isPrivileged()) { + return; + } final ContentResolver cr = mContentResolver; int ringerModeFromSettings = @@ -1712,6 +1729,9 @@ public class AudioService extends IAudioService.Stub } private void readUserRestrictions() { + if (!mSystemServer.isPrivileged()) { + return; + } final int currentUser = getCurrentUserId(); // Check the current user restriction. @@ -2782,6 +2802,9 @@ public class AudioService extends IAudioService.Stub } private void sendBroadcastToAll(Intent intent) { + if (!mSystemServer.isPrivileged()) { + return; + } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final long ident = Binder.clearCallingIdentity(); @@ -3174,12 +3197,12 @@ public class AudioService extends IAudioService.Stub } // only mute for the current user if (getCurrentUserId() == userId || userId == android.os.Process.SYSTEM_UID) { - final boolean currentMute = AudioSystem.isMicrophoneMuted(); + final boolean currentMute = mAudioSystem.isMicrophoneMuted(); final long identity = Binder.clearCallingIdentity(); - final int ret = AudioSystem.muteMicrophone(muted); + final int ret = mAudioSystem.muteMicrophone(muted); // update cache with the real state independently from what was set - mMicMuteFromSystemCached = AudioSystem.isMicrophoneMuted(); + mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted(); if (ret != AudioSystem.AUDIO_STATUS_OK) { Log.e(TAG, "Error changing mic mute state to " + muted + " current:" + mMicMuteFromSystemCached); @@ -4518,6 +4541,9 @@ public class AudioService extends IAudioService.Stub } private void broadcastRingerMode(String action, int ringerMode) { + if (!mSystemServer.isPrivileged()) { + return; + } // Send sticky broadcast Intent broadcast = new Intent(action); broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, ringerMode); @@ -4527,6 +4553,9 @@ public class AudioService extends IAudioService.Stub } private void broadcastVibrateSetting(int vibrateType) { + if (!mSystemServer.isPrivileged()) { + return; + } // Send broadcast if (mActivityManagerInternal.isSystemReady()) { Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); @@ -5258,6 +5287,9 @@ public class AudioService extends IAudioService.Stub } public int observeDevicesForStream_syncVSS(boolean checkOthers) { + if (!mSystemServer.isPrivileged()) { + return AudioSystem.DEVICE_NONE; + } final int devices = AudioSystem.getDevicesForStream(mStreamType); if (devices == mObservedDevices) { return devices; @@ -5998,10 +6030,7 @@ public class AudioService extends IAudioService.Stub break; case MSG_BROADCAST_MICROPHONE_MUTE: - mContext.sendBroadcastAsUser( - new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) - .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), - UserHandle.ALL); + mSystemServer.sendMicrophoneMuteChangedIntent(); break; } } diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 9f8f9f8fddde..40c13904fbc9 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -40,10 +40,11 @@ public class AudioSystemAdapter { /** * Create an adapter for AudioSystem that always succeeds, and does nothing. - * @return a no-op AudioSystem adapter + * Overridden methods can be configured + * @return a no-op AudioSystem adapter with configurable adapter */ - static final @NonNull AudioSystemAdapter getAlwaysOkAdapter() { - return new AudioSystemOkAdapter(); + static final @NonNull AudioSystemAdapter getConfigurableAdapter() { + return new AudioSystemConfigurableAdapter(); } /** @@ -113,10 +114,51 @@ public class AudioSystemAdapter { return AudioSystem.setParameters(keyValuePairs); } + /** + * Same as {@link AudioSystem#isMicrophoneMuted()}} + * Checks whether the microphone mute is on or off. + * @return true if microphone is muted, false if it's not + */ + public boolean isMicrophoneMuted() { + return AudioSystem.isMicrophoneMuted(); + } + + /** + * Same as {@link AudioSystem#muteMicrophone(boolean)} + * Sets the microphone mute on or off. + * + * @param on set <var>true</var> to mute the microphone; + * <var>false</var> to turn mute off + * @return command completion status see AUDIO_STATUS_OK, see AUDIO_STATUS_ERROR + */ + public int muteMicrophone(boolean on) { + return AudioSystem.muteMicrophone(on); + } + + /** + * Same as {@link AudioSystem#setCurrentImeUid(int)} + * Communicate UID of current InputMethodService to audio policy service. + */ + public int setCurrentImeUid(int uid) { + return AudioSystem.setCurrentImeUid(uid); + } + //-------------------------------------------------------------------- - protected static class AudioSystemOkAdapter extends AudioSystemAdapter { + protected static class AudioSystemConfigurableAdapter extends AudioSystemAdapter { private static final String TAG = "ASA"; + private boolean mIsMicMuted = false; + private boolean mMuteMicrophoneFails = false; + + public void configureIsMicrophoneMuted(boolean muted) { + mIsMicMuted = muted; + } + public void configureMuteMicrophoneToFail(boolean fail) { + mMuteMicrophoneFails = fail; + } + + //----------------------------------------------------------------- + // Overrides of AudioSystemAdapter @Override public int setDeviceConnectionState(int device, int state, String deviceAddress, String deviceName, int codecFormat) { @@ -152,5 +194,24 @@ public class AudioSystemAdapter { public int setParameters(String keyValuePairs) { return AudioSystem.AUDIO_STATUS_OK; } + + @Override + public boolean isMicrophoneMuted() { + return mIsMicMuted; + } + + @Override + public int muteMicrophone(boolean on) { + if (mMuteMicrophoneFails) { + return AudioSystem.AUDIO_STATUS_ERROR; + } + mIsMicMuted = on; + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override + public int setCurrentImeUid(int uid) { + return AudioSystem.AUDIO_STATUS_OK; + } } } diff --git a/services/core/java/com/android/server/audio/SystemServerAdapter.java b/services/core/java/com/android/server/audio/SystemServerAdapter.java new file mode 100644 index 000000000000..509f6be76f17 --- /dev/null +++ b/services/core/java/com/android/server/audio/SystemServerAdapter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 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.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.os.UserHandle; + +/** + * Provides an adapter to access functionality reserved to components running in system_server + * Functionality such as sending privileged broadcasts is to be accessed through the default + * adapter, whereas tests can inject a no-op adapter. + */ +public class SystemServerAdapter { + + protected final Context mContext; + + private SystemServerAdapter(@Nullable Context context) { + mContext = context; + } + /** + * Create a wrapper around privileged functionality. + * @return the adapter + */ + static final @NonNull SystemServerAdapter getDefaultAdapter(Context context) { + return new SystemServerAdapter(context); + } + + /** + * Create an adapter that does nothing. + * Use for running non-privileged tests, such as unit tests + * @return a no-op adapter + */ + static final @NonNull SystemServerAdapter getNoOpAdapter() { + return new NoOpSystemServerAdapter(); + } + + /** + * @return true if this is supposed to be run in system_server, false otherwise (e.g. for a + * unit test) + */ + public boolean isPrivileged() { + return true; + } + + /** + * Broadcast ACTION_MICROPHONE_MUTE_CHANGED + */ + public void sendMicrophoneMuteChangedIntent() { + mContext.sendBroadcastAsUser( + new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), + UserHandle.ALL); + } + + //-------------------------------------------------------------------- + protected static class NoOpSystemServerAdapter extends SystemServerAdapter { + + NoOpSystemServerAdapter() { + super(null); + } + + @Override + public boolean isPrivileged() { + return false; + } + + @Override + public void sendMicrophoneMuteChangedIntent() { + // no-op + } + } +} diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 109c1191523b..270a3b50b934 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -78,6 +78,7 @@ <uses-permission android:name="android.permission.DUMP"/> <uses-permission android:name="android.permission.READ_DREAM_STATE"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index 73a191dc78cc..22f8b9c8ae92 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -66,7 +66,7 @@ public class AudioDeviceBrokerTest { mContext = InstrumentationRegistry.getTargetContext(); mMockAudioService = mock(AudioService.class); - mSpyAudioSystem = spy(AudioSystemAdapter.getAlwaysOkAdapter()); + mSpyAudioSystem = spy(AudioSystemAdapter.getConfigurableAdapter()); mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem)); mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory); mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java new file mode 100644 index 000000000000..6185ae6d93f9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 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.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Looper; +import android.os.UserHandle; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +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.Spy; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class AudioServiceTest { + private static final String TAG = "AudioServiceTest"; + + private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100; + + private Context mContext; + private AudioSystemAdapter mAudioSystem; + @Spy private SystemServerAdapter mSpySystemServer; + // the class being unit-tested here + private AudioService mAudioService; + + private static boolean sLooperPrepared = false; + + @Before + public void setUp() throws Exception { + if (!sLooperPrepared) { + Looper.prepare(); + sLooperPrepared = true; + } + mContext = InstrumentationRegistry.getTargetContext(); + mAudioSystem = AudioSystemAdapter.getConfigurableAdapter(); + mSpySystemServer = spy(SystemServerAdapter.getNoOpAdapter()); + mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer); + } + + /** + * Test muting the mic reports the expected value, and the corresponding intent was fired + * @throws Exception + */ + @Test + public void testMuteMicrophone() throws Exception { + Log.i(TAG, "running testMuteMicrophone"); + Assert.assertNotNull(mAudioService); + final AudioSystemAdapter.AudioSystemConfigurableAdapter testAudioSystem = + (AudioSystemAdapter.AudioSystemConfigurableAdapter) mAudioSystem; + testAudioSystem.configureMuteMicrophoneToFail(false); + for (boolean muted : new boolean[] { true, false}) { + testAudioSystem.configureIsMicrophoneMuted(!muted); + mAudioService.setMicrophoneMute(muted, mContext.getOpPackageName(), + UserHandle.getCallingUserId()); + Assert.assertEquals("mic mute reporting wrong value", + muted, mAudioService.isMicrophoneMuted()); + // verify the intent for mic mute changed is supposed to be fired + Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); + verify(mSpySystemServer, times(1)) + .sendMicrophoneMuteChangedIntent(); + reset(mSpySystemServer); + } + } + + /** + * Test muting the mic with simulated failure reports the expected value, and the corresponding + * intent was fired + * @throws Exception + */ + @Test + public void testMuteMicrophoneWhenFail() throws Exception { + Log.i(TAG, "running testMuteMicrophoneWhenFail"); + Assert.assertNotNull(mAudioService); + final AudioSystemAdapter.AudioSystemConfigurableAdapter testAudioSystem = + (AudioSystemAdapter.AudioSystemConfigurableAdapter) mAudioSystem; + testAudioSystem.configureMuteMicrophoneToFail(true); + for (boolean muted : new boolean[] { true, false}) { + testAudioSystem.configureIsMicrophoneMuted(!muted); + mAudioService.setMicrophoneMute(muted, mContext.getOpPackageName(), + UserHandle.getCallingUserId()); + Assert.assertEquals("mic mute reporting wrong value", + !muted, mAudioService.isMicrophoneMuted()); + // verify the intent for mic mute changed is supposed to be fired + Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); + verify(mSpySystemServer, times(1)) + .sendMicrophoneMuteChangedIntent(); + reset(mSpySystemServer); + } + } +} |