summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorJoey Grover <joeygrover@gmail.com>2022-02-24 17:08:56 -0500
committerGitHub <noreply@github.com>2022-02-24 17:08:56 -0500
commitdab6bc68e84f4caa54865f6d2379625ebece00a5 (patch)
tree2a366f0f27785dd245e7e117e2cb7b84d17b463e /android
parent52192be1ca1b09bbf732584672cca175dd4d33b0 (diff)
parent03a0682f24d970806e6b627519668a0599762edf (diff)
downloadsdl_android-dab6bc68e84f4caa54865f6d2379625ebece00a5.tar.gz
Merge pull request #1771 from smartdevicelink/feature/android_12_fixes
Feature/android 12 fixes
Diffstat (limited to 'android')
-rwxr-xr-xandroid/hello_sdl_android/build.gradle4
-rwxr-xr-xandroid/hello_sdl_android/src/main/AndroidManifest.xml5
-rwxr-xr-xandroid/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/MainActivity.java43
-rwxr-xr-xandroid/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlReceiver.java33
-rwxr-xr-xandroid/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java9
-rw-r--r--android/sdl_android/build.gradle4
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java74
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java166
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java1
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java23
-rw-r--r--android/sdl_android/src/main/res/values/sdl.xml2
-rw-r--r--android/sdl_android/src/main/res/values/strings.xml1
12 files changed, 330 insertions, 35 deletions
diff --git a/android/hello_sdl_android/build.gradle b/android/hello_sdl_android/build.gradle
index 7640e89c5..5feeba93f 100755
--- a/android/hello_sdl_android/build.gradle
+++ b/android/hello_sdl_android/build.gradle
@@ -1,11 +1,11 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 30
+ compileSdkVersion 31
defaultConfig {
applicationId "com.sdl.hellosdlandroid"
minSdkVersion 16
- targetSdkVersion 30
+ targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
diff --git a/android/hello_sdl_android/src/main/AndroidManifest.xml b/android/hello_sdl_android/src/main/AndroidManifest.xml
index 1183e7b2c..415aa66c2 100755
--- a/android/hello_sdl_android/src/main/AndroidManifest.xml
+++ b/android/hello_sdl_android/src/main/AndroidManifest.xml
@@ -4,6 +4,8 @@
package="com.sdl.hellosdlandroid">
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"
+ tools:targetApi="31"/>
<uses-permission android:name="android.permission.INTERNET" />
<!-- Required to check if WiFi is enabled -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -30,6 +32,7 @@
tools:ignore="DeepLinks">
<activity
android:name=".MainActivity"
+ android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -40,6 +43,7 @@
<activity
android:name="com.smartdevicelink.transport.USBAccessoryAttachmentActivity"
+ android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
@@ -52,6 +56,7 @@
<service
android:name="com.sdl.hellosdlandroid.SdlService"
+ android:exported="true"
android:foregroundServiceType="connectedDevice">
</service>
<service
diff --git a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/MainActivity.java b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/MainActivity.java
index 43ad79ac7..8497e3b73 100755
--- a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/MainActivity.java
+++ b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/MainActivity.java
@@ -1,27 +1,66 @@
package com.sdl.hellosdlandroid;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
public class MainActivity extends AppCompatActivity {
+ private static final int REQUEST_CODE = 200;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- //If we are connected to a module we want to start our SdlService
+
+
if (BuildConfig.TRANSPORT.equals("MULTI") || BuildConfig.TRANSPORT.equals("MULTI_HB")) {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !checkPermission()) {
+ requestPermission();
+ return;
+ }
+ //If we are connected to a module we want to start our SdlService
SdlReceiver.queryForConnectedService(this);
- } else if (BuildConfig.TRANSPORT.equals("TCP")) {
+ } else if (BuildConfig.TRANSPORT.equals("TCP")){
Intent proxyIntent = new Intent(this, SdlService.class);
startService(proxyIntent);
}
}
+ private boolean checkPermission() {
+ return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(getApplicationContext(), BLUETOOTH_CONNECT);
+ }
+
+ private void requestPermission() {
+ ActivityCompat.requestPermissions(this, new String[]{BLUETOOTH_CONNECT}, REQUEST_CODE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case REQUEST_CODE:
+ if (grantResults.length > 0) {
+
+ boolean btConnectGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
+
+ if (btConnectGranted) {
+ SdlReceiver.queryForConnectedService(this);
+ }
+ }
+ break;
+ }
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
diff --git a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlReceiver.java b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlReceiver.java
index 32c0ab1d5..e130d3d78 100755
--- a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlReceiver.java
+++ b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlReceiver.java
@@ -1,11 +1,13 @@
package com.sdl.hellosdlandroid;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import com.smartdevicelink.transport.SdlBroadcastReceiver;
import com.smartdevicelink.transport.SdlRouterService;
+import com.smartdevicelink.transport.TransportConstants;
import com.smartdevicelink.util.DebugTool;
public class SdlReceiver extends SdlBroadcastReceiver {
@@ -16,13 +18,27 @@ public class SdlReceiver extends SdlBroadcastReceiver {
DebugTool.logInfo(TAG, "SDL Enabled");
intent.setClass(context, SdlService.class);
- // SdlService needs to be foregrounded in Android O and above
- // This will prevent apps in the background from crashing when they try to start SdlService
- // Because Android O doesn't allow background apps to start background services
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- context.startForegroundService(intent);
+ // Starting with Android S SdlService needs to be started from a foreground context.
+ // We will check the intent for a pendingIntent parcelable extra
+ // This pendingIntent allows us to start the SdlService from the context of the active router service which is in the foreground
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA);
+ if (pendingIntent != null) {
+ try {
+ pendingIntent.send(context, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ e.printStackTrace();
+ }
+ }
} else {
- context.startService(intent);
+ // SdlService needs to be foregrounded in Android O and above
+ // This will prevent apps in the background from crashing when they try to start SdlService
+ // Because Android O doesn't allow background apps to start background services
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent);
+ } else {
+ context.startService(intent);
+ }
}
}
@@ -35,4 +51,9 @@ public class SdlReceiver extends SdlBroadcastReceiver {
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent); // Required if overriding this method
}
+
+ @Override
+ public String getSdlServiceName() {
+ return SdlService.class.getSimpleName();
+ }
} \ No newline at end of file
diff --git a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java
index c6e9ce3ef..59daf2050 100755
--- a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java
+++ b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java
@@ -108,10 +108,13 @@ public class SdlService extends Service {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
- Notification serviceNotification = new Notification.Builder(this, channel.getId())
+ Notification.Builder builder = new Notification.Builder(this, channel.getId())
.setContentTitle("Connected through SDL")
- .setSmallIcon(R.drawable.ic_sdl)
- .build();
+ .setSmallIcon(R.drawable.ic_sdl);
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE);
+ }
+ Notification serviceNotification = builder.build();
startForeground(FOREGROUND_SERVICE_ID, serviceNotification);
}
}
diff --git a/android/sdl_android/build.gradle b/android/sdl_android/build.gradle
index f3dfdfda1..575ec5f93 100644
--- a/android/sdl_android/build.gradle
+++ b/android/sdl_android/build.gradle
@@ -1,10 +1,10 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 30
+ compileSdkVersion 31
defaultConfig {
minSdkVersion 16
- targetSdkVersion 30
+ targetSdkVersion 31
versionCode 21
versionName new File(projectDir.path, ('/../../VERSION')).text.trim()
buildConfigField "String", "VERSION_NAME", '\"' + versionName + '\"'
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
index 93f6cc850..7bed1b482 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
@@ -66,6 +66,7 @@ import java.util.Locale;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static com.smartdevicelink.transport.TransportConstants.FOREGROUND_EXTRA;
public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
@@ -73,6 +74,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "Sdl Broadcast Receiver";
protected static final String SDL_ROUTER_SERVICE_CLASS_NAME = "sdlrouterservice";
+ protected static final int ANDROID_12_ROUTER_SERVICE_VERSION = 16;
public static final String LOCAL_ROUTER_SERVICE_EXTRA = "router_service";
public static final String LOCAL_ROUTER_SERVICE_DID_START_OWN = "did_start";
@@ -90,6 +92,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
private static Thread.UncaughtExceptionHandler foregroundExceptionHandler = null;
private static final Object DEVICE_LISTENER_LOCK = new Object();
private static SdlDeviceListener sdlDeviceListener;
+ private static String serviceName = null;
public int getRouterServiceVersion() {
return SdlRouterService.ROUTER_SERVICE_VERSION_NUMBER;
@@ -98,6 +101,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
@Override
@CallSuper
public void onReceive(Context context, Intent intent) {
+
//Log.i(TAG, "Sdl Receiver Activated");
final String action = intent.getAction();
if (action == null) {
@@ -132,6 +136,10 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
}
+ if (serviceName == null) {
+ serviceName = getSdlServiceName();
+ }
+
boolean didStart = false;
if (localRouterClass == null) {
localRouterClass = defineLocalSdlRouterClass();
@@ -296,6 +304,18 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
String routerServicePackage = null;
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty() && sdlAppInfoList.get(0).getRouterServiceComponentName() != null) {
routerServicePackage = sdlAppInfoList.get(0).getRouterServiceComponentName().getPackageName();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ // 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
+ if (!isPreAndroid12RSOnDevice(sdlAppInfoList) && !AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, context, routerServicePackage) && sdlAppInfoList.size() > 1) {
+ for (SdlAppInfo appInfo : sdlAppInfoList) {
+ if (AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, context, appInfo.getRouterServiceComponentName().getPackageName())) {
+ //If this app in the list has BT Connect permissions, we want to use that apps RS
+ routerServicePackage = appInfo.getRouterServiceComponentName().getPackageName();
+ break;
+ }
+ }
+ }
+ }
}
DebugTool.logInfo(TAG, ": This app's package: " + myPackage);
DebugTool.logInfo(TAG, ": Router service app's package: " + routerServicePackage);
@@ -362,13 +382,16 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
}
/**
- * This method will set a new UncaughtExceptionHandler for the current thread. The only
- * purpose of the custom UncaughtExceptionHandler is to catch the rare occurrence that the
- * SdlRouterService can't be started fast enough by the system after calling
- * startForegroundService so the onCreate method doesn't get called before the foreground promise
- * timer expires. The new UncaughtExceptionHandler will catch that specific exception and tell the
- * main looper to continue forward. This still leaves the SdlRouterService killed, but prevents
- * an ANR to the app that makes the startForegroundService call.
+ * This method will set a new UncaughtExceptionHandler for the current thread.
+ * There are two exceptions we want to catch here. The first exception is the rare
+ * occurrence that the SdlRouterService can't be started fast enough by the system after calling
+ * startForegroundService so the onCreate method doesn't get called before the foreground
+ * promise timer expires. The second is for the instance where the developers "SdlService" class
+ * can't be started fast enough by the system after calling startForegroundService OR the app
+ * is unable to start the "SdlService" class because the developer did not export the service
+ * in the manifest. The new UncaughtExceptionHandler will catch these specific exception and
+ * tell the main looper to continue forward. This still leaves the respective Service killed,
+ * but prevents an ANR to the app that makes the startForegroundService call.
*/
static protected void setForegroundExceptionHandler() {
final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
@@ -378,10 +401,9 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
public void uncaughtException(Thread t, Throwable e) {
if (e != null
&& e instanceof AndroidRuntimeException
- && "android.app.RemoteServiceException".equals(e.getClass().getName()) //android.app.RemoteServiceException is a private class
+ && ("android.app.RemoteServiceException".equals(e.getClass().getName()) || "android.app.ForegroundServiceDidNotStartInTimeException".equals(e.getClass().getName())) //android.app.RemoteServiceException is a private class
&& e.getMessage() != null
- && e.getMessage().contains("SdlRouterService")) {
-
+ && (e.getMessage().contains("SdlRouterService")) || e.getMessage().contains(serviceName)) {
DebugTool.logInfo(TAG, "Handling failed startForegroundService call");
Looper.loop();
} else if (defaultUncaughtExceptionHandler != null) { //No other exception should be handled
@@ -598,6 +620,18 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
final List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator(), vehicleType);
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
ComponentName routerService = sdlAppInfoList.get(0).getRouterServiceComponentName();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ // 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
+ if (!isPreAndroid12RSOnDevice(sdlAppInfoList) && !AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, context, routerService.getPackageName()) && sdlAppInfoList.size() > 1) {
+ for (SdlAppInfo appInfo : sdlAppInfoList) {
+ if (AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, context, appInfo.getRouterServiceComponentName().getPackageName())) {
+ routerService = appInfo.getRouterServiceComponentName();
+ //If this app in the list has BT Connect permissions, we want to use that apps RS
+ break;
+ }
+ }
+ }
+ }
startRouterService(context, routerService, false, bluetoothDevice, true, vehicleType);
}
}
@@ -634,6 +668,16 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
}
}
+ private static boolean isPreAndroid12RSOnDevice(List<SdlAppInfo> sdlAppInfoList) {
+ for (SdlAppInfo appInfo : sdlAppInfoList) {
+ //If an installed app RS version is older than Android 12 update version (16)
+ if (appInfo.getRouterServiceVersion() < ANDROID_12_ROUTER_SERVICE_VERSION) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* We need to define this for local copy of the Sdl Router Service class.
* It will be the main point of connection for Sdl enabled apps
@@ -656,6 +700,16 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
*/
public abstract void onSdlEnabled(Context context, Intent intent);
+
+ /**
+ * The developer can override this method to return the name of the class that manages their
+ * SdlService. This method is used to ensure the SdlBroadcastReceivers exception catcher catches
+ * the correct exception that may be thrown by the app trying to start their SdlService. If this
+ * exception is not caught the user may experience an ANR for that app.
+ */
+ public String getSdlServiceName() {
+ return "SdlService";
+ }
//public abstract void onSdlDisabled(Context context); //Removing for now until we're able to abstract from developer
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
index db281a351..a4926372d 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
@@ -70,6 +70,7 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -119,6 +120,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
import static com.smartdevicelink.transport.TransportConstants.CONNECTED_DEVICE_STRING_EXTRA_NAME;
import static com.smartdevicelink.transport.TransportConstants.FOREGROUND_EXTRA;
import static com.smartdevicelink.transport.TransportConstants.FORMED_PACKET_EXTRA_NAME;
@@ -142,7 +145,7 @@ public class SdlRouterService extends Service {
/**
* <b> NOTE: DO NOT MODIFY THIS UNLESS YOU KNOW WHAT YOU'RE DOING.</b>
*/
- protected static final int ROUTER_SERVICE_VERSION_NUMBER = 15;
+ protected static final int ROUTER_SERVICE_VERSION_NUMBER = 16;
private static final String ROUTER_SERVICE_PROCESS = "com.smartdevicelink.router";
@@ -202,6 +205,7 @@ public class SdlRouterService extends Service {
private boolean wrongProcess = false;
private boolean initPassed = false;
private boolean hasCalledStartForeground = false;
+ private boolean hasConnectedBefore = false;
boolean firstStart = true;
public static HashMap<String, RegisteredApp> registeredApps;
@@ -215,6 +219,10 @@ public class SdlRouterService extends Service {
private boolean startSequenceComplete = false;
private VehicleType receivedVehicleType;
+ private boolean waitingForBTRuntimePermissions = false;
+ private Handler btPermissionsHandler;
+ private Runnable btPermissionsRunnable;
+ private final static int BT_PERMISSIONS_CHECK_FREQUENCY = 1000;
private ExecutorService packetExecutor = null;
ConcurrentHashMap<TransportType, PacketWriteTaskMaster> packetWriteTaskMasterMap = null;
@@ -365,6 +373,11 @@ public class SdlRouterService extends Service {
switch (msg.what) {
case TransportConstants.ROUTER_REQUEST_BT_CLIENT_CONNECT:
+ //Starting with Android 12 this use case will require the BLUETOOTH_SCAN PERMISSION
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !AndroidTools.isPermissionGranted(BLUETOOTH_SCAN, service.getApplicationContext(), service.getPackageName())) {
+ DebugTool.logError(TAG, "BLUETOOTH_SCAN Permissions not granted for this app");
+ break;
+ }
if (receivedBundle.getBoolean(TransportConstants.CONNECT_AS_CLIENT_BOOLEAN_EXTRA, false)
&& !connectAsClient) { //We check this flag to make sure we don't try to connect over and over again. On D/C we should set to false
//Log.d(TAG,"Attempting to connect as bt client");
@@ -1085,7 +1098,7 @@ public class SdlRouterService extends Service {
*
* @return true if this service is set up correctly
*/
- private boolean initCheck() {
+ private boolean initCheck(boolean isConnectedOverUSB) {
if (!processCheck()) {
DebugTool.logError(TAG, "Not using correct process. Shutting down");
wrongProcess = true;
@@ -1095,6 +1108,14 @@ public class SdlRouterService extends Service {
DebugTool.logError(TAG, "Bluetooth Permission is not granted. Shutting down");
return false;
}
+
+ // If Android 12 or newer make sure we have BLUETOOTH_CONNECT Runtime permission
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, this, this.getPackageName())) {
+ if (!isConnectedOverUSB) { //If BLUETOOTH_CONNECT permission is not granted We want to make sure we are connected over USB
+ return false;
+ }
+ }
+
if (!AndroidTools.isServiceExported(this, new ComponentName(this, this.getClass()))) { //We want to check to see if our service is actually exported
DebugTool.logError(TAG, "Service isn't exported. Shutting down");
return false;
@@ -1256,10 +1277,14 @@ public class SdlRouterService extends Service {
(HashMap<String, Object>) intent.getSerializableExtra(TransportConstants.VEHICLE_INFO_EXTRA)
);
}
+ boolean isConnectedOverUSB = false;
+ if (intent != null && intent.hasExtra(TransportConstants.CONNECTION_TYPE_EXTRA)) {
+ isConnectedOverUSB = TransportConstants.AOA_USB.equalsIgnoreCase(intent.getStringExtra(TransportConstants.CONNECTION_TYPE_EXTRA));
+ }
// Only trusting the first intent received to start the RouterService and run initial checks to avoid a case where an app could send incorrect data after the spp connection has started.
if (firstStart) {
firstStart = false;
- if (!initCheck()) { // Run checks on process and permissions
+ if (!initCheck(isConnectedOverUSB)) { // Run checks on process and permissions
deployNextRouterService();
closeSelf();
return START_REDELIVER_INTENT;
@@ -1296,6 +1321,7 @@ public class SdlRouterService extends Service {
}
boolean confirmedDevice = intent.getBooleanExtra(TransportConstants.CONFIRMED_SDL_DEVICE, false);
int timeout = getNotificationTimeout(address, confirmedDevice);
+ hasConnectedBefore = hasSDLConnected(address);
enterForeground("Waiting for connection...", timeout, false);
resetForegroundTimeOut(timeout);
@@ -1337,6 +1363,10 @@ public class SdlRouterService extends Service {
altTransportTimerHandler = null;
}
+ if (btPermissionsHandler != null && btPermissionsRunnable != null) {
+ btPermissionsHandler.removeCallbacks(btPermissionsRunnable);
+ }
+
DebugTool.logWarning(TAG, "Sdl Router Service Destroyed");
closing = true;
//No need for this Broadcast Receiver anymore
@@ -1499,6 +1529,10 @@ public class SdlRouterService extends Service {
builder = new Notification.Builder(this, SDL_NOTIFICATION_CHANNEL_ID);
}
+ if (hasConnectedBefore && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE);
+ }
+
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)) { //If we are in debug mode, include what app has the router service open
ComponentName name = new ComponentName(this, this.getClass());
builder.setContentTitle("SDL: " + name.getPackageName());
@@ -1521,7 +1555,8 @@ public class SdlRouterService extends Service {
// Create an intent that will be fired when the user clicks the notification.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(SDL_NOTIFICATION_FAQS_PAGE));
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
+ int flag = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0;
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, flag);
builder.setContentIntent(pendingIntent);
if (chronometerLength > (FOREGROUND_TIMEOUT / 1000) && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@@ -1569,12 +1604,22 @@ public class SdlRouterService extends Service {
private void safeStartForeground(int id, Notification notification) {
try {
if (notification == null) {
- //Try the NotificationCompat this time in case there was a previous error
- NotificationCompat.Builder builder =
- new NotificationCompat.Builder(this, SDL_NOTIFICATION_CHANNEL_ID)
+ if (hasConnectedBefore && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ Notification.Builder builder =
+ new Notification.Builder(this, SDL_NOTIFICATION_CHANNEL_ID)
.setContentTitle("SmartDeviceLink")
- .setContentText("Service Running");
- notification = builder.build();
+ .setContentText("Service Running")
+ .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE);
+ notification = builder.build();
+ } else {
+ //Try the NotificationCompat this time in case there was a previous error
+ NotificationCompat.Builder builder =
+ new NotificationCompat.Builder(this, SDL_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("SmartDeviceLink")
+ .setContentText("Service Running");
+
+ notification = builder.build();
+ }
}
startForeground(id, notification);
DebugTool.logInfo(TAG, "Entered the foreground - " + System.currentTimeMillis());
@@ -1684,6 +1729,9 @@ public class SdlRouterService extends Service {
*/
@SuppressWarnings("MissingPermission")
private boolean bluetoothAvailable() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, SdlRouterService.this, SdlRouterService.this.getPackageName())) {
+ return false;
+ }
try {
return (!(BluetoothAdapter.getDefaultAdapter() == null) && BluetoothAdapter.getDefaultAdapter().isEnabled());
} catch (NullPointerException e) { // only for BluetoothAdapter.getDefaultAdapter().isEnabled() call
@@ -1745,6 +1793,11 @@ public class SdlRouterService extends Service {
}
private synchronized void initBluetoothSerialService() {
+ if (waitingForBTRuntimePermissions) {
+ DebugTool.logWarning(TAG, "This app has not been granted the BLUETOOTH_CONNECT runtime permission");
+ return;
+ }
+
if (legacyModeEnabled) {
DebugTool.logInfo(TAG, "Not starting own bluetooth during legacy mode");
return;
@@ -1801,6 +1854,17 @@ public class SdlRouterService extends Service {
startService.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ //Starting in Android 12 we need to start services from a foreground context
+ //To enable developers to be able to start their SdlService from the "background"
+ //we will attach a pendingIntent as an extra to the intent
+ //the developer can use this pendingIntent to start their SdlService from the context of
+ //the active RouterService
+ Intent pending = new Intent();
+ PendingIntent pendingIntent = PendingIntent.getForegroundService(this, (int) System.currentTimeMillis(), pending, PendingIntent.FLAG_MUTABLE | Intent.FILL_IN_COMPONENT);
+ startService.putExtra(TransportConstants.PENDING_INTENT_EXTRA, pendingIntent);
+ }
+
AndroidTools.sendExplicitBroadcast(getApplicationContext(), startService, null);
//HARDWARE_CONNECTED
@@ -1808,6 +1872,31 @@ public class SdlRouterService extends Service {
//If we have clients
notifyClients(createHardwareConnectedMessage(record));
}
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && TransportType.USB.equals(record.getType()) && !AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, SdlRouterService.this, SdlRouterService.this.getPackageName())) {
+ //Delay starting bluetoothTransport when we are connected over USB and the app does not have the BLUETOOTH_CONNECT permissions
+ waitingForBTRuntimePermissions = true;
+ btPermissionsHandler = new Handler(Looper.myLooper());
+ //Continuously Check for the BLUETOOTH_CONNECT Permission
+ btPermissionsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!AndroidTools.isPermissionGranted(BLUETOOTH_CONNECT, SdlRouterService.this, SdlRouterService.this.getPackageName())) {
+ btPermissionsHandler.postDelayed(btPermissionsRunnable, BT_PERMISSIONS_CHECK_FREQUENCY);
+ } else {
+ waitingForBTRuntimePermissions = false;
+ initBluetoothSerialService();
+ final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if(notificationManager != null) {
+ notificationManager.cancel("SDL", TransportConstants.SDL_ERROR_NOTIFICATION_CHANNEL_ID_INT);
+ }
+ }
+ }
+ };
+ btPermissionsHandler.postDelayed(btPermissionsRunnable, BT_PERMISSIONS_CHECK_FREQUENCY);
+ //Present Notification to take user to permissions page for the app
+ showBTPermissionsNotification();
+ }
}
private Message createHardwareConnectedMessage(final TransportRecord record) {
@@ -3805,6 +3894,10 @@ public class SdlRouterService extends Service {
} else {
builder = new Notification.Builder(getApplicationContext(), TransportConstants.SDL_ERROR_NOTIFICATION_CHANNEL_ID);
}
+
+ if (hasConnectedBefore && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE);
+ }
ComponentName name = new ComponentName(this, this.getClass());
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)) { //If we are in debug mode, include what app has the router service open
builder.setContentTitle("SDL: " + name.getPackageName());
@@ -3843,4 +3936,59 @@ public class SdlRouterService extends Service {
DebugTool.logError(TAG, "notifySppError: Unable to retrieve notification Manager service");
}
}
+
+ private void showBTPermissionsNotification() {
+ Notification.Builder builder;
+ if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ builder = new Notification.Builder(getApplicationContext());
+ } else {
+ builder = new Notification.Builder(getApplicationContext(), TransportConstants.SDL_ERROR_NOTIFICATION_CHANNEL_ID);
+ }
+
+ ComponentName name = new ComponentName(this, this.getClass());
+ if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)) { //If we are in debug mode, include what app has the router service open
+ builder.setContentTitle("SDL: " + name.getPackageName());
+ } else {
+ builder.setContentTitle(getString(R.string.notification_title));
+ }
+ builder.setTicker(getString(R.string.sdl_error_notification_channel_name));
+ builder.setContentText(getString(R.string.allow_bluetooth_permissions));
+
+ //We should use icon from library resources if available
+ int trayId = getResources().getIdentifier("sdl_tray_icon", "drawable", getPackageName());
+
+ builder.setSmallIcon(trayId);
+ Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.spp_error);
+ builder.setLargeIcon(icon);
+
+ builder.setOngoing(false);
+ builder.setAutoCancel(true);
+
+ // Create an intent that will be fired when the user clicks the notification.
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package", getPackageName(), null);
+ intent.setData(uri);
+ int flag = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0;
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, flag);
+ builder.setContentIntent(pendingIntent);
+
+ final String tag = "SDL";
+ //Now we need to add a notification channel
+ final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager != null) {
+ notificationManager.cancel(tag, TransportConstants.SDL_ERROR_NOTIFICATION_CHANNEL_ID_INT);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel notificationChannel = new NotificationChannel(TransportConstants.SDL_ERROR_NOTIFICATION_CHANNEL_ID, getString(R.string.sdl_error_notification_channel_name), NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.enableLights(true);
+ notificationChannel.enableVibration(true);
+ notificationChannel.setShowBadge(false);
+ notificationManager.createNotificationChannel(notificationChannel);
+ builder.setChannelId(notificationChannel.getId());
+ }
+ Notification notification = builder.build();
+ notificationManager.notify(tag, TransportConstants.SDL_ERROR_NOTIFICATION_CHANNEL_ID_INT, notification);
+ } else {
+ DebugTool.logError(TAG, "Unable to retrieve notification Manager service");
+ }
+ }
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java
index 8e54de9a1..22ee5d1db 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java
@@ -167,6 +167,7 @@ public class USBAccessoryAttachmentActivity extends Activity {
return;
}
serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT);
+ serviceIntent.putExtra(TransportConstants.CONNECTION_TYPE_EXTRA, TransportConstants.AOA_USB);
ComponentName startedService;
try {
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java
index 4f554f0d7..2700d35d9 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java
@@ -48,6 +48,7 @@ import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.BatteryManager;
+import android.os.Build;
import android.os.Bundle;
import androidx.annotation.Nullable;
@@ -69,6 +70,9 @@ import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+
public class AndroidTools {
@@ -199,6 +203,25 @@ public class AndroidTools {
return sdlAppInfoList;
}
+ public static boolean isPermissionGranted(String permissionName, Context context, String servicePackageName) {
+ PackageManager packageManager = context.getPackageManager();
+ if (packageManager == null) {
+ return false;
+ }
+ PackageInfo packageInfo;
+ try {
+ packageInfo = packageManager.getPackageInfo(servicePackageName, PackageManager.GET_PERMISSIONS);
+ if (packageInfo == null) {
+ return false;
+ }
+ int permissionResult = packageManager.checkPermission(permissionName, packageInfo.packageName);
+ return permissionResult == PackageManager.PERMISSION_GRANTED;
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ DebugTool.logError(TAG, "servicePackageName not found while checking " + permissionName + " permission", e);
+ return false;
+ }
+ }
/**
* Sends the provided intent to the specified destinations making it an explicit intent, rather
diff --git a/android/sdl_android/src/main/res/values/sdl.xml b/android/sdl_android/src/main/res/values/sdl.xml
index 7bb919f0b..bd96c54ab 100644
--- a/android/sdl_android/src/main/res/values/sdl.xml
+++ b/android/sdl_android/src/main/res/values/sdl.xml
@@ -2,7 +2,7 @@
<resources>
<string name="sdl_router_service_version_name" translatable="false">sdl_router_version</string>
- <integer name="sdl_router_service_version_value">15</integer>
+ <integer name="sdl_router_service_version_value">16</integer>
<string name="sdl_router_service_is_custom_name" translatable="false">sdl_custom_router</string>
<string name="sdl_oem_vehicle_type_filter_name" translatable="false">sdl_oem_vehicle_type</string>
diff --git a/android/sdl_android/src/main/res/values/strings.xml b/android/sdl_android/src/main/res/values/strings.xml
index 89322299f..36a089366 100644
--- a/android/sdl_android/src/main/res/values/strings.xml
+++ b/android/sdl_android/src/main/res/values/strings.xml
@@ -5,6 +5,7 @@
<string name="lockscreen_device_image_description">Device Icon</string>
<string name="default_lockscreen_warning_message">Swipe down to dismiss, acknowledging that you are not the driver.</string>
<string name="spp_out_of_resource">Too many apps are using Bluetooth</string>
+ <string name="allow_bluetooth_permissions">Please grant this app the Nearby Devices Permission to use bluetooth</string>
<string name="notification_title">SmartDeviceLink</string>
<string name="sdl_error_notification_channel_name">SDL Error</string>
</resources> \ No newline at end of file