diff options
4 files changed, 246 insertions, 24 deletions
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 a15969d58..5113a9eb5 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 @@ -41,10 +41,12 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.hardware.usb.UsbManager; import android.os.Build; +import android.os.Debug; import android.os.Looper; import android.os.Parcelable; import android.util.AndroidRuntimeException; @@ -55,6 +57,7 @@ import com.smartdevicelink.transport.enums.TransportType; import com.smartdevicelink.transport.utl.SdlDeviceListener; import com.smartdevicelink.util.AndroidTools; import com.smartdevicelink.util.DebugTool; +import com.smartdevicelink.util.IntegrationValidator; import com.smartdevicelink.util.SdlAppInfo; import com.smartdevicelink.util.ServiceFinder; @@ -132,30 +135,10 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{ if (localRouterClass == null){ localRouterClass = defineLocalSdlRouterClass(); // we need to check this again because for USB apps, the returned class can still be null - if (localRouterClass != null) { - - // Check if the service declaration in AndroidManifest has the intent-filter action specified correctly - boolean serviceFilterHasAction = false; - String className = localRouterClass.getName(); - List<SdlAppInfo> services = AndroidTools.querySdlAppInfo(context, null); - for (SdlAppInfo sdlAppInfo : services) { - if(sdlAppInfo != null && sdlAppInfo.getRouterServiceComponentName() != null && className.equals((sdlAppInfo.getRouterServiceComponentName().getClassName()))){ - serviceFilterHasAction = true; - break; - } - } - if (!serviceFilterHasAction){ - DebugTool.logError(TAG, "WARNING: This application has not specified its intent-filter for the SdlRouterService. THIS WILL THROW AN EXCEPTION IN FUTURE RELEASES!!"); - } - - // Check if the service declaration in AndroidManifest has the router service version metadata specified correctly - ResolveInfo info = context.getPackageManager().resolveService(new Intent(context, localRouterClass), PackageManager.GET_META_DATA); - if (info != null) { - if (info.serviceInfo.metaData == null || !info.serviceInfo.metaData.containsKey(context.getString(R.string.sdl_router_service_version_name))) { - DebugTool.logError(TAG, "WARNING: This application has not specified its metadata tags for the SdlRouterService. THIS WILL THROW AN EXCEPTION IN FUTURE RELEASES!!"); - } - } else { - DebugTool.logError(TAG, "WARNING: This application has not specified its SdlRouterService correctly in the manifest. THIS WILL THROW AN EXCEPTION IN FUTURE RELEASES!!"); + if (false && AndroidTools.isDebugMode(context)) { + IntegrationValidator.ValidationResult result = IntegrationValidator.validate(context, localRouterClass); + if(!result.isSuccessful()){ + throw new RuntimeException(result.getResultText()); } } } 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 24d5ec850..6a0d9ddc1 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 @@ -95,6 +95,7 @@ import com.smartdevicelink.transport.utl.TransportRecord; import com.smartdevicelink.util.AndroidTools; import com.smartdevicelink.util.BitConverter; import com.smartdevicelink.util.DebugTool; +import com.smartdevicelink.util.IntegrationValidator; import com.smartdevicelink.util.SdlAppInfo; import org.json.JSONException; @@ -1104,6 +1105,12 @@ public class SdlRouterService extends Service{ @Override public void onCreate() { super.onCreate(); + if (AndroidTools.isDebugMode(getApplicationContext())) { + IntegrationValidator.ValidationResult result = IntegrationValidator.validate(getApplicationContext(), this.getClass()); + if(!result.isSuccessful()) { + throw new RuntimeException(result.getResultText()); + } + } //Add this first to avoid the runtime exceptions for the entire lifecycle of the service setRouterServiceExceptionHandler(); //This must be done regardless of if this service shuts down or not 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 bedc38028..0e75acd77 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 @@ -36,6 +36,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -191,4 +192,11 @@ public class AndroidTools { bis.close(); return result; } + + public static boolean isDebugMode(Context context){ + if(context != null && context.getApplicationInfo() != null){ + return 0 != ( context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ); + } + 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 new file mode 100644 index 000000000..77786a256 --- /dev/null +++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2020 Livio, 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 Livio Inc. 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.util; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Build; + +import com.smartdevicelink.R; +import com.smartdevicelink.transport.TransportConstants; + +import java.util.ArrayList; +import java.util.List; + +public class IntegrationValidator { + + private static final String TAG = "IntegrationValidator"; + private static final char CHECK_MARK = 0x2713; + private static final char FAIL_MARK = 0x2715; + + public static ValidationResult validate(Context context, Class localRouterClass) { + StringBuilder builder = new StringBuilder(); + builder.append("\n-----------------------------------"); + builder.append("\n Integration Validator Results: \n"); + builder.append("-----------------------------------\n"); + List<ValidationResult> results = new ArrayList<>(); + + results.add(checkPermissions(context)); + + if (localRouterClass != null) { + results.add(checkRoutServiceMetadata(context, localRouterClass)); + results.add(checkRouterServiceIntent(context, localRouterClass)); + }else{ + results.add(new ValidationResult(false, "SdlRouterService is not defined in SdlBroadcastReceiver and therefore some checks were not completed")); + } + + results.add( checkBroadcastReceiver(context) ); + + boolean success = true; + for(ValidationResult result : results) { + if(result.successful) { + builder.append(CHECK_MARK + " "); + } else { + success = false; + builder.append(FAIL_MARK + " "); + } + builder.append(result.resultText); + builder.append("\n\n"); + + + } + + if(!success) { + builder.append("Please see the guides for how to fix these issues at www.smartdevicelink.com"); + } + + return new ValidationResult(success, builder.toString()); + } + + private static ValidationResult checkPermissions(Context context) { + ValidationResult retVal = new ValidationResult(true,"Permission check passed"); + List<String> permissionList = new ArrayList<>(); + permissionList.add(Manifest.permission.BLUETOOTH); + permissionList.add(Manifest.permission.INTERNET); + permissionList.add(Manifest.permission.ACCESS_NETWORK_STATE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + permissionList.add(Manifest.permission.FOREGROUND_SERVICE); + } + try { + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); + String[] permissionInfos = packageInfo.requestedPermissions; + + if (permissionInfos != null && permissionInfos.length > 0) { + String permissionInfo; + for (int i = 0; i < permissionInfos.length; i++) { + permissionInfo = permissionInfos[i]; + permissionList.remove(permissionInfo); + } + } + } catch (Exception e) { } + + if (!permissionList.isEmpty()) { + StringBuilder builder = new StringBuilder(); + builder.append("This application is missing permissions: \n"); + for (String permission : permissionList) { + builder.append(" - "); + builder.append(permission); + builder.append("\n"); + } + retVal.successful = false; + retVal.resultText = builder.toString(); + } + + return retVal; + } + + public static ValidationResult checkBroadcastReceiver(Context context) { + ValidationResult retVal = new ValidationResult(true,"SdlBroadcastReceiver check passed"); + try { + Intent intent = new Intent(); + intent.setAction(TransportConstants.START_ROUTER_SERVICE_ACTION); + List<ResolveInfo> sdlReceivers = context.getPackageManager().queryBroadcastReceivers(intent, 0); + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_RECEIVERS); + ActivityInfo[] receivers = packageInfo.receivers; + + if (receivers != null && receivers.length > 0) { + ActivityInfo receiver; + for (int i = 0; i < receivers.length; i++) { + + receiver = receivers[i]; + if (receiver != null) { + int j = 0; + for (ResolveInfo sdlReceiver : sdlReceivers) { + if (receiver.name.equals(sdlReceiver.activityInfo.name)) { + return retVal; + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + retVal.successful = false; + retVal.resultText = "This application has not specified its SDL Receiver properly."; + return retVal; + } + + // Check if the service declaration in AndroidManifest has the router service version metadata specified correctly + private static ValidationResult checkRoutServiceMetadata(Context context, Class localRouterClass) { + ValidationResult retVal = new ValidationResult(true,"SdlRouterService entry and metadata checks passed"); + + ResolveInfo info = context.getPackageManager().resolveService(new Intent(context, localRouterClass), PackageManager.GET_META_DATA); + if (info != null) { + if (info.serviceInfo.metaData == null || !info.serviceInfo.metaData.containsKey(context.getString(R.string.sdl_router_service_version_name))) { + retVal.successful = false; + retVal.resultText = "This application has not specified its metadata tags for the SdlRouterService."; + } + } else { + retVal.successful = false; + retVal.resultText = "This application has not specified its SdlRouterService correctly in the manifest."; + } + return retVal; + } + + // Check if the service declaration in AndroidManifest has the intent-filter action specified correctly + private static ValidationResult checkRouterServiceIntent(Context context, Class localRouterClass) { + ValidationResult retVal = new ValidationResult(true,"SdlRouterService intent filter check passed"); + + boolean serviceFilterHasAction = false; + String className = localRouterClass.getName(); + List<SdlAppInfo> services = AndroidTools.querySdlAppInfo(context, null); + for (SdlAppInfo sdlAppInfo : services) { + if (sdlAppInfo != null && sdlAppInfo.getRouterServiceComponentName() != null + && className.equals((sdlAppInfo.getRouterServiceComponentName().getClassName()))) { + serviceFilterHasAction = true; + break; + } + } + if (!serviceFilterHasAction) { + retVal.successful = false; + retVal.resultText = "This application has not specified its intent-filter for the SdlRouterService."; + } + + return retVal; + } + + /** + * Results from a validation check. + * Includes if the check was successful and a human readable string to describe the results. + */ + public static final class ValidationResult { + boolean successful; + String resultText; + + ValidationResult(boolean successful, String resultText) { + this.successful = successful; + this.resultText = resultText; + } + + public boolean isSuccessful() { + return successful; + } + + public String getResultText() { + return resultText; + } + } +} |