diff options
author | John Thrun <jthrun@uievolution.com> | 2017-10-04 15:17:07 -0700 |
---|---|---|
committer | John Thrun <jthrun@uievolution.com> | 2017-10-04 15:17:07 -0700 |
commit | 87152cafad8e219a8136a92ac84a8ffceae7c3a6 (patch) | |
tree | 67ef6ae5443321cfe303d6488575f9303b082077 | |
parent | 90a87fac34b1b771763e73afebac0b4a4006a094 (diff) | |
parent | bf2f2b405ca10e777832b32eadfe14ed21d6a74d (diff) | |
download | sdl_android-87152cafad8e219a8136a92ac84a8ffceae7c3a6.tar.gz |
Merge branch 'develop_f' into feature/haptic_projection
13 files changed, 662 insertions, 79 deletions
diff --git a/.travis.yml b/.travis.yml index 5df5d6198..fb7fc4dbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,19 +5,20 @@ android: components: # use the latest revision of Android SDK Tools - tools - - platform-tools - tools + - platform-tools - ndk-bundle # The BuildTools version used by your project - - build-tools-25.0.2 + - build-tools-26.0.1 # The SDK version used to compile your project - - android-22 + - android-26 + - android-22 #For emulator # Specify at least one system image, # if you need to run emulator(s) during your tests - sys-img-armeabi-v7a-android-22 - # - sys-img-x86-android-17 + # - sys-img-x86-android-26 # Android Support Repos - extra-android-m2repository diff --git a/sdl_android/build.gradle b/sdl_android/build.gradle index 599c7ba87..ca93c3073 100644 --- a/sdl_android/build.gradle +++ b/sdl_android/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 22 + compileSdkVersion 26 buildToolsVersion "25.0.2" defaultConfig { minSdkVersion 8 diff --git a/sdl_android/src/androidTest/java/com/smartdevicelink/test/encoder/EncoderUtilsTest.java b/sdl_android/src/androidTest/java/com/smartdevicelink/test/encoder/EncoderUtilsTest.java new file mode 100644 index 000000000..083a5dd3f --- /dev/null +++ b/sdl_android/src/androidTest/java/com/smartdevicelink/test/encoder/EncoderUtilsTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.test.encoder; + +import android.annotation.TargetApi; +import android.media.MediaFormat; +import android.os.Build; + +import com.smartdevicelink.encoder.EncoderUtils; + +import junit.framework.TestCase; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * This is a unit test class for the SmartDeviceLink library project class : + * {@link com.smartdevicelink.encoder.EncoderUtils} + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +public class EncoderUtilsTest extends TestCase { + public void testGetCodecSpecificDataWithNull() { + byte[] result = EncoderUtils.getCodecSpecificData(null); + assertNull(result); + } + + public void testGetCodecSpecificDataForAVC() { + // example of SPS NAL unit with 4-byte start code + byte[] sps = new byte[] { + 0x00, 0x00, 0x00, 0x01, + 0x67, 0x42, (byte)0xC0, 0x0A, (byte)0xA6, 0x11, 0x11, (byte)0xE8, + 0x40, 0x00, 0x00, (byte)0xFA, 0x40, 0x00, 0x3A, (byte)0x98, + 0x23, (byte)0xC4, (byte)0x89, (byte)0x84, 0x60 + }; + // example of PPS NAL unit with 4-byte start code + byte[] pps = new byte[] { + 0x00, 0x00, 0x00, 0x01, + 0x68, (byte)0xC8, 0x42, 0x0F, 0x13, 0x20 + }; + + ByteBuffer spsByteBuffer = ByteBuffer.allocate(sps.length); + spsByteBuffer.put(sps); + spsByteBuffer.flip(); + + ByteBuffer ppsByteBuffer = ByteBuffer.allocate(pps.length); + ppsByteBuffer.put(pps); + ppsByteBuffer.flip(); + + MediaFormat format = MediaFormat.createVideoFormat("video/avc", 16, 16); + format.setByteBuffer("csd-0", spsByteBuffer); + format.setByteBuffer("csd-1", ppsByteBuffer); + + byte[] result = EncoderUtils.getCodecSpecificData(format); + assertNotNull(result); + + byte[] expected = new byte[sps.length + pps.length]; + System.arraycopy(sps, 0, expected, 0, sps.length); + System.arraycopy(pps, 0, expected, sps.length, pps.length); + assertTrue("Output codec specific data doesn't match", Arrays.equals(expected, result)); + } + + public void testGetCodecSpecificDataWithInvalidAVCData() { + // testing an error case when the encoder emits SPS only (which should not happen) + byte[] sps = new byte[] { + 0x00, 0x00, 0x00, 0x01, + 0x67, 0x42, (byte)0xC0, 0x0A, (byte)0xA6, 0x11, 0x11, (byte)0xE8, + 0x40, 0x00, 0x00, (byte)0xFA, 0x40, 0x00, 0x3A, (byte)0x98, + 0x23, (byte)0xC4, (byte)0x89, (byte)0x84, 0x60 + }; + + ByteBuffer spsByteBuffer = ByteBuffer.allocate(sps.length); + spsByteBuffer.put(sps); + spsByteBuffer.flip(); + + MediaFormat format = MediaFormat.createVideoFormat("video/avc", 16, 16); + format.setByteBuffer("csd-0", spsByteBuffer); + // no PPS + + byte[] result = EncoderUtils.getCodecSpecificData(format); + assertNull(result); + } + + public void testGetCodecSpecificDataForUnknownCodec() { + MediaFormat format = MediaFormat.createVideoFormat("video/raw", 16, 16); + byte[] result = EncoderUtils.getCodecSpecificData(format); + assertNull("For unsupported codec, getCodecSpecificData should return null", result); + } +} diff --git a/sdl_android/src/main/java/com/smartdevicelink/encoder/EncoderUtils.java b/sdl_android/src/main/java/com/smartdevicelink/encoder/EncoderUtils.java new file mode 100644 index 000000000..362564da8 --- /dev/null +++ b/sdl_android/src/main/java/com/smartdevicelink/encoder/EncoderUtils.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017, Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.encoder; + +import android.annotation.TargetApi; +import android.media.MediaFormat; +import android.os.Build; +import android.util.Log; + +import java.nio.ByteBuffer; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +public final class EncoderUtils { + private final static String TAG = "EncoderUtils"; + + /** + * Extracts codec-specific data from MediaFormat instance + * + * Currently, only AVC is supported. + * + * @param format MediaFormat instance retrieved from MediaCodec + * @return byte array containing codec-specific data, or null if an error occurred + */ + public static byte[] getCodecSpecificData(MediaFormat format) { + if (format == null) { + return null; + } + + String name = format.getString(MediaFormat.KEY_MIME); + if (name == null) { + return null; + } + + // same as MediaFormat.MIMETYPE_VIDEO_AVC but it requires API level 21 + if (name.equals("video/avc")) { + return getAVCCodecSpecificData(format); + } else { + Log.w(TAG, "Retrieving codec-specific data for " + name + " is not supported"); + return null; + } + } + + /** + * Extracts H.264 codec-specific data (SPS and PPS) from MediaFormat instance + * + * The codec-specific data is in byte-stream format; 4-byte start codes (0x00 0x00 0x00 0x01) + * are added in front of SPS and PPS NAL units. + * + * @param format MediaFormat instance retrieved from MediaCodec + * @return byte array containing codec-specific data, or null if an error occurred + */ + private static byte[] getAVCCodecSpecificData(MediaFormat format) { + // For H.264, "csd-0" contains SPS and "csd-1" contains PPS. Refer to the documentation + // of MediaCodec. + if (!(format.containsKey("csd-0") && format.containsKey("csd-1"))) { + Log.w(TAG, "H264 codec specific data not found"); + return null; + } + + ByteBuffer sps = format.getByteBuffer("csd-0"); + int spsLen = sps.remaining(); + ByteBuffer pps = format.getByteBuffer("csd-1"); + int ppsLen = pps.remaining(); + + byte[] output = new byte[spsLen + ppsLen]; + try { + sps.get(output, 0, spsLen); + pps.get(output, spsLen, ppsLen); + } catch (Exception e) { + // should not happen + Log.w(TAG, "Error while copying H264 codec specific data: " + e); + return null; + } + + return output; + } + + private EncoderUtils() {} +} diff --git a/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java b/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java index dccabff0b..196f5d98c 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java +++ b/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java @@ -9,13 +9,16 @@ import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Build; +import android.util.Log; import android.view.Surface; import com.smartdevicelink.proxy.interfaces.IVideoStreamListener; @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class SdlEncoder { - + + private static final String TAG = "SdlEncoder"; + // parameters for the encoder private static final String _MIME_TYPE = "video/avc"; // H.264/AVC video // private static final String MIME_TYPE = "video/mp4v-es"; //MPEG4 video @@ -32,7 +35,9 @@ public class SdlEncoder { // allocate one of these up front so we don't need to do it every time private MediaCodec.BufferInfo mBufferInfo; - + + // Codec-specific data (SPS and PPS) + private byte[] mH264CodecSpecificData = null; public SdlEncoder () { } @@ -120,6 +125,7 @@ public class SdlEncoder { } mOutputStream = null; } + mH264CodecSpecificData = null; } /** @@ -153,8 +159,33 @@ public class SdlEncoder { // not expected for an encoder encoderOutputBuffers = mEncoder.getOutputBuffers(); } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + if (mH264CodecSpecificData == null) { + MediaFormat format = mEncoder.getOutputFormat(); + mH264CodecSpecificData = EncoderUtils.getCodecSpecificData(format); + } else { + Log.w(TAG, "Output format change notified more than once, ignoring."); + } } else if (encoderStatus < 0) { } else { + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // If we already retrieve codec specific data via OUTPUT_FORMAT_CHANGED event, + // we do not need this data. + if (mH264CodecSpecificData != null) { + mBufferInfo.size = 0; + } else { + Log.i(TAG, "H264 codec specific data not retrieved yet."); + } + } + // append SPS and PPS in front of every IDR NAL unit + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0 + && mBufferInfo.size != 0 + && mH264CodecSpecificData != null) { + try { + mOutputStream.write(mH264CodecSpecificData, 0, + mH264CodecSpecificData.length); + } catch (Exception e) {} + } + if (mBufferInfo.size != 0) { byte[] dataToWrite = new byte[mBufferInfo.size]; encoderOutputBuffers[encoderStatus].get(dataToWrite, diff --git a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java index 5c27a3ea8..dc284dbae 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java +++ b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java @@ -3446,7 +3446,7 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase> // Test if SdlConnection is null
synchronized(CONNECTION_REFERENCE_LOCK) {
- if (getIsConnected()) {
+ if (!getIsConnected()) {
SdlTrace.logProxyEvent("Application attempted to send and RPCRequest without a connected transport.", SDL_LIB_TRACE_KEY);
throw new SdlException("There is no valid connection to SDL. sendRPCRequest cannot be called until SDL has been connected.", SdlExceptionCause.SDL_UNAVAILABLE);
}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java b/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java index 66e415f29..92dce9dbe 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java +++ b/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java @@ -22,6 +22,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.os.Build; import android.util.Log; import com.smartdevicelink.util.HttpRequestTask; @@ -117,7 +118,7 @@ public class RouterServiceValidator { if(this.service != null){ Log.d(TAG, "Supplied service name of " + this.service.getClassName()); - if(!isServiceRunning(context,this.service)){ + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O && !isServiceRunning(context,this.service)){ //This means our service isn't actually running, so set to null. Hopefully we can find a real router service after this. service = null; Log.w(TAG, "Supplied service is not actually running."); @@ -129,11 +130,17 @@ public class RouterServiceValidator { } } if(this.service == null){ - this.service= componentNameForServiceRunning(pm); //Change this to an array if multiple services are started? - if(this.service == null){ //if this is still null we know there is no service running so we can return false + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O ) { + this.service = componentNameForServiceRunning(pm); //Change this to an array if multiple services are started? + if (this.service == null) { //if this is still null we know there is no service running so we can return false + wakeUpRouterServices(); + return false; + } + }else{ wakeUpRouterServices(); return false; } + } //Log.d(TAG, "Checking app package: " + service.getClassName()); diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java index 7312e78ef..94bb3c51e 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java +++ b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java @@ -1,22 +1,39 @@ package com.smartdevicelink.transport; -import java.util.List; -import java.util.Locale; -import java.util.Vector; -import java.util.concurrent.ConcurrentLinkedQueue; - -import com.smartdevicelink.util.AndroidTools; -import com.smartdevicelink.transport.RouterServiceValidator.TrustedListCallback; - +import android.annotation.TargetApi; import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Build; import android.util.Log; +import com.smartdevicelink.transport.RouterServiceValidator.TrustedListCallback; +import com.smartdevicelink.util.AndroidTools; +import com.smartdevicelink.util.ServiceFinder; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Vector; +import java.util.concurrent.ConcurrentLinkedQueue; + +import static com.smartdevicelink.transport.TransportConstants.BIND_LOCATION_CLASS_NAME_EXTRA; +import static com.smartdevicelink.transport.TransportConstants.BIND_LOCATION_PACKAGE_NAME_EXTRA; +import static com.smartdevicelink.transport.TransportConstants.FOREGROUND_EXTRA; +import static com.smartdevicelink.transport.TransportConstants.SEND_PACKET_TO_APP_LOCATION_EXTRA_NAME; + public abstract class SdlBroadcastReceiver extends BroadcastReceiver{ private static final String TAG = "Sdl Broadcast Receiver"; @@ -129,10 +146,10 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{ return; } } - + Log.d(TAG, "Check for local router"); if(localRouterClass!=null){ //If there is a supplied router service lets run some logic regarding starting one - if(!didStart){ + if(!didStart){Log.d(TAG, "attempting to wake up router service"); didStart = wakeUpRouterService(context, true,false); } @@ -147,42 +164,109 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{ context.sendBroadcast(restart); } } - - private boolean wakeUpRouterService(Context context, boolean ping, boolean altTransportWake){ - if(!isRouterServiceRunning(context, ping)){ - //If there isn't a service running we should try to start one - //The under class should have implemented this.... - - //So let's start up our service since no copy is running - Intent serviceIntent = new Intent(context, localRouterClass); - if(altTransportWake){ - serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT); - } - try { - context.startService(serviceIntent); - }catch (SecurityException e){ - Log.e(TAG, "Security exception, process is bad"); - return false; // Let's exit, we can't start the service - } - return true; - }else{ - if(altTransportWake && runningBluetoothServicePackage!=null && runningBluetoothServicePackage.size()>0){ - Intent serviceIntent = new Intent(); - serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT); - //context.startService(serviceIntent); - for(ComponentName compName: runningBluetoothServicePackage){ - serviceIntent.setComponent(compName); - context.startService(serviceIntent); - } - return true; - } - return false; - } + @TargetApi(Build.VERSION_CODES.O) + private boolean wakeUpRouterService(final Context context, final boolean ping, final boolean altTransportWake){ + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + if (!isRouterServiceRunning(context, ping)) { + //If there isn't a service running we should try to start one + //The under class should have implemented this.... + Log.d(TAG, "No router service running, starting ours"); + //So let's start up our service since no copy is running + Intent serviceIntent = new Intent(context, localRouterClass); + if (altTransportWake) { + serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT); + } + try { + context.startService(serviceIntent); + } catch (SecurityException e) { + Log.e(TAG, "Security exception, process is bad"); + return false; // Let's exit, we can't start the service + } + return true; + } else { + if (altTransportWake && runningBluetoothServicePackage != null && runningBluetoothServicePackage.size() > 0) { + wakeRouterServiceAltTransport(context); + return true; + } + return false; + } + }else{ //We are android Oreo or newer + ServiceFinder finder = new ServiceFinder(context, context.getPackageName(), new ServiceFinder.ServiceFinderCallback() { + @Override + public void onComplete(Vector<ComponentName> routerServices) { + runningBluetoothServicePackage = new Vector<ComponentName>(); + runningBluetoothServicePackage.addAll(routerServices); + if (runningBluetoothServicePackage.isEmpty()) { + //If there isn't a service running we should try to start one + //We will try to sort the SDL enabled apps and find the one that's been installed the longest + Intent serviceIntent; + final PackageManager packageManager = context.getPackageManager(); + Vector<ResolveInfo> apps = new Vector(AndroidTools.getSdlEnabledApps(context, "").values()); //we want our package + if (apps != null && !apps.isEmpty()) { + Collections.sort(apps, new Comparator<ResolveInfo>() { + @Override + public int compare(ResolveInfo resolveInfo, ResolveInfo t1) { + try { + PackageInfo thisPack = packageManager.getPackageInfo(resolveInfo.activityInfo.packageName, 0); + PackageInfo itPack = packageManager.getPackageInfo(t1.activityInfo.packageName, 0); + if (thisPack.lastUpdateTime < itPack.lastUpdateTime) { + return -1; + } else if (thisPack.lastUpdateTime > itPack.lastUpdateTime) { + return 1; + } + + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return 0; + } + }); + String packageName = apps.get(0).activityInfo.packageName; + serviceIntent = new Intent(); + serviceIntent.setComponent(new ComponentName(packageName, packageName +".SdlRouterService")); + } else{ + Log.d(TAG, "No router service running, starting ours"); + //So let's start up our service since no copy is running + serviceIntent = new Intent(context, localRouterClass); + + } + if (altTransportWake) { + serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT); + } + try { + serviceIntent.putExtra(FOREGROUND_EXTRA, true); + context.startForegroundService(serviceIntent); + + } catch (SecurityException e) { + Log.e(TAG, "Security exception, process is bad"); + } + } else { + if (altTransportWake && runningBluetoothServicePackage != null && runningBluetoothServicePackage.size() > 0) { + wakeRouterServiceAltTransport(context); + return; + } + return; + } + } + }); + return true; + } + } + + private void wakeRouterServiceAltTransport(Context context){ + Intent serviceIntent = new Intent(); + serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT); + for (ComponentName compName : runningBluetoothServicePackage) { + serviceIntent.setComponent(compName); + context.startService(serviceIntent); + + } } /** - * Determines if an instance of the Router Service is currently running on the device. + * Determines if an instance of the Router Service is currently running on the device.<p> + * <b>Note:</b> This method no longer works on Android Oreo or newer * @param context A context to access Android system services through. * @param pingService Set this to true if you want to make sure the service is up and listening to bluetooth * @return True if a SDL Router Service is currently running, false otherwise. @@ -192,31 +276,30 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{ Log.e(TAG, "Can't look for router service, context supplied was null"); return false; } - ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - if(runningBluetoothServicePackage==null){ + if (runningBluetoothServicePackage == null) { runningBluetoothServicePackage = new Vector<ComponentName>(); - }else{ + } else { runningBluetoothServicePackage.clear(); } + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + manager.getRunningAppProcesses(); List<RunningServiceInfo> runningServices = null; - try{ + try { runningServices = manager.getRunningServices(Integer.MAX_VALUE); - }catch(NullPointerException e){ + } catch (NullPointerException e) { Log.e(TAG, "Can't get list of running services"); return false; } - for (RunningServiceInfo service : runningServices) { + for (RunningServiceInfo service : runningServices) { //We will check to see if it contains this name, should be pretty specific - //Log.d(TAG, "Found Service: "+ service.service.getClassName()); - if ((service.service.getClassName()).toLowerCase(Locale.US).contains(SDL_ROUTER_SERVICE_CLASS_NAME) && AndroidTools.isServiceExported(context, service.service)) { - - runningBluetoothServicePackage.add(service.service); //Store which instance is running - if(pingService){ + //Log.d(TAG, "Found Service: "+ service.service.getClassName()); + if ((service.service.getClassName()).toLowerCase(Locale.US).contains(SDL_ROUTER_SERVICE_CLASS_NAME) && AndroidTools.isServiceExported(context, service.service)) { + runningBluetoothServicePackage.add(service.service); //Store which instance is running + if (pingService) { pingRouterService(context, service.service.getPackageName(), service.service.getClassName()); - } - } - } - + } + } + } return runningBluetoothServicePackage.size() > 0; } @@ -241,33 +324,46 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{ // This service could not be started } } - + /** * This call will reach out to all SDL related router services to check if they're connected. If a the router service is connected, it will react by pinging all clients. This receiver will then * receive that ping and if the router service is trusted, the onSdlEnabled method will be called. * @param context */ - public static void queryForConnectedService(Context context){ + public static void queryForConnectedService(final Context context){ //Leverage existing call. Include ping bit - requestTransportStatus(context,null,true); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ + ServiceFinder finder = new ServiceFinder(context, context.getPackageName(), new ServiceFinder.ServiceFinderCallback() { + @Override + public void onComplete(Vector<ComponentName> routerServices) { + runningBluetoothServicePackage = new Vector<ComponentName>(); + runningBluetoothServicePackage.addAll(routerServices); + requestTransportStatus(context,null,true,false); + } + }); + + }else{ + requestTransportStatus(context,null,true,true); + } } /** * If a Router Service is running, this method determines if that service is connected to a device over some form of transport. * @param context A context to access Android system services through. If null is passed, this will always return false * @param callback Use this callback to find out if the router service is connected or not. */ + @Deprecated public static void requestTransportStatus(Context context, final SdlRouterStatusProvider.ConnectedStatusCallback callback){ - requestTransportStatus(context,callback,false); + requestTransportStatus(context,callback,false, true); } - private static void requestTransportStatus(Context context, final SdlRouterStatusProvider.ConnectedStatusCallback callback, final boolean triggerRouterServicePing){ + private static void requestTransportStatus(Context context, final SdlRouterStatusProvider.ConnectedStatusCallback callback, final boolean triggerRouterServicePing, final boolean lookForServices){ if(context == null){ if(callback!=null){ callback.onConnectionStatusUpdate(false, null,context); } return; } - if(isRouterServiceRunning(context,false) && !runningBluetoothServicePackage.isEmpty()){ //So there is a service up, let's see if it's connected + if((!lookForServices || isRouterServiceRunning(context,false)) && !runningBluetoothServicePackage.isEmpty()){ //So there is a service up, let's see if it's connected final ConcurrentLinkedQueue<ComponentName> list = new ConcurrentLinkedQueue<ComponentName>(runningBluetoothServicePackage); final SdlRouterStatusProvider.ConnectedStatusCallback sdlBrCallback = new SdlRouterStatusProvider.ConnectedStatusCallback() { diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java index d3a4fd051..5ca8cec0e 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java +++ b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java @@ -1,6 +1,5 @@ package com.smartdevicelink.transport; -import static com.smartdevicelink.proxy.constants.Names.info; import static com.smartdevicelink.transport.TransportConstants.CONNECTED_DEVICE_STRING_EXTRA_NAME; import static com.smartdevicelink.transport.TransportConstants.FORMED_PACKET_EXTRA_NAME; import static com.smartdevicelink.transport.TransportConstants.HARDWARE_DISCONNECTED; @@ -23,11 +22,14 @@ import org.json.JSONException; import org.json.JSONObject; import android.Manifest; + import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -72,6 +74,30 @@ import com.smartdevicelink.transport.utl.ByteAraryMessageAssembler; import com.smartdevicelink.transport.utl.ByteArrayMessageSpliter; import com.smartdevicelink.util.AndroidTools; import com.smartdevicelink.util.BitConverter; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static com.smartdevicelink.transport.TransportConstants.CONNECTED_DEVICE_STRING_EXTRA_NAME; +import static com.smartdevicelink.transport.TransportConstants.FOREGROUND_EXTRA; +import static com.smartdevicelink.transport.TransportConstants.FORMED_PACKET_EXTRA_NAME; +import static com.smartdevicelink.transport.TransportConstants.HARDWARE_DISCONNECTED; +import static com.smartdevicelink.transport.TransportConstants.SDL_NOTIFICATION_CHANNEL_ID; +import static com.smartdevicelink.transport.TransportConstants.SDL_NOTIFICATION_CHANNEL_NAME; +import static com.smartdevicelink.transport.TransportConstants.SEND_PACKET_TO_APP_LOCATION_EXTRA_NAME; /** * <b>This class should not be modified by anyone outside of the approved contributors of the SmartDeviceLink project.</b> * This service is a central point of communication between hardware and the registered clients. It will multiplex a single transport @@ -181,6 +207,7 @@ public class SdlRouterService extends Service{ registrationIntent.setAction(action); registrationIntent.putExtra(TransportConstants.BIND_LOCATION_PACKAGE_NAME_EXTRA, this.getPackageName()); registrationIntent.putExtra(TransportConstants.BIND_LOCATION_CLASS_NAME_EXTRA, this.getClass().getName()); + registrationIntent.setFlags((Intent.FLAG_RECEIVER_FOREGROUND)); return registrationIntent; } @@ -889,6 +916,9 @@ public class SdlRouterService extends Service{ } } if(intent != null ){ + if(intent.getBooleanExtra(FOREGROUND_EXTRA, false)){ + enterForeground(); + } if(intent.hasExtra(TransportConstants.PING_ROUTER_SERVICE_EXTRA)){ //Make sure we are listening on RFCOMM if(startSequenceComplete){ //We only check if we are sure we are already through the start up process @@ -1029,18 +1059,37 @@ public class SdlRouterService extends Service{ notification = builder.getNotification(); }else{ + if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) { + //Now we need to add a notification channel + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + String channelId = SDL_NOTIFICATION_CHANNEL_ID; + CharSequence channelName = SDL_NOTIFICATION_CHANNEL_NAME; + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, importance); + notificationChannel.enableLights(false); + notificationChannel.enableVibration(false); + notificationManager.createNotificationChannel(notificationChannel); + builder.setChannelId(channelId); + + } notification = builder.build(); } if(notification == null){ Log.e(TAG, "Notification was null"); + return; } startForeground(FOREGROUND_SERVICE_ID, notification); isForeground = true; } - + private void exitForeground(){ if(isForeground){ + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){ + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.deleteNotificationChannel(TransportConstants.SDL_NOTIFICATION_CHANNEL_ID); + } + this.stopForeground(true); } } @@ -1741,7 +1790,7 @@ public class SdlRouterService extends Service{ } } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @TargetApi(Build.VERSION_CODES.HONEYCOMB) private boolean removeAllSessionsWithAppId(String appId){ synchronized(SESSION_LOCK){ if(sessionMap!=null){ diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java b/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java index 89187d524..1d1b3b792 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java +++ b/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java @@ -7,6 +7,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -488,7 +489,7 @@ public class TransportBroker { } //Make sure we know where to bind to if(this.routerService==null){ - if(!isRouterServiceRunning(getContext())){//We should be able to ignore this case because of the validation now + if((Build.VERSION.SDK_INT < Build.VERSION_CODES.O) && !isRouterServiceRunning(getContext())){//We should be able to ignore this case because of the validation now Log.d(TAG,whereToReply + " found no router service. Shutting down."); this.onHardwareDisconnected(null); return false; diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java b/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java index 6203aa3e7..8db44f500 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java +++ b/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java @@ -10,6 +10,7 @@ package com.smartdevicelink.transport; */ public class TransportConstants { public static final String START_ROUTER_SERVICE_ACTION ="sdl.router.startservice"; + public static final String FOREGROUND_EXTRA = "foreground"; public static final String BIND_LOCATION_PACKAGE_NAME_EXTRA = "BIND_LOCATION_PACKAGE_NAME_EXTRA"; public static final String BIND_LOCATION_CLASS_NAME_EXTRA = "BIND_LOCATION_CLASS_NAME_EXTRA"; @@ -54,6 +55,12 @@ public class TransportConstants { public static final String PING_ROUTER_SERVICE_EXTRA = "ping.router.service"; + public static final String SDL_NOTIFICATION_CHANNEL_ID = "sdl_notification_channel"; + public static final String SDL_NOTIFICATION_CHANNEL_NAME = "SmartDeviceLink"; + + + + /** * This class houses all important router service versions */ diff --git a/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java b/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java index ff8819ec3..bc55dcb07 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java +++ b/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java @@ -2,9 +2,16 @@ package com.smartdevicelink.util; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; + +import com.smartdevicelink.transport.TransportConstants; + +import java.util.HashMap; +import java.util.List; public class AndroidTools { /** @@ -22,4 +29,24 @@ public class AndroidTools { } return false; } + /** + * Get all SDL enabled apps. If the package name is null, it will return all apps. However, if the package name is included, the + * resulting hash map will not include the app with that package name. + * @param context + * @param myPackageName + * @return + */ + public static HashMap<String,ResolveInfo> getSdlEnabledApps(Context context, String myPackageName){ + Intent intent = new Intent(TransportConstants.START_ROUTER_SERVICE_ACTION); + List<ResolveInfo> infos = context.getPackageManager().queryBroadcastReceivers(intent, 0); + HashMap<String,ResolveInfo> sdlMultiList = new HashMap<String,ResolveInfo>(); + for(ResolveInfo info: infos){ + if(info.activityInfo.applicationInfo.packageName.equals(myPackageName)){ + continue; //Ignoring my own package + } + sdlMultiList.put(info.activityInfo.packageName, info); + } + return sdlMultiList; + } + } diff --git a/sdl_android/src/main/java/com/smartdevicelink/util/ServiceFinder.java b/sdl_android/src/main/java/com/smartdevicelink/util/ServiceFinder.java new file mode 100644 index 000000000..c115d046c --- /dev/null +++ b/sdl_android/src/main/java/com/smartdevicelink/util/ServiceFinder.java @@ -0,0 +1,141 @@ +package com.smartdevicelink.util; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ResolveInfo; +import android.os.Handler; +import android.util.Log; + +import com.smartdevicelink.transport.SdlRouterService; + +import java.util.HashMap; +import java.util.Vector; + +import static com.smartdevicelink.transport.TransportConstants.BIND_LOCATION_CLASS_NAME_EXTRA; +import static com.smartdevicelink.transport.TransportConstants.BIND_LOCATION_PACKAGE_NAME_EXTRA; +import static com.smartdevicelink.transport.TransportConstants.SEND_PACKET_TO_APP_LOCATION_EXTRA_NAME; + +/** + * Created by Joey Grover on 8/18/17. + */ + +public class ServiceFinder { + public static final String TAG = ServiceFinder.class.getSimpleName(); + + private static final int TIMEOUT = 1000; + final String receiverLocation; + final Context context; + final ServiceFinderCallback callback; + final Vector<ComponentName> services; + final HashMap<String, ResolveInfo> sdlMultiMap; + final Handler timeoutHandler; + final Runnable timeoutRunnable; + + + public ServiceFinder(Context context, String packageName, final ServiceFinderCallback callback) { + this.receiverLocation = packageName + ".ServiceFinder"; + this.context = context.getApplicationContext(); + this.callback = callback; + this.services = new Vector<>(); + + this.sdlMultiMap = AndroidTools.getSdlEnabledApps(context, packageName); + + this.context.registerReceiver(mainServiceReceiver, new IntentFilter(this.receiverLocation)); + + timeoutRunnable = new Runnable() { + @Override + public void run() { + onFinished(); + } + }; + timeoutHandler = new Handler(); + timeoutHandler.postDelayed(timeoutRunnable, TIMEOUT + (50 * packageName.length())); + + //Send out our broadcast + context.sendBroadcast(createQueryIntent(this.receiverLocation)); + + + } + + BroadcastReceiver mainServiceReceiver = new BroadcastReceiver() { + private final Object LIST_LOCK = new Object(); + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Received intent " + intent); + if (intent != null) { + String packageName = intent.getStringExtra(BIND_LOCATION_PACKAGE_NAME_EXTRA); + String className = intent.getStringExtra(BIND_LOCATION_CLASS_NAME_EXTRA); + Log.d(TAG, "Received intent from package: " + packageName + ". Classname: " + className); + synchronized (LIST_LOCK) { + //Add to running services + services.add(new ComponentName(packageName, className)); + //Remove from our waiting for response list + sdlMultiMap.remove(packageName); + + //If list is empty, return to callback and unregister + if (sdlMultiMap.isEmpty() && callback != null) { + timeoutHandler.removeCallbacks(timeoutRunnable); + onFinished(); + } + } + } + } + }; + + private void onFinished() { + if (callback != null) { + callback.onComplete(services); + } + context.unregisterReceiver(mainServiceReceiver); + + } + +// /** +// * Get all SDL enabled apps. If the package name is null, it will return all apps. However, if the package name is included, the +// * resulting hash map will not include the app with that package name. +// * +// * @param context +// * @param packageName +// * @return +// */ +// public static HashMap<String, ResolveInfo> getSdlEnabledApps(Context context, String packageName) { +// Intent intent = new Intent(TransportConstants.START_ROUTER_SERVICE_ACTION); +// PackageManager manager = context.getPackageManager(); +// List<ResolveInfo> infos = manager.queryBroadcastReceivers(intent, 0); +// HashMap<String, ResolveInfo> sdlMultiMap = new HashMap<String, ResolveInfo>(); +// for (ResolveInfo info : infos) { +// //Log.d(TAG, "Sdl enabled app: " + info.activityInfo.packageName); +// if (info.activityInfo.applicationInfo.packageName.equals(packageName)) { +// //Log.d(TAG, "Ignoring my own package"); +// continue; +// } +// +// sdlMultiMap.put(info.activityInfo.packageName, info); +// try { +// ServiceInfo[] services = manager.getPackageInfo(info.activityInfo.applicationInfo.packageName, PackageManager.GET_SERVICES).services; +// for (int i = 0; i < services.length; i++) { +// Log.d(TAG, "Found : " + services[i].name); +// } +// } catch (PackageManager.NameNotFoundException e) { +// e.printStackTrace(); +// } +// } +// return sdlMultiMap; +// } + + private static Intent createQueryIntent(String receiverLocation) { + Intent intent = new Intent(); + intent.setAction(SdlRouterService.REGISTER_WITH_ROUTER_ACTION); + intent.putExtra(SEND_PACKET_TO_APP_LOCATION_EXTRA_NAME, receiverLocation); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + return intent; + } + + public interface ServiceFinderCallback { + void onComplete(Vector<ComponentName> routerServices); + } +} |