1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.audiopolicytest;
18 
19 import android.app.Activity;
20 import android.media.AudioAttributes;
21 import android.media.AudioFormat;
22 import android.media.AudioManager;
23 import android.media.AudioRecord;
24 import android.media.audiopolicy.AudioMix;
25 import android.media.audiopolicy.AudioMixingRule;
26 import android.media.audiopolicy.AudioPolicy;
27 import android.os.Bundle;
28 import android.os.Looper;
29 import android.os.RemoteCallback;
30 import android.util.Log;
31 
32 // This activity will register a dynamic audio policy to intercept media playback and launch
33 // a thread that will capture audio from the policy mix and crash after the time indicated by
34 // intent extra "captureDurationMs" has elapsed
35 public class AudioPolicyDeathTestActivity extends Activity  {
36     private static final String TAG = "AudioPolicyDeathTestActivity";
37 
38     private static final int SAMPLE_RATE = 48000;
39     private static final int RECORD_TIME_MS = 1000;
40 
41     private AudioManager mAudioManager = null;
42     private AudioPolicy mAudioPolicy = null;
43 
AudioPolicyDeathTestActivity()44     public AudioPolicyDeathTestActivity() {
45     }
46 
47     @Override
onCreate(Bundle icicle)48     public void onCreate(Bundle icicle) {
49         super.onCreate(icicle);
50 
51         mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
52 
53         AudioAttributes attributes = new AudioAttributes.Builder()
54                 .setUsage(AudioAttributes.USAGE_MEDIA).build();
55         AudioMixingRule.Builder audioMixingRuleBuilder = new AudioMixingRule.Builder()
56                 .addRule(attributes, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
57 
58         AudioFormat audioFormat = new AudioFormat.Builder()
59                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
60                 .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
61                 .setSampleRate(SAMPLE_RATE)
62                 .build();
63 
64         AudioMix audioMix = new AudioMix.Builder(audioMixingRuleBuilder.build())
65                 .setFormat(audioFormat)
66                 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
67                 .build();
68 
69         AudioPolicy.Builder audioPolicyBuilder = new AudioPolicy.Builder(getApplicationContext());
70         audioPolicyBuilder.addMix(audioMix)
71                 .setLooper(Looper.getMainLooper());
72         mAudioPolicy = audioPolicyBuilder.build();
73 
74         int result = mAudioManager.registerAudioPolicy(mAudioPolicy);
75         if (result == AudioManager.SUCCESS) {
76             AudioRecord audioRecord = mAudioPolicy.createAudioRecordSink(audioMix);
77             if (audioRecord != null && audioRecord.getState() != AudioRecord.STATE_UNINITIALIZED) {
78                 int captureDurationMs = getIntent().getIntExtra(
79                                 getString(R.string.capture_duration_key), RECORD_TIME_MS);
80                 AudioCapturingThread thread =
81                         new AudioCapturingThread(audioRecord, captureDurationMs);
82                 thread.start();
83             } else {
84                 Log.w(TAG, "AudioRecord creation failed");
85                 result = AudioManager.ERROR_NO_INIT;
86             }
87         } else {
88             Log.w(TAG, "registerAudioPolicy failed, status: " + result);
89         }
90 
91         RemoteCallback cb =
92                 (RemoteCallback) getIntent().getExtras().get(getString(R.string.callback_key));
93         Bundle res = new Bundle();
94         res.putInt(getString(R.string.status_key), result);
95         Log.i(TAG, "policy " + (result ==  AudioManager.SUCCESS ? "" : "un")
96                 + "successfully registered");
97         cb.sendResult(res);
98     }
99 
100     @Override
onDestroy()101     public void onDestroy() {
102         super.onDestroy();
103         if (mAudioManager != null && mAudioPolicy != null) {
104             mAudioManager.unregisterAudioPolicy(mAudioPolicy);
105         }
106     }
107 
108     // A thread that captures audio from the supplied AudioRecord and crashes after the supplied
109     // duration has elapsed
110     private static class AudioCapturingThread extends Thread {
111         private final AudioRecord mAudioRecord;
112         private final int mDurationMs;
113 
AudioCapturingThread(AudioRecord record, int durationMs)114         AudioCapturingThread(AudioRecord record, int durationMs) {
115             super();
116             mAudioRecord = record;
117             mDurationMs = durationMs;
118         }
119 
120         @Override
121         @SuppressWarnings("ConstantOverflow")
run()122         public void run() {
123             int samplesLeft = mDurationMs * SAMPLE_RATE * mAudioRecord.getChannelCount() / 1000;
124             short[] readBuffer = new short[samplesLeft / 10];
125             mAudioRecord.startRecording();
126             long startTimeMs = System.currentTimeMillis();
127             long elapsedTimeMs = 0;
128             do {
129                 int read = readBuffer.length < samplesLeft ? readBuffer.length : samplesLeft;
130                 read = mAudioRecord.read(readBuffer, 0, read);
131                 elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
132                 if (read < 0) {
133                     Log.w(TAG, "read error: " + read);
134                     break;
135                 }
136                 samplesLeft -= read;
137             } while (elapsedTimeMs < mDurationMs && samplesLeft > 0);
138 
139             // force process to crash
140             int i = 1 / 0;
141         }
142     }
143 }
144