Merge "AudioPolicy: add test for dynamic audio policy client death"
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. */
|
||||
@@ -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);
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
"include-filter": "android.media.audio.cts.SpatializerTest"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AudioPolicyDeathTest",
|
||||
"options": [
|
||||
{
|
||||
"include-annotation": "android.platform.test.annotations.Presubmit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user