AudioPolicyDeathTest: fix flakiness

In some contexts, launching the activity that registers
the dynamic audio policy can take more than 1 second which
causes the test to fail.
Add a callback confirming that the activity is running and
wait for the callback before proceeding with the test.

Bug: 206614157
Test: atest AudioPolicyDeathTest
Change-Id: Iac892e7a679c99973af5a1fe15b7b189299c4b47
This commit is contained in:
Eric Laurent
2023-01-06 19:45:38 +01:00
parent b0f1e19c1a
commit 39adc8aa6d
3 changed files with 110 additions and 39 deletions

View File

@@ -2,4 +2,7 @@
<resources>
<!-- name of the app [CHAR LIMIT=25]-->
<string name="app_name">Audio Policy APIs Tests</string>
<string name="capture_duration_key">captureDurationMs</string>
<string name="callback_key">callback</string>
<string name="status_key">result</string>
</resources>

View File

@@ -20,6 +20,8 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNoException;
import static org.junit.Assume.assumeTrue;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -30,6 +32,8 @@ import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
@@ -39,33 +43,62 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class AudioPolicyDeathTest {
private static final String TAG = "AudioPolicyDeathTest";
private static final int SAMPLE_RATE = 48000;
private static final int PLAYBACK_TIME_MS = 2000;
private static final int PLAYBACK_TIME_MS = 4000;
private static final int RECORD_TIME_MS = 1000;
private static final int ACTIVITY_TIMEOUT_SEC = 5;
private static final int BROADCAST_TIMEOUT_SEC = 10;
private static final int MAX_ATTEMPTS = 5;
private static final int DELAY_BETWEEN_ATTEMPTS_MS = 2000;
private static final IntentFilter AUDIO_NOISY_INTENT_FILTER =
new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private class MyBroadcastReceiver extends BroadcastReceiver {
private boolean mReceived = false;
private CountDownLatch mLatch = new CountDownLatch(1);
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
synchronized (this) {
mReceived = true;
notify();
}
mLatch.countDown();
}
}
public synchronized boolean received() {
return mReceived;
public void reset() {
mLatch = new CountDownLatch(1);
}
public boolean waitForBroadcast() {
boolean received = false;
long startTimeMs = System.currentTimeMillis();
long elapsedTimeMs = 0;
Log.i(TAG, "waiting for broadcast");
while (elapsedTimeMs < BROADCAST_TIMEOUT_SEC && !received) {
try {
received = mLatch.await(BROADCAST_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.w(TAG, "wait interrupted");
}
elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
}
Log.i(TAG, "broadcast " + (received ? "" : "NOT ") + "received");
return received;
}
}
private final MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
private Context mContext;
@@ -85,31 +118,55 @@ public class AudioPolicyDeathTest {
public void testPolicyClientDeathSendBecomingNoisyIntent() {
mContext.registerReceiver(mReceiver, AUDIO_NOISY_INTENT_FILTER);
// Launch process registering a dynamic auido policy and dying after PLAYBACK_TIME_MS/2 ms
Intent intent = new Intent(mContext, AudioPolicyDeathTestActivity.class);
intent.putExtra("captureDurationMs", PLAYBACK_TIME_MS / 2);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
boolean result = false;
for (int numAttempts = 1; numAttempts <= MAX_ATTEMPTS && !result; numAttempts++) {
mReceiver.reset();
AudioTrack track = createAudioTrack();
track.play();
synchronized (mReceiver) {
long startTimeMs = System.currentTimeMillis();
long elapsedTimeMs = 0;
while (elapsedTimeMs < PLAYBACK_TIME_MS && !mReceiver.received()) {
try {
mReceiver.wait(PLAYBACK_TIME_MS - elapsedTimeMs);
} catch (InterruptedException e) {
Log.w(TAG, "wait interrupted");
CompletableFuture<Integer> callbackReturn = new CompletableFuture<>();
RemoteCallback cb = new RemoteCallback((Bundle res) -> {
callbackReturn.complete(
res.getInt(mContext.getResources().getString(R.string.status_key)));
});
// Launch process registering a dynamic auido policy and dying after RECORD_TIME_MS ms
// RECORD_TIME_MS must be shorter than PLAYBACK_TIME_MS
Intent intent = new Intent(mContext, AudioPolicyDeathTestActivity.class);
intent.putExtra(mContext.getResources().getString(R.string.capture_duration_key),
RECORD_TIME_MS);
intent.putExtra(mContext.getResources().getString(R.string.callback_key), cb);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
Integer status = AudioManager.ERROR;
try {
status = callbackReturn.get(ACTIVITY_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
assumeNoException(e);
}
assumeTrue(status != null && status == AudioManager.SUCCESS);
Log.i(TAG, "Activity started");
AudioTrack track = null;
try {
track = createAudioTrack();
track.play();
result = mReceiver.waitForBroadcast();
} finally {
if (track != null) {
track.stop();
track.release();
}
}
if (!result) {
try {
Log.i(TAG, "Retrying after attempt: " + numAttempts);
Thread.sleep(DELAY_BETWEEN_ATTEMPTS_MS);
} catch (InterruptedException e) {
}
elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
}
}
track.stop();
track.release();
assertTrue(mReceiver.received());
assertTrue(result);
}
private AudioTrack createAudioTrack() {

View File

@@ -26,6 +26,7 @@ import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
import android.os.Bundle;
import android.os.Looper;
import android.os.RemoteCallback;
import android.util.Log;
// This activity will register a dynamic audio policy to intercept media playback and launch
@@ -71,19 +72,29 @@ public class AudioPolicyDeathTestActivity extends Activity {
mAudioPolicy = audioPolicyBuilder.build();
int result = mAudioManager.registerAudioPolicy(mAudioPolicy);
if (result != AudioManager.SUCCESS) {
if (result == AudioManager.SUCCESS) {
AudioRecord audioRecord = mAudioPolicy.createAudioRecordSink(audioMix);
if (audioRecord != null && audioRecord.getState() != AudioRecord.STATE_UNINITIALIZED) {
int captureDurationMs = getIntent().getIntExtra(
getString(R.string.capture_duration_key), RECORD_TIME_MS);
AudioCapturingThread thread =
new AudioCapturingThread(audioRecord, captureDurationMs);
thread.start();
} else {
Log.w(TAG, "AudioRecord creation failed");
result = AudioManager.ERROR_NO_INIT;
}
} else {
Log.w(TAG, "registerAudioPolicy failed, status: " + result);
return;
}
AudioRecord audioRecord = mAudioPolicy.createAudioRecordSink(audioMix);
if (audioRecord == null) {
Log.w(TAG, "AudioRecord creation failed");
return;
}
int captureDurationMs = getIntent().getIntExtra("captureDurationMs", RECORD_TIME_MS);
AudioCapturingThread thread = new AudioCapturingThread(audioRecord, captureDurationMs);
thread.start();
RemoteCallback cb =
(RemoteCallback) getIntent().getExtras().get(getString(R.string.callback_key));
Bundle res = new Bundle();
res.putInt(getString(R.string.status_key), result);
Log.i(TAG, "policy " + (result == AudioManager.SUCCESS ? "" : "un")
+ "successfully registered");
cb.sendResult(res);
}
@Override