summaryrefslogtreecommitdiff
path: root/android/sdl_android/src/main
diff options
context:
space:
mode:
authorBilal Alsharifi <599206+bilal-alsharifi@users.noreply.github.com>2020-04-29 13:02:16 -0400
committerGitHub <noreply@github.com>2020-04-29 13:02:16 -0400
commitb7a905eba90d49bb9132e69ab295713238d4abcc (patch)
tree54a21a344b63a21f3bd9391b34f869a2ba99881d /android/sdl_android/src/main
parent797b7a14a7fd67fdf6faa504865e4e22228858ef (diff)
parent9fe2e3481ba6ca1f3c82f740ade9c76fe842ed20 (diff)
downloadsdl_android-b7a905eba90d49bb9132e69ab295713238d4abcc.tar.gz
Merge pull request #1337 from smartdevicelink/release/4.11-RC4.11.0
v4.11 Release
Diffstat (limited to 'android/sdl_android/src/main')
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java35
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/file/FileManager.java13
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java14
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java189
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenDeviceIconManager.java214
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java36
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java51
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java2
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java17
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexUsbTransport.java8
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java197
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java2
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java25
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterStatusProvider.java3
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java42
-rw-r--r--android/sdl_android/src/main/res/values/sdl.xml2
16 files changed, 746 insertions, 104 deletions
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java
index 41202fab6..b0ff164cd 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java
@@ -44,6 +44,7 @@ import android.util.Log;
import com.smartdevicelink.exception.SdlException;
import com.smartdevicelink.managers.audio.AudioStreamManager;
import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.FileManagerConfig;
import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate;
import com.smartdevicelink.managers.lockscreen.LockScreenConfig;
@@ -120,6 +121,7 @@ public class SdlManager extends BaseSdlManager{
private SdlManagerListener managerListener;
private List<Class<? extends SdlSecurityBase>> sdlSecList;
private LockScreenConfig lockScreenConfig;
+ private FileManagerConfig fileManagerConfig;
private ServiceEncryptionListener serviceEncryptionListener;
// Managers
@@ -308,7 +310,7 @@ public class SdlManager extends BaseSdlManager{
protected void initialize(){
// Instantiate sub managers
this.permissionManager = new PermissionManager(_internalInterface);
- this.fileManager = new FileManager(_internalInterface, context);
+ this.fileManager = new FileManager(_internalInterface, context, fileManagerConfig);
if (lockScreenConfig.isEnabled()) {
this.lockScreenManager = new LockScreenManager(lockScreenConfig, context, _internalInterface);
}
@@ -513,6 +515,8 @@ public class SdlManager extends BaseSdlManager{
protected LockScreenConfig getLockScreenConfig() { return lockScreenConfig; }
+ protected FileManagerConfig getFileManagerConfig() { return fileManagerConfig; }
+
// SENDING REQUESTS
/**
@@ -863,6 +867,19 @@ public class SdlManager extends BaseSdlManager{
}
@Override
+ public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() {
+ return proxy.getRegisterAppInterfaceResponse();
+ }
+
+ @Override
+ public Object getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener, boolean forceUpdate) {
+ if (proxy != null && proxy.getSystemCapabilityManager() != null) {
+ return proxy.getSystemCapabilityManager().getCapability(systemCapabilityType, scListener, forceUpdate);
+ }
+ return null;
+ }
+
+ @Override
public boolean isCapabilitySupported(SystemCapabilityType systemCapabilityType){
return proxy.isCapabilitySupported(systemCapabilityType);
}
@@ -1030,6 +1047,17 @@ public class SdlManager extends BaseSdlManager{
}
/**
+ * Sets the FileManagerConfig for the session.<br>
+ * <strong>Note: If not set, the default configuration value of 1 will be set for
+ * artworkRetryCount and fileRetryCount in FileManagerConfig</strong>
+ * @param fileManagerConfig - configuration options
+ */
+ public Builder setFileManagerConfig (final FileManagerConfig fileManagerConfig){
+ sdlManager.fileManagerConfig = fileManagerConfig;
+ return this;
+ }
+
+ /**
* Sets the LockScreenConfig for the session. <br>
* <strong>Note: If not set, the default configuration will be used.</strong>
* @param lockScreenConfig - configuration options
@@ -1181,6 +1209,11 @@ public class SdlManager extends BaseSdlManager{
sdlManager.lockScreenConfig = new LockScreenConfig();
}
+ if(sdlManager.fileManagerConfig == null){
+ //if FileManagerConfig is not set use default
+ sdlManager.fileManagerConfig = new FileManagerConfig();
+ }
+
if (sdlManager.hmiLanguage == null){
sdlManager.hmiLanguage = Language.EN_US;
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/FileManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/FileManager.java
index a07653414..3e1702346 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/FileManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/FileManager.java
@@ -65,6 +65,7 @@ public class FileManager extends BaseFileManager {
private final WeakReference<Context> context;
+ @Deprecated
public FileManager(ISdl internalInterface, Context context) {
// setup
@@ -73,6 +74,18 @@ public class FileManager extends BaseFileManager {
}
/**
+ * Constructor for FileManager
+ * @param internalInterface an instance of the ISdl interface that can be used for common SDL operations (sendRpc, addRpcListener, etc)
+ * @param context an instances of Context interface to global information for application
+ * @param fileManagerConfig an instance of the FileManagerConfig gives access to artworkRetryCount and fileRetryCount to let us if those file types can be re-upload if they fail
+ */
+ public FileManager(ISdl internalInterface, Context context, FileManagerConfig fileManagerConfig) {
+ // setup
+ super(internalInterface, fileManagerConfig);
+ this.context = new WeakReference<>(context);
+ }
+
+ /**
* Creates and returns a PutFile request that would upload a given SdlFile
* @param file SdlFile with fileName and one of A) fileData, B) Uri, or C) resourceID set
* @return a valid PutFile request if SdlFile contained a fileName and sufficient data
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
index e7db70ee6..5f14cb2ed 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
@@ -51,38 +51,38 @@ public class SdlArtwork extends SdlFile implements Cloneable{
/**
* Creates a new instance of SdlArtwork
*/
- public SdlArtwork(){}
+ public SdlArtwork() {}
/**
* Creates a new instance of SdlArtwork
- * @param fileName a String value representing the name that will be used to store the file in the head unit
+ * @param fileName a String value representing the name that will be used to store the file in the head unit. You can pass null if you want the library to auto generate the name
* @param fileType a FileType enum value representing the type of the file
* @param id an int value representing the android resource id of the file
* @param persistentFile a boolean value that indicates if the file is meant to persist between sessions / ignition cycles
*/
- public SdlArtwork(@NonNull String fileName, @NonNull FileType fileType, int id, boolean persistentFile) {
+ public SdlArtwork(String fileName, @NonNull FileType fileType, int id, boolean persistentFile) {
super(fileName, fileType, id, persistentFile);
}
/**
* Creates a new instance of SdlArtwork
- * @param fileName a String value representing the name that will be used to store the file in the head unit
+ * @param fileName a String value representing the name that will be used to store the file in the head unit. You can pass null if you want the library to auto generate the name
* @param fileType a FileType enum value representing the type of the file
* @param uri a URI value representing a file's location. Currently, it only supports local files
* @param persistentFile a boolean value that indicates if the file is meant to persist between sessions / ignition cycles
*/
- public SdlArtwork(@NonNull String fileName, @NonNull FileType fileType, Uri uri, boolean persistentFile) {
+ public SdlArtwork(String fileName, @NonNull FileType fileType, Uri uri, boolean persistentFile) {
super(fileName, fileType, uri, persistentFile);
}
/**
* Creates a new instance of SdlArtwork
- * @param fileName a String value representing the name that will be used to store the file in the head unit
+ * @param fileName a String value representing the name that will be used to store the file in the head unit. You can pass null if you want the library to auto generate the name
* @param fileType a FileType enum value representing the type of the file
* @param data a byte array representing the data of the file
* @param persistentFile a boolean value that indicates if the file is meant to persist between sessions / ignition cycles
*/
- public SdlArtwork(@NonNull String fileName, @NonNull FileType fileType, byte[] data, boolean persistentFile) {
+ public SdlArtwork(String fileName, @NonNull FileType fileType, byte[] data, boolean persistentFile) {
super(fileName, fileType, data, persistentFile);
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
index 3635c9551..5d7e73f5c 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
@@ -38,63 +38,69 @@ import android.support.annotation.NonNull;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.StaticIconName;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
/**
* A class representing data to be uploaded to core
*/
public class SdlFile{
- private String fileName;
- private int id = -1;
- private Uri uri;
- private byte[] fileData;
- private FileType fileType;
- private boolean persistentFile;
- private boolean isStaticIcon;
+ private String fileName;
+ private int id = -1;
+ private Uri uri;
+ private byte[] fileData;
+ private FileType fileType;
+ private boolean persistentFile;
+ private boolean isStaticIcon;
+ private boolean shouldAutoGenerateName;
+ // Overwrite property by default is set to true in SdlFile constructors indicating that a file can be overwritten
+ private boolean overwrite = true;
/**
* Creates a new instance of SdlFile
*/
- public SdlFile(){}
+ public SdlFile() { }
/**
* Creates a new instance of SdlFile
- * @param fileName a String value representing the name that will be used to store the file in the head unit
+ * @param fileName a String value representing the name that will be used to store the file in the head unit. You can pass null if you want the library to auto generate the name
* @param fileType a FileType enum value representing the type of the file
* @param id an int value representing the android resource id of the file
* @param persistentFile a boolean value that indicates if the file is meant to persist between sessions / ignition cycles
*/
- public SdlFile(@NonNull String fileName, @NonNull FileType fileType, int id, boolean persistentFile){
- this.fileName = fileName;
- this.fileType = fileType;
- this.id = id;
- this.persistentFile = persistentFile;
+ public SdlFile(String fileName, @NonNull FileType fileType, int id, boolean persistentFile){
+ setName(fileName);
+ setType(fileType);
+ setResourceId(id);
+ setPersistent(persistentFile);
}
/**
* Creates a new instance of SdlFile
- * @param fileName a String value representing the name that will be used to store the file in the head unit
+ * @param fileName a String value representing the name that will be used to store the file in the head unit. You can pass null if you want the library to auto generate the name
* @param fileType a FileType enum value representing the type of the file
* @param uri a URI value representing a file's location. Currently, it only supports local files
* @param persistentFile a boolean value that indicates if the file is meant to persist between sessions / ignition cycles
*/
- public SdlFile(@NonNull String fileName, @NonNull FileType fileType, Uri uri, boolean persistentFile){
- this.fileName = fileName;
- this.fileType = fileType;
- this.uri = uri;
- this.persistentFile = persistentFile;
+ public SdlFile(String fileName, @NonNull FileType fileType, Uri uri, boolean persistentFile){
+ setName(fileName);
+ setType(fileType);
+ setUri(uri);
+ setPersistent(persistentFile);
}
/**
* Creates a new instance of SdlFile
- * @param fileName a String value representing the name that will be used to store the file in the head unit
+ * @param fileName a String value representing the name that will be used to store the file in the head unit. You can pass null if you want the library to auto generate the name
* @param fileType a FileType enum value representing the type of the file
* @param data a byte array representing the data of the file
* @param persistentFile a boolean value that indicates if the file is meant to persist between sessions / ignition cycles
*/
- public SdlFile(@NonNull String fileName, @NonNull FileType fileType, byte[] data, boolean persistentFile){
- this.fileName = fileName;
- this.fileType = fileType;
- this.fileData = data;
- this.persistentFile = persistentFile;
+ public SdlFile(String fileName, @NonNull FileType fileType, byte[] data, boolean persistentFile){
+ setName(fileName);
+ setType(fileType);
+ setFileData(data);
+ setPersistent(persistentFile);
}
/**
@@ -102,18 +108,30 @@ public class SdlFile{
* @param staticIconName a StaticIconName enum value representing the name of a static file that comes pre-shipped with the head unit
*/
public SdlFile(@NonNull StaticIconName staticIconName){
- this.fileName = staticIconName.toString();
- this.fileData = staticIconName.toString().getBytes();
- this.persistentFile = false;
- this.isStaticIcon = true;
+ setName(staticIconName.toString());
+ setFileData(staticIconName.toString().getBytes());
+ setPersistent(false);
+ setStaticIcon(true);
}
/**
* Sets the name of the file
- * @param fileName a String value representing the name that will be used to store the file in the head unit
+ * @param fileName a String value representing the name that will be used to store the file in the head unit. You can pass null if you want the library to auto generate the name
*/
- public void setName(@NonNull String fileName){
- this.fileName = fileName;
+ public void setName(String fileName) {
+ if (fileName != null) {
+ this.shouldAutoGenerateName = false;
+ this.fileName = fileName;
+ } else {
+ this.shouldAutoGenerateName = true;
+ if (this.getFileData() != null) {
+ this.fileName = generateFileNameFromData(this.getFileData());
+ } else if (this.getUri() != null) {
+ this.fileName = generateFileNameFromUri(this.getUri());
+ } else if (this.getResourceId() != 0) {
+ this.fileName = generateFileNameFromResourceId(this.getResourceId());
+ }
+ }
}
/**
@@ -130,6 +148,9 @@ public class SdlFile{
*/
public void setResourceId(int id){
this.id = id;
+ if (shouldAutoGenerateName) {
+ this.fileName = generateFileNameFromResourceId(id);
+ }
}
/**
@@ -146,6 +167,9 @@ public class SdlFile{
*/
public void setUri(Uri uri){
this.uri = uri;
+ if (shouldAutoGenerateName && uri != null) {
+ this.fileName = generateFileNameFromUri(uri);
+ }
}
/**
@@ -162,6 +186,9 @@ public class SdlFile{
*/
public void setFileData(byte[] data){
this.fileData = data;
+ if (shouldAutoGenerateName && data != null) {
+ this.fileName = generateFileNameFromData(data);
+ }
}
/**
@@ -219,4 +246,98 @@ public class SdlFile{
public boolean isStaticIcon() {
return isStaticIcon;
}
-} \ No newline at end of file
+
+ /**
+ * Gets the overwrite property for an SdlFile by default its set to true
+ * @return a boolean value that indicates if a file can be overwritten.
+ */
+ public boolean getOverwrite() {
+ return overwrite;
+ }
+
+ /**
+ * Sets the overwrite property for an SdlFile by default its set to true
+ * @param overwrite a boolean value that indicates if a file can be overwritten
+ */
+ public void setOverwrite(boolean overwrite) {
+ this.overwrite = overwrite;
+ }
+
+ /**
+ * Generates a file name from data by hashing the data and returning the last 16 chars
+ * @param data a byte array representing the data of the file
+ * @return a String value representing the name that will be used to store the file in the head unit
+ */
+ private String generateFileNameFromData(@NonNull byte[] data) {
+ String result;
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("md5");
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ byte[] hash = new byte[0];
+ if (messageDigest != null) {
+ hash = messageDigest.digest(data);
+ }
+ StringBuilder stringBuilder = new StringBuilder(2 * hash.length);
+ for (byte b : hash) {
+ stringBuilder.append(String.format("%02x", b & 0xff));
+ }
+ String hashString = stringBuilder.toString();
+ result = hashString.substring(hashString.length() - 16);
+ return result;
+ }
+
+ /**
+ * Generates a file name from uri by hashing the uri string and returning the last 16 chars
+ * @param uri a URI value representing a file's location
+ * @return a String value representing the name that will be used to store the file in the head unit
+ */
+ private String generateFileNameFromUri(@NonNull Uri uri) {
+ return generateFileNameFromData(uri.toString().getBytes());
+ }
+
+ /**
+ * Generates a file name from resourceId by hashing the id and returning the last 16 chars
+ * @param id an int value representing the android resource id of the file
+ * @return a String value representing the name that will be used to store the file in the head unit
+ */
+ private String generateFileNameFromResourceId(int id) {
+ return generateFileNameFromData("ResourceId".concat(String.valueOf(id)).getBytes());
+ }
+
+ /**
+ * Used to compile hashcode for SdlFile for use to compare in overridden equals method
+ * @return Custom hashcode of SdlFile variables
+ */
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result += ((getName() == null) ? 0 : Integer.rotateLeft(getName().hashCode(), 1));
+ result += ((getUri() == null) ? 0 : Integer.rotateLeft(getUri().hashCode(), 2));
+ result += ((getFileData() == null) ? 0 : Integer.rotateLeft(getFileData().hashCode(), 3));
+ result += ((getType() == null) ? 0 : Integer.rotateLeft(getType().hashCode(), 4));
+ result += Integer.rotateLeft(Boolean.valueOf(isStaticIcon()).hashCode(), 5);
+ result += Integer.rotateLeft(Boolean.valueOf(isPersistent()).hashCode(), 6);
+ result += Integer.rotateLeft(Integer.valueOf(getResourceId()).hashCode(), 7);
+ return result;
+ }
+
+ /**
+ * Uses our custom hashCode for SdlFile objects
+ * @param o - The object to compare
+ * @return boolean of whether the objects are the same or not
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) return false;
+ // if this is the same memory address, it's the same
+ if (this == o) return true;
+ // if this is not an instance of SdlFile, not the same
+ if (!(o instanceof SdlFile)) return false;
+ // return comparison
+ return hashCode() == o.hashCode();
+ }
+}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenDeviceIconManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenDeviceIconManager.java
new file mode 100644
index 000000000..b2b8e6b14
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenDeviceIconManager.java
@@ -0,0 +1,214 @@
+package com.smartdevicelink.managers.lockscreen;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import com.smartdevicelink.util.AndroidTools;
+import com.smartdevicelink.util.DebugTool;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * <strong>LockScreenDeviceIconManager</strong> <br>
+ *
+ * The LockScreenDeviceIconManager handles the logic of caching and retrieving cached lock screen icons <br>
+ *
+ */
+class LockScreenDeviceIconManager {
+
+ private Context context;
+ private static final String SDL_DEVICE_STATUS_SHARED_PREFS = "sdl.lockScreenIcon";
+ private static final String STORED_ICON_DIRECTORY_PATH = "sdl/lock_screen_icon/";
+
+ interface OnIconRetrievedListener {
+ void onImageRetrieved(Bitmap icon);
+ void onError(String info);
+ }
+
+ LockScreenDeviceIconManager(Context context) {
+ this.context = context;
+ File lockScreenDirectory = new File(context.getCacheDir(), STORED_ICON_DIRECTORY_PATH);
+ lockScreenDirectory.mkdirs();
+ }
+
+ /**
+ * Will try to return a lock screen icon either from cache or downloaded
+ * if it fails iconRetrievedListener.OnError will be called with corresponding error message
+ * @param iconURL url that the lock screen icon is downloaded from
+ * @param iconRetrievedListener an interface that will implement onIconReceived and OnError methods
+ */
+ void retrieveIcon(String iconURL, OnIconRetrievedListener iconRetrievedListener) {
+ Bitmap icon = null;
+ try {
+ if (isIconCachedAndValid(iconURL)) {
+ DebugTool.logInfo("Icon Is Up To Date");
+ icon = getFileFromCache(iconURL);
+ if (icon == null) {
+ DebugTool.logInfo("Icon from cache was null, attempting to re-download");
+ icon = AndroidTools.downloadImage(iconURL);
+ if (icon != null) {
+ saveFileToCache(icon, iconURL);
+ } else {
+ iconRetrievedListener.onError("Icon downloaded was null");
+ return;
+ }
+ }
+ iconRetrievedListener.onImageRetrieved(icon);
+ } else {
+ // The icon is unknown or expired. Download the image, save it to the cache, and update the archive file
+ DebugTool.logInfo("Lock Screen Icon Update Needed");
+ icon = AndroidTools.downloadImage(iconURL);
+ if (icon != null) {
+ saveFileToCache(icon, iconURL);
+ iconRetrievedListener.onImageRetrieved(icon);
+ } else {
+ iconRetrievedListener.onError("Icon downloaded was null");
+ }
+ }
+ } catch (IOException e) {
+ iconRetrievedListener.onError("device Icon Error Downloading, Will attempt to grab cached Icon even if expired: \n" + e.toString());
+ icon = getFileFromCache(iconURL);
+ if (icon != null) {
+ iconRetrievedListener.onImageRetrieved(icon);
+ } else {
+ iconRetrievedListener.onError("Unable to retrieve icon from cache");
+ }
+ }
+ }
+
+ /**
+ * Will decide if a cached icon is available and up to date
+ * @param iconUrl url will be hashed and used to look up last updated timestamp in shared preferences
+ * @return True when icon details are in shared preferences and less than 30 days old, False if icon details are too old or not found
+ */
+ private boolean isIconCachedAndValid(String iconUrl) {
+ String iconHash = getMD5HashFromIconUrl(iconUrl);
+ SharedPreferences sharedPref = this.context.getSharedPreferences(SDL_DEVICE_STATUS_SHARED_PREFS, Context.MODE_PRIVATE);
+ String iconLastUpdatedTime = sharedPref.getString(iconHash, null);
+ if(iconLastUpdatedTime == null) {
+ DebugTool.logInfo("No Icon Details Found In Shared Preferences");
+ return false;
+ } else {
+ DebugTool.logInfo("Icon Details Found");
+ long lastUpdatedTime = 0;
+ try {
+ lastUpdatedTime = Long.parseLong(iconLastUpdatedTime);
+ } catch (NumberFormatException e) {
+ DebugTool.logInfo("Invalid time stamp stored to shared preferences, clearing cache and share preferences");
+ clearIconDirectory();
+ sharedPref.edit().clear().commit();
+ }
+ long currentTime = System.currentTimeMillis();
+
+ long timeDifference = currentTime - lastUpdatedTime;
+ long daysBetweenLastUpdate = timeDifference / (1000 * 60 * 60 * 24);
+ return daysBetweenLastUpdate < 30;
+ }
+ }
+
+ /**
+ * Will try to save icon to cache
+ * @param icon the icon bitmap that should be saved to cache
+ * @param iconUrl the url where the icon was retrieved will be hashed and used for file and file details lookup
+ */
+ private void saveFileToCache(Bitmap icon, String iconUrl) {
+ String iconHash = getMD5HashFromIconUrl(iconUrl);
+ File f = new File(this.context.getCacheDir() + "/" + STORED_ICON_DIRECTORY_PATH, iconHash);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ icon.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
+ byte[] bitmapData = bos.toByteArray();
+
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(f);
+ fos.write(bitmapData);
+ fos.flush();
+ fos.close();
+ writeDeviceIconParametersToSharedPreferences(iconHash);
+ } catch (Exception e) {
+ DebugTool.logError("Failed to save icon to cache");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Will try to retrieve icon bitmap from cached directory
+ * @param iconUrl the url where the icon was retrieved will be hashed and used to look up file location
+ * @return bitmap of device icon or null if it fails to find the icon or read from shared preferences
+ */
+ private Bitmap getFileFromCache(String iconUrl) {
+ String iconHash = getMD5HashFromIconUrl(iconUrl);
+ SharedPreferences sharedPref = this.context.getSharedPreferences(SDL_DEVICE_STATUS_SHARED_PREFS, Context.MODE_PRIVATE);
+ String iconLastUpdatedTime = sharedPref.getString(iconHash, null);
+
+ if (iconLastUpdatedTime != null) {
+ Bitmap cachedIcon = BitmapFactory.decodeFile(this.context.getCacheDir() + "/" + STORED_ICON_DIRECTORY_PATH + "/" + iconHash);
+ if(cachedIcon == null) {
+ DebugTool.logError("Failed to get Bitmap from decoding file cache");
+ clearIconDirectory();
+ sharedPref.edit().clear().commit();
+ return null;
+ } else {
+ return cachedIcon;
+ }
+ } else {
+ DebugTool.logError("Failed to get shared preferences");
+ return null;
+ }
+ }
+
+ /**
+ * Will write information about the icon to shared preferences
+ * icon information will have a look up key of the hashed icon url and the current timestamp to indicated when the icon was last updated.
+ * @param iconHash the url where the icon was retrieved will be hashed and used lookup key
+ */
+ private void writeDeviceIconParametersToSharedPreferences(String iconHash) {
+ SharedPreferences sharedPref = this.context.getSharedPreferences(SDL_DEVICE_STATUS_SHARED_PREFS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(iconHash, String.valueOf(System.currentTimeMillis()));
+ editor.commit();
+ }
+
+ /**
+ * Create an MD5 hash of the icon url for file storage and lookup/shared preferences look up
+ * @param iconUrl the url where the icon was retrieved
+ * @return MD5 hash of the icon URL
+ */
+ private String getMD5HashFromIconUrl(String iconUrl) {
+ String iconHash = null;
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] messageDigest = md.digest(iconUrl.getBytes());
+ BigInteger no = new BigInteger(1, messageDigest);
+ String hashtext = no.toString(16);
+ while (hashtext.length() < 32) {
+ hashtext = "0" + hashtext;
+ }
+ iconHash = hashtext;
+ } catch (NoSuchAlgorithmException e) {
+ DebugTool.logError("Unable to hash icon url");
+ e.printStackTrace();
+ }
+ return iconHash;
+ }
+
+ /**
+ * Clears all files in the directory where lock screen icons are cached
+ */
+ private void clearIconDirectory() {
+ File iconDir = new File(context.getCacheDir() + "/" + STORED_ICON_DIRECTORY_PATH);
+ if (iconDir.listFiles() != null) {
+ for (File child : iconDir.listFiles()) {
+ child.delete();
+ }
+ }
+ }
+}
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 30ed1b575..2e81894ed 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
@@ -54,9 +54,8 @@ import com.smartdevicelink.proxy.rpc.enums.LockScreenStatus;
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.io.IOException;
import java.lang.ref.WeakReference;
/**
@@ -82,11 +81,14 @@ public class LockScreenManager extends BaseSubManager {
private boolean mLockScreenHasBeenDismissed, lockscreenDismissReceiverRegistered, receivedFirstDDNotification;
private String mLockscreenWarningMsg;
private BroadcastReceiver mLockscreenDismissedReceiver;
+ private LockScreenDeviceIconManager mLockScreenDeviceIconManager;
public LockScreenManager(LockScreenConfig lockScreenConfig, Context context, ISdl internalInterface){
super(internalInterface);
this.context = new WeakReference<>(context);
+ this.mLockScreenDeviceIconManager = new LockScreenDeviceIconManager(context);
+
// set initial class variables
hmiLevel = HMILevel.HMI_NONE;
@@ -231,7 +233,7 @@ public class LockScreenManager extends BaseSubManager {
if (msg.getRequestType() == RequestType.LOCK_SCREEN_ICON_URL &&
msg.getUrl() != null) {
// send intent to activity to download icon from core
- deviceIconUrl = msg.getUrl();
+ deviceIconUrl = msg.getUrl().replace("http://", "https://");
downloadDeviceIcon(deviceIconUrl);
}
}
@@ -375,17 +377,25 @@ public class LockScreenManager extends BaseSubManager {
new Thread(new Runnable(){
@Override
public void run(){
- try{
- deviceLogo = AndroidTools.downloadImage(url);
- Intent intent = new Intent(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_DOWNLOADED);
- intent.putExtra(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_EXTRA, deviceLogoEnabled);
- intent.putExtra(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_BITMAP, deviceLogo);
- if (context.get() != null) {
- context.get().sendBroadcast(intent);
+ mLockScreenDeviceIconManager.retrieveIcon(url, new LockScreenDeviceIconManager.OnIconRetrievedListener() {
+ @Override
+ public void onImageRetrieved(Bitmap icon) {
+ deviceLogo = icon;
+ if(deviceLogo != null) {
+ Intent intent = new Intent(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_DOWNLOADED);
+ intent.putExtra(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_EXTRA, deviceLogoEnabled);
+ intent.putExtra(SDLLockScreenActivity.LOCKSCREEN_DEVICE_LOGO_BITMAP, deviceLogo);
+ if (context.get() != null) {
+ context.get().sendBroadcast(intent);
+ }
+ }
}
- }catch(IOException e){
- Log.e(TAG, "device Icon Error Downloading");
- }
+
+ @Override
+ public void onError(String info) {
+ DebugTool.logError(info);
+ }
+ });
}
}).start();
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java
index bb257b915..b6f49a9a4 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java
@@ -65,6 +65,7 @@ import com.smartdevicelink.proxy.rpc.enums.HMILevel;
import com.smartdevicelink.proxy.rpc.enums.PredefinedWindows;
import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import com.smartdevicelink.proxy.rpc.enums.TouchType;
+import com.smartdevicelink.proxy.rpc.enums.VideoStreamingState;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
import com.smartdevicelink.streaming.video.SdlRemoteDisplay;
import com.smartdevicelink.streaming.video.VideoStreamingParameters;
@@ -89,12 +90,13 @@ public class VideoStreamManager extends BaseVideoStreamManager {
private float[] touchScalar = {1.0f,1.0f}; //x, y
private HapticInterfaceManager hapticManager;
private SdlMotionEvent sdlMotionEvent = null;
- private HMILevel hmiLevel;
+ private OnHMIStatus currentOnHMIStatus;
private StreamingStateMachine stateMachine;
private VideoStreamingParameters parameters;
private IVideoStreamListener streamListener;
private boolean isTransportAvailable = false;
private boolean hasStarted;
+ private String vehicleMake = null;
// INTERNAL INTERFACES
@@ -113,7 +115,7 @@ public class VideoStreamManager extends BaseVideoStreamManager {
return;
}
VideoStreamingCapability capability = (VideoStreamingCapability) internalInterface.getCapability(SystemCapabilityType.VIDEO_STREAMING);
- if(capability != null && capability.getIsHapticSpatialDataSupported()){
+ if(capability != null && Boolean.TRUE.equals(capability.getIsHapticSpatialDataSupported())){
hapticManager = new HapticInterfaceManager(internalInterface);
}
startEncoder();
@@ -125,10 +127,11 @@ public class VideoStreamManager extends BaseVideoStreamManager {
@Override
public void onServiceEnded(SdlSession session, SessionType type) {
if(SessionType.NAV.equals(type)){
- stateMachine.transitionToState(StreamingStateMachine.NONE);
if(remoteDisplay!=null){
stopStreaming();
}
+ stateMachine.transitionToState(StreamingStateMachine.NONE);
+ transitionToState(SETTING_UP);
}
}
@@ -144,13 +147,18 @@ public class VideoStreamManager extends BaseVideoStreamManager {
@Override
public void onNotified(RPCNotification notification) {
if(notification != null){
- OnHMIStatus onHMIStatus = (OnHMIStatus)notification;
+ OnHMIStatus onHMIStatus = (OnHMIStatus) notification;
if (onHMIStatus.getWindowID() != null && onHMIStatus.getWindowID() != PredefinedWindows.DEFAULT_WINDOW.getValue()) {
return;
}
- hmiLevel = onHMIStatus.getHmiLevel();
- if(hmiLevel.equals(HMILevel.HMI_FULL)){
- checkState();
+ OnHMIStatus prevOnHMIStatus = currentOnHMIStatus;
+ currentOnHMIStatus = onHMIStatus;
+ if (!HMILevel.HMI_NONE.equals(currentOnHMIStatus.getHmiLevel()) && VideoStreamManager.this.parameters == null) {
+ getVideoStreamingParams();
+ }
+ checkState();
+ if (hasStarted && (isHMIStateVideoStreamCapable(prevOnHMIStatus)) && (!isHMIStateVideoStreamCapable(currentOnHMIStatus))) {
+ internalInterface.stopVideoService();
}
}
}
@@ -171,12 +179,14 @@ public class VideoStreamManager extends BaseVideoStreamManager {
};
// MANAGER APIs
-
public VideoStreamManager(ISdl internalInterface){
super(internalInterface);
+ if(internalInterface != null && internalInterface.getRegisterAppInterfaceResponse() != null &&
+ internalInterface.getRegisterAppInterfaceResponse().getVehicleType() != null) {
+ vehicleMake = internalInterface.getRegisterAppInterfaceResponse().getVehicleType().getMake();
+ }
virtualDisplayEncoder = new VirtualDisplayEncoder();
- hmiLevel = HMILevel.HMI_NONE;
// Listen for video service events
internalInterface.addServiceListener(SessionType.NAV, serviceListener);
@@ -191,7 +201,6 @@ public class VideoStreamManager extends BaseVideoStreamManager {
@Override
public void start(CompletionListener listener) {
isTransportAvailable = internalInterface.isTransportForServiceAvailable(SessionType.NAV);
- getVideoStreamingParams();
checkState();
super.start(listener);
}
@@ -199,21 +208,26 @@ public class VideoStreamManager extends BaseVideoStreamManager {
private synchronized void checkState(){
if(this.getState() == SETTING_UP
&& isTransportAvailable
- && hmiLevel != null
- && hmiLevel.equals(HMILevel.HMI_FULL)
+ && isHMIStateVideoStreamCapable(currentOnHMIStatus)
&& parameters != null){
stateMachine.transitionToState(StreamingStateMachine.READY);
transitionToState(READY);
}
}
+ boolean isHMIStateVideoStreamCapable(OnHMIStatus onHMIStatus) {
+ HMILevel hmiLevel = (onHMIStatus != null && onHMIStatus.getHmiLevel() != null) ? onHMIStatus.getHmiLevel() : HMILevel.HMI_NONE;
+ VideoStreamingState videoStreamingState = (onHMIStatus != null && onHMIStatus.getVideoStreamingState() != null) ? onHMIStatus.getVideoStreamingState() : VideoStreamingState.STREAMABLE;
+ return (hmiLevel.equals(HMILevel.HMI_FULL) || hmiLevel.equals(HMILevel.HMI_LIMITED)) && videoStreamingState.equals(VideoStreamingState.STREAMABLE);
+ }
+
private void getVideoStreamingParams(){
if(internalInterface.getProtocolVersion().getMajor() >= 5) {
internalInterface.getCapability(SystemCapabilityType.VIDEO_STREAMING, new OnSystemCapabilityListener() {
@Override
public void onCapabilityRetrieved(Object capability) {
VideoStreamingParameters params = new VideoStreamingParameters();
- params.update((VideoStreamingCapability)capability); //Streaming parameters are ready time to stream
+ params.update((VideoStreamingCapability)capability, vehicleMake); //Streaming parameters are ready time to stream
VideoStreamManager.this.parameters = params;
checkState();
@@ -265,7 +279,7 @@ public class VideoStreamManager extends BaseVideoStreamManager {
@Override
public void onCapabilityRetrieved(Object capability) {
VideoStreamingParameters params = new VideoStreamingParameters();
- params.update((VideoStreamingCapability)capability); //Streaming parameters are ready time to stream
+ params.update((VideoStreamingCapability)capability, vehicleMake); //Streaming parameters are ready time to stream
startStreaming(params, encrypted);
}
@@ -298,13 +312,12 @@ public class VideoStreamManager extends BaseVideoStreamManager {
*/
protected void startStreaming(VideoStreamingParameters parameters, boolean encrypted){
this.parameters = parameters;
- if(hmiLevel != HMILevel.HMI_FULL){
- Log.e(TAG, "Cannot start video service if HMILevel is not FULL.");
+ if (!isHMIStateVideoStreamCapable(currentOnHMIStatus)) {
+ Log.e(TAG, "Cannot start video service in the current HMI status");
return;
}
//Start the video service
this.internalInterface.startVideoService(parameters, encrypted);
-
}
/**
@@ -394,7 +407,7 @@ public class VideoStreamManager extends BaseVideoStreamManager {
* @return boolean (true = yes, false = no)
*/
public boolean isStreaming(){
- return (stateMachine.getState() == StreamingStateMachine.STARTED) && (hmiLevel == HMILevel.HMI_FULL);
+ return (stateMachine.getState() == StreamingStateMachine.STARTED) && (isHMIStateVideoStreamCapable(currentOnHMIStatus));
}
/**
@@ -402,7 +415,7 @@ public class VideoStreamManager extends BaseVideoStreamManager {
* @return boolean (true = not paused, false = paused)
*/
public boolean isPaused(){
- return (hasStarted && stateMachine.getState() == StreamingStateMachine.STOPPED) || (hmiLevel != HMILevel.HMI_FULL);
+ return (hasStarted && stateMachine.getState() == StreamingStateMachine.STOPPED) || (!isHMIStateVideoStreamCapable(currentOnHMIStatus));
}
/**
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java
index e787fb77d..d046fe5ad 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java
@@ -1022,7 +1022,7 @@ public class RPCRequestFactory {
{
DeviceInfo msg = new DeviceInfo();
msg.setHardware(android.os.Build.MODEL);
- msg.setOs(DeviceInfo.DEVICE_OS);
+ msg.setOs("Android");
msg.setOsVersion(Build.VERSION.RELEASE);
msg.setCarrier(carrierName);
return msg;
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
index 4a0668617..ad9969163 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
@@ -445,11 +445,24 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
}
@Override
+ public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() {
+ return SdlProxyBase.this.getRegisterAppInterfaceResponse();
+ }
+
+ @Override
public void getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener) {
SdlProxyBase.this.getCapability(systemCapabilityType, scListener);
}
@Override
+ public Object getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener, boolean forceUpdate) {
+ if (_systemCapabilityManager != null) {
+ return _systemCapabilityManager.getCapability(systemCapabilityType, scListener, forceUpdate);
+ }
+ return null;
+ }
+
+ @Override
public SdlMsgVersion getSdlMsgVersion(){
try {
return SdlProxyBase.this.getSdlMsgVersion();
@@ -6721,7 +6734,7 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setHardware(android.os.Build.MODEL);
- deviceInfo.setOs(DeviceInfo.DEVICE_OS);
+ deviceInfo.setOs("Android");
deviceInfo.setOsVersion(Build.VERSION.RELEASE);
deviceInfo.setCarrier(carrierName);
@@ -8368,7 +8381,7 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
return;
}
VideoStreamingCapability capability = (VideoStreamingCapability)_systemCapabilityManager.getCapability(SystemCapabilityType.VIDEO_STREAMING);
- if(capability != null && capability.getIsHapticSpatialDataSupported()){
+ if(capability != null && Boolean.TRUE.equals(capability.getIsHapticSpatialDataSupported())){
hapticManager = new HapticInterfaceManager(internalInterface);
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexUsbTransport.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexUsbTransport.java
index 7ebcec1c2..d72987a88 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexUsbTransport.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexUsbTransport.java
@@ -249,7 +249,7 @@ public class MultiplexUsbTransport extends MultiplexBaseTransport{
if(!stateProgress){//We are trying to weed through the bad packet info until we get something
//Log.w(TAG, "Packet State Machine did not move forward from state - "+ psm.getState()+". PSM being Reset.");
psm.reset();
- buffer = new byte[READ_BUFFER_SIZE];
+ continue; //Move to the next iteration of the loop
}
if(psm.getState() == SdlPsm.FINISHED_STATE){
@@ -259,9 +259,11 @@ public class MultiplexUsbTransport extends MultiplexBaseTransport{
packet.setTransportRecord(getTransportRecord());
handler.obtainMessage(SdlRouterService.MESSAGE_READ, packet).sendToTarget();
}
- //We put a trace statement in the message read so we can avoid all the extra bytes
+ //Reset the PSM now that we have a finished packet.
+ //We will continue to loop through the data to see if any other packet
+ //is present.
psm.reset();
- buffer = new byte[READ_BUFFER_SIZE]; //FIXME just do an array copy and send off
+ continue; //Move to the next iteration of the loop
}
}
} catch (IOException e) {
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java
index 06729d62a..227b0b894 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/RouterServiceValidator.java
@@ -43,13 +43,18 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.os.AsyncTask;
import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;
import com.smartdevicelink.util.AndroidTools;
+import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.HttpRequestTask;
import com.smartdevicelink.util.HttpRequestTask.HttpRequestTaskCallback;
+import com.smartdevicelink.util.ServiceFinder;
import org.json.JSONArray;
import org.json.JSONException;
@@ -60,6 +65,11 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
+import java.util.Vector;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* This class will tell us if the currently running router service is valid or not.
@@ -138,11 +148,15 @@ public class RouterServiceValidator {
setSecurityLevel(config.securityLevel);
inDebugMode = inDebugMode();
}
-
+
/**
* Main function to call to ensure we are connecting to a validated router service
* @return whether or not the currently running router service can be trusted.
+ *
+ * Due to SDL 0220 proposal, we should use validateAsync always.
+ * This function remains only for backward compatibility.
*/
+ @Deprecated
public boolean validate(){
if(securityLevel == -1){
@@ -201,6 +215,187 @@ public class RouterServiceValidator {
}
/**
+ * Asynchronously validate the target RouterService, which includes finding the right RouterService.
+ * @param callback: callback gets called when validation finishes.
+ */
+ public void validateAsync(final ValidationStatusCallback callback) {
+ if(securityLevel == -1){
+ securityLevel = getSecurityLevel(context);
+ }
+
+ final PackageManager pm = context.getPackageManager();
+ //Grab the package for the currently running router service. We need this call regardless of if we are in debug mode or not.
+
+ if(this.service != null){
+ DebugTool.logInfo("Supplied service name of " + this.service.getClassName());
+ 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;
+ DebugTool.logWarning("Supplied service is not actually running.");
+ } else {
+ // If the running router service is created by this app, the validation is good by default
+ if (this.service.getPackageName().equals(context.getPackageName()) && callback != null) {
+ callback.onFinishedValidation(true, this.service);
+ return;
+ }
+ }
+ }
+
+ if(this.service == null){
+ DebugTool.logInfo("about finding the best Router by using retrieveBestRouterServiceName");
+ new FindRouterTask(new FindConnectedRouterCallback() {
+ @Override
+ public void onFound(ComponentName component) {
+ DebugTool.logInfo("FindConnectedRouterCallback.onFound got called. Package=" + component);
+ checkTrustedRouter(callback, pm, component);
+ }
+
+ @Override
+ public void onFailed() {
+ DebugTool.logInfo("FindConnectedRouterCallback.onFailed was called");
+ if (callback != null) {
+ callback.onFinishedValidation(false, null);
+ }
+ }
+ }).execute(this.context);
+ } else {
+ // already found the RouterService
+ checkTrustedRouter(callback, pm, service);
+ }
+
+ }
+
+ /**
+ * checkTrustedRouter: This checks to see if the given component is Trusted RouterService,
+ * and calls ValidationStatusCallback#onFinishedValidation.
+ *
+ * @param callback
+ * @param pm
+ * @param component
+ */
+ private void checkTrustedRouter(final ValidationStatusCallback callback, final PackageManager pm, final ComponentName component) {
+ String packageName = appPackageForComponentName(component, pm);
+ boolean valid = (securityLevel == MultiplexTransportConfig.FLAG_MULTI_SECURITY_OFF);
+
+ if(!valid && packageName!=null){//Make sure there is a service running
+ if(wasInstalledByAppStore(packageName)){ //Was this package installed from a trusted app store
+ if( isTrustedPackage(packageName, pm)){//Is this package on the list of trusted apps.
+ valid = true;
+ }
+ }
+ }
+ if (callback != null) {
+ callback.onFinishedValidation(valid, component);
+ if (valid) {
+ synchronized (this) {
+ this.service = component;
+ }
+ }
+ }
+ }
+ /**
+ * This method retrieves the best routerservice name asynchronously.
+ * @param context
+ */
+ //private void retrieveBestRouterServiceName(Context context) {
+ // FindRouterTask task = new FindRouterTask(null);
+ // task.execute(context);
+ //}
+
+ /**
+ * FindRouterTask: AsyncTask to find the connected RouterService.
+ */
+ class FindRouterTask extends AsyncTask<Context, Void, ComponentName> {
+ FindConnectedRouterCallback mCallback;
+ final Handler mHandler = new Handler(Looper.getMainLooper());
+ final Integer TIMEOUT_MSEC = 10000; // 10 sec
+
+ FindRouterTask(FindConnectedRouterCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ protected ComponentName doInBackground(final Context... contexts) {
+ // let's use ServiceFinder here
+ final BlockingQueue<ComponentName> serviceQueue = new LinkedBlockingQueue<>();
+ final AtomicInteger _counter = new AtomicInteger(0);
+ Context context = contexts[0];
+ final Thread _currentThread = Thread.currentThread();
+ new ServiceFinder(context, context.getPackageName(), new ServiceFinder.ServiceFinderCallback() {
+ @Override
+ public void onComplete(Vector<ComponentName> routerServices) {
+ // OK, we found the routerServices. Let's see one-by-one.
+ if (routerServices == null || routerServices.isEmpty()) {
+ _currentThread.interrupt();
+ return;
+ }
+
+
+ final int numServices = routerServices.size();
+ for (ComponentName name: routerServices) {
+ final SdlRouterStatusProvider provider = new SdlRouterStatusProvider(contexts[0], name, new SdlRouterStatusProvider.ConnectedStatusCallback() {
+ @Override
+ public void onConnectionStatusUpdate(final boolean connected, final ComponentName service, final Context context) {
+ // make sure this part runs on main thread.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ _counter.incrementAndGet();
+ if (connected) {
+ DebugTool.logInfo("We found the connected service (" + service + "); currentThread is " + Thread.currentThread().getName());
+ serviceQueue.add(service);
+ } else if (_counter.get() == numServices) {
+ DebugTool.logInfo("SdlRouterStatusProvider returns service=" + service + "; connected=" + connected);
+ _currentThread.interrupt();
+ }
+ }
+ });
+ }
+ });
+ DebugTool.logInfo("about checkIsConnected; thread=" + Thread.currentThread().getName());
+ provider.checkIsConnected();
+ }
+ }
+ });
+
+ try {
+ ComponentName found = serviceQueue.poll(TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
+ return found;
+ } catch(InterruptedException e) {
+ DebugTool.logInfo("FindRouterTask was interrupted because connected Router cannot be found");
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(ComponentName componentName) {
+ DebugTool.logInfo("onPostExecute componentName=" + componentName);
+ super.onPostExecute(componentName);
+ if (mCallback != null) {
+ if (componentName != null && componentName.getPackageName() != null && componentName.getPackageName().length() != 0) {
+ mCallback.onFound(componentName);
+ } else {
+ mCallback.onFailed();
+ }
+ }
+ }
+
+ }
+
+ /**
+ * FindConnectedRouterCallback
+ * Used internally for validating router service.
+ */
+ private interface FindConnectedRouterCallback {
+ void onFound(ComponentName component);
+ void onFailed();
+ }
+
+ interface ValidationStatusCallback {
+ void onFinishedValidation(boolean valid, ComponentName name);
+ }
+
+ /**
* This will ensure that all router services are aware that there are no valid router services running and should start up
*/
private void wakeUpRouterServices(){
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 92f19e56d..ba1cc80a0 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
@@ -303,7 +303,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
* main looper to continue forward. This still leaves the SdlRouterService killed, but prevents
* an ANR to the app that makes the startForegroundService call.
*/
- static private void setForegroundExceptionHandler() {
+ static protected void setForegroundExceptionHandler() {
final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
if(defaultUncaughtExceptionHandler != foregroundExceptionHandler){
foregroundExceptionHandler = new Thread.UncaughtExceptionHandler() {
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 3abd61094..3313fd0f3 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
@@ -69,6 +69,7 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
@@ -138,7 +139,7 @@ public class SdlRouterService extends Service{
/**
* <b> NOTE: DO NOT MODIFY THIS UNLESS YOU KNOW WHAT YOU'RE DOING.</b>
*/
- protected static final int ROUTER_SERVICE_VERSION_NUMBER = 10;
+ protected static final int ROUTER_SERVICE_VERSION_NUMBER = 11;
private static final String ROUTER_SERVICE_PROCESS = "com.smartdevicelink.router";
@@ -1343,6 +1344,7 @@ public class SdlRouterService extends Service{
// If this is the first time the service has ever connected to this device we want
// to ensure we have a record of it
setSDLConnectedStatus(address, false);
+ return FOREGROUND_TIMEOUT;
}
}
// If this is a new device or hasn't connected through SDL we want to limit the exposure
@@ -1443,8 +1445,9 @@ public class SdlRouterService extends Service{
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
builder.setContentIntent(pendingIntent);
- if(chronometerLength > 0 && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if(chronometerLength > (FOREGROUND_TIMEOUT/1000) && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//The countdown method is only available in SDKs >= 24
+ // Only add countdown if it is over the min timeout
builder.setWhen(chronometerLength + System.currentTimeMillis());
builder.setUsesChronometer(true);
builder.setChronometerCountDown(true);
@@ -1515,7 +1518,23 @@ public class SdlRouterService extends Service{
private void exitForeground(){
synchronized (NOTIFICATION_LOCK) {
if (isForeground && !isPrimaryTransportConnected()) { //Ensure that the service is in the foreground and no longer connected to a transport
- this.stopForeground(true);
+ DebugTool.logInfo("SdlRouterService to exit foreground");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ this.stopForeground(Service.STOP_FOREGROUND_DETACH);
+ }else{
+ stopForeground(false);
+ }
+ NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager!= null){
+ try {
+ notificationManager.cancelAll();
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ notificationManager.deleteNotificationChannel(SDL_NOTIFICATION_CHANNEL_ID);
+ }
+ } catch (Exception e) {
+ DebugTool.logError("Issue when removing notification and channel", e);
+ }
+ }
isForeground = false;
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterStatusProvider.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterStatusProvider.java
index fcd17a0fb..53e1dbbed 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterStatusProvider.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterStatusProvider.java
@@ -133,13 +133,14 @@ public class SdlRouterStatusProvider {
context.startService(bindingIntent);
}else {
bindingIntent.putExtra(FOREGROUND_EXTRA, true);
+ SdlBroadcastReceiver.setForegroundExceptionHandler(); //Prevent ANR in case the OS takes too long to start the service
context.startForegroundService(bindingIntent);
}
bindingIntent.setAction( TransportConstants.BIND_REQUEST_TYPE_STATUS);
return context.bindService(bindingIntent, routerConnection, Context.BIND_AUTO_CREATE);
}
-
+
private void unBindFromService(){
try{
if(context!=null && routerConnection!=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 19c223d87..1a0951503 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
@@ -59,7 +59,6 @@ import java.util.List;
public class TransportManager extends TransportManagerBase{
private static final String TAG = "TransportManager";
-
TransportBrokerImpl transport;
//Legacy Transport
@@ -67,6 +66,7 @@ public class TransportManager extends TransportManagerBase{
LegacyBluetoothHandler legacyBluetoothHandler;
final WeakReference<Context> contextWeakReference;
+ final MultiplexTransportConfig mConfig;
/**
@@ -78,32 +78,40 @@ public class TransportManager extends TransportManagerBase{
public TransportManager(MultiplexTransportConfig config, TransportEventListener listener){
super(config,listener);
+ this.mConfig = config;
+
if(config.service == null) {
config.service = SdlBroadcastReceiver.consumeQueuedRouterService();
}
contextWeakReference = new WeakReference<>(config.context);
-
- RouterServiceValidator validator = new RouterServiceValidator(config);
- if(validator.validate()){
- transport = new TransportBrokerImpl(config.context, config.appId,config.service);
- }else{
- enterLegacyMode("Router service is not trusted. Entering legacy mode");
- }
}
+ /**
+ * start internally validates the target ROuterService, which was done in ctor before.
+ */
@Override
- public void start(){
- if(transport != null){
- if (!transport.start()){
- //Unable to connect to a router service
- if(transportListener != null){
- transportListener.onError("Unable to connect with the router service");
+ public void start() {
+ final RouterServiceValidator validator = new RouterServiceValidator(mConfig);
+ validator.validateAsync(new RouterServiceValidator.ValidationStatusCallback() {
+ @Override
+ public void onFinishedValidation(boolean valid, ComponentName name) {
+ DebugTool.logInfo("onFinishedValidation valid=" + valid + "; name=" + ((name == null)? "null" : name.getPackageName()));
+ if (valid) {
+ mConfig.service = name;
+ transport = new TransportBrokerImpl(contextWeakReference.get(), mConfig.appId, mConfig.service);
+ DebugTool.logInfo("TransportManager start got called; transport=" + transport);
+ if(transport != null){
+ transport.start();
+ }
+ } else {
+ enterLegacyMode("Router service is not trusted. Entering legacy mode");
+ if(legacyBluetoothTransport != null){
+ legacyBluetoothTransport.start();
+ }
}
}
- }else if(legacyBluetoothTransport != null){
- legacyBluetoothTransport.start();
- }
+ });
}
@Override
diff --git a/android/sdl_android/src/main/res/values/sdl.xml b/android/sdl_android/src/main/res/values/sdl.xml
index 2b3a85a05..16553ad29 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 @@
<resources>
<string name="sdl_router_service_version_name" translatable="false">sdl_router_version</string>
- <integer name="sdl_router_service_version_value">10</integer>
+ <integer name="sdl_router_service_version_value">11</integer>
<string name="sdl_router_service_is_custom_name" translatable="false">sdl_custom_router</string>
</resources>