1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.audio; 18 19 import android.media.AudioAttributes; 20 import android.media.AudioDeviceCallback; 21 import android.media.AudioDeviceInfo; 22 import android.media.AudioManager; 23 import android.media.AudioMixerAttributes; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.widget.TextView; 28 29 import com.android.cts.verifier.PassFailButtons; 30 import com.android.cts.verifier.R; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.concurrent.CountDownLatch; 35 import java.util.concurrent.Executors; 36 import java.util.concurrent.TimeUnit; 37 import java.util.concurrent.atomic.AtomicBoolean; 38 39 /** 40 * PreferredMixerAttributesTestActivity is used to test if the Android device supports setting 41 * preferred mixer attributes for USB devices. 42 */ 43 public class PreferredMixerAttributesTestActivity extends PassFailButtons.Activity { 44 private static final String TAG = "PreferredMixerAttributesTestActivity"; 45 46 private static final long TIMEOUT_MS = 1000; 47 48 private AudioManager mAudioManager; 49 private List<AudioDeviceInfo> mUsbDevices = new ArrayList<>(); 50 51 private HandlerThread mPreferredMixerAttrTestThread; 52 private Handler mPreferredMixerAttrTestHandler; 53 54 private TextView mUsbStatusTextView; 55 private TextView mTestResultTextView; 56 private TextView mFailureMsgTextView; 57 58 private TestAudioDeviceCallback mAudioDeviceCallback; 59 60 @Override onCreate(Bundle savedInstanceState)61 protected void onCreate(Bundle savedInstanceState) { 62 super.onCreate(savedInstanceState); 63 setContentView(R.layout.audio_preferred_mixer_attributes); 64 65 mAudioManager = getSystemService(AudioManager.class); 66 67 mUsbStatusTextView = (TextView) findViewById(R.id.usbDeviceConnectionStatus); 68 mTestResultTextView = (TextView) findViewById(R.id.preferredMixerAttributesTestStatus); 69 mFailureMsgTextView = (TextView) findViewById(R.id.preferredMixerAttributesTestFailure); 70 71 setInfoResources(R.string.audio_preferred_mixer_attributes_test, 72 R.string.audio_preferred_mixer_attributes_test_info, -1); 73 setPassFailButtonClickListeners(); 74 75 mAudioDeviceCallback = new TestAudioDeviceCallback(); 76 } 77 78 @Override onResume()79 public void onResume() { 80 super.onResume(); 81 82 startBackgroundThread(); 83 mAudioManager.registerAudioDeviceCallback( 84 mAudioDeviceCallback, mPreferredMixerAttrTestHandler); 85 detectUsbDeviceConnectionAndRunTest(); 86 } 87 88 @Override onPause()89 public void onPause() { 90 mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); 91 stopBackgroundThread(); 92 super.onPause(); 93 } 94 startBackgroundThread()95 private void startBackgroundThread() { 96 mPreferredMixerAttrTestThread = new HandlerThread("PreferredMixerAttrBackground"); 97 mPreferredMixerAttrTestThread.start(); 98 mPreferredMixerAttrTestHandler = new Handler(mPreferredMixerAttrTestThread.getLooper()); 99 } 100 stopBackgroundThread()101 private void stopBackgroundThread() { 102 mPreferredMixerAttrTestThread.quitSafely(); 103 try { 104 mPreferredMixerAttrTestThread.join(); 105 mPreferredMixerAttrTestThread = null; 106 mPreferredMixerAttrTestHandler = null; 107 } catch (InterruptedException e) { 108 e.printStackTrace(); 109 } 110 } 111 detectUsbDeviceConnectionAndRunTest()112 private void detectUsbDeviceConnectionAndRunTest() { 113 if (!detectUSBDevice()) { 114 updateUI(R.string.audio_preferred_mixer_attributes_test_connect_usb_device, 115 R.string.empty, 116 R.string.empty, 117 false /*passButtonEnabled*/); 118 return; 119 } 120 updateUI(R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 121 R.string.audio_preferred_mixer_attributes_test_running, 122 R.string.empty, 123 false /*passButtonEnabled*/); 124 mPreferredMixerAttrTestHandler.post(new Runnable() { 125 @Override 126 public void run() { 127 displayTestResult(); 128 } 129 }); 130 } 131 displayTestResult()132 private void displayTestResult() { 133 for (AudioDeviceInfo device : mUsbDevices) { 134 List<AudioMixerAttributes> supportedMixerAttrs = 135 mAudioManager.getSupportedMixerAttributes(device); 136 if (supportedMixerAttrs.isEmpty()) { 137 updateUI(R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 138 R.string.result_failure, 139 R.string.audio_can_not_set_preferred_mixer_attributes, 140 false /*passButtonEnabled*/); 141 return; 142 } 143 AudioAttributes attr = new AudioAttributes.Builder() 144 .setUsage(AudioAttributes.USAGE_MEDIA).build(); 145 MyPreferredMixerAttributesListener listener = 146 new MyPreferredMixerAttributesListener(attr, device.getId()); 147 mAudioManager.addOnPreferredMixerAttributesChangedListener( 148 Executors.newSingleThreadExecutor(), listener); 149 for (AudioMixerAttributes mixerAttr : supportedMixerAttrs) { 150 listener.reset(); 151 if (!mAudioManager.setPreferredMixerAttributes(attr, device, mixerAttr)) { 152 testComplete( 153 R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 154 R.string.result_failure, 155 R.string.audio_set_preferred_mixer_attributes_failed, 156 false /*passButtonEnabled*/, 157 listener); 158 return; 159 } 160 listener.await(TIMEOUT_MS); 161 if (!listener.isPreferredMixerAttributesChanged()) { 162 testComplete( 163 R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 164 R.string.result_failure, 165 R.string.audio_no_callback_for_preferred_mixer_attributes_changed, 166 false /*passButtonEnabled*/, 167 listener); 168 return; 169 } 170 if (!mixerAttr.equals(mAudioManager.getPreferredMixerAttributes(attr, device))) { 171 testComplete( 172 R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 173 R.string.result_failure, 174 R.string.audio_get_preferred_mixer_attributes_not_equal, 175 false /*passButtonEnabled*/, 176 listener); 177 return; 178 } 179 listener.reset(); 180 if (!mAudioManager.clearPreferredMixerAttributes(attr, device)) { 181 testComplete( 182 R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 183 R.string.result_failure, 184 R.string.audio_clear_preferred_mixer_attributes_failed, 185 false /*passButtonEnabled*/, 186 listener); 187 return; 188 } 189 listener.await(TIMEOUT_MS); 190 if (!listener.isPreferredMixerAttributesChanged()) { 191 testComplete( 192 R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 193 R.string.result_failure, 194 R.string.audio_no_callback_for_preferred_mixer_attributes_changed, 195 false /*passButtonEnabled*/, 196 listener); 197 return; 198 } 199 if (mAudioManager.getPreferredMixerAttributes(attr, device) != null) { 200 testComplete( 201 R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 202 R.string.result_failure, 203 R.string.audio_get_preferred_mixer_attributes_should_be_null, 204 false /*passButtonEnabled*/, 205 listener); 206 return; 207 } 208 } 209 mAudioManager.removeOnPreferredMixerAttributesChangedListener(listener); 210 } 211 testComplete(R.string.audio_preferred_mixer_attributes_test_usb_device_connected, 212 R.string.result_success, 213 R.string.empty, 214 true /*passButtonEnabled*/, 215 null /*listener*/); 216 } 217 testComplete(int usbStatusResId, int testStatusResId, int failureMsgResId, boolean passButtonEnabled, MyPreferredMixerAttributesListener listener)218 private void testComplete(int usbStatusResId, int testStatusResId, int failureMsgResId, 219 boolean passButtonEnabled, MyPreferredMixerAttributesListener listener) { 220 if (listener != null) { 221 mAudioManager.removeOnPreferredMixerAttributesChangedListener(listener); 222 } 223 updateUI(usbStatusResId, testStatusResId, failureMsgResId, passButtonEnabled); 224 } 225 updateUI(int usbStatusResId, int testStatusResId, int failureMsgResId, boolean passButtonEnabled)226 private void updateUI(int usbStatusResId, int testStatusResId, int failureMsgResId, 227 boolean passButtonEnabled) { 228 runOnUiThread(new Runnable() { 229 @Override 230 public void run() { 231 mUsbStatusTextView.setText(usbStatusResId); 232 mTestResultTextView.setText(testStatusResId); 233 mFailureMsgTextView.setText(failureMsgResId); 234 getPassButton().setEnabled(passButtonEnabled); 235 } 236 }); 237 } 238 detectUSBDevice()239 private boolean detectUSBDevice() { 240 mUsbDevices.clear(); 241 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL); 242 for (AudioDeviceInfo deviceInfo : deviceInfos) { 243 if (deviceInfo.isSink() && (deviceInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE 244 || deviceInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET)) { 245 mUsbDevices.add(deviceInfo); 246 } 247 } 248 return !mUsbDevices.isEmpty(); 249 } 250 251 private class TestAudioDeviceCallback extends AudioDeviceCallback { onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)252 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 253 detectUsbDeviceConnectionAndRunTest(); 254 } 255 onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)256 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 257 detectUsbDeviceConnectionAndRunTest(); 258 } 259 } 260 261 private final class MyPreferredMixerAttributesListener 262 implements AudioManager.OnPreferredMixerAttributesChangedListener { 263 private final AudioAttributes mAttr; 264 private final int mDeviceId; 265 266 private CountDownLatch mCountDownLatch; 267 private AtomicBoolean mIsCalled = new AtomicBoolean(false); 268 MyPreferredMixerAttributesListener(AudioAttributes attr, int deviceId)269 MyPreferredMixerAttributesListener(AudioAttributes attr, int deviceId) { 270 mAttr = attr; 271 mDeviceId = deviceId; 272 reset(); 273 } 274 275 @Override onPreferredMixerAttributesChanged(AudioAttributes attributes, AudioDeviceInfo device, AudioMixerAttributes mixerAttributes)276 public void onPreferredMixerAttributesChanged(AudioAttributes attributes, 277 AudioDeviceInfo device, AudioMixerAttributes mixerAttributes) { 278 if (device.getId() == mDeviceId && mAttr.equals(attributes)) { 279 mIsCalled.set(true); 280 } 281 mCountDownLatch.countDown(); 282 } 283 reset()284 public void reset() { 285 mIsCalled.set(false); 286 mCountDownLatch = new CountDownLatch(1); 287 } 288 await(long timeoutMs)289 void await(long timeoutMs) { 290 try { 291 mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS); 292 } catch (InterruptedException e) { 293 } 294 } 295 isPreferredMixerAttributesChanged()296 public boolean isPreferredMixerAttributesChanged() { 297 return mIsCalled.get(); 298 } 299 } 300 } 301