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:
- Create ScreenCaptureService.
- Requesting user permission to capture screen.
- 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());
}
Instead of
Now we need to ask for permission
Nine days of searching... I love Android developers. Without any warning in the manual, the permission was moved to a new package, while the old one was not marked as deprecated.