Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c09d6d1
Add FGS type connectedDevice to Manifest
JulianKast Aug 10, 2023
97f0fd2
Add new UsbTransferProvider constructor
JulianKast Aug 10, 2023
773b61a
Modify deployNextRouterService to take in a ParcelFileDescriptor in t…
JulianKast Aug 10, 2023
e5d61fc
Add ServicePermissionUtil to AndroidTools to check for permissions fo…
JulianKast Aug 10, 2023
6543c44
When the PFD is received in the RS, check for fgs permissions and dep…
JulianKast Aug 10, 2023
9c3ad2a
update logic in UsbTransferProvider.bindService for starting the RS i…
JulianKast Aug 10, 2023
fe87adb
add logic to SdlReceiver to check if we have permission before starti…
JulianKast Aug 11, 2023
227ca75
add checks for null
JulianKast Aug 11, 2023
6e4c1c3
remove debugging log
JulianKast Aug 15, 2023
aedfbb9
update unit test api level to api 34
JulianKast Aug 15, 2023
b414113
revert api level for unit test
JulianKast Aug 16, 2023
745719d
update unit test jdk to 17
JulianKast Aug 17, 2023
53ad1fc
Revert test change in yml file
JulianKast Aug 18, 2023
cf351c9
Add try catch for entering foreground in SdlService for TCP connections
JulianKast Aug 18, 2023
2d49e36
remove extra space in RS
JulianKast Aug 23, 2023
c31afdf
add spacing between methods
JulianKast Aug 23, 2023
ad5f699
Add a targetApi to FOREGROUND_SERVICE_CONNECTED_DEVICE
JulianKast Sep 1, 2023
0b6be0d
move logic around and add java docs
JulianKast Sep 1, 2023
a452ff2
Merge branch 'develop' into bugfix/issue_1859_foreground_service_type
JulianKast Oct 25, 2023
97a989f
Merge branch 'develop' into bugfix/issue_1859_foreground_service_type
JulianKast Oct 25, 2023
647c5d9
Refactor method name closeUSBAccessoryAttachmentActivity to acknowled…
JulianKast Nov 6, 2023
4314d9d
Remove inner class ServicePermissionUtil in AndroidTool
JulianKast Nov 6, 2023
bb4940e
add null check to hasUsbAccessoryPermission and checkPermission in An…
JulianKast Nov 6, 2023
404725a
Make new UsbTransferProvider constructor protected
JulianKast Nov 6, 2023
2ccb87d
Remove reference to UsbTransferProvider in RS
JulianKast Nov 6, 2023
177456f
Clear out context in UsbTransferProvider.cancel()
JulianKast Nov 6, 2023
2de8c9e
Rename storedIntent and storedContext to cachedIntent and cachedConte…
JulianKast Nov 7, 2023
3275d18
Add javaDocs to AndroidTools.hasUsbAccessoryPermission
JulianKast Nov 7, 2023
5a3ff07
Add JavaDocs to AndroidTools.checkPermission
JulianKast Nov 7, 2023
8b16303
Add java doc to AndroidTools.hasForegroundServiceTypePermission
JulianKast Nov 7, 2023
6b65585
Update javaDocs for deployNextRouterService in the RS
JulianKast Nov 7, 2023
5bad185
Use context from mUsbReceiver instead of storing it in SdlReceiver
JulianKast Nov 7, 2023
762ffc9
Merge branch 'develop' into bugfix/issue_1859_foreground_service_type
JulianKast Nov 20, 2023
3727e30
Rename PendingIntent to pendingIntentToStartService and cachedIntent …
JulianKast Nov 28, 2023
4c9833e
Rename mUsbReceiver to usbPermissionReceiver
JulianKast Nov 28, 2023
c05011a
Clear out intents if there is no available USB device
JulianKast Nov 28, 2023
97b138e
Use newly created method in AndroidTools to register receiver for And…
JulianKast Nov 28, 2023
92905fa
Try to let UsbTransferProvider unbind before setting context to null
JulianKast Nov 28, 2023
8e9299a
Rename applicaitonContext to context, fix JavaDocs
JulianKast Nov 28, 2023
a12b93c
Update startAltTransportTimer in RS
JulianKast Nov 28, 2023
1d8e2cd
Unregister receiver to prevent leak, change exception catching to a g…
JulianKast Nov 28, 2023
2114255
Remove whitespace
JulianKast Nov 28, 2023
4840581
remove whitespace
JulianKast Nov 28, 2023
8333284
Unregister usbPermissionReceiver, revert unregister change for SdlRec…
JulianKast Nov 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions android/hello_sdl_android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
tools:targetApi="31"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"
tools:targetApi="33"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
tools:targetApi="34"/>
<uses-permission android:name="android.permission.INTERNET" />
<!-- Required to check if WiFi is enabled -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package com.sdl.hellosdlandroid;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.Build;

