AudioPolicy: add test for dynamic audio policy client death
Add a test verifying that an AUDIO_BECOMING_NOISY intent is broadcast when an app having registered a dynamic audio policy that intercepts an active media plaiback dies. Bug: 186581483 Test: AudioPolicyDeathTest Change-Id: I1db5087ef97b08bb567c253c117bfdf5215494fb
This commit is contained in:
@@ -24,13 +24,22 @@
|
|||||||
|
|
||||||
<application>
|
<application>
|
||||||
<uses-library android:name="android.test.runner" />
|
<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">
|
android:screenOrientation="landscape" android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</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>
|
</application>
|
||||||
|
|
||||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
<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.app.Activity;
|
||||||
import android.os.Bundle;
|
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. */
|
/** Called when the activity is first created. */
|
||||||
@@ -100,7 +100,7 @@ final class AudioVolumesTestRule extends ExternalResource {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
ActivityScenario.launch(AudioPolicyTestActivity.class);
|
ActivityScenario.launch(AudioVolumeTestActivity.class);
|
||||||
|
|
||||||
mContext = getApplicationContext();
|
mContext = getApplicationContext();
|
||||||
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
|||||||
@@ -13,6 +13,14 @@
|
|||||||
"include-filter": "android.media.audio.cts.SpatializerTest"
|
"include-filter": "android.media.audio.cts.SpatializerTest"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AudioPolicyDeathTest",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"include-annotation": "android.platform.test.annotations.Presubmit"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user