2023-10-23

Screen capture (mediaProjection) on Android 14

I'm trying to adapt my app to run on Android 14. It worked fine on Android 13. But in SDK version 34 I get an exception when I try to start the foreground service.

Caused by: java.lang.SecurityException: 
Starting FGS with type mediaProjection callerApp=ProcessRecord
targetSDK=34 requires permissions: all of the permissions allOf=true
[android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION] 
any of the permissions allOf=false 
[android.permission.CAPTURE_VIDEO_OUTPUT, android:project_media]
at android.os.Parcel.createExceptionOrNull(Parcel.java:3057)
at android.os.Parcel.createException(Parcel.java:3041)
at android.os.Parcel.readException(Parcel.java:3024)
at android.os.Parcel.readException(Parcel.java:2966)
at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:6761)
at android.app.Service.startForeground(Service.java:862)
at .service.ScreenCaptureService.startWithNotification(ScreenCaptureService.java:147)

This message looks like I need to get permission to access the camera. But I don't use the camera at all in my app.

Doc:

The app must set the foregroundServiceType attribute to FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION in the element of the app's manifest file. For an app targeting SDK version U or later, the user must have granted the app with the permission to start a projection, before the app starts a foreground service with the type android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION. Additionally, the app must have started the foreground service with that type before calling this API here, or else it'll receive a SecurityException from this API call, unless it's a privileged app.

Manifest:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />

<service
   android:name=".service.ScreenCaptureService"
   android:enabled="true"
   android:exported="false"
   android:foregroundServiceType="mediaProjection"
   android:stopWithTask="false" />

My actions:

  1. Create ScreenCaptureService.
  2. Requesting user permission to capture screen.
  3. If permission is granted, the foreground service starts.
public int onStartCommand(Intent intent, int flags, int startId) {
   final String intent_action = intent.getAction();

   switch (intent_action) {
      case ACTION_START_SERVICE:
        _is_alive = true;
        _window_name = intent.getStringExtra(EXTRA_WINDOW_NAME);
        startActivity(new Intent(this, ScreenCapturePermissionActivity.class)
        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
           break;
      case ACTION_PERMISSION_RESULT:
        handleScreenCapPermResult();
           break;
      case ACTION_STOP_SERVICE:
      default:
        freeServiceResources();
   }

   return START_STICKY;
}
private void startWithNotification() {
        Notification notification =
                new NotificationCompat.Builder(
                    App.context(), 
                    getString(R.string.sys_notification_chan_ID))
                .setSmallIcon(R.drawable.ic_foreground_notification)
                .setOngoing(true)
                .setContentText(getString(R.string.foreground_service_message))
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();


        if (Build.VERSION.SDK_INT > 28)
            startForeground(
              1, 
              notification, 
              ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
        else
            startForeground(1, notification);
}

UPDATE: I think this is a bug in the SDK that comes with Android Studio. ContextCompat.checkSelfPermission always returns PERMISSION_DENIED regardless of when permission is requested. I checked all launch options. The result is always the same. And mediaProjection does not start with the received intent.

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   ActivityResultLauncher<Intent> startMediaProjection =
      registerForActivityResult(new ActivityResultContracts
             .StartActivityForResult(),
             result -> {
                if (result.getResultCode() == RESULT_OK) {
                    Log.e("DEBUG", "RESULT_OK");
                    if (android.os.Build.VERSION.SDK_INT > 33) {
                       int perm_code = 
                           ContextCompat.checkSelfPermission(
                           this,
                           Manifest
                            .permission
                             .FOREGROUND_SERVICE_MEDIA_PROJECTION);
                       if (perm_code == PackageManager.PERMISSION_GRANTED) {
                         Log.e("DEBUG", "PERMISSION_GRANTED");
                       } else {
                         Log.e("DEBUG", "PERMISSION_DENIED");                 
                       }
                   } else {
                     Log.e("DEBUG", "PERMISSION_GRANTED");
                   }
              else {
                 Log.e("DEBUG", "RESULT_CANCELED");
              }

     creenCaptureService.handlePermission();
     finish();
     });

  final MediaProjectionManager manager =
                     getSystemService(MediaProjectionManager.class);    
  startMediaProjection.launch(manager.createScreenCaptureIntent());
}


No comments:

Post a Comment