Skip to content

Commit dab6bc6

Browse files
authored
Merge pull request #1771 from smartdevicelink/feature/android_12_fixes
Feature/android 12 fixes
2 parents 52192be + 03a0682 commit dab6bc6

14 files changed

Lines changed: 336 additions & 36 deletions

File tree

android/hello_sdl_android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
apply plugin: 'com.android.application'
22

33
android {
4-
compileSdkVersion 30
4+
compileSdkVersion 31
55
defaultConfig {
66
applicationId "com.sdl.hellosdlandroid"
77
minSdkVersion 16
8-
targetSdkVersion 30
8+
targetSdkVersion 31
99
versionCode 1
1010
versionName "1.0"
1111
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

android/hello_sdl_android/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
package="com.sdl.hellosdlandroid">
55

66
<uses-permission android:name="android.permission.BLUETOOTH" />
7+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"
8+
tools:targetApi="31"/>
79
<uses-permission android:name="android.permission.INTERNET" />
810
<!-- Required to check if WiFi is enabled -->
911
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -30,6 +32,7 @@
3032
tools:ignore="DeepLinks">
3133
<activity
3234
android:name=".MainActivity"
35+
android:exported="true"
3336
android:label="@string/app_name">
3437
<intent-filter>
3538
<action android:name="android.intent.action.MAIN" />
@@ -40,6 +43,7 @@
4043

4144
<activity
4245
android:name="com.smartdevicelink.transport.USBAccessoryAttachmentActivity"
46+
android:exported="true"
4347
android:launchMode="singleTop">
4448
<intent-filter>
4549
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
@@ -52,6 +56,7 @@
5256

5357
<service
5458
android:name="com.sdl.hellosdlandroid.SdlService"
59+
android:exported="true"
5560
android:foregroundServiceType="connectedDevice">
5661
</service>
5762
<service

android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/MainActivity.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,66 @@
11
package com.sdl.hellosdlandroid;
22

33
import android.content.Intent;
4+
import android.content.pm.PackageManager;
5+
import android.os.Build;
46
import android.os.Bundle;
57
import android.view.Menu;
68
import android.view.MenuItem;
79

10+
import androidx.annotation.NonNull;
811
import androidx.appcompat.app.AppCompatActivity;
12+
import androidx.core.app.ActivityCompat;
13+
import androidx.core.content.ContextCompat;
14+
15+
import static android.Manifest.permission.BLUETOOTH_CONNECT;
916

1017
public class MainActivity extends AppCompatActivity {
1118

19+
private static final int REQUEST_CODE = 200;
20+
1221
@Override
1322
protected void onCreate(Bundle savedInstanceState) {
1423
super.onCreate(savedInstanceState);
1524
setContentView(R.layout.activity_main);
16-
//If we are connected to a module we want to start our SdlService
25+
26+
1727
if (BuildConfig.TRANSPORT.equals("MULTI") || BuildConfig.TRANSPORT.equals("MULTI_HB")) {
28+
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !checkPermission()) {
29+
requestPermission();
30+
return;
31+
}
32+
//If we are connected to a module we want to start our SdlService
1833
SdlReceiver.queryForConnectedService(this);
19-
} else if (BuildConfig.TRANSPORT.equals("TCP")) {
34+
} else if (BuildConfig.TRANSPORT.equals("TCP")){
2035
Intent proxyIntent = new Intent(this, SdlService.class);
2136
startService(proxyIntent);
2237
}
2338
}
2439

40+
private boolean checkPermission() {
41+
return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(getApplicationContext(), BLUETOOTH_CONNECT);
42+
}
43+
44+
private void requestPermission() {
45+
ActivityCompat.requestPermissions(this, new String[]{BLUETOOTH_CONNECT}, REQUEST_CODE);
46+
}
47+
48+
@Override
49+
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
50+
switch (requestCode) {
51+
case REQUEST_CODE:
52+
if (grantResults.length > 0) {
53+
54+
boolean btConnectGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
55+
56+
if (btConnectGranted) {
57+
SdlReceiver.queryForConnectedService(this);
58+
}
59+
}
60+
break;
61+
}
62+
}
63+
2564
@Override
2665
public boolean onCreateOptionsMenu(Menu menu) {
2766
// Inflate the menu; this adds items to the action bar if it is present.

android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlReceiver.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.sdl.hellosdlandroid;
22

3+
import android.app.PendingIntent;
34
import android.content.Context;
45
import android.content.Intent;
56
import android.os.Build;
67

78
import com.smartdevicelink.transport.SdlBroadcastReceiver;
89
import com.smartdevicelink.transport.SdlRouterService;
10+
import com.smartdevicelink.transport.TransportConstants;
911
import com.smartdevicelink.util.DebugTool;
1012

1113
public class SdlReceiver extends SdlBroadcastReceiver {
@@ -16,13 +18,27 @@ public void onSdlEnabled(Context context, Intent intent) {
1618
DebugTool.logInfo(TAG, "SDL Enabled");
1719
intent.setClass(context, SdlService.class);
1820

19-
// SdlService needs to be foregrounded in Android O and above
20-
// This will prevent apps in the background from crashing when they try to start SdlService
21-
// Because Android O doesn't allow background apps to start background services
22-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
23-
context.startForegroundService(intent);
21+
// Starting with Android S SdlService needs to be started from a foreground context.
22+
// We will check the intent for a pendingIntent parcelable extra
23+
// This pendingIntent allows us to start the SdlService from the context of the active router service which is in the foreground
24+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
25+
PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA);
26+
if (pendingIntent != null) {
27+
try {
28+
pendingIntent.send(context, 0, intent);
29+
} catch (PendingIntent.CanceledException e) {
30+
e.printStackTrace();
31+
}
32+
}
2433
} else {
25-
context.startService(intent);
34+
// SdlService needs to be foregrounded in Android O and above
35+
// This will prevent apps in the background from crashing when they try to start SdlService
36+
// Because Android O doesn't allow background apps to start background services
37+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
38+
context.startForegroundService(intent);
39+
} else {
40+
context.startService(intent);
41+
}
2642
}
2743
}
2844

