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.audiopolicytest; 18 19 import static androidx.test.core.app.ApplicationProvider.getApplicationContext; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assume.assumeNoException; 24 import static org.junit.Assume.assumeTrue; 25 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.PackageManager; 31 import android.media.AudioAttributes; 32 import android.media.AudioFormat; 33 import android.media.AudioManager; 34 import android.media.AudioTrack; 35 import android.os.Bundle; 36 import android.os.RemoteCallback; 37 import android.platform.test.annotations.Presubmit; 38 import android.util.Log; 39 40 import androidx.test.ext.junit.runners.AndroidJUnit4; 41 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 46 import java.util.concurrent.CompletableFuture; 47 import java.util.concurrent.CountDownLatch; 48 import java.util.concurrent.ExecutionException; 49 import java.util.concurrent.TimeUnit; 50 import java.util.concurrent.TimeoutException; 51 52 @Presubmit 53 @RunWith(AndroidJUnit4.class) 54 public class AudioPolicyDeathTest { 55 private static final String TAG = "AudioPolicyDeathTest"; 56 57 private static final int SAMPLE_RATE = 48000; 58 private static final int PLAYBACK_TIME_MS = 4000; 59 private static final int RECORD_TIME_MS = 1000; 60 private static final int ACTIVITY_TIMEOUT_SEC = 5; 61 private static final int BROADCAST_TIMEOUT_SEC = 10; 62 private static final int MAX_ATTEMPTS = 5; 63 private static final int DELAY_BETWEEN_ATTEMPTS_MS = 2000; 64 65 private static final IntentFilter AUDIO_NOISY_INTENT_FILTER = 66 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); 67 68 private class MyBroadcastReceiver extends BroadcastReceiver { 69 private CountDownLatch mLatch = new CountDownLatch(1); 70 71 @Override onReceive(Context context, Intent intent)72 public void onReceive(Context context, Intent intent) { 73 if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) { 74 mLatch.countDown(); 75 } 76 } 77 reset()78 public void reset() { 79 mLatch = new CountDownLatch(1); 80 } 81 waitForBroadcast()82 public boolean waitForBroadcast() { 83 boolean received = false; 84 long startTimeMs = System.currentTimeMillis(); 85 long elapsedTimeMs = 0; 86 87 Log.i(TAG, "waiting for broadcast"); 88 89 while (elapsedTimeMs < BROADCAST_TIMEOUT_SEC && !received) { 90 try { 91 received = mLatch.await(BROADCAST_TIMEOUT_SEC, TimeUnit.SECONDS); 92 } catch (InterruptedException e) { 93 Log.w(TAG, "wait interrupted"); 94 } 95 elapsedTimeMs = System.currentTimeMillis() - startTimeMs; 96 } 97 Log.i(TAG, "broadcast " + (received ? "" : "NOT ") + "received"); 98 return received; 99 } 100 } 101 102 private final MyBroadcastReceiver mReceiver = new MyBroadcastReceiver(); 103 104 private Context mContext; 105 106 @Before setUp()107 public void setUp() { 108 mContext = getApplicationContext(); 109 assertEquals(PackageManager.PERMISSION_GRANTED, 110 mContext.checkSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)); 111 } 112 113 //----------------------------------------------------------------- 114 // Tests that an AUDIO_BECOMING_NOISY intent is broadcast when an app having registered 115 // a dynamic audio policy that intercepts an active media playback dies 116 //----------------------------------------------------------------- 117 @Test testPolicyClientDeathSendBecomingNoisyIntent()118 public void testPolicyClientDeathSendBecomingNoisyIntent() { 119 mContext.registerReceiver(mReceiver, AUDIO_NOISY_INTENT_FILTER); 120 121 boolean result = false; 122 for (int numAttempts = 1; numAttempts <= MAX_ATTEMPTS && !result; numAttempts++) { 123 mReceiver.reset(); 124 125 CompletableFuture<Integer> callbackReturn = new CompletableFuture<>(); 126 RemoteCallback cb = new RemoteCallback((Bundle res) -> { 127 callbackReturn.complete( 128 res.getInt(mContext.getResources().getString(R.string.status_key))); 129 }); 130 131 // Launch process registering a dynamic auido policy and dying after RECORD_TIME_MS ms 132 // RECORD_TIME_MS must be shorter than PLAYBACK_TIME_MS 133 Intent intent = new Intent(mContext, AudioPolicyDeathTestActivity.class); 134 intent.putExtra(mContext.getResources().getString(R.string.capture_duration_key), 135 RECORD_TIME_MS); 136 intent.putExtra(mContext.getResources().getString(R.string.callback_key), cb); 137 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 138 139 mContext.startActivity(intent); 140 141 Integer status = AudioManager.ERROR; 142 try { 143 status = callbackReturn.get(ACTIVITY_TIMEOUT_SEC, TimeUnit.SECONDS); 144 } catch (InterruptedException | ExecutionException | TimeoutException e) { 145 assumeNoException(e); 146 } 147 assumeTrue(status != null && status == AudioManager.SUCCESS); 148 149 Log.i(TAG, "Activity started"); 150 AudioTrack track = null; 151 try { 152 track = createAudioTrack(); 153 track.play(); 154 result = mReceiver.waitForBroadcast(); 155 } finally { 156 if (track != null) { 157 track.stop(); 158 track.release(); 159 } 160 } 161 if (!result) { 162 try { 163 Log.i(TAG, "Retrying after attempt: " + numAttempts); 164 Thread.sleep(DELAY_BETWEEN_ATTEMPTS_MS); 165 } catch (InterruptedException e) { 166 } 167 } 168 } 169 assertTrue(result); 170 } 171 createAudioTrack()172 private AudioTrack createAudioTrack() { 173 AudioFormat format = new AudioFormat.Builder() 174 .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) 175 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 176 .setSampleRate(SAMPLE_RATE) 177 .build(); 178 179 short[] data = new short[PLAYBACK_TIME_MS * SAMPLE_RATE * format.getChannelCount() / 1000]; 180 AudioAttributes attributes = 181 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); 182 183 AudioTrack track = new AudioTrack(attributes, format, data.length, 184 AudioTrack.MODE_STATIC, AudioManager.AUDIO_SESSION_ID_GENERATE); 185 track.write(data, 0, data.length); 186 187 return track; 188 } 189 } 190