Merge "AudioPolicy: add test for dynamic audio policy client death"

This commit is contained in:
Eric Laurent
2022-12-08 09:13:45 +00:00
committed by Android (Google) Code Review
6 changed files with 285 additions and 4 deletions

View File

@@ -24,13 +24,22 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:label="@string/app_name" android:name="AudioPolicyTestActivity"
<activity android:label="@string/app_name" android:name="AudioVolumeTestActivity"
android:screenOrientation="landscape" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:label="@string/app_name" android:name="AudioPolicyDeathTestActivity"
android:screenOrientation="landscape"
android:process=":AudioPolicyDeathTestActivityProcess"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 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.audiopolicytest;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@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 IntentFilter AUDIO_NOISY_INTENT_FILTER =
new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private class MyBroadcastReceiver extends BroadcastReceiver {
private boolean mReceived = false;
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
synchronized (this) {
mReceived = true;
notify();
}
}
}
public synchronized boolean received() {
return mReceived;
}
}
private final MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
private Context mContext;
@Before
public void setUp() {
mContext = getApplicationContext();
assertEquals(PackageManager.PERMISSION_GRANTED,
mContext.checkSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING));
}
//-----------------------------------------------------------------
// Tests that an AUDIO_BECOMING_NOISY intent is broadcast when an app having registered
// a dynamic audio policy that intercepts an active media playback dies
//-----------------------------------------------------------------
@Test
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);
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");
}
elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
}
}
track.stop();
track.release();
assertTrue(mReceiver.received());
}
private AudioTrack createAudioTrack() {
AudioFormat format = new AudioFormat.Builder()
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(SAMPLE_RATE)
.build();
short[] data = new short[PLAYBACK_TIME_MS * SAMPLE_RATE * format.getChannelCount() / 1000];
AudioAttributes attributes =
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
AudioTrack track = new AudioTrack(attributes, format, data.length,
AudioTrack.MODE_STATIC, AudioManager.AUDIO_SESSION_ID_GENERATE);
track.write(data, 0, data.length);
return track;
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 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.audiopolicytest;
import android.app.Activity;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
// This activity will register a dynamic audio policy to intercept media playback and launch
// a thread that will capture audio from the policy mix and crash after the time indicated by
// intent extra "captureDurationMs" has elapsed
public class AudioPolicyDeathTestActivity extends Activity {
private static final String TAG = "AudioPolicyDeathTestActivity";
private static final int SAMPLE_RATE = 48000;
private static final int RECORD_TIME_MS = 1000;
private AudioManager mAudioManager = null;
private AudioPolicy mAudioPolicy = null;
public AudioPolicyDeathTestActivity() {
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA).build();
AudioMixingRule.Builder audioMixingRuleBuilder = new AudioMixingRule.Builder()
.addRule(attributes, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
AudioFormat audioFormat = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.setSampleRate(SAMPLE_RATE)
.build();
AudioMix audioMix = new AudioMix.Builder(audioMixingRuleBuilder.build())
.setFormat(audioFormat)
.setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
.build();
AudioPolicy.Builder audioPolicyBuilder = new AudioPolicy.Builder(getApplicationContext());
audioPolicyBuilder.addMix(audioMix)
.setLooper(Looper.getMainLooper());
mAudioPolicy = audioPolicyBuilder.build();
int result = mAudioManager.registerAudioPolicy(mAudioPolicy);
if (result != AudioManager.SUCCESS) {
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();
}
@Override
public void onDestroy() {
super.onDestroy();
if (mAudioManager != null && mAudioPolicy != null) {
mAudioManager.unregisterAudioPolicy(mAudioPolicy);
}
}
// A thread that captures audio from the supplied AudioRecord and crashes after the supplied
// duration has elapsed
private static class AudioCapturingThread extends Thread {
private final AudioRecord mAudioRecord;
private final int mDurationMs;
AudioCapturingThread(AudioRecord record, int durationMs) {
super();
mAudioRecord = record;
mDurationMs = durationMs;
}
@Override
@SuppressWarnings("ConstantOverflow")
public void run() {
int samplesLeft = mDurationMs * SAMPLE_RATE * mAudioRecord.getChannelCount() / 1000;
short[] readBuffer = new short[samplesLeft / 10];
mAudioRecord.startRecording();
long startTimeMs = System.currentTimeMillis();
long elapsedTimeMs = 0;
do {
int read = readBuffer.length < samplesLeft ? readBuffer.length : samplesLeft;
read = mAudioRecord.read(readBuffer, 0, read);
elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
if (read < 0) {
Log.w(TAG, "read error: " + read);
break;
}
samplesLeft -= read;
} while (elapsedTimeMs < mDurationMs && samplesLeft > 0);
// force process to crash
int i = 1 / 0;
}
}
}

View File

@@ -19,9 +19,9 @@ package com.android.audiopolicytest;
import android.app.Activity;
import android.os.Bundle;
public class AudioPolicyTestActivity extends Activity {
public class AudioVolumeTestActivity extends Activity {
public AudioPolicyTestActivity() {
public AudioVolumeTestActivity() {
}
/** Called when the activity is first created. */

View File

@@ -100,7 +100,7 @@ final class AudioVolumesTestRule extends ExternalResource {
@Before
public void setUp() throws Exception {
ActivityScenario.launch(AudioPolicyTestActivity.class);
ActivityScenario.launch(AudioVolumeTestActivity.class);
mContext = getApplicationContext();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

View File

@@ -13,6 +13,14 @@
"include-filter": "android.media.audio.cts.SpatializerTest"
}
]
},
{
"name": "AudioPolicyDeathTest",
"options": [
{
"include-annotation": "android.platform.test.annotations.Presubmit"
}
]
}
]
}