@@ -35,4 +51,9 @@ public Class<? extends SdlRouterService> defineLocalSdlRouterClass() {
3551
public void onReceive(Context context, Intent intent) {
3652
super.onReceive(context, intent); // Required if overriding this method
3753
}
54+
55+
@Override
56+
public String getSdlServiceName() {
57+
return SdlService.class.getSimpleName();
58+
}
3859
}

android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,13 @@ public void enterForeground() {
108108
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
109109
if (notificationManager != null) {
110110
notificationManager.createNotificationChannel(channel);
111-
Notification serviceNotification = new Notification.Builder(this, channel.getId())
111+
Notification.Builder builder = new Notification.Builder(this, channel.getId())
112112
.setContentTitle("Connected through SDL")
113-
.setSmallIcon(R.drawable.ic_sdl)
114-
.build();
113+
.setSmallIcon(R.drawable.ic_sdl);
114+
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
115+
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE);
116+
}
117+
Notification serviceNotification = builder.build();
115118
startForeground(FOREGROUND_SERVICE_ID, serviceNotification);
116119
}
117120
}

android/sdl_android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
apply plugin: 'com.android.library'
22

33
android {
4-
compileSdkVersion 30
4+
compileSdkVersion 31
55
defaultConfig {
66
minSdkVersion 16
7-
targetSdkVersion 30
7+
targetSdkVersion 31
88
versionCode 21
99
versionName new File(projectDir.path, ('/../../VERSION')).text.trim()
1010
buildConfigField "String", "VERSION_NAME", '\"' + versionName + '\"'

android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,15 @@
6666
import java.util.Vector;
6767
import java.util.concurrent.ConcurrentLinkedQueue;
6868

69+
import static android.Manifest.permission.BLUETOOTH_CONNECT;
6970
import static com.smartdevicelink.transport.TransportConstants.FOREGROUND_EXTRA;
7071

7172
public 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

Comments
 (0)