1
1
package com .genymobile .scrcpy ;
2
2
3
+ import com .genymobile .scrcpy .wrappers .ServiceManager ;
4
+
3
5
import android .annotation .SuppressLint ;
4
6
import android .annotation .TargetApi ;
7
+ import android .content .ComponentName ;
8
+ import android .content .Intent ;
5
9
import android .media .AudioFormat ;
6
10
import android .media .AudioRecord ;
7
11
import android .media .AudioTimestamp ;
12
16
import android .os .Handler ;
13
17
import android .os .HandlerThread ;
14
18
import android .os .Looper ;
19
+ import android .os .SystemClock ;
15
20
16
21
import java .io .IOException ;
17
22
import java .nio .ByteBuffer ;
@@ -179,7 +184,7 @@ public void start() {
179
184
thread = new Thread (() -> {
180
185
try {
181
186
encode ();
182
- } catch (ConfigurationException e ) {
187
+ } catch (ConfigurationException | AudioCaptureForegroundException e ) {
183
188
// Do not print stack trace, a user-friendly error-message has already been logged
184
189
} catch (IOException e ) {
185
190
Ln .e ("Audio encoding error" , e );
@@ -218,8 +223,34 @@ private synchronized void waitEnded() {
218
223
}
219
224
}
220
225
226
+ private static void startWorkaroundAndroid11 () {
227
+ if (Build .VERSION .SDK_INT == Build .VERSION_CODES .R ) {
228
+ // Android 11 requires Apps to be at foreground to record audio.
229
+ // Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground.
230
+ // But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android
231
+ // shell ("com.android.shell").
232
+ // If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the
233
+ // foreground.
234
+ if (Build .VERSION .SDK_INT == Build .VERSION_CODES .R ) {
235
+ Intent intent = new Intent (Intent .ACTION_MAIN );
236
+ intent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
237
+ intent .addCategory (Intent .CATEGORY_LAUNCHER );
238
+ intent .setComponent (new ComponentName (FakeContext .PACKAGE_NAME , "com.android.shell.HeapDumpActivity" ));
239
+ ServiceManager .getActivityManager ().startActivityAsUserWithFeature (intent );
240
+ // Wait for activity to start
241
+ SystemClock .sleep (150 );
242
+ }
243
+ }
244
+ }
245
+
246
+ private static void stopWorkaroundAndroid11 () {
247
+ if (Build .VERSION .SDK_INT == Build .VERSION_CODES .R ) {
248
+ ServiceManager .getActivityManager ().forceStopPackage (FakeContext .PACKAGE_NAME );
249
+ }
250
+ }
251
+
221
252
@ TargetApi (Build .VERSION_CODES .M )
222
- public void encode () throws IOException , ConfigurationException {
253
+ public void encode () throws IOException , ConfigurationException , AudioCaptureForegroundException {
223
254
if (Build .VERSION .SDK_INT < Build .VERSION_CODES .R ) {
224
255
Ln .w ("Audio disabled: it is not supported before Android 11" );
225
256
streamer .writeDisableStream (false );
@@ -242,8 +273,20 @@ public void encode() throws IOException, ConfigurationException {
242
273
mediaCodec .setCallback (new EncoderCallback (), new Handler (mediaCodecThread .getLooper ()));
243
274
mediaCodec .configure (format , null , null , MediaCodec .CONFIGURE_FLAG_ENCODE );
244
275
245
- recorder = createAudioRecord ();
246
- recorder .startRecording ();
276
+ startWorkaroundAndroid11 ();
277
+ try {
278
+ recorder = createAudioRecord ();
279
+ recorder .startRecording ();
280
+ } catch (UnsupportedOperationException e ) {
281
+ if (Build .VERSION .SDK_INT == Build .VERSION_CODES .R ) {
282
+ Ln .e ("Failed to start audio capture" );
283
+ Ln .e ("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy." );
284
+ throw new AudioCaptureForegroundException ();
285
+ }
286
+ throw e ;
287
+ } finally {
288
+ stopWorkaroundAndroid11 ();
289
+ }
247
290
recorderStarted = true ;
248
291
249
292
final MediaCodec mediaCodecRef = mediaCodec ;
0 commit comments