Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 46ad01db authored by Eric Laurent's avatar Eric Laurent
Browse files

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
parent 686f2652
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -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"
+132 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+132 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -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. */
+1 −1
Original line number Diff line number Diff line
@@ -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);
Loading