6666import java .util .Vector ;
6767import java .util .concurrent .ConcurrentLinkedQueue ;
6868
69+ import static android .Manifest .permission .BLUETOOTH_CONNECT ;
6970import static com .smartdevicelink .transport .TransportConstants .FOREGROUND_EXTRA ;
7071
7172public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
7273
7374 private static final String TAG = "Sdl Broadcast Receiver" ;
7475
7576 protected static final String SDL_ROUTER_SERVICE_CLASS_NAME = "sdlrouterservice" ;
77+ protected static final int ANDROID_12_ROUTER_SERVICE_VERSION = 16 ;
7678
7779 public static final String LOCAL_ROUTER_SERVICE_EXTRA = "router_service" ;
7880 public static final String LOCAL_ROUTER_SERVICE_DID_START_OWN = "did_start" ;
@@ -90,6 +92,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
9092 private static Thread .UncaughtExceptionHandler foregroundExceptionHandler = null ;
9193 private static final Object DEVICE_LISTENER_LOCK = new Object ();
9294 private static SdlDeviceListener sdlDeviceListener ;
95+ private static String serviceName = null ;
9396
9497 public int getRouterServiceVersion () {
9598 return SdlRouterService .ROUTER_SERVICE_VERSION_NUMBER ;
@@ -98,6 +101,7 @@ public int getRouterServiceVersion() {
98101 @ Override
99102 @ CallSuper
100103 public void onReceive (Context context , Intent intent ) {
104+
101105 //Log.i(TAG, "Sdl Receiver Activated");
102106 final String action = intent .getAction ();
103107 if (action == null ) {
@@ -132,6 +136,10 @@ public void onReceive(Context context, Intent intent) {
132136 device = intent .getParcelableExtra (BluetoothDevice .EXTRA_DEVICE );
133137 }
134138
139+ if (serviceName == null ) {
140+ serviceName = getSdlServiceName ();
141+ }
142+
135143 boolean didStart = false ;
136144 if (localRouterClass == null ) {
137145 localRouterClass = defineLocalSdlRouterClass ();
@@ -296,6 +304,18 @@ public void onComplete(Vector<ComponentName> routerServices) {
296304 String routerServicePackage = null ;
297305 if (sdlAppInfoList != null && !sdlAppInfoList .isEmpty () && sdlAppInfoList .get (0 ).getRouterServiceComponentName () != null ) {
298306 routerServicePackage = sdlAppInfoList .get (0 ).getRouterServiceComponentName ().getPackageName ();
307+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
308+ // If all apps have a RS newer than the Android 12 update, chosen app does not have BT Connect permissions, and more than 1 sdl app is installed
309+ if (!isPreAndroid12RSOnDevice (sdlAppInfoList ) && !AndroidTools .isPermissionGranted (BLUETOOTH_CONNECT , context , routerServicePackage ) && sdlAppInfoList .size () > 1 ) {
310+ for (SdlAppInfo appInfo : sdlAppInfoList ) {
311+ if (AndroidTools .isPermissionGranted (BLUETOOTH_CONNECT , context , appInfo .getRouterServiceComponentName ().getPackageName ())) {
312+ //If this app in the list has BT Connect permissions, we want to use that apps RS
313+ routerServicePackage = appInfo .getRouterServiceComponentName ().getPackageName ();
314+ break ;
315+ }
316+ }
317+ }
318+ }
299319 }
300320 DebugTool .logInfo (TAG , ": This app's package: " + myPackage );
301321 DebugTool .logInfo (TAG , ": Router service app's package: " + routerServicePackage );
@@ -362,13 +382,16 @@ private void wakeRouterServiceAltTransport(Context context) {
362382 }
363383
364384 /**
365- * This method will set a new UncaughtExceptionHandler for the current thread. The only
366- * purpose of the custom UncaughtExceptionHandler is to catch the rare occurrence that the
367- * SdlRouterService can't be started fast enough by the system after calling
368- * startForegroundService so the onCreate method doesn't get called before the foreground promise
369- * timer expires. The new UncaughtExceptionHandler will catch that specific exception and tell the
370- * main looper to continue forward. This still leaves the SdlRouterService killed, but prevents
371- * an ANR to the app that makes the startForegroundService call.
385+ * This method will set a new UncaughtExceptionHandler for the current thread.
386+ * There are two exceptions we want to catch here. The first exception is the rare
387+ * occurrence that the SdlRouterService can't be started fast enough by the system after calling
388+ * startForegroundService so the onCreate method doesn't get called before the foreground
389+ * promise timer expires. The second is for the instance where the developers "SdlService" class
390+ * can't be started fast enough by the system after calling startForegroundService OR the app
391+ * is unable to start the "SdlService" class because the developer did not export the service
392+ * in the manifest. The new UncaughtExceptionHandler will catch these specific exception and
393+ * tell the main looper to continue forward. This still leaves the respective Service killed,
394+ * but prevents an ANR to the app that makes the startForegroundService call.
372395 */
373396 static protected void setForegroundExceptionHandler () {
374397 final Thread .UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread .getDefaultUncaughtExceptionHandler ();
@@ -378,10 +401,9 @@ static protected void setForegroundExceptionHandler() {
378401 public void uncaughtException (Thread t , Throwable e ) {
379402 if (e != null
380403 && e instanceof AndroidRuntimeException
381- && "android.app.RemoteServiceException" .equals (e .getClass ().getName ()) //android.app.RemoteServiceException is a private class
404+ && ( "android.app.RemoteServiceException" .equals (e .getClass ().getName ()) || "android.app.ForegroundServiceDidNotStartInTimeException" . equals ( e . getClass (). getName ())) //android.app.RemoteServiceException is a private class
382405 && e .getMessage () != null
383- && e .getMessage ().contains ("SdlRouterService" )) {
384-
406+ && (e .getMessage ().contains ("SdlRouterService" )) || e .getMessage ().contains (serviceName )) {
385407 DebugTool .logInfo (TAG , "Handling failed startForegroundService call" );
386408 Looper .loop ();
387409 } else if (defaultUncaughtExceptionHandler != null ) { //No other exception should be handled
@@ -598,6 +620,18 @@ public boolean onTransportConnected(Context context, BluetoothDevice bluetoothDe
598620 final List <SdlAppInfo > sdlAppInfoList = AndroidTools .querySdlAppInfo (context , new SdlAppInfo .BestRouterComparator (), vehicleType );
599621 if (sdlAppInfoList != null && !sdlAppInfoList .isEmpty ()) {
600622 ComponentName routerService = sdlAppInfoList .get (0 ).getRouterServiceComponentName ();
623+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
624+ // If all apps have a RS newer than the Android 12 update, chosen app does not have BT Connect permissions, and more than 1 sdl app is installed
625+ if (!isPreAndroid12RSOnDevice (sdlAppInfoList ) && !AndroidTools .isPermissionGranted (BLUETOOTH_CONNECT , context , routerService .getPackageName ()) && sdlAppInfoList .size () > 1 ) {
626+ for (SdlAppInfo appInfo : sdlAppInfoList ) {
627+ if (AndroidTools .isPermissionGranted (BLUETOOTH_CONNECT , context , appInfo .getRouterServiceComponentName ().getPackageName ())) {
628+ routerService = appInfo .getRouterServiceComponentName ();
629+ //If this app in the list has BT Connect permissions, we want to use that apps RS
630+ break ;
631+ }
632+ }
633+ }
634+ }
601635 startRouterService (context , routerService , false , bluetoothDevice , true , vehicleType );
602636 }
603637 }
@@ -634,6 +668,16 @@ public static ComponentName consumeQueuedRouterService() {
634668 }
635669 }
636670
671+ private static boolean isPreAndroid12RSOnDevice (List <SdlAppInfo > sdlAppInfoList ) {
672+ for (SdlAppInfo appInfo : sdlAppInfoList ) {
673+ //If an installed app RS version is older than Android 12 update version (16)
674+ if (appInfo .getRouterServiceVersion () < ANDROID_12_ROUTER_SERVICE_VERSION ) {
675+ return true ;
676+ }
677+ }
678+ return false ;
679+ }
680+
637681 /**
638682 * We need to define this for local copy of the Sdl Router Service class.
639683 * It will be the main point of connection for Sdl enabled apps
@@ -656,6 +700,16 @@ public static ComponentName consumeQueuedRouterService() {
656700 */
657701 public abstract void onSdlEnabled (Context context , Intent intent );
658702
703+
704+ /**
705+ * The developer can override this method to return the name of the class that manages their
706+ * SdlService. This method is used to ensure the SdlBroadcastReceivers exception catcher catches
707+ * the correct exception that may be thrown by the app trying to start their SdlService. If this
708+ * exception is not caught the user may experience an ANR for that app.
709+ */
710+ public String getSdlServiceName () {
711+ return "SdlService" ;
712+ }
659713 //public abstract void onSdlDisabled(Context context); //Removing for now until we're able to abstract from developer
660714
661715
0 commit comments