@ -59,27 +59,21 @@ public final class AudioCapture {
}
private static void startWorkaroundAndroid11 ( ) {
if ( Build . VERSION . SDK_INT = = Build . VERSION_CODES . R ) {
// Android 11 requires Apps to be at foreground to record audio.
// Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground.
// 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
// shell ("com.android.shell").
// If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the
// foreground.
if ( Build . VERSION . SDK_INT = = Build . VERSION_CODES . R ) {
Intent intent = new Intent ( Intent . ACTION_MAIN ) ;
intent . addFlags ( Intent . FLAG_ACTIVITY_NEW_TASK ) ;
intent . addCategory ( Intent . CATEGORY_LAUNCHER ) ;
intent . setComponent ( new ComponentName ( FakeContext . PACKAGE_NAME , "com.android.shell.HeapDumpActivity" ) ) ;
ServiceManager . getActivityManager ( ) . startActivityAsUserWithFeature ( intent ) ;
}
}
// Android 11 requires Apps to be at foreground to record audio.
// Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground.
// 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
// shell ("com.android.shell").
// If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the
// foreground.
Intent intent = new Intent ( Intent . ACTION_MAIN ) ;
intent . addFlags ( Intent . FLAG_ACTIVITY_NEW_TASK ) ;
intent . addCategory ( Intent . CATEGORY_LAUNCHER ) ;
intent . setComponent ( new ComponentName ( FakeContext . PACKAGE_NAME , "com.android.shell.HeapDumpActivity" ) ) ;
ServiceManager . getActivityManager ( ) . startActivityAsUserWithFeature ( intent ) ;
}
private static void stopWorkaroundAndroid11 ( ) {
if ( Build . VERSION . SDK_INT = = Build . VERSION_CODES . R ) {
ServiceManager . getActivityManager ( ) . forceStopPackage ( FakeContext . PACKAGE_NAME ) ;
}
ServiceManager . getActivityManager ( ) . forceStopPackage ( FakeContext . PACKAGE_NAME ) ;
}
private void tryStartRecording ( int attempts , int delayMs ) throws AudioCaptureForegroundException {
@ -87,32 +81,36 @@ public final class AudioCapture {
// Wait for activity to start
SystemClock . sleep ( delayMs ) ;
try {
recorder = createAudioRecord ( ) ;
recorder . startRecording ( ) ;
startRecording ( ) ;
return ; // it worked
} catch ( UnsupportedOperationException e ) {
if ( Build . VERSION . SDK_INT = = Build . VERSION_CODES . R ) {
if ( attempts = = 0 ) {
Ln . e ( "Failed to start audio capture" ) ;
Ln . e ( "On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting "
+ "scrcpy." ) ;
throw new AudioCaptureForegroundException ( ) ;
} else {
Ln . d ( "Failed to start audio capture, retrying..." ) ;
}
if ( attempts = = 0 ) {
Ln . e ( "Failed to start audio capture" ) ;
Ln . e ( "On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " +
"scrcpy." ) ;
throw new AudioCaptureForegroundException ( ) ;
} else {
throw e ;
Ln . d ( "Failed to start audio capture, retrying..." ) ;
}
}
}
}
private void startRecording ( ) {
recorder = createAudioRecord ( ) ;
recorder . startRecording ( ) ;
}
public void start ( ) throws AudioCaptureForegroundException {
startWorkaroundAndroid11 ( ) ;
try {
tryStartRecording ( 3 , 100 ) ;
} finally {
stopWorkaroundAndroid11 ( ) ;
if ( Build . VERSION . SDK_INT = = Build . VERSION_CODES . R ) {
startWorkaroundAndroid11 ( ) ;
try {
tryStartRecording ( 3 , 100 ) ;
} finally {
stopWorkaroundAndroid11 ( ) ;
}
} else {
startRecording ( ) ;
}
}