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