I have an Android Wear OS emergency app, which monitors the user and looks out for emergency events. When the app is closed, I use a foreground service, to keep monitoring in the background. This works fine and the app registers the event and both vibrates and makes a ping sound. When the app registers the emergency event, even if the app is in the background and the screen is black, I want the app to do one of the following:
- Preferably: Open the apps main activity where the user can take action
- Alternatively: Wake up the screen and show a notification, so the user knows the event has been identified
I have tried a bunch of different approaches and I'm aware this is difficult on purpose, with the hardening of restrictions. But surely it is possible.
My first approach is this:
private fun openApp() {
AppLogger.d(TAG, "Open App Intent")
val notificationManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val saveRequestOptionsBundle: Bundle? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
} else {
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
ActivityOptions.makeBasic().apply {
pendingIntentCreatorBackgroundActivityStartMode = mode
}.toBundle()
} else {
null
}
val openAppIntent = Intent(this, MainActivity::class.java)
val openAppPendingIntent = PendingIntent.getActivity(
this,
1,
openAppIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
saveRequestOptionsBundle
)
val notification = NotificationCompat.Builder(this, getString(R.string.emergency_notification_channel_id))
.setContentTitle(getString(R.string.emergency_notification_title))
.setFullScreenIntent(openAppPendingIntent, true)
.setContentText(getString(R.string.event))
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setContentIntent(openAppPendingIntent)
.setSmallIcon(R.drawable.ic_app_logo)
.setAutoCancel(false)
.build()
notificationManager.notify(11, notification)
}
My second approach was this:
private fun openApp1() {
Log.d(TAG, "Open App Intent")
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { // API 36+
val activityOptions = ActivityOptions.makeBasic().apply {
pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
}
val launchActivityPendingIntent: PendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
activityOptions.toBundle()
)
try {
launchActivityPendingIntent.send()
} catch (e: PendingIntent.CanceledException) {
AppLogger.e(TAG, "PendingIntent was cancelled", e)
}
} else {
startActivity(intent)
}
}
Both approaches gave me the error:
Background activity launch blocked! [callingPackage: com.mypackage; callingPackageTargetSdk: 36; callingUid: 10251; callingPid: -1; appSwitchState: 1; callingUidHasAnyVisibleWindow: false; callingUidProcState: FOREGROUND_SERVICE; isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BackgroundStartPrivileges[allowsBackgroundActivityStarts=true, allowsBackgroundForegroundServiceStarts=true, originatingToken=android.os.Binder@8]; intent: Intent { cmp=com.mypackage.MainActivity }; callerApp: null; balAllowedByPiCreator: BSP.ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; resultIfPiCreatorAllowsBal: BAL_BLOCK; hasRealCaller: true; isCallForResult: false; isPendingIntent: true; autoOptInReason: null; realCallingPackage: com.samsung.android.wearable.sysui; realCallingPackageTargetSdk: 33; realCallingUid: 10; realCallingPid: 1; realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: BOUND_FOREGROUND_SERVICE; isRealCallingUidPersistentSystemProcess: false; originatingPendingIntent: PendingIntentRecord{13ca919 com.mypackage startActivity (allowlist: 87:+30s0ms/0/NOTIFICATION_SERVICE/NotificationManagerService)}; realCallerApp: ProcessRecord{55c 12:com.samsung.android.wearable.sysui/u0}; realInVisibleTask: false; balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: BAL_BLOCK]
I have all of the permissions:
- USE_FULL_SCREEN_INTENT
- SYSTEM_ALERT_WINDOW
- POST_NOTIFICATIONS
My mainactivity in manifest has these:
- android:launchMode="singleTask"
- android:turnScreenOn="true"
- android:showWhenLocked="true"
And my notification channel looks like this:
// Emergency Channel
String emergency_channel_id = getString(R.string.emergency_notification_channel_id);
CharSequence emergency_name = getString(R.string.emergency_notification_channel_name);
String emergency_description = getString(R.string.emergency_notification_channel_description);
NotificationChannel emergency_channel = new NotificationChannel(
emergency_channel_id,
emergency_name,
NotificationManager.IMPORTANCE_HIGH
);
emergency_channel.setDescription(emergency_description);
emergency_channel.enableLights(true);
emergency_channel.setLightColor(Color.RED);
emergency_channel.enableVibration(true);
emergency_channel.setVibrationPattern(new long[]{0, 500, 200, 500, 200, 500});
emergency_channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
emergency_channel.setBypassDnd(true);
emergency_channel.setShowBadge(true);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build();
emergency_channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM), audioAttributes);
notificationManager.createNotificationChannel(emergency_channel);
I've gone through a lot of different StackOverflow questions and official Android update docs, but nothing seems to get past this BAL_BLOCK.
Have anybody figured it out or know the best practice for this?