diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index d8d652c77d..db6fc40aa9 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -3,7 +3,7 @@ name: GitHub CI
on: [push, pull_request, workflow_dispatch]
jobs:
- test:
+ test_Android:
runs-on: macOS-latest
steps:
@@ -15,7 +15,7 @@ jobs:
- name: Setup JDK
uses: actions/setup-java@v1
with:
- java-version: 1.8
+ java-version: 11
- name: Sdl Android Tests
# For more info, please check out: https://github.com/marketplace/actions/android-emulator-runner
@@ -26,6 +26,25 @@ jobs:
- name: Hello Sdl Android Tests
run: ./android/gradlew -p ./android/hello_sdl_android test
+
+ - name: Codecov
+ uses: codecov/codecov-action@v1.0.13
+ with:
+ yml: ./codecov.yml
+
+ test_Java:
+ runs-on: macOS-latest
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ submodules: true
+
+ - name: Setup JDK
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
- name: Sdl JavaSE Tests
run: ./javaSE/gradlew -p ./javaSE/javaSE test
@@ -36,7 +55,3 @@ jobs:
- name: Sdl JavaEE Tests
run: ./javaEE/gradlew -p ./javaEE/javaEE test
- - name: Codecov
- uses: codecov/codecov-action@v1.0.13
- with:
- yml: ./codecov.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2bcecba4e4..7da36c3168 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-# 5.6.1 Release Notes
+# 5.7.0 Release Notes
## Summary:
@@ -6,8 +6,44 @@
|---|---|
| **Protocol** | 5.4.1 |
| **RPC** | 8.0.0 |
-| **Tested Targeting** | Android 33 |
+| **Tested Targeting** | Android 34 |
## Bug Fix / Enhancements:
-- [Nearby Devices permission on Android 12 and Above #1839](https://github.com/smartdevicelink/sdl_java_suite/issues/1839)
\ No newline at end of file
+- [Add pending intent to ping intents] (https://github.com/smartdevicelink/sdl_java_suite/pull/1844
+
+- [Fixes #1845 typo in SdlBroadcastReceiver for uncaughtException handler](https://github.com/smartdevicelink/sdl_java_suite/pull/1846
+
+- [Bugfix/issue #1842 - NPE in BaseTextAndGraphicsManager](https://github.com/smartdevicelink/sdl_java_suite/pull/1847
+
+- [Fix case for UncaughtExceptionHandler in SdlBroadcastReceiver for RemoteServiceException](https://github.com/smartdevicelink/sdl_java_suite/pull/1849
+
+- [Update gradle to 7.4.2](https://github.com/smartdevicelink/sdl_java_suite/pull/1853
+
+- [Fix NPE in SdlRouterService](https://github.com/smartdevicelink/sdl_java_suite/pull/1870
+
+- [Bugfix/issue 1867 Bluetooth notification error fix](https://github.com/smartdevicelink/sdl_java_suite/pull/1868
+
+- [Clean LifecycleManager before closing](https://github.com/smartdevicelink/sdl_java_suite/pull/1866
+
+- [Move router service message sending to its own thread](https://github.com/smartdevicelink/sdl_java_suite/pull/1871
+
+- [Bugfix/issue 1863 update gradle build variants for hello_sdl](https://github.com/smartdevicelink/sdl_java_suite/pull/1864
+
+- [Android 14 add package to PendingIntents in RouterService](https://github.com/smartdevicelink/sdl_java_suite/pull/1862
+
+- [Android 14 Runtime registered broadcast receivers export behavior](https://github.com/smartdevicelink/sdl_java_suite/pull/1858
+
+- [Add logic to handle RAI response failure](https://github.com/smartdevicelink/sdl_java_suite/pull/1873
+
+- [Android 14 foreground service type required](https://github.com/smartdevicelink/sdl_java_suite/pull/1860
+
+- [Update integration validator with latest permissions and checks for exported](https://github.com/smartdevicelink/sdl_java_suite/pull/1875
+
+- [Fix SDLLockScreenActivityEspressoTest for newer devices](https://github.com/smartdevicelink/sdl_java_suite/pull/1877
+
+- [Remove only app logic for device listener start](https://github.com/smartdevicelink/sdl_java_suite/pull/1879)
+
+- [Fix NPE in TransportBroker](https://github.com/smartdevicelink/sdl_java_suite/pull/1882)
+
+**Full Changelog**: https://github.com/smartdevicelink/sdl_java_suite/compare/5.6.1...RC_5.7.0
\ No newline at end of file
diff --git a/VERSION b/VERSION
index b7c75422bc..3b867ccd76 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.6.1
+5.7.0
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
index 125800c07f..b6c2dd894d 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.1'
+ classpath 'com.android.tools.build:gradle:7.4.2'
// NOTE: Do not place your application dependencies here; they belong
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index d62774b231..c1077f26d3 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
diff --git a/android/hello_sdl_android/build.gradle b/android/hello_sdl_android/build.gradle
index fa8a08a932..a3f5563248 100755
--- a/android/hello_sdl_android/build.gradle
+++ b/android/hello_sdl_android/build.gradle
@@ -1,15 +1,21 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 33
+ compileSdkVersion 34
defaultConfig {
applicationId "com.sdl.hellosdlandroid"
minSdkVersion 16
- targetSdkVersion 33
+ targetSdkVersion 34
versionCode 1
versionName "1.0"
+ resValue "string", "app_name", "Hello Sdl Android"
buildConfigField 'String', 'APP_TYPE', '"DEFAULT"'
buildConfigField 'String', 'REQUIRE_AUDIO_OUTPUT', '"FALSE"'
+ buildConfigField 'String', 'SDL_APP_NAME', '"Hello Sdl"'
+ buildConfigField 'String', 'SDL_APP_ID', '"8678309"'
+ manifestPlaceholders = [
+ appIcon: "@mipmap/ic_launcher"
+ ]
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
diff --git a/android/hello_sdl_android/src/main/AndroidManifest.xml b/android/hello_sdl_android/src/main/AndroidManifest.xml
index 6b577c5a25..cdb26819db 100755
--- a/android/hello_sdl_android/src/main/AndroidManifest.xml
+++ b/android/hello_sdl_android/src/main/AndroidManifest.xml
@@ -8,6 +8,8 @@
tools:targetApi="31"/>
+
@@ -28,7 +30,7 @@
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 e130d3d78a..1facc6868c 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,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");
@@ -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) {
@@ -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);
+ }
+ }
}
\ 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 be147af0b2..1b42e0179d 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
@@ -60,10 +60,8 @@ public class SdlService extends Service {
private static final String TAG = "SDL Service";
- private static final String APP_NAME = "Hello Sdl";
private static final String APP_NAME_ES = "Hola Sdl";
private static final String APP_NAME_FR = "Bonjour Sdl";
- private static final String APP_ID = "8678309";
private static final String ICON_FILENAME = "hello_sdl_icon.png";
private static final String SDL_IMAGE_FILENAME = "sdl_full_image.png";
@@ -104,18 +102,25 @@ public void onCreate() {
@SuppressLint("NewApi")
public void enterForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(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);
}
}
}
@@ -161,14 +166,14 @@ private void startProxy() {
} else {
securityLevel = MultiplexTransportConfig.FLAG_MULTI_SECURITY_OFF;
}
- transport = new MultiplexTransportConfig(this, APP_ID, securityLevel);
+ transport = new MultiplexTransportConfig(this, BuildConfig.SDL_APP_ID, securityLevel);
if (BuildConfig.REQUIRE_AUDIO_OUTPUT.equals("TRUE") ) {
((MultiplexTransportConfig)transport).setRequiresAudioSupport(true);
}
} else if (BuildConfig.TRANSPORT.equals("TCP")) {
transport = new TCPTransportConfig(TCP_PORT, DEV_MACHINE_IP_ADDRESS, true);
} else if (BuildConfig.TRANSPORT.equals("MULTI_HB")) {
- MultiplexTransportConfig mtc = new MultiplexTransportConfig(this, APP_ID, MultiplexTransportConfig.FLAG_MULTI_SECURITY_OFF);
+ MultiplexTransportConfig mtc = new MultiplexTransportConfig(this, BuildConfig.SDL_APP_ID, MultiplexTransportConfig.FLAG_MULTI_SECURITY_OFF);
mtc.setRequiresHighBandwidth(true);
transport = mtc;
}
@@ -215,8 +220,8 @@ public void onError(String info, Exception e) {
@Override
public LifecycleConfigurationUpdate managerShouldUpdateLifecycle(Language language, Language hmiLanguage) {
boolean isNeedUpdate = false;
- String appName = APP_NAME;
- String ttsName = APP_NAME;
+ String appName = BuildConfig.SDL_APP_NAME;
+ String ttsName = BuildConfig.SDL_APP_NAME;
switch (language) {
case ES_MX:
isNeedUpdate = true;
@@ -260,7 +265,7 @@ public boolean onSystemInfoReceived(SystemInfo systemInfo) {
SdlArtwork appIcon = new SdlArtwork(ICON_FILENAME, FileType.GRAPHIC_PNG, R.mipmap.ic_launcher, true);
// The manager builder sets options for your session
- SdlManager.Builder builder = new SdlManager.Builder(this, APP_ID, APP_NAME, listener);
+ SdlManager.Builder builder = new SdlManager.Builder(this, BuildConfig.SDL_APP_ID, BuildConfig.SDL_APP_NAME, listener);
builder.setAppTypes(appType);
builder.setTransportType(transport);
builder.setAppIcon(appIcon);
@@ -375,7 +380,7 @@ private void performWelcomeSpeak() {
*/
private void performWelcomeShow() {
sdlManager.getScreenManager().beginTransaction();
- sdlManager.getScreenManager().setTextField1(APP_NAME);
+ sdlManager.getScreenManager().setTextField1(BuildConfig.SDL_APP_NAME);
sdlManager.getScreenManager().setTextField2(WELCOME_SHOW);
sdlManager.getScreenManager().setPrimaryGraphic(new SdlArtwork(SDL_IMAGE_FILENAME, FileType.GRAPHIC_PNG, R.drawable.sdl, true));
sdlManager.getScreenManager().commit(new CompletionListener() {
diff --git a/android/hello_sdl_android/src/main/res/values/strings.xml b/android/hello_sdl_android/src/main/res/values/strings.xml
index 6f96374c61..12b248c844 100755
--- a/android/hello_sdl_android/src/main/res/values/strings.xml
+++ b/android/hello_sdl_android/src/main/res/values/strings.xml
@@ -1,7 +1,6 @@
- Hello Sdl Android
Hello SDL!
Settings
diff --git a/android/sdl_android/build.gradle b/android/sdl_android/build.gradle
index 8bf0122f33..96d498fe15 100644
--- a/android/sdl_android/build.gradle
+++ b/android/sdl_android/build.gradle
@@ -1,11 +1,11 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 33
+ compileSdkVersion 34
defaultConfig {
minSdkVersion 16
- targetSdkVersion 33
- versionCode 25
+ targetSdkVersion 34
+ versionCode 26
versionName new File(projectDir.path, ('/../../VERSION')).text.trim()
buildConfigField "String", "VERSION_NAME", '\"' + versionName + '\"'
resValue "string", "SDL_LIB_VERSION", '\"' + versionName + '\"'
@@ -23,8 +23,8 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
@@ -51,12 +51,12 @@ dependencies {
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0'
testImplementation 'junit:junit:4.12'
- testImplementation 'org.mockito:mockito-core:3.0.0'
- androidTestImplementation 'org.mockito:mockito-core:3.0.0'
- androidTestImplementation 'org.mockito:mockito-android:3.0.0'
+ testImplementation 'org.mockito:mockito-core:5.7.0'
+ androidTestImplementation 'org.mockito:mockito-core:5.7.0'
+ androidTestImplementation 'org.mockito:mockito-android:5.7.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
- androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
}
buildscript {
diff --git a/android/sdl_android/src/androidTest/AndroidManifest.xml b/android/sdl_android/src/androidTest/AndroidManifest.xml
index 559fccf22b..5f488ca929 100644
--- a/android/sdl_android/src/androidTest/AndroidManifest.xml
+++ b/android/sdl_android/src/androidTest/AndroidManifest.xml
@@ -1,4 +1,4 @@
-
@@ -16,7 +16,7 @@
-
+
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java
index 1315d2b668..9661b752b1 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java
@@ -113,7 +113,7 @@ public void tearDown() throws Exception {
assertNull(csm.currentSystemContext);
assertNull(csm.defaultMainWindowCapability);
- assertEquals(csm.transactionQueue.getTasksAsList().size(), 0);
+ assertNull(csm.transactionQueue);
assertFalse(csm.isVROptional);
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/transport/TransportBrokerTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/transport/TransportBrokerTest.java
index 977bb30201..4ee6926aa7 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/transport/TransportBrokerTest.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/transport/TransportBrokerTest.java
@@ -117,24 +117,24 @@ public void testSendMessageToRouterService() {
broker.routerServiceMessenger = null;
broker.isBound = true;
- assertFalse(broker.sendMessageToRouterService(message));
+ assertFalse(broker.sendMessageToRouterService(message, 0));
broker.routerServiceMessenger = new Messenger(handler); //So it's not ambiguous
broker.isBound = false;
- assertFalse(broker.sendMessageToRouterService(message));
+ assertFalse(broker.sendMessageToRouterService(message, 0));
broker.isBound = true;
broker.registeredWithRouterService = true;
message = null;
- assertFalse(broker.sendMessageToRouterService(message));
+ assertFalse(broker.sendMessageToRouterService(message, 0));
message = new Message();
- assertTrue(broker.sendMessageToRouterService(message));
+ assertTrue(broker.sendMessageToRouterService(message, 0));
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java
index 667522372a..3c625f3b40 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java
@@ -437,6 +437,11 @@ public void onDecoderError(Exception e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
decoder = new AudioDecoder(audioSource, context.get(), sdlSampleRate, sdlSampleType, decoderListener);
} else {
+ if (getTransactionQueue() == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot push audio source");
+ finish(completionListener, false);
+ return;
+ }
// this BaseAudioDecoder subclass uses methods deprecated with api 21
decoder = new AudioDecoderCompat(getTransactionQueue(), audioSource, context.get(), sdlSampleRate, sdlSampleType, decoderListener);
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java
index ae8ba7b234..0ff1d08130 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java
@@ -167,6 +167,7 @@ void onTransportDisconnected(String info, boolean availablePrimary, BaseTranspor
DebugTool.logInfo(TAG, "notifying RPC session ended, but potential primary transport available");
cycle(SdlDisconnectedReason.PRIMARY_TRANSPORT_CYCLE_REQUEST);
} else {
+ clean(false);
onClose(info, null, null);
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java
index 3f6cfb4f32..ee78b0498d 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java
@@ -56,6 +56,7 @@
import com.smartdevicelink.proxy.rpc.enums.PredefinedWindows;
import com.smartdevicelink.proxy.rpc.enums.RequestType;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
+import com.smartdevicelink.util.AndroidTools;
import com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
@@ -134,7 +135,9 @@ public void start(CompletionListener listener) {
public void dispose() {
// send broadcast to close lock screen if open
if (context.get() != null) {
- context.get().sendBroadcast(new Intent(SDLLockScreenActivity.CLOSE_LOCK_SCREEN_ACTION));
+ Intent intent = new Intent(SDLLockScreenActivity.CLOSE_LOCK_SCREEN_ACTION)
+ .setPackage(context.get().getPackageName());
+ context.get().sendBroadcast(intent);
try {
context.get().unregisterReceiver(mLockscreenDismissedReceiver);
lockscreenDismissReceiverRegistered = false;
@@ -332,7 +335,9 @@ private void launchLockScreenActivity() {
// pass in icon, background color, and custom view
if (lockScreenEnabled && isApplicationForegrounded && context.get() != null) {
if (isLockscreenDismissible && !lockscreenDismissReceiverRegistered) {
- context.get().registerReceiver(mLockscreenDismissedReceiver, new IntentFilter(SDLLockScreenActivity.KEY_LOCKSCREEN_DISMISSED));
+ AndroidTools.registerReceiver(context.get(), mLockscreenDismissedReceiver,
+ new IntentFilter(SDLLockScreenActivity.KEY_LOCKSCREEN_DISMISSED),
+ Context.RECEIVER_NOT_EXPORTED);
lockscreenDismissReceiverRegistered = true;
}
@@ -373,7 +378,9 @@ private void closeLockScreenActivity() {
if (context.get() != null) {
LockScreenStatus status = getLockScreenStatus();
if (status == LockScreenStatus.OFF || (status == LockScreenStatus.OPTIONAL && displayMode != LockScreenConfig.DISPLAY_MODE_OPTIONAL_OR_REQUIRED)) {
- context.get().sendBroadcast(new Intent(SDLLockScreenActivity.CLOSE_LOCK_SCREEN_ACTION));
+ Intent intent = new Intent(SDLLockScreenActivity.CLOSE_LOCK_SCREEN_ACTION)
+ .setPackage(context.get().getPackageName());
+ context.get().sendBroadcast(intent);
}
}
lastIntentUsed = null;
@@ -428,6 +435,7 @@ public void onImageRetrieved(Bitmap icon) {
intent.putExtra(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_EXTRA, deviceLogoEnabled);
intent.putExtra(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_BITMAP, deviceLogo);
if (context.get() != null) {
+ intent.setPackage(context.get().getPackageName());
context.get().sendBroadcast(intent);
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/SDLLockScreenActivity.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/SDLLockScreenActivity.java
index 01de088c10..5594da18bb 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/SDLLockScreenActivity.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/SDLLockScreenActivity.java
@@ -52,6 +52,7 @@
import android.widget.TextView;
import com.smartdevicelink.R;
+import com.smartdevicelink.util.AndroidTools;
public class SDLLockScreenActivity extends Activity {
@@ -106,7 +107,8 @@ protected void onCreate(Bundle savedInstanceState) {
lockscreenFilter.addAction(LOCKSCREEN_DEVICE_LOGO_DOWNLOADED);
// register broadcast receivers
- registerReceiver(lockScreenBroadcastReceiver, lockscreenFilter);
+ AndroidTools.registerReceiver(this, lockScreenBroadcastReceiver, lockscreenFilter,
+ RECEIVER_NOT_EXPORTED);
}
@Override
@@ -284,7 +286,9 @@ private class SwipeUpGestureListener extends GestureDetector.SimpleOnGestureList
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
if ((event2.getY() - event1.getY()) > MIN_SWIPE_DISTANCE) {
- sendBroadcast(new Intent(KEY_LOCKSCREEN_DISMISSED));
+ Intent intent = new Intent(KEY_LOCKSCREEN_DISMISSED)
+ .setPackage(getPackageName());
+ sendBroadcast(intent);
finish();
}
return true;
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceMessageEmitter.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceMessageEmitter.java
new file mode 100644
index 0000000000..76d64c91de
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceMessageEmitter.java
@@ -0,0 +1,91 @@
+package com.smartdevicelink.transport;
+
+import android.os.Message;
+
+import androidx.annotation.RestrictTo;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class RouterServiceMessageEmitter extends Thread {
+
+ protected final Object QUEUE_LOCK = new Object();
+ private boolean isHalted = false, isWaiting = false;
+ private final Callback callback;
+ private final Queue queue = new LinkedList<>();
+
+ public RouterServiceMessageEmitter(Callback callback) {
+ this.setName("RouterServiceMessageEmitter");
+ this.setDaemon(true);
+ this.callback = callback;
+ }
+
+ @Override
+ public void run() {
+ while (!isHalted) {
+ try {
+ Message message;
+ synchronized (QUEUE_LOCK) {
+ message = getNextTask();
+ if (message != null && callback != null) {
+ callback.onMessageToSend(message);
+ } else {
+ isWaiting = true;
+ QUEUE_LOCK.wait();
+ isWaiting = false;
+ }
+ }
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+
+ protected void alert() {
+ if (isWaiting) {
+ synchronized (QUEUE_LOCK) {
+ QUEUE_LOCK.notify();
+ }
+ }
+ }
+
+ protected void close() {
+ this.isHalted = true;
+ if (queue != null) {
+ queue.clear();
+ }
+ }
+
+ /**
+ * Insert the task in the queue where it belongs
+ *
+ * @param message the new Message that needs to be added to the queue to be handled
+ */
+ public void add(Message message) {
+ synchronized (this) {
+ if (message != null && queue != null) {
+ queue.add(message);
+ }
+ }
+ }
+
+ /**
+ * Remove the head of the queue
+ *
+ * @return the old head of the queue
+ */
+ private Message getNextTask() {
+ synchronized (this) {
+ if (queue != null) {
+ return queue.poll();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ protected interface Callback {
+ boolean onMessageToSend(Message message);
+ }
+}
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 2bfe13ef1f..a4c3be210d 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
@@ -129,6 +129,7 @@ public void onReceive(Context context, Intent intent) {
if (action.equalsIgnoreCase(TransportConstants.ACTION_USB_ACCESSORY_ATTACHED)) {
DebugTool.logInfo(TAG, "Usb connected");
+ setForegroundExceptionHandler();
intent.setAction(null);
onSdlEnabled(context, intent);
return;
@@ -189,6 +190,7 @@ public void onListObtained(boolean successful) {
finalIntent.putExtra(UsbManager.EXTRA_ACCESSORY, (Parcelable) null);
}
}
+ setForegroundExceptionHandler();
onSdlEnabled(finalContext, finalIntent);
}
@@ -330,16 +332,19 @@ public void onComplete(Vector routerServices) {
DebugTool.logInfo(TAG, ": This app's package: " + myPackage);
DebugTool.logInfo(TAG, ": Router service app's package: " + routerService.getPackageName());
if (myPackage != null && myPackage.equalsIgnoreCase(routerService.getPackageName())) {
- //If the device is not null the listener should start as well as the
- //case where this app was installed after BT connected and is the
- //only SDL app installed on the device. (Rare corner case)
- if (device != null || sdlAppInfoList.size() == 1) {
+ //If this app should be hosting the RS it's time to start the device
+ //listener. If the BT device is not null and has already connected,
+ //this app's RS will be started immediately. Otherwise the device
+ //listener will act as a gate keeper to prevent unnecessary notifications.
+ if (device != null || AndroidTools.isBluetoothDeviceConnected()) {
SdlDeviceListener sdlDeviceListener = getSdlDeviceListener(context, device);
if (!sdlDeviceListener.isRunning()) {
sdlDeviceListener.start();
+ } else {
+ DebugTool.logInfo(TAG, "Device listener is already running");
}
} else {
- DebugTool.logInfo(TAG, "Not starting device listener, bluetooth device is null and other SDL apps installed.");
+ DebugTool.logInfo(TAG, "No bluetooth device and no device connected");
}
} else if (isPreAndroid12RSOnDevice) {
//If the RS app has the BLUETOOTH_CONNECT permission that means it
@@ -421,9 +426,9 @@ static protected void setForegroundExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
if (e != null
&& e instanceof AndroidRuntimeException
- && ("android.app.RemoteServiceException".equals(e.getClass().getName()) || "android.app.ForegroundServiceDidNotStartInTimeException".equals(e.getClass().getName())) //android.app.RemoteServiceException is a private class
+ && ("android.app.RemoteServiceException".equals(e.getClass().getName()) || e.getClass().getName().contains("ForegroundService")) //android.app.RemoteServiceException is a private class
&& e.getMessage() != null
- && (e.getMessage().contains("SdlRouterService")) || e.getMessage().contains(serviceName)) {
+ && (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
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 04c9a2b0ec..4a73b2c7ea 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
@@ -145,7 +145,7 @@ public class SdlRouterService extends Service {
/**
* NOTE: DO NOT MODIFY THIS UNLESS YOU KNOW WHAT YOU'RE DOING.
*/
- protected static final int ROUTER_SERVICE_VERSION_NUMBER = 16;
+ protected static final int ROUTER_SERVICE_VERSION_NUMBER = 17;
private static final String ROUTER_SERVICE_PROCESS = "com.smartdevicelink.router";
@@ -239,7 +239,6 @@ public class SdlRouterService extends Service {
* Executor for making sure clients are still running during trying times
*/
private ScheduledExecutorService clientPingExecutor = null;
- Intent pingIntent = null;
private boolean isPingingClients = false;
int pingCount = 0;
@@ -818,10 +817,8 @@ public void handleMessage(Message msg) {
}
}
if (service.isPrimaryTransportConnected() && ((TransportConstants.ROUTER_STATUS_FLAG_TRIGGER_PING & flags) == TransportConstants.ROUTER_STATUS_FLAG_TRIGGER_PING)) {
- if (service.pingIntent == null) {
- service.initPingIntent();
- }
- AndroidTools.sendExplicitBroadcast(service.getApplicationContext(), service.pingIntent, null);
+ AndroidTools.sendExplicitBroadcast(service.getApplicationContext(),
+ service.createPingIntent(), null);
}
break;
default:
@@ -865,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());
@@ -903,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:
@@ -922,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();
+ }
+ }
+ }
}
/* **************************************************************************************************************************************
@@ -1167,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 sdlAppInfoList = AndroidTools.querySdlAppInfo(getApplicationContext(), new SdlAppInfo.BestRouterComparator(), null);
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
ComponentName name = new ComponentName(this, this.getClass());
@@ -1181,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());
}
@@ -1213,7 +1242,7 @@ public void startUpSequence() {
IntentFilter filter = new IntentFilter();
filter.addAction(REGISTER_WITH_ROUTER_ACTION);
- registerReceiver(mainServiceReceiver, filter);
+ AndroidTools.registerReceiver(this, mainServiceReceiver, filter, RECEIVER_EXPORTED);
if (!connectAsClient) {
if (bluetoothAvailable()) {
@@ -1285,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;
}
@@ -1861,6 +1890,7 @@ public void onTransportConnected(final TransportRecord record) {
//the developer can use this pendingIntent to start their SdlService from the context of
//the active RouterService
Intent pending = new Intent();
+ pending.setPackage(getPackageName());
PendingIntent pendingIntent = PendingIntent.getForegroundService(this, (int) System.currentTimeMillis(), pending, PendingIntent.FLAG_MUTABLE | Intent.FILL_IN_COMPONENT);
startService.putExtra(TransportConstants.PENDING_INTENT_EXTRA, pendingIntent);
}
@@ -2634,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;
@@ -2942,6 +2968,9 @@ protected PacketWriteTask getNextTask(TransportType transportType) {
long currentPriority = -Long.MAX_VALUE, peekWeight;
synchronized (REGISTERED_APPS_LOCK) {
PacketWriteTask peekTask;
+ if (registeredApps == null) {
+ return null;
+ }
for (RegisteredApp app : registeredApps.values()) {
peekTask = app.peekNextTask(transportType);
if (peekTask != null) {
@@ -2967,16 +2996,30 @@ protected PacketWriteTask getNextTask(TransportType transportType) {
return null;
}
- private void initPingIntent() {
- pingIntent = new Intent();
+ private Intent createPingIntent() {
+ Intent pingIntent = new Intent();
pingIntent.setAction(TransportConstants.START_ROUTER_SERVICE_ACTION);
pingIntent.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_EXTRA, true);
pingIntent.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_APP_PACKAGE, getBaseContext().getPackageName());
pingIntent.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_CMP_NAME, new ComponentName(SdlRouterService.this, SdlRouterService.this.getClass()));
pingIntent.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_PING, true);
+ 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();
+ pending.setPackage(getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getForegroundService(this, (int) System.currentTimeMillis(), pending, PendingIntent.FLAG_MUTABLE | Intent.FILL_IN_COMPONENT);
+ pingIntent.putExtra(TransportConstants.PENDING_INTENT_EXTRA, pendingIntent);
+ }
+
if (receivedVehicleType != null) {
pingIntent.putExtra(TransportConstants.VEHICLE_INFO_EXTRA, receivedVehicleType.getStore());
}
+
+ return pingIntent;
}
private void startClientPings() {
@@ -3001,6 +3044,7 @@ private void startClientPings() {
clientPingExecutor.scheduleAtFixedRate(new Runnable() {
List sdlApps;
+ Intent pingIntent;
@Override
public void run() {
@@ -3010,7 +3054,7 @@ public void run() {
return;
}
if (pingIntent == null) {
- initPingIntent();
+ pingIntent = createPingIntent();
}
if (sdlApps == null) {
@@ -3039,7 +3083,6 @@ private void stopClientPings() {
clientPingExecutor = null;
isPingingClients = false;
}
- pingIntent = null;
}
/* ****************************************************************************************************************************************
@@ -3899,6 +3942,13 @@ public void clear() {
@TargetApi(11)
@SuppressLint("NewApi")
private void notifySppError() {
+ synchronized (FOREGROUND_NOTIFICATION_LOCK) {
+ // Check first to see if the RouterService is in the Foreground
+ // This is to prevent the notification appearing in error
+ if (!this.isForeground) {
+ return;
+ }
+ }
Notification.Builder builder;
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
builder = new Notification.Builder(getApplicationContext());
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java
index 52f7182306..cc4b0529dc 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java
@@ -91,6 +91,7 @@ public class TransportBroker {
Messenger routerServiceMessenger = null;
final Messenger clientMessenger;
+ private RouterServiceMessageEmitter routerServiceMessageEmitter;
boolean isBound = false, registeredWithRouterService = false;
private String routerPackage = null, routerClassName = null;
@@ -109,6 +110,13 @@ private void initRouterConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
DebugTool.logInfo(TAG, "Bound to service " + className.toString());
+ routerServiceMessageEmitter = new RouterServiceMessageEmitter(new RouterServiceMessageEmitter.Callback() {
+ @Override
+ public boolean onMessageToSend(Message message) {
+ return sendMessageToRouterService(message, 0);
+ }
+ });
+ routerServiceMessageEmitter.start();
routerServiceMessenger = new Messenger(service);
isBound = true;
//So we just established our connection
@@ -118,7 +126,7 @@ public void onServiceConnected(ComponentName className, IBinder service) {
public void onServiceDisconnected(ComponentName className) {
DebugTool.logInfo(TAG, "Unbound from service " + className.getClassName());
- routerServiceMessenger = null;
+ shutDownRouterServiceMessenger();
registeredWithRouterService = false;
isBound = false;
onHardwareDisconnected(null, null);
@@ -127,7 +135,12 @@ public void onServiceDisconnected(ComponentName className) {
}
protected boolean sendMessageToRouterService(Message message) {
- return sendMessageToRouterService(message, 0);
+ if (routerServiceMessageEmitter != null) {
+ routerServiceMessageEmitter.add(message);
+ routerServiceMessageEmitter.alert();
+ }
+ // Updated to only return true as we have added sending messages to SdlRouterService to be on a different thread.
+ return true;
}
protected boolean sendMessageToRouterService(Message message, int retryCount) {
@@ -152,7 +165,7 @@ protected boolean sendMessageToRouterService(Message message, int retryCount) {
} catch (InterruptedException e1) {
e1.printStackTrace();
}
- return sendMessageToRouterService(message, retryCount++);
+ return sendMessageToRouterService(message, ++retryCount);
} else {
//DeadObject, time to kill our connection
DebugTool.logInfo(TAG, "Dead object while attempting to send packet");
@@ -431,7 +444,7 @@ public boolean start() {
public void resetSession() {
synchronized (INIT_LOCK) {
unregisterWithRouterService();
- routerServiceMessenger = null;
+ shutDownRouterServiceMessenger();
unBindFromRouterService();
isBound = false;
}
@@ -445,7 +458,7 @@ public void stop() {
synchronized (INIT_LOCK) {
unregisterWithRouterService();
unBindFromRouterService();
- routerServiceMessenger = null;
+ shutDownRouterServiceMessenger();
currentContext = null;
}
@@ -629,8 +642,7 @@ private void unregisterWithRouterService() {
} else {
DebugTool.logWarning(TAG, "Unable to unregister, not bound to router service");
}
-
- routerServiceMessenger = null;
+ shutDownRouterServiceMessenger();
}
protected ComponentName getRouterService() {
@@ -747,4 +759,15 @@ public void removeSession(long sessionId) {
msg.setData(bundle);
this.sendMessageToRouterService(msg);
}
+
+ /**
+ * Method to shut down RouterServiceMessenger
+ */
+ private void shutDownRouterServiceMessenger() {
+ if (routerServiceMessageEmitter != null) {
+ routerServiceMessageEmitter.close();
+ }
+ routerServiceMessageEmitter = null;
+ routerServiceMessenger = null;
+ }
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
index 6c3050301b..c449026b96 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
@@ -50,6 +50,7 @@
import com.smartdevicelink.transport.enums.TransportType;
import com.smartdevicelink.transport.utl.SdlDeviceListener;
import com.smartdevicelink.transport.utl.TransportRecord;
+import com.smartdevicelink.util.AndroidTools;
import com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
@@ -412,7 +413,8 @@ synchronized void enterLegacyMode(final String info) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- contextWeakReference.get().registerReceiver(legacyDisconnectReceiver, intentFilter);
+ AndroidTools.registerReceiver(contextWeakReference.get(),
+ legacyDisconnectReceiver, intentFilter, Context.RECEIVER_EXPORTED);
}
} else {
new Handler(Looper.myLooper()).postDelayed(new Runnable() {
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java
index b588f15123..875f070c6e 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java
@@ -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;
@@ -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)) {
@@ -161,6 +182,7 @@ public void cancel() {
if (isBound) {
unBindFromService();
}
+ context = null;
}
private boolean bindToService() {
@@ -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);
}
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 2700d35d9f..ddbfd47367 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
@@ -32,6 +32,11 @@
package com.smartdevicelink.util;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -47,10 +52,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;
@@ -392,4 +400,101 @@ public static void saveVehicleType(Context context, VehicleType vehicleType, Str
return null;
}
}
+
+ /**
+ * A helper method to handle adding flags to registering a run time broadcast receiver.
+ *
+ * @param context a context that will be used to register the receiver with
+ * @param receiver the receiver that will be registered
+ * @param filter the filter that will be use to filter intents sent to the broadcast receiver
+ * @param flags any flags that should be used to register the receiver. In most cases this
+ * will be {@link Context#RECEIVER_NOT_EXPORTED} or
+ * {@link Context#RECEIVER_EXPORTED}
+ * @see Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * @see Context#registerReceiver(BroadcastReceiver, IntentFilter, int)
+ */
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ public static void registerReceiver(Context context, BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ if (context != null && receiver != null && filter != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.registerReceiver(receiver, filter, flags);
+ } else {
+ context.registerReceiver(receiver, filter);
+ }
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * A method that will check to see if there is a bluetooth device possibly connected. It will
+ * only check the headset and A2DP profiles. This is only to be used as a check for starting
+ * the SdlDeviceListener and not a direct start of the router service.
+ * @return if a bluetooth device is connected
+ */
+ @SuppressLint("MissingPermission")
+ public static boolean isBluetoothDeviceConnected() {
+ try {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) {
+ int headsetState = bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
+ int a2dpState = bluetoothAdapter.getProfileConnectionState(BluetoothProfile.A2DP);
+ return headsetState == BluetoothAdapter.STATE_CONNECTING
+ || headsetState == BluetoothAdapter.STATE_CONNECTED
+ || a2dpState == BluetoothAdapter.STATE_CONNECTING
+ || a2dpState == BluetoothAdapter.STATE_CONNECTED;
+ }
+ } catch (Exception e) {
+ DebugTool.logError(TAG, "Unable to check for connected bluetooth device", e);
+ }
+ return false;
+ }
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java
index 8e904a45fa..f31c048f5f 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java
@@ -114,6 +114,15 @@ private static ValidationResult checkPermissions(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
permissionList.add(Manifest.permission.FOREGROUND_SERVICE);
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissionList.add(Manifest.permission.BLUETOOTH_CONNECT);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ permissionList.add(Manifest.permission.POST_NOTIFICATIONS);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ permissionList.add(Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE);
+ }
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
String[] permissionInfos = packageInfo.requestedPermissions;
@@ -161,6 +170,10 @@ public static ValidationResult checkBroadcastReceiver(Context context) {
int j = 0;
for (ResolveInfo sdlReceiver : sdlReceivers) {
if (receiver.name.equals(sdlReceiver.activityInfo.name)) {
+ if (!receiver.exported) {
+ retVal.successful = false;
+ retVal.resultText = "This application has not marked its SdlBroadcastReceiver as exported";
+ }
return retVal;
}
}
@@ -185,6 +198,11 @@ private static ValidationResult checkRoutServiceMetadata(Context context, Class
retVal.successful = false;
retVal.resultText = "This application has not specified its metadata tags for the SdlRouterService.";
}
+
+ if (!info.serviceInfo.exported) {
+ retVal.successful = false;
+ retVal.resultText = "This application has not marked its SdlRouterService as exported.";
+ }
} else {
retVal.successful = false;
retVal.resultText = "This application has not specified its SdlRouterService correctly in the manifest.";
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java
index dc0bde5ed4..e374da5224 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java
@@ -261,8 +261,8 @@ private void updateBroadcastReceiver() {
}
unregisterBroadcastReceiver();
//Re-register receiver
- context.registerReceiver(broadcastReceiver, intentFilter);
-
+ AndroidTools.registerReceiver(context, broadcastReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED);
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/ServiceFinder.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/ServiceFinder.java
index 0848446efa..7b887f44a7 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/util/ServiceFinder.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/ServiceFinder.java
@@ -74,7 +74,8 @@ public ServiceFinder(Context context, String packageName, final ServiceFinderCal
this.sdlMultiMap = AndroidTools.getSdlEnabledApps(context, packageName);
- this.context.registerReceiver(mainServiceReceiver, new IntentFilter(this.receiverLocation));
+ AndroidTools.registerReceiver(this.context, mainServiceReceiver,
+ new IntentFilter(this.receiverLocation), Context.RECEIVER_EXPORTED);
timeoutRunnable = new Runnable() {
@Override
diff --git a/android/sdl_android/src/main/res/values/sdl.xml b/android/sdl_android/src/main/res/values/sdl.xml
index bd96c54abb..112007a7e1 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 @@
sdl_router_version
- 16
+ 17
sdl_custom_router
sdl_oem_vehicle_type
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
index da6bae4e62..90381ef59d 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
@@ -159,6 +159,13 @@ public void onComplete(boolean success, int bytesAvailable, Collection f
}
private void listRemoteFilesWithCompletionListener(final FileManagerCompletionListener completionListener) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot retrieve uploaded files");
+ if (completionListener != null) {
+ completionListener.onComplete(false, bytesAvailable, null, "Queue is null, cannot upload files");
+ }
+ return;
+ }
ListFilesOperation operation = new ListFilesOperation(internalInterface, new FileManagerCompletionListener() {
@Override
public void onComplete(boolean success, int bytesAvailable, Collection fileNames, String errorMessage) {
@@ -196,6 +203,13 @@ public void onComplete(boolean success, int bytesAvailable, Collection f
}
private void deleteRemoteFileWithNamePrivate(@NonNull final String fileName, final FileManagerCompletionListener listener) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot delete files");
+ if (listener != null) {
+ listener.onComplete(false, bytesAvailable, null, "Queue is null, cannot delete files");
+ }
+ return;
+ }
DeleteFileOperation operation = new DeleteFileOperation(internalInterface, fileName, mutableRemoteFileNames, new FileManagerCompletionListener() {
@Override
public void onComplete(boolean success, int bytesAvailable, Collection fileNames, String errorMessage) {
@@ -406,6 +420,14 @@ private void uploadFilePrivate(@NonNull final SdlFile file, final FileManagerCom
}
private void sdl_uploadFilePrivate(@NonNull final SdlFile file, final FileManagerCompletionListener listener) {
+
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot upload files");
+ if (listener != null) {
+ listener.onComplete(false, bytesAvailable, null, "Queue is null, cannot upload files");
+ }
+ return;
+ }
final SdlFile fileClone = file.clone();
SdlFileWrapper fileWrapper = new SdlFileWrapper(fileClone, new FileManagerCompletionListener() {
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
index f1b20237bb..df833e49c4 100644
--- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
@@ -407,7 +407,15 @@ public void onReceived(RPCMessage message) {
//We have begun
DebugTool.logInfo(TAG, "RAI Response");
BaseLifecycleManager.this.raiResponse = (RegisterAppInterfaceResponse) message;
- SdlMsgVersion rpcVersion = ((RegisterAppInterfaceResponse) message).getSdlMsgVersion();
+ if (!BaseLifecycleManager.this.raiResponse.getSuccess()) {
+ String info = "App registration was not successful, result = " + BaseLifecycleManager.this.raiResponse.getResultCode();
+ DebugTool.logError(TAG, info);
+ clean(false);
+ onClose(info, null, SdlDisconnectedReason.SDL_REGISTRATION_ERROR);
+ return;
+ }
+
+ SdlMsgVersion rpcVersion = BaseLifecycleManager.this.raiResponse.getSdlMsgVersion();
if (rpcVersion != null) {
BaseLifecycleManager.this.rpcSpecVersion = new Version(rpcVersion.getMajorVersion(), rpcVersion.getMinorVersion(), rpcVersion.getPatchVersion());
} else {
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
index 10db9c54ca..7ff5f35c98 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
@@ -140,6 +140,17 @@ public void dispose() {
public void presentAlert(AlertView alert, AlertCompletionListener listener) {
if (getState() == ERROR) {
DebugTool.logWarning(TAG, "Alert Manager In Error State");
+ if (listener != null) {
+ listener.onComplete(false, null);
+ }
+ return;
+ }
+
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, Cannot present Alert.");
+ if (listener != null) {
+ listener.onComplete(false, null);
+ }
return;
}
@@ -149,6 +160,9 @@ public void presentAlert(AlertView alert, AlertCompletionListener listener) {
if (alert.getSoftButtons() != null) {
if (!BaseScreenManager.checkAndAssignButtonIds(alert.getSoftButtons(), BaseScreenManager.ManagerLocation.ALERT_MANAGER)) {
DebugTool.logError(TAG, "Attempted to set soft button objects for Alert, but multiple buttons had the same id.");
+ if (listener != null) {
+ listener.onComplete(false, null);
+ }
return;
}
softButtonObjects.addAll(alert.getSoftButtons());
@@ -216,10 +230,14 @@ protected SoftButtonObject getSoftButtonObjectById(int buttonId) {
private void updateTransactionQueueSuspended() {
if (!isAlertRPCAllowed || currentWindowCapability == null) {
DebugTool.logInfo(TAG, String.format("Suspending the transaction queue. Current permission status is false: %b, window capabilities are null: %b", isAlertRPCAllowed, currentWindowCapability == null));
- transactionQueue.pause();
+ if (transactionQueue != null) {
+ transactionQueue.pause();
+ }
} else {
DebugTool.logInfo(TAG, "Starting the transaction queue");
- transactionQueue.resume();
+ if (transactionQueue != null) {
+ transactionQueue.resume();
+ }
}
}
@@ -332,6 +350,11 @@ public void onNotified(RPCNotification notification) {
// Updates pending task with new DisplayCapabilities
void updatePendingOperationsWithNewWindowCapability() {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot update any Alert operations with new " +
+ "WindowCapability");
+ return;
+ }
for (Task task : transactionQueue.getTasksAsList()) {
if (!(task instanceof PresentAlertOperation)) {
continue;
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
index ab9159c33d..df54c5c8b3 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
@@ -158,6 +158,10 @@ public void onCapabilityRetrieved(Object capability) {
// Auto-send an updated Show if we have new capabilities
if (softButtonObjects != null && !softButtonObjects.isEmpty() && softButtonCapabilities != null && !softButtonCapabilitiesEquals(oldSoftButtonCapabilities, softButtonCapabilities)) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot update SoftButtons");
+ return;
+ }
SoftButtonReplaceOperation operation = new SoftButtonReplaceOperation(internalInterface, fileManager, softButtonCapabilities, softButtonObjects, getCurrentMainField1(), isDynamicGraphicSupported);
transactionQueue.add(operation, false);
}
@@ -261,10 +265,14 @@ private Queue newTransactionQueue() {
private void updateTransactionQueueSuspended() {
if (softButtonCapabilities == null || HMILevel.HMI_NONE.equals(currentHMILevel)) {
DebugTool.logInfo(TAG, String.format("Suspending the transaction queue. Current HMI level is NONE: %b, soft button capabilities are null: %b", HMILevel.HMI_NONE.equals(currentHMILevel), softButtonCapabilities == null));
- transactionQueue.pause();
+ if (transactionQueue != null) {
+ transactionQueue.pause();
+ }
} else {
DebugTool.logInfo(TAG, "Starting the transaction queue");
- transactionQueue.resume();
+ if (transactionQueue != null) {
+ transactionQueue.resume();
+ }
}
}
@@ -323,6 +331,10 @@ protected void setSoftButtonObjects(@NonNull List list) {
batchQueue.clear();
batchQueue.add(operation);
} else {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot add SoftButtons");
+ return;
+ }
transactionQueue.clear();
transactionQueue.add(operation, false);
}
@@ -357,6 +369,10 @@ private void transitionSoftButton() {
}
batchQueue.add(operation);
} else {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot transition SoftButton state");
+ return;
+ }
transactionQueue.add(operation, false);
}
}
@@ -397,6 +413,10 @@ protected SoftButtonObject getSoftButtonObjectById(int buttonId) {
* @param batchUpdates Set true if the manager should batch updates together, or false if it should send them as soon as they happen
*/
protected void setBatchUpdates(boolean batchUpdates) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot update SoftButtons");
+ return;
+ }
this.batchUpdates = batchUpdates;
if (!this.batchUpdates) {
@@ -425,6 +445,11 @@ protected String getCurrentMainField1() {
* @param currentMainField1 the String that will be set to TextField1 on the current template
*/
protected void setCurrentMainField1(String currentMainField1) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot update MainField1 for SoftButton " +
+ "operations");
+ return;
+ }
this.currentMainField1 = currentMainField1;
for (Task task : transactionQueue.getTasksAsList()) {
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java
index 92bc828176..7e1033dfae 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java
@@ -158,10 +158,14 @@ private Queue newTransactionQueue() {
private void updateTransactionQueueSuspended() {
if (defaultMainWindowCapability == null || HMILevel.HMI_NONE.equals(currentHMILevel)) {
DebugTool.logInfo(TAG, String.format("Suspending the transaction queue. Current HMI level is NONE: %b, window capabilities are null: %b", HMILevel.HMI_NONE.equals(currentHMILevel), defaultMainWindowCapability == null));
- transactionQueue.pause();
+ if (transactionQueue != null) {
+ transactionQueue.pause();
+ }
} else {
DebugTool.logInfo(TAG, "Starting the transaction queue");
- transactionQueue.resume();
+ if (transactionQueue != null) {
+ transactionQueue.resume();
+ }
}
}
@@ -181,6 +185,13 @@ protected void update(CompletionListener listener) {
}
private synchronized void sdlUpdate(final CompletionListener listener) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot perform a text/graphic update");
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ return;
+ }
CurrentScreenDataUpdatedListener currentScreenDataUpdateListener = new CurrentScreenDataUpdatedListener() {
@Override
@@ -225,6 +236,10 @@ void resetFieldsToCurrentScreenData() {
//Updates pending task with current screen data
void updatePendingOperationsWithNewScreenData() {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null");
+ return;
+ }
for (Task task : transactionQueue.getTasksAsList()) {
if (!(task instanceof TextAndGraphicUpdateOperation)) {
continue;
@@ -237,6 +252,10 @@ void updatePendingOperationsWithNewScreenData() {
}
void updatePendingOperationsWithFailedScreenState(TextAndGraphicState errorState) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null");
+ return;
+ }
for (Task task : transactionQueue.getTasksAsList()) {
if (!(task instanceof TextAndGraphicUpdateOperation)) {
continue;
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
index 0aae717514..4938f35816 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
@@ -93,7 +93,7 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
HashSet preloadedChoices;
// We will pass operations into this to be completed
- final Queue transactionQueue;
+ Queue transactionQueue;
PresentKeyboardOperation currentlyPresentedKeyboardOperation;
@@ -106,8 +106,7 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
super(internalInterface);
// prepare operations queue
- transactionQueue = internalInterface.getTaskmaster().createQueue("ChoiceSetManagerQueue", 1, false);
- transactionQueue.pause(); // pause until HMI ready
+ transactionQueue = newTransactionQueue();
// capabilities
currentSystemContext = SystemContext.SYSCTXT_MAIN;
@@ -133,9 +132,11 @@ public void start(CompletionListener listener) {
@Override
public void dispose() {
-
- // cancel the operations
- transactionQueue.close();
+ // Cancel the operations
+ if (transactionQueue != null) {
+ transactionQueue.close();
+ transactionQueue = null;
+ }
currentHMILevel = null;
currentSystemContext = null;
@@ -154,7 +155,17 @@ public void dispose() {
super.dispose();
}
+ private Queue newTransactionQueue() {
+ Queue queue = internalInterface.getTaskmaster().createQueue("ChoiceSetManagerQueue", 1, false);
+ queue.pause();
+ return queue;
+ }
+
private void checkVoiceOptional() {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot check VR option");
+ return;
+ }
CheckChoiceVROptionalOperation checkChoiceVR = new CheckChoiceVROptionalOperation(internalInterface, new CheckChoiceVROptionalInterface() {
@Override
@@ -173,7 +184,9 @@ public void onError(String error) {
// checking VR will always be first in the queue.
// If pre-load operations were added while this was in progress
// clear it from the queue onError.
- transactionQueue.clear();
+ if (transactionQueue != null) {
+ transactionQueue.clear();
+ }
}
});
transactionQueue.add(checkChoiceVR, false);
@@ -195,6 +208,14 @@ public void preloadChoices(@NonNull List choices, @Nullable final Co
return;
}
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot preload choice set");
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ return;
+ }
+
final LinkedHashSet choicesToUpload = new LinkedHashSet<>(choices);
if (choicesToUpload.size() == 0) {
@@ -237,6 +258,11 @@ public void deleteChoices(@NonNull List choices) {
return;
}
+ if (transactionQueue == null) {
+ DebugTool.logWarning(TAG, "Queue is null, cannot delete choices");
+ return;
+ }
+
DeleteChoicesOperation deleteChoicesOperation = new DeleteChoicesOperation(internalInterface, new HashSet<>(choices), preloadedChoices, new ChoicesOperationCompletionListener() {
@Override
public void onComplete(boolean success, HashSet updatedLoadedChoiceCells) {
@@ -274,6 +300,14 @@ public void presentChoiceSet(@NonNull final ChoiceSet choiceSet, @Nullable final
}
private void sendPresentOperation(final ChoiceSet choiceSet, KeyboardListener keyboardListener, InteractionMode mode) {
+ if (transactionQueue == null) {
+ String error = "Queue is null, cannot present choice set";
+ DebugTool.logError(TAG, error);
+ if (choiceSet != null && choiceSet.getChoiceSetSelectionListener() != null) {
+ choiceSet.getChoiceSetSelectionListener().onError(error);
+ }
+ return;
+ }
if (mode == null) {
mode = InteractionMode.MANUAL_ONLY;
@@ -341,6 +375,11 @@ public Integer presentKeyboard(@NonNull String initialText, @Nullable KeyboardPr
return null;
}
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot present keyboard");
+ return null;
+ }
+
customKeyboardConfig = createValidKeyboardConfigurationBasedOnKeyboardCapabilitiesFromConfiguration(customKeyboardConfig);
if (customKeyboardConfig == null) {
if (this.keyboardConfiguration != null) {
@@ -372,6 +411,11 @@ public void dismissKeyboard(@NonNull Integer cancelID) {
return;
}
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot dismiss keyboard");
+ return;
+ }
+
// First, attempt to cancel the currently executing keyboard operation (Once an operation has started it is removed from the operationQueue)
if (currentlyPresentedKeyboardOperation != null && currentlyPresentedKeyboardOperation.getCancelID().equals(cancelID)) {
currentlyPresentedKeyboardOperation.dismissKeyboard();
@@ -484,6 +528,11 @@ ChoiceCell findIfPresent(ChoiceCell cell, HashSet set) {
}
private void updatePendingTasksWithCurrentPreloads() {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot update pending operations with current" +
+ " preloaded choices");
+ return;
+ }
for (Task task : this.transactionQueue.getTasksAsList()) {
if (task.getState() == Task.IN_PROGRESS || task.getState() == Task.CANCELED) {
continue;
@@ -542,6 +591,11 @@ public void onNotified(RPCNotification notification) {
}
HMILevel oldHMILevel = currentHMILevel;
currentHMILevel = onHMIStatus.getHmiLevel();
+ currentSystemContext = onHMIStatus.getSystemContext();
+
+ if (transactionQueue == null) {
+ return;
+ }
if (currentHMILevel == HMILevel.HMI_NONE) {
transactionQueue.pause();
@@ -551,8 +605,6 @@ public void onNotified(RPCNotification notification) {
transactionQueue.resume();
}
- currentSystemContext = onHMIStatus.getSystemContext();
-
if (currentSystemContext == SystemContext.SYSCTXT_HMI_OBSCURED || currentSystemContext == SystemContext.SYSCTXT_ALERT) {
transactionQueue.pause();
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
index 78d7a96e8b..8de0d72a1a 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
@@ -139,10 +139,14 @@ private Queue newTransactionQueue() {
private void updateTransactionQueueSuspended() {
if (currentHMILevel == HMILevel.HMI_NONE || currentSystemContext == SystemContext.SYSCTXT_MENU) {
DebugTool.logInfo(TAG, String.format("Suspending the transaction queue. Current HMI level is: %s, current system context is: %s", currentHMILevel, currentSystemContext));
- transactionQueue.pause();
+ if (transactionQueue != null) {
+ transactionQueue.pause();
+ }
} else {
DebugTool.logInfo(TAG, "Starting the transaction queue");
- transactionQueue.resume();
+ if (transactionQueue != null) {
+ transactionQueue.resume();
+ }
}
}
@@ -163,6 +167,11 @@ public DynamicMenuUpdatesMode getDynamicMenuUpdatesMode() {
* @param cells - the menu cells that are to be sent to the head unit, including their sub-cells.
*/
public void setMenuCells(@NonNull List cells) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot perform a text/graphic update");
+ return;
+ }
+
if (cells == null) {
DebugTool.logError(TAG, "Cells list is null. Skipping...");
return;
@@ -206,6 +215,10 @@ public List getMenuCells() {
}
private boolean openMenuPrivate(MenuCell cell) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "The queue is null, cannot open submenu");
+ return false;
+ }
MenuCell foundClonedCell = null;
if (cell != null) {
@@ -269,6 +282,10 @@ public boolean openSubMenu(@NonNull MenuCell cell) {
* @param menuConfiguration - The default menuConfiguration
*/
public void setMenuConfiguration(@NonNull final MenuConfiguration menuConfiguration) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot set menu configuration");
+ return;
+ }
if (menuConfiguration.equals(this.menuConfiguration)) {
DebugTool.logInfo(TAG, "New menu configuration is equal to existing one, will not set new configuration");
return;
@@ -380,6 +397,9 @@ private boolean callListenerForCells(List cells, OnCommand command) {
}
private void updateMenuReplaceOperationsWithNewCurrentMenu() {
+ if (transactionQueue == null) {
+ return;
+ }
for (Task task : transactionQueue.getTasksAsList()) {
if (task instanceof MenuReplaceOperation) {
((MenuReplaceOperation) task).setCurrentMenu(this.currentMenuCells);
@@ -388,6 +408,9 @@ private void updateMenuReplaceOperationsWithNewCurrentMenu() {
}
private void updateMenuReplaceOperationsWithNewWindowCapability() {
+ if (transactionQueue == null) {
+ return;
+ }
for (Task task : transactionQueue.getTasksAsList()) {
if (task instanceof MenuReplaceOperation) {
((MenuReplaceOperation) task).setWindowCapability(this.windowCapability);
@@ -396,6 +419,9 @@ private void updateMenuReplaceOperationsWithNewWindowCapability() {
}
private void updateMenuReplaceOperationsWithNewMenuConfiguration() {
+ if (transactionQueue == null) {
+ return;
+ }
for (Task task : transactionQueue.getTasksAsList()) {
if (task instanceof MenuReplaceOperation) {
((MenuReplaceOperation) task).setMenuConfiguration(currentMenuConfiguration);
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java
index ec07e5305e..9939ff826e 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java
@@ -120,16 +120,24 @@ private Queue newTransactionQueue() {
private void updateTransactionQueueSuspended() {
if (HMILevel.HMI_NONE.equals(currentHMILevel)) {
DebugTool.logInfo(TAG, "Suspending the transaction queue. Current HMI level is NONE");
- transactionQueue.pause();
+ if (transactionQueue != null) {
+ transactionQueue.pause();
+ }
} else {
DebugTool.logInfo(TAG, "Starting the transaction queue");
- transactionQueue.resume();
+ if (transactionQueue != null) {
+ transactionQueue.resume();
+ }
}
}
// SETTERS
public void setVoiceCommands(List voiceCommands) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot set voice commands");
+ return;
+ }
// we actually need voice commands to set.
if (voiceCommands == null) {
@@ -196,6 +204,10 @@ private void cleanTransactionQueue() {
}
private void updatePendingOperations(List newCurrentVoiceCommands) {
+ if (transactionQueue == null) {
+ DebugTool.logError(TAG, "Queue is null, cannot update pending operations");
+ return;
+ }
for (Task operation : transactionQueue.getTasksAsList()) {
if (operation.getState() == Task.IN_PROGRESS) {
continue;
diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java
index 6d0f67a71c..50d96116e8 100644
--- a/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java
+++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java
@@ -32,5 +32,5 @@
// THIS FILE IS AUTO GENERATED, DO NOT MODIFY!!
public final class BuildConfig {
- public static final String VERSION_NAME = "5.6.1";
+ public static final String VERSION_NAME = "5.7.0";
}
\ No newline at end of file