6363import android .os .Handler ;
6464import android .os .IBinder ;
6565import android .os .IBinder .DeathRecipient ;
66+ import android .os .Looper ;
6667import android .os .Message ;
6768import android .os .Messenger ;
6869import android .os .Parcel ;
6970import android .os .ParcelFileDescriptor ;
7071import android .os .Parcelable ;
7172import android .os .RemoteException ;
72- import android .service .notification .StatusBarNotification ;
7373import android .support .annotation .NonNull ;
7474import android .support .v4 .app .NotificationCompat ;
75+ import android .util .AndroidRuntimeException ;
7576import android .util .Log ;
7677import android .util .SparseArray ;
7778import android .util .SparseIntArray ;
@@ -139,7 +140,7 @@ public class SdlRouterService extends Service{
139140 /**
140141 * <b> NOTE: DO NOT MODIFY THIS UNLESS YOU KNOW WHAT YOU'RE DOING.</b>
141142 */
142- protected static final int ROUTER_SERVICE_VERSION_NUMBER = 11 ;
143+ protected static final int ROUTER_SERVICE_VERSION_NUMBER = 12 ;
143144
144145 private static final String ROUTER_SERVICE_PROCESS = "com.smartdevicelink.router" ;
145146
@@ -185,11 +186,18 @@ public class SdlRouterService extends Service{
185186 * Preference location where the service stores known SDL status based on device address
186187 */
187188 protected static final String SDL_DEVICE_STATUS_SHARED_PREFS = "sdl.device.status" ;
189+ /**
190+ * Preference location where generic key/values can be stored
191+ */
192+ protected static final String SDL_ROUTER_SERVICE_PREFS = "sdl.router.service.prefs" ;
193+ protected static final String KEY_AVOID_NOTIFICATION_CHANNEL_DELETE = "avoidNotificationChannelDelete" ;
194+
188195
189196
190197
191198 private static boolean connectAsClient = false ;
192199 private static boolean closing = false ;
200+ private static Thread .UncaughtExceptionHandler routerServiceExceptionHandler = null ;
193201
194202 private Handler altTransportTimerHandler , foregroundTimeoutHandler ;
195203 private Runnable altTransportTimerRunnable , foregroundTimeoutRunnable ;
@@ -1170,6 +1178,9 @@ protected void deployNextRouterService(){
11701178 }
11711179
11721180 public void startUpSequence (){
1181+
1182+ setRouterServiceExceptionHandler ();
1183+
11731184 IntentFilter disconnectFilter = new IntentFilter ();
11741185 disconnectFilter .addAction (BluetoothDevice .ACTION_CLASS_CHANGED );
11751186 disconnectFilter .addAction (BluetoothDevice .ACTION_ACL_DISCONNECTED );
@@ -1196,6 +1207,39 @@ public void startUpSequence(){
11961207 startSequenceComplete = true ;
11971208 }
11981209
1210+ /**
1211+ * This method will set a new UncaughtExceptionHandler for the current thread. The only
1212+ * purpose of the custom UncaughtExceptionHandler is to catch the rare occurrence that the
1213+ * a specific mobile device/OS can't properly handle the deletion and creation of the foreground
1214+ * notification channel that is necessary for foreground services after Android Oreo.
1215+ * The new UncaughtExceptionHandler will catch that specific exception and tell the
1216+ * main looper to continue forward. This still leaves the SdlRouterService killed, but prevents
1217+ * an ANR to the app that makes the startForegroundService call. It will set a flag that will
1218+ * prevent the channel from being deleted in the future and therefore avoiding this exception.
1219+ */
1220+ protected void setRouterServiceExceptionHandler () {
1221+ final Thread .UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread .getDefaultUncaughtExceptionHandler ();
1222+ if (defaultUncaughtExceptionHandler != routerServiceExceptionHandler ) {
1223+ routerServiceExceptionHandler = new Thread .UncaughtExceptionHandler () {
1224+ @ Override
1225+ public void uncaughtException (Thread t , Throwable e ) {
1226+ if (e != null
1227+ && e instanceof AndroidRuntimeException
1228+ && "android.app.RemoteServiceException" .equals (e .getClass ().getName ()) //android.app.RemoteServiceException is a private class
1229+ && e .getMessage ().contains ("invalid channel for service notification" )) { //This is the message received in the exception for notification channel issues
1230+
1231+ // Set the flag to not delete the notification channel to avoid this exception in the future
1232+ SdlRouterService .this .setSdlRouterServicePrefs (KEY_AVOID_NOTIFICATION_CHANNEL_DELETE , true );
1233+ Looper .loop ();
1234+ } else if (defaultUncaughtExceptionHandler != null ) { //No other exception should be handled
1235+ defaultUncaughtExceptionHandler .uncaughtException (t , e );
1236+ }
1237+ }
1238+ };
1239+ Thread .setDefaultUncaughtExceptionHandler (routerServiceExceptionHandler );
1240+ }
1241+ }
1242+
11991243
12001244 @ SuppressLint ({"NewApi" , "MissingPermission" })
12011245 @ Override
@@ -1528,7 +1572,7 @@ private void exitForeground(){
15281572 if (notificationManager != null ){
15291573 try {
15301574 notificationManager .cancelAll ();
1531- if ( Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
1575+ if ( Build .VERSION .SDK_INT >= Build .VERSION_CODES .O && ! getBooleanPref ( KEY_AVOID_NOTIFICATION_CHANNEL_DELETE , false ) ) {
15321576 notificationManager .deleteNotificationChannel (SDL_NOTIFICATION_CHANNEL_ID );
15331577 }
15341578 } catch (Exception e ) {
@@ -2445,6 +2489,33 @@ protected boolean hasSDLConnected(String address){
24452489 return preferences .contains (address ) && preferences .getBoolean (address ,false );
24462490 }
24472491
2492+ /**
2493+ * Set specific settings through key/value to the SDL_ROUTER_SERVICE_PREFS
2494+ * @param key the key of the pair to set in the preferences
2495+ * @param value boolean to attach to key in the preferences
2496+ */
2497+ protected void setSdlRouterServicePrefs (String key , boolean value ){
2498+ SharedPreferences preferences = this .getSharedPreferences (SDL_ROUTER_SERVICE_PREFS , Context .MODE_PRIVATE );
2499+ SharedPreferences .Editor editor = preferences .edit ();
2500+ editor .putBoolean (key ,value );
2501+ editor .commit ();
2502+ Log .d (TAG , "Preference set: " + key + " : " + value );
2503+ }
2504+
2505+ /**
2506+ * Retrieves a boolean value for the given key in the SDL_ROUTER_SERVICE_PREFS
2507+ * @param key the string key that will be used to retrieve the boolean value
2508+ * @param defaultValue if they key does not exist or there is no value to be found, this is the
2509+ * value that will be returned
2510+ * @return the value associated with the supplied key or defaultValue if one does not exist
2511+ */
2512+ protected boolean getBooleanPref (String key , boolean defaultValue ){
2513+ SharedPreferences preferences = this .getSharedPreferences (SDL_ROUTER_SERVICE_PREFS , Context .MODE_PRIVATE );
2514+ if (preferences != null ){
2515+ return preferences .getBoolean (key , defaultValue );
2516+ }
2517+ return false ;
2518+ }
24482519
24492520
24502521 /* ***********************************************************************************************************************************************************************
0 commit comments