import com.smartdevicelink.transport.SdlBroadcastReceiver;
import com.smartdevicelink.transport.SdlRouterService;
import com.smartdevicelink.transport.TransportConstants;
import com.smartdevicelink.util.AndroidTools;
import com.smartdevicelink.util.DebugTool;

public class SdlReceiver extends SdlBroadcastReceiver {
private static final String TAG = "SdlBroadcastReceiver";

private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
private PendingIntent pendingIntentToStartService;
private Intent startSdlServiceIntent;

@Override
public void onSdlEnabled(Context context, Intent intent) {
DebugTool.logInfo(TAG, "SDL Enabled");
Expand All @@ -24,6 +33,15 @@ public void onSdlEnabled(Context context, Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA);
if (pendingIntent != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
if (!AndroidTools.hasForegroundServiceTypePermission(context)) {
requestUsbAccessory(context);
startSdlServiceIntent = intent;
this.pendingIntentToStartService = pendingIntent;
DebugTool.logInfo(TAG, "Permission missing for ForegroundServiceType connected device." + context);
return;
}
}
try {
pendingIntent.send(context, 0, intent);
} catch (PendingIntent.CanceledException e) {
Expand Down Expand Up @@ -56,4 +74,47 @@ public void onReceive(Context context, Intent intent) {
public String getSdlServiceName() {
return SdlService.class.getSimpleName();
}

private final BroadcastReceiver usbPermissionReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action) && context != null && startSdlServiceIntent != null && pendingIntentToStartService != null) {
if (AndroidTools.hasForegroundServiceTypePermission(context)) {
try {
pendingIntentToStartService.send(context, 0, startSdlServiceIntent);
context.unregisterReceiver(this);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};

/**
* Request permission from USB Accessory if USB accessory is not null.
* If the user has not granted the BLUETOOTH_CONNECT permission,
* we can request the USB Accessory permission to satisfy the requirements for
* FOREGROUND_SERVICE_CONNECTED_DEVICE and can start our service and allow
* it to enter the foreground. FOREGROUND_SERVICE_CONNECTED_DEVICE is a requirement
* in Android 14
*/
private void requestUsbAccessory(Context context) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();
if (accessoryList == null || accessoryList.length == 0) {
startSdlServiceIntent = null;
pendingIntentToStartService = null;
return;
}
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);

AndroidTools.registerReceiver(context, usbPermissionReceiver, filter,
Context.RECEIVER_EXPORTED);

for (final UsbAccessory usbAccessory : accessoryList) {
manager.requestPermission(usbAccessory, mPermissionIntent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,25 @@ public void onCreate() {
@SuppressLint("NewApi")
public void enterForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(BuildConfig.SDL_APP_ID, "SdlService", NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
Notification.Builder builder = new Notification.Builder(this, channel.getId())
.setContentTitle("Connected through SDL")
.setSmallIcon(R.drawable.ic_sdl);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE);
try {
NotificationChannel channel = new NotificationChannel(BuildConfig.SDL_APP_ID, "SdlService", NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
Notification.Builder builder = new Notification.Builder(this, channel.getId())
.setContentTitle("Connected through SDL")
.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);
}
Notification serviceNotification = builder.build();
startForeground(FOREGROUND_SERVICE_ID, serviceNotification);
} catch (Exception e) {
// This should only catch for TCP connections on Android 14+ due to needing
// permissions for ForegroundServiceType ConnectedDevice that don't make sense for
// a TCP connection
DebugTool.logError(TAG, "Unable to start service in foreground", e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,14 @@ public void handleMessage(Message msg) {
ParcelFileDescriptor parcelFileDescriptor = (ParcelFileDescriptor) msg.obj;

if (parcelFileDescriptor != null) {
// Added requirements with Android 14, Checking if we have proper permission to enter the foreground for Foreground service type connectedDevice.
// If we do not have permission to enter the Foreground, we pass off hosting the RouterService to another app.
if (!AndroidTools.hasForegroundServiceTypePermission(service.getApplicationContext())) {
service.deployNextRouterService(parcelFileDescriptor);
acknowledgeUSBAccessoryReceived(msg);
return;
}

//New USB constructor with PFD
service.usbTransport = new MultiplexUsbTransport(parcelFileDescriptor, service.usbHandler, msg.getData());

Expand Down Expand Up @@ -900,16 +908,7 @@ public void onReceive(Context context, Intent intent) {


}

if (msg.replyTo != null) {
Message message = Message.obtain();
message.what = TransportConstants.ROUTER_USB_ACC_RECEIVED;
try {
msg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
acknowledgeUSBAccessoryReceived(msg);

break;
case TransportConstants.ALT_TRANSPORT_CONNECTED:
Expand All @@ -919,6 +918,18 @@ public void onReceive(Context context, Intent intent) {
break;
}
}

private void acknowledgeUSBAccessoryReceived(Message msg) {
if (msg.replyTo != null) {
Message message = Message.obtain();
message.what = TransportConstants.ROUTER_USB_ACC_RECEIVED;
try {
msg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}

/* **************************************************************************************************************************************
Expand Down Expand Up @@ -1164,9 +1175,16 @@ public void onCreate() {
}

/**
* The method will attempt to start up the next router service in line based on the sorting criteria of best router service.
* The method will attempt to start up the next router service in line based on the sorting
* criteria of best router service.
* If a ParcelFileDescriptor is not null, we pass it along to the next RouterService to give
* it a chane to connected via AOA. This only happens on Android 14 and above when the app
* selected to host the RouterService does not satisfy the requirements for permission
* FOREGROUND_SERVICE_CONNECTED_DEVICE. By passing along the usbPfd, it will give the next
* RouterService selected a chance to connect.
* @param usbPfd a ParcelFileDescriptor used for AOA connections.
*/
protected void deployNextRouterService() {
protected void deployNextRouterService(ParcelFileDescriptor usbPfd) {
List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(getApplicationContext(), new SdlAppInfo.BestRouterComparator(), null);
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
ComponentName name = new ComponentName(this, this.getClass());
Expand All @@ -1178,11 +1196,25 @@ protected void deployNextRouterService() {
SdlAppInfo nextUp = sdlAppInfoList.get(i + 1);
Intent serviceIntent = new Intent();
serviceIntent.setComponent(nextUp.getRouterServiceComponentName());
if (usbPfd != null) {
serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT);
serviceIntent.putExtra(TransportConstants.CONNECTION_TYPE_EXTRA, TransportConstants.AOA_USB);
serviceIntent.putExtra(FOREGROUND_EXTRA, true);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
startService(serviceIntent);
} else {
try {
startForegroundService(serviceIntent);
if (usbPfd != null) {
new UsbTransferProvider(getApplicationContext(), nextUp.getRouterServiceComponentName(), usbPfd, new UsbTransferProvider.UsbTransferCallback() {
@Override
public void onUsbTransferUpdate(boolean success) {
closeSelf();
}
});
}

} catch (Exception e) {
DebugTool.logError(TAG, "Unable to start next SDL router service. " + e.getMessage());
}
Expand Down Expand Up @@ -1282,7 +1314,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
if (firstStart) {
firstStart = false;
if (!initCheck(isConnectedOverUSB)) { // Run checks on process and permissions
deployNextRouterService();
deployNextRouterService(null);
closeSelf();
return START_REDELIVER_INTENT;
}
Expand Down Expand Up @@ -2632,15 +2664,11 @@ protected static LocalRouterService getLocalRouterService(Intent launchIntent, C
* This method is used to check for the newest version of this class to make sure the latest and greatest is up and running.
*/
private void startAltTransportTimer() {
if (Looper.myLooper() == null) {
Looper.prepare();
}

if (altTransportTimerHandler != null && altTransportTimerRunnable != null) {
altTransportTimerHandler.removeCallbacks(altTransportTimerRunnable);
}

altTransportTimerHandler = new Handler(Looper.myLooper());
altTransportTimerHandler = new Handler(Looper.getMainLooper());
altTransportTimerRunnable = new Runnable() {
public void run() {
altTransportTimerHandler = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import android.content.ServiceConnection;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
Expand Down Expand Up @@ -130,6 +131,26 @@ public UsbTransferProvider(Context context, ComponentName service, UsbAccessory

}

protected UsbTransferProvider(Context context, ComponentName service, ParcelFileDescriptor usbPfd, UsbTransferCallback callback) {
if (context == null || service == null || usbPfd == null) {
throw new IllegalStateException("Supplied params are not correct. Context == null? " + (context == null) + " ComponentName == null? " + (service == null) + " Usb PFD == null? " + usbPfd);
}
if (usbPfd.getFileDescriptor() != null && usbPfd.getFileDescriptor().valid()) {
this.context = context;
this.routerService = service;
this.callback = callback;
this.clientMessenger = new Messenger(new ClientHandler(this));
this.usbPfd = usbPfd;
checkIsConnected();
} else {
DebugTool.logError(TAG, "Unable to open accessory");
clientMessenger = null;
if (callback != null) {
callback.onUsbTransferUpdate(false);
}
}
}

@SuppressLint("NewApi")
private ParcelFileDescriptor getFileDescriptor(UsbAccessory accessory, Context context) {
if (AndroidTools.isUSBCableConnected(context)) {
Expand Down Expand Up @@ -161,6 +182,7 @@ public void cancel() {
if (isBound) {
unBindFromService();
}
context = null;
}

private boolean bindToService() {
Expand All @@ -173,7 +195,12 @@ private boolean bindToService() {
Intent bindingIntent = new Intent();
bindingIntent.setClassName(this.routerService.getPackageName(), this.routerService.getClassName());//This sets an explicit intent
//Quickly make sure it's just up and running
context.startService(bindingIntent);
bindingIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(bindingIntent);
} else {
context.startForegroundService(bindingIntent);
}
bindingIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_USB_PROVIDER);
return context.bindService(bindingIntent, routerConnection, Context.BIND_AUTO_CREATE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
Expand All @@ -49,10 +50,13 @@
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

import com.smartdevicelink.marshal.JsonRPCMarshaller;
import com.smartdevicelink.proxy.rpc.VehicleType;
Expand Down Expand Up @@ -417,4 +421,54 @@ public static void registerReceiver(Context context, BroadcastReceiver receiver,
}
}
}

/**
* A helper method is used to see if this app has permission for UsbAccessory.
* We need UsbAccessory permission if we are plugged in via AOA and do not have BLUETOOTH_CONNECT
* permission for our service to enter the foreground on Android UPSIDE_DOWN_CAKE and greater
* @param context a context that will be used to check the permission.
* @return true if connected via AOA and we have UsbAccessory permission.
*/
public static boolean hasUsbAccessoryPermission(Context context) {
if (context == null) {
return false;
}
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (manager == null || manager.getAccessoryList() == null) {
return false;
}
for (final UsbAccessory usbAccessory : manager.getAccessoryList()) {
if (manager.hasPermission(usbAccessory)) {
return true;
}
}
return false;
}

/**
* Helper method used to check permission passed in.
* @param context Context used to check permission
* @param permission String representing permission that is being checked.
* @return true if app has permission.
*/
public static boolean checkPermission(Context context, String permission) {
if (context == null) {
return false;
}
return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission);
}

/**
* A helper method used for Android 14 or greater to check if app has necessary permissions
* to have a service enter the foreground.
* @param context context used to check permissions.
* @return true if app has permission to have a service enter foreground or if Android version < 14
*/
public static boolean hasForegroundServiceTypePermission(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
return true;
}
return checkPermission(context,
Manifest.permission.BLUETOOTH_CONNECT) || hasUsbAccessoryPermission(context);
}
}