Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android 14 foreground service type required #1860

Merged
merged 44 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 20 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,28 @@
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 pendingIntent;
private Context storedContext;
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
private Intent storedIntent;

@Override
public void onSdlEnabled(Context context, Intent intent) {
DebugTool.logInfo(TAG, "SDL Enabled");
Expand All @@ -24,6 +34,16 @@ 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.ServicePermissionUtil.hasForegroundServiceTypePermission(context)) {
requestUsbAccessory(context);
storedIntent = intent;
storedContext = context;
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
this.pendingIntent = 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 +76,43 @@ public void onReceive(Context context, Intent intent) {
public String getSdlServiceName() {
return SdlService.class.getSimpleName();
}

private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
joeygrover marked this conversation as resolved.
Show resolved Hide resolved

public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action) && storedContext != null && storedIntent != null && pendingIntent != null) {
if (AndroidTools.ServicePermissionUtil.hasForegroundServiceTypePermission(storedContext)) {
try {
pendingIntent.send(storedContext, 0, storedIntent);
} catch (PendingIntent.CanceledException 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);
if (manager.getAccessoryList() == null) {
return;
}
joeygrover marked this conversation as resolved.
Show resolved Hide resolved
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
context.registerReceiver(mUsbReceiver, filter, Context.RECEIVER_EXPORTED);
}
joeygrover marked this conversation as resolved.
Show resolved Hide resolved
for (final UsbAccessory usbAccessory : manager.getAccessoryList()) {
joeygrover marked this conversation as resolved.
Show resolved Hide resolved
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;
joeygrover marked this conversation as resolved.
Show resolved Hide resolved

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.ServicePermissionUtil.hasForegroundServiceTypePermission(service.getApplicationContext())) {
service.deployNextRouterService(parcelFileDescriptor);
closeUSBAccessoryAttachmentActivity(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();
}
}
closeUSBAccessoryAttachmentActivity(msg);

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

private void closeUSBAccessoryAttachmentActivity(Message msg) {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1166,7 +1177,7 @@ 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.
*/
protected void deployNextRouterService() {
protected void deployNextRouterService(ParcelFileDescriptor usbPfd) {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
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 +1189,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) {
UsbTransferProvider usbTransferProvider = new UsbTransferProvider(getApplicationContext(), nextUp.getRouterServiceComponentName(), usbPfd, new UsbTransferProvider.UsbTransferCallback() {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
@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 +1307,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
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;
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
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

}

public UsbTransferProvider(Context context, ComponentName service, ParcelFileDescriptor usbPfd, UsbTransferCallback callback) {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -173,7 +194,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 @@ -32,6 +32,7 @@

package com.smartdevicelink.util;

import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
Expand All @@ -47,10 +48,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 @@ -392,4 +396,31 @@ public static void saveVehicleType(Context context, VehicleType vehicleType, Str
return null;
}
}

public static class ServicePermissionUtil {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
public static boolean hasUsbAccessoryPermission(Context context) {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
if (manager == null || manager.getAccessoryList() == null) {
return false;
}
for (final UsbAccessory usbAccessory : manager.getAccessoryList()) {
if (manager.hasPermission(usbAccessory)) {
return true;
}
}
return false;
}

public static boolean checkPermission(Context applicationContext, String permission) {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(applicationContext, permission);
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
}

public static boolean hasForegroundServiceTypePermission(Context context) {
JulianKast marked this conversation as resolved.
Show resolved Hide resolved
// if Build is less than Android 14, we don't need either permission to enter the foreground.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
return true;
}
return ServicePermissionUtil.checkPermission(context, Manifest.permission.BLUETOOTH_CONNECT) || ServicePermissionUtil.hasUsbAccessoryPermission(context);
}
}
}
Loading