summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShinichi Watanabe <swatanabe@xevo.com>2021-01-22 18:58:53 +0900
committerShinichi Watanabe <swatanabe@xevo.com>2021-01-22 18:58:53 +0900
commitd37b8e546d50f9a07248b7f041f9bc8d9411a8c0 (patch)
tree4ab052beadaf0d534d528b90bf27c081fe245473
parent19e10a2e4d06710670bc2590f3b8874656a4dce8 (diff)
parent4ee9071bda600f9cc119a3c7c85cd850d75af482 (diff)
downloadsdl_android-d37b8e546d50f9a07248b7f041f9bc8d9411a8c0.tar.gz
Merge remote-tracking branch 'upstream/integration/stable_frame_rate' into feature/issue-1569
-rw-r--r--android/sdl_android/build.gradle2
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/audio/AudioStreamManagerTest.java19
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java721
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java5
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java5
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperationTest.java4
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java14
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java31
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java297
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/RPCStructTests.java59
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/RPCGenericTests.java2
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/requests/SetMediaClockTimerTests.java8
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompat.java111
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompatOperation.java84
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java10
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/file/FileManager.java118
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java5
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/ISdl.java7
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java608
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java103
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/DispatchGroup.java65
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/FileManagerCompletionListener.java42
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/ListFilesOperation.java111
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/SdlFileWrapper.java56
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java377
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java14
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java10
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonReplaceOperation.java14
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperation.java25
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java1
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java25
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java230
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java33
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java230
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java18
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java61
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java47
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/ShowConstantTbt.java30
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java7
-rw-r--r--javaEE/javaEE/build.gradle2
-rw-r--r--javaSE/javaSE/build.gradle2
-rw-r--r--javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/FileManager.java93
-rw-r--r--javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java5
45 files changed, 2492 insertions, 1232 deletions
diff --git a/android/sdl_android/build.gradle b/android/sdl_android/build.gradle
index 7419e64dd..378273a7a 100644
--- a/android/sdl_android/build.gradle
+++ b/android/sdl_android/build.gradle
@@ -43,7 +43,7 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'com.smartdevicelink:bson_java_port:1.2.2'
- api 'com.livio.taskmaster:taskmaster:0.3.0'
+ api 'com.livio.taskmaster:taskmaster:0.4.0'
api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
api 'androidx.annotation:annotation:1.1.0'
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0'
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/audio/AudioStreamManagerTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/audio/AudioStreamManagerTest.java
index ce8183b9d..d81b9ccc6 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/audio/AudioStreamManagerTest.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/audio/AudioStreamManagerTest.java
@@ -9,6 +9,7 @@ import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.livio.taskmaster.Taskmaster;
import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.audio.AudioStreamManager.SampleType;
@@ -50,6 +51,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class AudioStreamManagerTest extends TestCase {
public static final String TAG = AudioStreamManagerTest.class.getSimpleName();
@@ -107,7 +109,7 @@ public class AudioStreamManagerTest extends TestCase {
}
};
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
SystemCapabilityManager systemCapabilityManager = mock(SystemCapabilityManager.class);
doReturn(systemCapabilityManager).when(internalInterface).getSystemCapabilityManager();
AudioPassThruCapabilities audioCapabilities = new AudioPassThruCapabilities(SamplingRate._16KHZ, BitsPerSample._16_BIT, AudioType.PCM);
@@ -299,7 +301,7 @@ public class AudioStreamManagerTest extends TestCase {
}
};
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
SystemCapabilityManager systemCapabilityManager = mock(SystemCapabilityManager.class);
doReturn(systemCapabilityManager).when(internalInterface).getSystemCapabilityManager();
doReturn(true).when(internalInterface).isConnected();
@@ -530,7 +532,7 @@ public class AudioStreamManagerTest extends TestCase {
}
};
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
SystemCapabilityManager systemCapabilityManager = mock(SystemCapabilityManager.class);
doReturn(systemCapabilityManager).when(internalInterface).getSystemCapabilityManager();
doReturn(true).when(internalInterface).isConnected();
@@ -609,7 +611,7 @@ public class AudioStreamManagerTest extends TestCase {
}
};
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
SystemCapabilityManager systemCapabilityManager = mock(SystemCapabilityManager.class);
doReturn(systemCapabilityManager).when(internalInterface).getSystemCapabilityManager();
doReturn(true).when(internalInterface).isConnected();
@@ -747,4 +749,13 @@ public class AudioStreamManagerTest extends TestCase {
stream.write((int) ((audiolength >> 16) & 0xff));
stream.write((int) ((audiolength >> 24) & 0xff));
}
+
+ private ISdl createISdlMock() {
+ ISdl internalInterface = mock(ISdl.class);
+ Taskmaster taskmaster = new Taskmaster.Builder().build();
+ taskmaster.start();
+
+ when(internalInterface.getTaskmaster()).thenReturn(taskmaster);
+ return internalInterface;
+ }
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java
index e45d455e0..90bedd434 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java
@@ -2,14 +2,17 @@ package com.smartdevicelink.managers.file;
import android.content.Context;
import android.net.Uri;
+import android.os.Handler;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.livio.taskmaster.Taskmaster;
import com.smartdevicelink.managers.BaseSubManager;
import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
import com.smartdevicelink.managers.file.filetypes.SdlFile;
+import com.smartdevicelink.protocol.enums.SessionType;
import com.smartdevicelink.proxy.RPCMessage;
import com.smartdevicelink.proxy.RPCRequest;
import com.smartdevicelink.proxy.rpc.DeleteFile;
@@ -18,11 +21,12 @@ import com.smartdevicelink.proxy.rpc.ListFiles;
import com.smartdevicelink.proxy.rpc.ListFilesResponse;
import com.smartdevicelink.proxy.rpc.PutFile;
import com.smartdevicelink.proxy.rpc.PutFileResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.Result;
import com.smartdevicelink.proxy.rpc.enums.StaticIconName;
-import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
import com.smartdevicelink.test.TestValues;
+import com.smartdevicelink.util.Version;
import org.junit.Before;
import org.junit.Test;
@@ -30,20 +34,21 @@ import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* This is a unit test class for the SmartDeviceLink library manager class :
@@ -54,73 +59,21 @@ public class FileManagerTests {
public static final String TAG = "FileManagerTests";
private Context mTestContext;
private SdlFile validFile;
+ private Handler mainHandler;
+ private Version rpcVersion;
+ private long mtuSize;
// SETUP / HELPERS
@Before
public void setUp() throws Exception {
mTestContext = getInstrumentation().getTargetContext();
- validFile = new SdlFile();
- validFile.setName(TestValues.GENERAL_STRING);
- validFile.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile.setPersistent(false);
+ mainHandler = new Handler(mTestContext.getMainLooper());
+ rpcVersion = new Version(7, 0, 0);
+ mtuSize = 131072;
+ validFile = new SdlFile(TestValues.GENERAL_STRING, FileType.BINARY, TestValues.GENERAL_STRING.getBytes(), false);
}
- private Answer<Void> onPutFileFailureOnError = new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- Object[] args = invocation.getArguments();
- RPCRequest message = (RPCRequest) args[0];
- if (message instanceof PutFile) {
- int correlationId = message.getCorrelationID();
- PutFileResponse putFileResponse = new PutFileResponse(false, Result.REJECTED);
- putFileResponse.setInfo("Binary data empty");
- message.getOnRPCResponseListener().onResponse(correlationId, putFileResponse);
- }
- return null;
- }
- };
-
- private Answer<Void> onSendRequestsFailOnError = new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- Object[] args = invocation.getArguments();
- List<RPCRequest> rpcs = (List<RPCRequest>) args[0];
- OnMultipleRequestListener listener = (OnMultipleRequestListener) args[1];
- if (rpcs.get(0) instanceof PutFile) {
- for (RPCRequest message : rpcs) {
- int correlationId = message.getCorrelationID();
- listener.addCorrelationId(correlationId);
- PutFileResponse putFileResponse = new PutFileResponse(false, Result.REJECTED);
- putFileResponse.setInfo("Binary data empty");
- listener.onResponse(correlationId, putFileResponse);
- }
- listener.onFinished();
- }
- return null;
- }
- };
-
- private Answer<Void> onListFileUploadSuccess = new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- Object[] args = invocation.getArguments();
- List<RPCRequest> rpcs = (List<RPCRequest>) args[0];
- OnMultipleRequestListener listener = (OnMultipleRequestListener) args[1];
- if (rpcs.get(0) instanceof PutFile) {
- for (RPCRequest message : rpcs) {
- int correlationId = message.getCorrelationID();
- listener.addCorrelationId(correlationId);
- PutFileResponse putFileResponse = new PutFileResponse();
- putFileResponse.setSuccess(true);
- listener.onResponse(correlationId, putFileResponse);
- }
- listener.onFinished();
- }
- return null;
- }
- };
-
private Answer<Void> onListFilesSuccess = new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
@@ -169,104 +122,72 @@ public class FileManagerTests {
}
};
- private Answer<Void> onListDeleteRequestSuccess = new Answer<Void>() {
+ private Answer<Void> onDeleteFileSuccess = new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
- List<RPCRequest> rpcs = (List<RPCRequest>) args[0];
- OnMultipleRequestListener listener = (OnMultipleRequestListener) args[1];
- if (rpcs.get(0) instanceof DeleteFile) {
- for (RPCRequest message : rpcs) {
- int correlationId = message.getCorrelationID();
- listener.addCorrelationId(correlationId);
- DeleteFileResponse deleteFileResponse = new DeleteFileResponse();
- deleteFileResponse.setSuccess(true);
- listener.onResponse(correlationId, deleteFileResponse);
- }
- listener.onFinished();
+ RPCRequest message = (RPCRequest) args[0];
+ if (message instanceof DeleteFile) {
+ int correlationId = message.getCorrelationID();
+ DeleteFileResponse deleteFileResponse = new DeleteFileResponse();
+ deleteFileResponse.setSuccess(true);
+ message.getOnRPCResponseListener().onResponse(correlationId, deleteFileResponse);
}
return null;
}
};
- private Answer<Void> onListDeleteRequestFail = new Answer<Void>() {
+ private Answer<Void> onDeleteFileFailure = new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
- List<RPCRequest> rpcs = (List<RPCRequest>) args[0];
- OnMultipleRequestListener listener = (OnMultipleRequestListener) args[1];
- if (rpcs.get(0) instanceof DeleteFile) {
- for (RPCRequest message : rpcs) {
- int correlationId = message.getCorrelationID();
- listener.addCorrelationId(correlationId);
- DeleteFileResponse deleteFileResponse = new DeleteFileResponse(false, Result.REJECTED);
- deleteFileResponse.setInfo("Binary data empty");
- listener.onResponse(correlationId, deleteFileResponse);
- }
- listener.onFinished();
- }
- return null;
- }
- };
-
- private Answer<Void> onSendRequestsFailPartialOnError = new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- Object[] args = invocation.getArguments();
- List<RPCRequest> rpcs = (List<RPCRequest>) args[0];
- OnMultipleRequestListener listener = (OnMultipleRequestListener) args[1];
- if (rpcs.get(0) instanceof PutFile) {
- boolean flip = false;
- for (RPCRequest message : rpcs) {
- int correlationId = message.getCorrelationID();
- listener.addCorrelationId(correlationId);
- PutFileResponse putFileResponse = new PutFileResponse();
- if (flip) {
- putFileResponse.setSuccess(true);
- flip = false;
- listener.onResponse(correlationId, putFileResponse);
- } else {
- flip = true;
- putFileResponse.setSuccess(false);
- putFileResponse.setResultCode(Result.REJECTED);
- putFileResponse.setInfo("Binary data empty");
- listener.onResponse(correlationId, putFileResponse);
- }
- }
- listener.onFinished();
+ RPCRequest message = (RPCRequest) args[0];
+ if (message instanceof DeleteFile) {
+ int correlationId = message.getCorrelationID();
+ DeleteFileResponse deleteFileResponse = new DeleteFileResponse(false, Result.REJECTED);
+ deleteFileResponse.setInfo("Binary data empty");
+ message.getOnRPCResponseListener().onResponse(correlationId, deleteFileResponse);
}
return null;
}
};
- // TESTS
-
/**
* Test deleting list of files, success
*/
@Test
public void testDeleteRemoteFilesWithNamesSuccess() {
- final ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onListDeleteRequestSuccess).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ doAnswer(onDeleteFileSuccess).when(internalInterface).sendRPC(any(DeleteFile.class));
- final List<String> fileNames = new ArrayList<>();
- fileNames.add("Julian");
- fileNames.add("Jake");
+ final List<String> fileNames = Arrays.asList("Julian", "Jake");
FileManagerConfig fileManagerConfig = new FileManagerConfig();
- fileManagerConfig.setFileRetryCount(2);
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ }
+ });
+
+ fileManager.mutableRemoteFileNames.addAll(fileNames);
+
fileManager.deleteRemoteFilesWithNames(fileNames, new MultipleFileCompletionListener() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertTrue(errors == null);
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertNull(errors);
+ }
+ });
}
});
}
@@ -278,27 +199,36 @@ public class FileManagerTests {
*/
@Test
public void testDeleteRemoteFilesWithNamesFail() {
- final ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onListDeleteRequestFail).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ doAnswer(onDeleteFileFailure).when(internalInterface).sendRPC(any(DeleteFile.class));
- final List<String> fileNames = new ArrayList<>();
- fileNames.add("Julian");
- fileNames.add("Jake");
+ final List<String> fileNames = Arrays.asList("Julian", "Jake");
FileManagerConfig fileManagerConfig = new FileManagerConfig();
- fileManagerConfig.setFileRetryCount(2);
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ }
+ });
+
+ fileManager.mutableRemoteFileNames.addAll(fileNames);
fileManager.deleteRemoteFilesWithNames(fileNames, new MultipleFileCompletionListener() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertTrue(errors.size() == 2);
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals(2, errors.size());
+ }
+ });
}
});
}
@@ -310,10 +240,10 @@ public class FileManagerTests {
*/
@Test
public void testFileUploadRetry() {
- final ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onPutFileFailureOnError).when(internalInterface).sendRPC(any(PutFile.class));
+ doAnswer(onPutFileFailure).when(internalInterface).sendRPC(any(PutFile.class));
FileManagerConfig fileManagerConfig = new FileManagerConfig();
fileManagerConfig.setFileRetryCount(2);
@@ -324,17 +254,28 @@ public class FileManagerTests {
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success1);
+ }
+ });
+
fileManager.uploadFile(validFile, new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertFalse(success);
+ public void onComplete(final boolean success2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success2);
+ verify(internalInterface, times(4)).sendRPC(any(RPCMessage.class));
+ }
+ });
}
});
}
});
- verify(internalInterface, times(4)).sendRPC(any(RPCMessage.class));
}
/**
@@ -342,24 +283,12 @@ public class FileManagerTests {
*/
@Test
public void testArtworkUploadRetry() {
- final ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onPutFileFailureOnError).when(internalInterface).sendRPC(any(PutFile.class));
-
- final SdlFile validFile2 = new SdlFile();
- validFile2.setName(TestValues.GENERAL_STRING + "2");
- validFile2.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile2.setPersistent(false);
- validFile2.setType(FileType.GRAPHIC_PNG);
-
- final SdlFile validFile3 = new SdlFile();
- validFile3.setName(TestValues.GENERAL_STRING + "3");
- validFile3.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile3.setPersistent(false);
- validFile3.setType(FileType.GRAPHIC_BMP);
+ doAnswer(onPutFileFailure).when(internalInterface).sendRPC(any(PutFile.class));
- validFile.setType(FileType.GRAPHIC_JPEG);
+ final SdlArtwork validArtwork = new SdlArtwork(TestValues.GENERAL_STRING + "1", FileType.GRAPHIC_JPEG, TestValues.GENERAL_STRING.getBytes(), false);
FileManagerConfig fileManagerConfig = new FileManagerConfig();
fileManagerConfig.setArtworkRetryCount(2);
@@ -367,33 +296,28 @@ public class FileManagerTests {
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
- fileManager.uploadFile(validFile, new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- assertFalse(success);
- verify(internalInterface, times(4)).sendRPC(any(RPCMessage.class));
- }
- });
-
- fileManager.uploadFile(validFile2, new CompletionListener() {
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
@Override
- public void onComplete(boolean success) {
- assertFalse(success);
- verify(internalInterface, times(7)).sendRPC(any(RPCMessage.class));
+ public void run() {
+ assertTrue(success1);
}
});
- fileManager.uploadFile(validFile3, new CompletionListener() {
+ fileManager.uploadArtwork(validArtwork, new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertFalse(success);
+ public void onComplete(final boolean success2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success2);
+ verify(internalInterface, times(4)).sendRPC(any(RPCMessage.class));
+ }
+ });
}
});
}
});
- verify(internalInterface, times(10)).sendRPC(any(RPCMessage.class));
}
/**
@@ -401,41 +325,44 @@ public class FileManagerTests {
*/
@Test
public void testListFilesUploadRetry() {
- final ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onSendRequestsFailOnError).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
-
- SdlFile validFile2 = new SdlFile();
- validFile2.setName(TestValues.GENERAL_STRING + "2");
- validFile2.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile2.setPersistent(false);
- validFile2.setType(FileType.GRAPHIC_JPEG);
+ doAnswer(onPutFileFailure).when(internalInterface).sendRPC(any(PutFile.class));
- validFile.setType(FileType.AUDIO_WAVE);
+ final SdlArtwork validFile2 = new SdlArtwork(TestValues.GENERAL_STRING + "2", FileType.GRAPHIC_JPEG, TestValues.GENERAL_STRING.getBytes(), false);
- final List<SdlFile> list = new ArrayList<>();
- list.add(validFile);
- list.add(validFile2);
+ final List<SdlFile> list = Arrays.asList(validFile, validFile2);
FileManagerConfig fileManagerConfig = new FileManagerConfig();
- fileManagerConfig.setArtworkRetryCount(2);
- fileManagerConfig.setFileRetryCount(4);
+ fileManagerConfig.setFileRetryCount(3);
+ fileManagerConfig.setArtworkRetryCount(4);
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- fileManager.uploadFiles(list, new MultipleFileCompletionListener() {
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertTrue(errors.size() == 2); // We need to make sure it kept track of both Files
+ public void run() {
+ assertTrue(success1);
}
});
+ fileManager.uploadFiles(list, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals(2, errors.size()); // We need to make sure it kept track of both Files
+ verify(internalInterface, times(9)).sendRPC(any(PutFile.class));
+ }
+ });
+ }
+ });
}
});
- verify(internalInterface, times(5)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
}
/**
@@ -443,7 +370,7 @@ public class FileManagerTests {
*/
@Test
public void testInitializationSuccess() {
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
@@ -451,11 +378,16 @@ public class FileManagerTests {
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
- assertEquals(fileManager.getState(), BaseSubManager.READY);
- assertEquals(fileManager.getRemoteFileNames(), TestValues.GENERAL_STRING_LIST);
- assertEquals(TestValues.GENERAL_INT, fileManager.getBytesAvailable());
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ assertEquals(fileManager.getState(), BaseSubManager.READY);
+ assertEquals(fileManager.getRemoteFileNames(), TestValues.GENERAL_STRING_LIST);
+ assertEquals(TestValues.GENERAL_INT, fileManager.getBytesAvailable());
+ }
+ });
}
});
}
@@ -465,8 +397,7 @@ public class FileManagerTests {
*/
@Test
public void testFileUploadSuccess() {
- ISdl internalInterface = mock(ISdl.class);
-
+ ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
@@ -475,19 +406,31 @@ public class FileManagerTests {
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success1);
+ }
+ });
+
fileManager.uploadFile(validFile, new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success2);
+ assertTrue(fileManager.getRemoteFileNames().contains(validFile.getName()));
+ assertTrue(fileManager.hasUploadedFile(validFile));
+ assertEquals(TestValues.GENERAL_INT, fileManager.getBytesAvailable());
+ }
+ });
+
}
});
}
});
- assertTrue(fileManager.getRemoteFileNames().contains(validFile.getName()));
- assertTrue(fileManager.hasUploadedFile(validFile));
- assertEquals(TestValues.GENERAL_INT, fileManager.getBytesAvailable());
}
/**
@@ -495,7 +438,7 @@ public class FileManagerTests {
*/
@Test
public void testFileUploadFailure() {
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
doAnswer(onPutFileFailure).when(internalInterface).sendRPC(any(PutFile.class));
@@ -504,14 +447,24 @@ public class FileManagerTests {
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success1);
+ }
+ });
fileManager.uploadFile(validFile, new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertFalse(success);
- assertFalse(fileManager.getRemoteFileNames().contains(validFile.getName()));
- assertFalse(fileManager.hasUploadedFile(validFile));
+ public void onComplete(final boolean success2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success2);
+ assertFalse(fileManager.getRemoteFileNames().contains(validFile.getName()));
+ assertFalse(fileManager.hasUploadedFile(validFile));
+ }
+ });
}
});
}
@@ -523,7 +476,7 @@ public class FileManagerTests {
*/
@Test
public void testFileUploadForStaticIcon() {
- ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
@@ -531,18 +484,28 @@ public class FileManagerTests {
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success1);
+ }
+ });
SdlArtwork artwork = new SdlArtwork(StaticIconName.ALBUM);
fileManager.uploadFile(artwork, new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success2);
+ verify(internalInterface, times(1)).sendRPC(any(RPCMessage.class));
+ }
+ });
}
});
}
});
- verify(internalInterface, times(1)).sendRPC(any(RPCMessage.class));
}
/**
@@ -550,31 +513,41 @@ public class FileManagerTests {
*/
@Test
public void testMultipleFileUploadsForStaticIcon() {
- ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onListFileUploadSuccess).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
FileManagerConfig fileManagerConfig = new FileManagerConfig();
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
- SdlArtwork artwork = new SdlArtwork(StaticIconName.ALBUM);
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success1);
+ }
+ });
+
+ SdlArtwork artwork1 = new SdlArtwork(StaticIconName.ALBUM);
SdlArtwork artwork2 = new SdlArtwork(StaticIconName.FILENAME);
- List<SdlArtwork> testStaticIconUpload = new ArrayList<>();
- testStaticIconUpload.add(artwork);
- testStaticIconUpload.add(artwork2);
+ List<SdlArtwork> testStaticIconUpload = Arrays.asList(artwork1, artwork2);
+
fileManager.uploadFiles(testStaticIconUpload, new MultipleFileCompletionListener() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertTrue(errors == null);
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals(2, errors.size());
+ verify(internalInterface, times(0)).sendRPC(any(PutFile.class));
+ }
+ });
}
});
}
});
- verify(internalInterface, times(0)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
}
/**
@@ -582,10 +555,10 @@ public class FileManagerTests {
*/
@Test
public void testMultipleFileUploadsForPartialStaticIcon() {
- ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onListFileUploadSuccess).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
FileManagerConfig fileManagerConfig = new FileManagerConfig();
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
@@ -595,19 +568,21 @@ public class FileManagerTests {
assertTrue(success);
SdlArtwork artwork = new SdlArtwork(StaticIconName.ALBUM);
SdlArtwork artwork2 = new SdlArtwork(StaticIconName.FILENAME);
- List<SdlFile> testFileuploads = new ArrayList<>();
- testFileuploads.add(artwork);
- testFileuploads.add(artwork2);
- testFileuploads.add(validFile);
- fileManager.uploadFiles(testFileuploads, new MultipleFileCompletionListener() {
+ List<SdlFile> testFileUploads = Arrays.asList(artwork, artwork2, validFile);
+ fileManager.uploadFiles(testFileUploads, new MultipleFileCompletionListener() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertTrue(errors == null);
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals(2, errors.size());
+ verify(internalInterface, times(1)).sendRPC(any(PutFile.class));
+ }
+ });
}
});
}
});
- verify(internalInterface, times(1)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
}
/**
@@ -615,7 +590,7 @@ public class FileManagerTests {
*/
@Test
public void testInvalidSdlFileInput() {
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
@@ -623,27 +598,35 @@ public class FileManagerTests {
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
- SdlFile sdlFile = new SdlFile();
- // Don't set name
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success1);
+ }
+ });
+
+ SdlFile sdlFile;
+
+ // Test 1 - Don't set name
+ sdlFile = new SdlFile();
sdlFile.setFileData(TestValues.GENERAL_BYTE_ARRAY);
checkForUploadFailure(fileManager, sdlFile);
+ // Test 2 - Don't set data
sdlFile = new SdlFile();
sdlFile.setName(TestValues.GENERAL_STRING);
- // Don't set data
checkForUploadFailure(fileManager, sdlFile);
+ // Test 3 - Give an invalid resource ID
sdlFile = new SdlFile();
sdlFile.setName(TestValues.GENERAL_STRING);
- // Give an invalid resource ID
sdlFile.setResourceId(TestValues.GENERAL_INT);
checkForUploadFailure(fileManager, sdlFile);
+ // Test4 - Set invalid Uri
sdlFile = new SdlFile();
sdlFile.setName(TestValues.GENERAL_STRING);
- // Set invalid Uri
Uri testUri = Uri.parse("http://www.google.com");
sdlFile.setUri(testUri);
checkForUploadFailure(fileManager, sdlFile);
@@ -658,19 +641,17 @@ public class FileManagerTests {
* @param sdlFile - SdlFile with invalid data to test uploading
*/
private void checkForUploadFailure(FileManager fileManager, SdlFile sdlFile) {
- boolean error = false;
-
- try {
- fileManager.uploadFile(sdlFile, new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- }
- });
- } catch (IllegalArgumentException e) {
- error = true;
- }
-
- assertTrue(error);
+ fileManager.uploadFile(sdlFile, new CompletionListener() {
+ @Override
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success);
+ }
+ });
+ }
+ });
}
/**
@@ -683,8 +664,7 @@ public class FileManagerTests {
// Set invalid type
for (FileType fileType : FileType.values()) {
boolean shouldError = true, didError = false;
- if (fileType.equals(FileType.GRAPHIC_BMP) || fileType.equals(FileType.GRAPHIC_PNG)
- || fileType.equals(FileType.GRAPHIC_JPEG)) {
+ if (fileType.equals(FileType.GRAPHIC_BMP) || fileType.equals(FileType.GRAPHIC_PNG) || fileType.equals(FileType.GRAPHIC_JPEG)) {
shouldError = false;
}
try {
@@ -701,85 +681,31 @@ public class FileManagerTests {
*/
@Test
public void testMultipleFileUpload() {
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onListFileUploadSuccess).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
FileManagerConfig fileManagerConfig = new FileManagerConfig();
-
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
public void onComplete(boolean success) {
- assertTrue(success);
- final List<SdlFile> filesToUpload = new ArrayList<>();
- filesToUpload.add(validFile);
-
- SdlFile validFile2 = new SdlFile();
- validFile2.setName(TestValues.GENERAL_STRING + "2");
- validFile2.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile2.setPersistent(false);
- validFile2.setType(FileType.GRAPHIC_JPEG);
- filesToUpload.add(validFile2);
-
+ SdlFile validFile2 = new SdlFile(TestValues.GENERAL_STRING + "2", FileType.GRAPHIC_JPEG, TestValues.GENERAL_STRING.getBytes(), false);
+ List<SdlFile> filesToUpload = Arrays.asList(validFile, validFile2);
fileManager.uploadFiles(filesToUpload, new MultipleFileCompletionListener() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertNull(errors);
- }
- });
- }
- });
- }
-
- /**
- * Testing uploading multiple files with some failing.
- */
- @Test
- public void testMultipleFileUploadPartialFailure() {
- ISdl internalInterface = mock(ISdl.class);
-
- doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onSendRequestsFailPartialOnError).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
-
- SdlFile validFile2 = new SdlFile();
- validFile2.setName(TestValues.GENERAL_STRING + "2");
- validFile2.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile2.setPersistent(false);
- validFile2.setType(FileType.GRAPHIC_JPEG);
-
- SdlFile validFile3 = new SdlFile();
- validFile3.setName(TestValues.GENERAL_STRING + "3");
- validFile3.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile3.setPersistent(false);
- validFile3.setType(FileType.GRAPHIC_JPEG);
-
- validFile.setType(FileType.AUDIO_WAVE);
-
- final List<SdlFile> filesToUpload = new ArrayList<>();
- filesToUpload.add(validFile);
- filesToUpload.add(validFile2);
- filesToUpload.add(validFile3);
-
- FileManagerConfig fileManagerConfig = new FileManagerConfig();
- fileManagerConfig.setArtworkRetryCount(0);
- fileManagerConfig.setFileRetryCount(0);
- final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
- fileManager.start(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- fileManager.uploadFiles(filesToUpload,
- new MultipleFileCompletionListener() {
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertTrue(errors.size() == 2);
+ public void run() {
+ assertNull(errors);
}
});
+ }
+ });
}
});
- assertFalse(fileManager.hasUploadedFile(validFile) && fileManager.hasUploadedFile(validFile3));
- assertTrue(fileManager.hasUploadedFile(validFile2));
}
/**
@@ -787,37 +713,26 @@ public class FileManagerTests {
*/
@Test
public void testMultipleArtworkUploadSuccess() {
- ISdl internalInterface = mock(ISdl.class);
+ ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onListFileUploadSuccess).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
FileManagerConfig fileManagerConfig = new FileManagerConfig();
final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
fileManager.start(new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
- int fileNum = 1;
- final List<SdlArtwork> artworkToUpload = new ArrayList<>();
- SdlArtwork sdlArtwork = new SdlArtwork();
- sdlArtwork.setName("art" + fileNum++);
- Uri uri = Uri.parse("android.resource://" + mTestContext.getPackageName() + "/drawable/ic_sdl");
- sdlArtwork.setUri(uri);
- sdlArtwork.setType(FileType.GRAPHIC_PNG);
- artworkToUpload.add(sdlArtwork);
-
- sdlArtwork = new SdlArtwork();
- sdlArtwork.setName("art" + fileNum++);
- uri = Uri.parse("android.resource://" + mTestContext.getPackageName() + "/drawable/sdl_tray_icon");
- sdlArtwork.setUri(uri);
- sdlArtwork.setType(FileType.GRAPHIC_PNG);
- artworkToUpload.add(sdlArtwork);
-
- fileManager.uploadFiles(artworkToUpload,
- new MultipleFileCompletionListener() {
+ public void onComplete(boolean success1) {
+ SdlArtwork sdlArtwork1 = new SdlArtwork("artwork1", FileType.GRAPHIC_JPEG, Uri.parse("android.resource://" + mTestContext.getPackageName() + "/drawable/ic_sdl"), false);
+ SdlArtwork sdlArtwork2 = new SdlArtwork("artwork2", FileType.GRAPHIC_PNG, Uri.parse("android.resource://" + mTestContext.getPackageName() + "/drawable/sdl_tray_icon"), false);
+ final List<SdlArtwork> artworkToUpload = Arrays.asList(sdlArtwork1, sdlArtwork2);
+
+ fileManager.uploadFiles(artworkToUpload, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
@Override
- public void onComplete(Map<String, String> errors) {
+ public void run() {
assertNull(errors);
List<String> uploadedFileNames = fileManager.getRemoteFileNames();
for (SdlArtwork artwork : artworkToUpload) {
@@ -825,29 +740,8 @@ public class FileManagerTests {
}
}
});
- }
- });
- }
-
- /**
- * Testing uploading persistent SdlFile
- */
- @Test
- public void testPersistentFileUploaded() {
- ISdl internalInterface = mock(ISdl.class);
-
- doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
-
- final SdlFile file = new SdlFile();
- file.setName(TestValues.GENERAL_STRING_LIST.get(0));
- file.setPersistent(true);
-
- FileManagerConfig fileManagerConfig = new FileManagerConfig();
- final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
- fileManager.start(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- assertTrue(fileManager.hasUploadedFile(file));
+ }
+ });
}
});
}
@@ -870,7 +764,7 @@ public class FileManagerTests {
*/
@Test
public void testOverwriteFileProperty() {
- ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
@@ -881,24 +775,32 @@ public class FileManagerTests {
fileManager.start(new CompletionListener() {
@Override
public void onComplete(boolean success) {
- assertTrue(success);
fileManager.uploadFile(validFile, new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success1);
+ }
+ });
validFile.setOverwrite(false);
fileManager.uploadFile(validFile, new CompletionListener() {
@Override
- public void onComplete(boolean success) {
- assertTrue(success);
+ public void onComplete(final boolean success2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success2);
+ verify(internalInterface, times(2)).sendRPC(any(RPCMessage.class));
+ }
+ });
}
});
-
}
});
}
});
- verify(internalInterface, times(2)).sendRPC(any(RPCMessage.class));
}
/**
@@ -907,20 +809,14 @@ public class FileManagerTests {
*/
@Test
public void testOverWriteFilePropertyListFiles() {
- final ISdl internalInterface = mock(ISdl.class);
+ final ISdl internalInterface = createISdlMock();
doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
- doAnswer(onListFileUploadSuccess).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
- final SdlFile validFile2 = new SdlFile();
- validFile2.setName(TestValues.GENERAL_STRING + "2");
- validFile2.setFileData(TestValues.GENERAL_BYTE_ARRAY);
- validFile2.setPersistent(false);
- validFile2.setType(FileType.GRAPHIC_JPEG);
+ final SdlArtwork validFile2 = new SdlArtwork(TestValues.GENERAL_STRING + "2", FileType.GRAPHIC_JPEG, TestValues.GENERAL_STRING.getBytes(), false);
- final List<SdlFile> list = new ArrayList<>();
- list.add(validFile);
- list.add(validFile2);
+ final List<SdlFile> list = Arrays.asList(validFile, validFile2);
FileManagerConfig fileManagerConfig = new FileManagerConfig();
fileManagerConfig.setArtworkRetryCount(2);
@@ -937,8 +833,14 @@ public class FileManagerTests {
validFile2.setOverwrite(false);
fileManager.uploadFiles(list, new MultipleFileCompletionListener() {
@Override
- public void onComplete(Map<String, String> errors) {
- assertNull(errors);
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertNull(errors);
+ verify(internalInterface, times(2)).sendRPC(any(PutFile.class));
+ }
+ });
}
});
}
@@ -946,7 +848,6 @@ public class FileManagerTests {
}
});
- verify(internalInterface, times(1)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
}
/**
@@ -1005,4 +906,20 @@ public class FileManagerTests {
artwork2 = new SdlFile("image1", FileType.GRAPHIC_PNG, 1, false);
assertTrue(artwork1.equals(artwork2));
}
+
+ // Asserts on Taskmaster threads will fail silently so we need to do the assertions on main thread if the code is triggered from Taskmaster
+ private void assertOnMainThread(Runnable runnable) {
+ mainHandler.post(runnable);
+ }
+
+ private ISdl createISdlMock() {
+ ISdl internalInterface = mock(ISdl.class);
+ Taskmaster taskmaster = new Taskmaster.Builder().build();
+ taskmaster.start();
+
+ when(internalInterface.getTaskmaster()).thenReturn(taskmaster);
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(rpcVersion));
+ when(internalInterface.getMtu(any(SessionType.class))).thenReturn(mtuSize);
+ return internalInterface;
+ }
} \ No newline at end of file
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java
index fc6ba1566..928b7bd09 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java
@@ -1040,6 +1040,11 @@ public class SystemCapabilityManagerTests {
}
@Override
+ public long getMtu(SessionType serviceType) {
+ return 0;
+ }
+
+ @Override
public boolean isTransportForServiceAvailable(SessionType serviceType) {
return false;
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java
index 01635dd7d..7719e2996 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java
@@ -8,6 +8,7 @@ import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.FileManager;
import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.managers.file.filetypes.SdlFile;
import com.smartdevicelink.managers.lifecycle.OnSystemCapabilityListener;
import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager;
import com.smartdevicelink.protocol.enums.FunctionID;
@@ -129,6 +130,10 @@ public class SoftButtonManagerTests {
doAnswer(onFileManagerUploadAnswer).when(fileManager).uploadArtworks(any(List.class), any(MultipleFileCompletionListener.class));
+ // We still want the mock fileManager to use the real implementation for fileNeedsUpload()
+ when(fileManager.fileNeedsUpload(any(SdlFile.class))).thenCallRealMethod();
+
+
// Create softButtonManager
Taskmaster taskmaster = new Taskmaster.Builder().build();
taskmaster.start();
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperationTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperationTest.java
index a893a76a7..4af040863 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperationTest.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperationTest.java
@@ -176,6 +176,10 @@ public class TextAndGraphicUpdateOperationTest {
// mock things
internalInterface = mock(ISdl.class);
fileManager = mock(FileManager.class);
+
+ // We still want the mock fileManager to use the real implementation for fileNeedsUpload()
+ when(fileManager.fileNeedsUpload(any(SdlFile.class))).thenCallRealMethod();
+
setUpCompletionListener();
textField1 = "It is";
textField2 = "Wednesday";
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java
index db592dfb6..b8ab310ca 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java
@@ -40,6 +40,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.FileManager;
import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.managers.file.filetypes.SdlFile;
import com.smartdevicelink.proxy.rpc.ImageField;
import com.smartdevicelink.proxy.rpc.TextField;
import com.smartdevicelink.proxy.rpc.WindowCapability;
@@ -63,7 +64,9 @@ import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
public class PreloadChoicesOperationTests {
@@ -101,6 +104,10 @@ public class PreloadChoicesOperationTests {
ISdl internalInterface = mock(ISdl.class);
FileManager fileManager = mock(FileManager.class);
+
+ // We still want the mock fileManager to use the real implementation for fileNeedsUpload()
+ when(fileManager.fileNeedsUpload(any(SdlFile.class))).thenCallRealMethod();
+
preloadChoicesOperation = new PreloadChoicesOperation(internalInterface, fileManager, null, windowCapability, true, cellsToPreload, null);
}
@@ -147,13 +154,6 @@ public class PreloadChoicesOperationTests {
preloadChoicesOperationEmptyCapability = new PreloadChoicesOperation(internalInterface, fileManager, null, windowCapability, true, cellsToPreload, null);
}
-
- @Test
- public void testArtworkNeedsUpload() {
- boolean test = preloadChoicesOperation.artworkNeedsUpload(TestValues.GENERAL_ARTWORK);
- assertTrue(test);
- }
-
@Test
public void testArtworksToUpload() {
List<SdlArtwork> artworksToUpload = preloadChoicesOperation.artworksToUpload();
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java
index 98fc161db..8fd025aac 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java
@@ -34,6 +34,7 @@ package com.smartdevicelink.managers.screen.menu;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.livio.taskmaster.Taskmaster;
import com.smartdevicelink.managers.BaseSubManager;
import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
@@ -56,7 +57,6 @@ import java.util.Collections;
import java.util.List;
import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
@@ -66,6 +66,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
public class VoiceCommandManagerTests {
@@ -112,19 +113,19 @@ public class VoiceCommandManagerTests {
};
doAnswer(onCommandAnswer).when(internalInterface).addOnRPCNotificationListener(eq(FunctionID.ON_COMMAND), any(OnRPCNotificationListener.class));
+ Taskmaster taskmaster = new Taskmaster.Builder().build();
+ taskmaster.start();
+ when(internalInterface.getTaskmaster()).thenReturn(taskmaster);
voiceCommandManager = new VoiceCommandManager(internalInterface);
// Check some stuff during setup
- assertEquals(voiceCommandManager.currentHMILevel, HMILevel.HMI_NONE);
+ assertNull(voiceCommandManager.currentHMILevel);
assertEquals(voiceCommandManager.getState(), BaseSubManager.SETTING_UP);
assertEquals(voiceCommandManager.lastVoiceCommandId, voiceCommandIdMin);
- assertFalse(voiceCommandManager.hasQueuedUpdate);
- assertFalse(voiceCommandManager.waitingOnHMIUpdate);
assertNotNull(voiceCommandManager.commandListener);
assertNotNull(voiceCommandManager.hmiListener);
assertNull(voiceCommandManager.voiceCommands);
- assertNull(voiceCommandManager.oldVoiceCommands);
- assertNull(voiceCommandManager.inProgressUpdate);
+ assertNull(voiceCommandManager.currentVoiceCommands);
}
@After
@@ -134,11 +135,8 @@ public class VoiceCommandManagerTests {
assertEquals(voiceCommandManager.lastVoiceCommandId, voiceCommandIdMin);
assertNull(voiceCommandManager.voiceCommands);
- assertNull(voiceCommandManager.oldVoiceCommands);
+ assertNull(voiceCommandManager.currentVoiceCommands);
assertNull(voiceCommandManager.currentHMILevel);
- assertNull(voiceCommandManager.inProgressUpdate);
- assertFalse(voiceCommandManager.hasQueuedUpdate);
- assertFalse(voiceCommandManager.waitingOnHMIUpdate);
// after everything, make sure we are in the correct state
assertEquals(voiceCommandManager.getState(), BaseSubManager.SHUTDOWN);
}
@@ -162,9 +160,6 @@ public class VoiceCommandManagerTests {
voiceCommandManager.currentHMILevel = HMILevel.HMI_NONE;
voiceCommandManager.setVoiceCommands(commands);
- // updating voice commands before HMI is ready
- assertNull(voiceCommandManager.inProgressUpdate);
- assertTrue(voiceCommandManager.waitingOnHMIUpdate);
// these are the 2 commands we have waiting
assertEquals(voiceCommandManager.voiceCommands.size(), 2);
assertEquals(voiceCommandManager.currentHMILevel, HMILevel.HMI_NONE);
@@ -173,20 +168,10 @@ public class VoiceCommandManagerTests {
sendFakeCoreOnHMIFullNotifications();
// Listener should be triggered - which sets new HMI level and should proceed to send our pending update
assertEquals(voiceCommandManager.currentHMILevel, HMILevel.HMI_FULL);
- // This being false means it received the hmi notification and sent the pending commands
- assertFalse(voiceCommandManager.waitingOnHMIUpdate);
}
@Test
public void testUpdatingCommands() {
-
- // we have previously sent 2 VoiceCommand objects. we will now update it and have just one
-
- // make sure the system returns us 2 delete commands
- assertEquals(voiceCommandManager.deleteCommandsForVoiceCommands(commands).size(), 2);
- // when we only send one command to update, we should only be returned one add command
- assertEquals(voiceCommandManager.addCommandsForVoiceCommands(Collections.singletonList(command)).size(), 1);
-
// Send a new single command, and test that its listener works, as it gets called from the VCM
voiceCommandManager.setVoiceCommands(Collections.singletonList(command3));
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java
new file mode 100644
index 000000000..1c6f2b1c4
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java
@@ -0,0 +1,297 @@
+package com.smartdevicelink.managers.screen.menu;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.rpc.AddCommand;
+import com.smartdevicelink.proxy.rpc.AddCommandResponse;
+import com.smartdevicelink.proxy.rpc.DeleteCommand;
+import com.smartdevicelink.proxy.rpc.DeleteCommandResponse;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+import com.smartdevicelink.util.DebugTool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@RunWith(AndroidJUnit4.class)
+public class VoiceCommandUpdateOperationTests {
+
+ private static final String TAG = "VoiceCommandReplaceOperationTests";
+ VoiceCommandUpdateOperation voiceCommandUpdateOperation;
+ ISdl internalInterface;
+ VoiceCommandUpdateOperation.VoiceCommandChangesListener voiceCommandChangesListener;
+ List<String> list1 = Collections.singletonList("Command one");
+ List<String> list2 = Collections.singletonList("Command two");
+ List<String> list3 = Collections.singletonList("Command three");
+ List<String> list4 = Collections.singletonList("Command four");
+
+
+ VoiceCommand voiceCommand1 = new VoiceCommand(list1, new VoiceCommandSelectionListener() {
+ @Override
+ public void onVoiceCommandSelected() {
+
+ }
+ });
+
+ VoiceCommand voiceCommand2 = new VoiceCommand(list2, new VoiceCommandSelectionListener() {
+ @Override
+ public void onVoiceCommandSelected() {
+
+ }
+ });
+
+ VoiceCommand voiceCommand3 = new VoiceCommand(list3, new VoiceCommandSelectionListener() {
+ @Override
+ public void onVoiceCommandSelected() {
+
+ }
+ });
+
+ VoiceCommand voiceCommand4 = new VoiceCommand(list4, new VoiceCommandSelectionListener() {
+ @Override
+ public void onVoiceCommandSelected() {
+
+ }
+ });
+
+ List<VoiceCommand> deleteList = new ArrayList<>();
+ List<VoiceCommand> addList = new ArrayList<>();
+
+ @Before
+ public void setup() {
+ deleteList.clear();
+ addList.clear();
+ voiceCommand1.setCommandId(1);
+ voiceCommand2.setCommandId(2);
+ voiceCommand3.setCommandId(3);
+ voiceCommand4.setCommandId(4);
+ deleteList.add(voiceCommand1);
+ deleteList.add(voiceCommand2);
+ addList.add(voiceCommand3);
+ addList.add(voiceCommand4);
+ }
+
+ @Test
+ public void verifyCanceledTaskDoesNotSendAnyRPCs() {
+ internalInterface = mock(ISdl.class);
+
+ voiceCommandChangesListener = new VoiceCommandUpdateOperation.VoiceCommandChangesListener() {
+ @Override
+ public void updateVoiceCommands(List<VoiceCommand> newCurrentVoiceCommands, HashMap<RPCRequest, String> errorObject) {
+
+ }
+ };
+
+ voiceCommandUpdateOperation = new VoiceCommandUpdateOperation(internalInterface, deleteList, addList, voiceCommandChangesListener);
+ voiceCommandUpdateOperation.cancelTask();
+ voiceCommandUpdateOperation.onExecute();
+ verify(internalInterface, times(0)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ }
+
+ @Test
+ public void verifyErrorObjectIsSetCorrectly() {
+ internalInterface = mock(ISdl.class);
+
+ doAnswer(new Answer<Void>() {
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ DeleteCommand deleteCommand = null;
+ AddCommand addCommand = null;
+
+ try {
+ deleteCommand = (DeleteCommand) ((List<Object>)invocation.getArguments()[0]).get(0);
+ } catch (Exception e) {
+ DebugTool.logInfo(TAG, "not DeleteCommands: " + e);
+ }
+
+ try {
+ addCommand = (AddCommand) ((List<Object>)invocation.getArguments()[0]).get(0);
+ } catch (Exception e) {
+ DebugTool.logInfo(TAG, "not AddCommands: " + e);
+ }
+
+ if (deleteCommand != null) {
+ DeleteCommandResponse badResponse = new DeleteCommandResponse();
+ badResponse.setSuccess(false);
+ List<DeleteCommand> deleteCommands = ((List<DeleteCommand>)invocation.getArguments()[0]);
+ for (DeleteCommand command : deleteCommands) {
+ badResponse.setCorrelationID(command.getCorrelationID());
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onResponse(command.getCorrelationID(), badResponse);
+ }
+ } else if (addCommand != null) {
+ AddCommandResponse badResponse = new AddCommandResponse();
+ badResponse.setSuccess(false);
+ List<AddCommand> addCommands = ((List<AddCommand>)invocation.getArguments()[0]);
+ for (AddCommand command : addCommands) {
+ badResponse.setCorrelationID(command.getCorrelationID());
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onResponse(command.getCorrelationID(), badResponse);
+ }
+ } else {
+ DebugTool.logInfo(TAG, "CallBacks failed");
+ return null;
+ }
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onFinished();
+ return null;
+ }
+ }).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+
+ voiceCommandChangesListener = new VoiceCommandUpdateOperation.VoiceCommandChangesListener() {
+ @Override
+ public void updateVoiceCommands(List<VoiceCommand> newCurrentVoiceCommands, HashMap<RPCRequest, String> errorObject) {
+ assertEquals(4, errorObject.size());
+ assertEquals(2, newCurrentVoiceCommands.size());
+ assertEquals(newCurrentVoiceCommands.get(0).getVoiceCommands().get(0), voiceCommand1.getVoiceCommands().get(0));
+ assertEquals(newCurrentVoiceCommands.get(1).getVoiceCommands().get(0), voiceCommand2.getVoiceCommands().get(0));
+ }
+ };
+
+ VoiceCommandUpdateOperation.VoiceCommandChangesListener listenerSpy = Mockito.spy(voiceCommandChangesListener);
+
+ voiceCommandUpdateOperation = new VoiceCommandUpdateOperation(internalInterface, deleteList, addList, listenerSpy);
+ voiceCommandUpdateOperation.onExecute();
+
+ verify(listenerSpy, times(1)).updateVoiceCommands(any(List.class), any(HashMap.class));
+ }
+
+ @Test
+ public void verifySuccessIsSetCorrectly() {
+ internalInterface = mock(ISdl.class);
+
+ doAnswer(new Answer<Void>() {
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ DeleteCommand deleteCommand = null;
+ AddCommand addCommand = null;
+
+ try {
+ deleteCommand = (DeleteCommand) ((List<Object>)invocation.getArguments()[0]).get(0);
+ } catch (Exception e) {
+ DebugTool.logInfo(TAG, "not DeleteCommands: " + e);
+ }
+
+ try {
+ addCommand = (AddCommand) ((List<Object>)invocation.getArguments()[0]).get(0);
+ } catch (Exception e) {
+ DebugTool.logInfo(TAG, "not AddCommands: " + e);
+ }
+
+ if (deleteCommand != null) {
+ DeleteCommandResponse successResponse = new DeleteCommandResponse();
+ successResponse.setSuccess(true);
+ List<DeleteCommand> deleteCommands = ((List<DeleteCommand>)invocation.getArguments()[0]);
+ for (DeleteCommand command : deleteCommands) {
+ successResponse.setCorrelationID(command.getCorrelationID());
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onResponse(command.getCorrelationID(), successResponse);
+ }
+ } else if (addCommand != null) {
+ AddCommandResponse successResponse = new AddCommandResponse();
+ successResponse.setSuccess(true);
+ List<AddCommand> addCommands = ((List<AddCommand>)invocation.getArguments()[0]);
+ for (AddCommand command : addCommands) {
+ successResponse.setCorrelationID(command.getCorrelationID());
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onResponse(command.getCorrelationID(), successResponse);
+ }
+ } else {
+ DebugTool.logInfo(TAG, "CallBacks failed");
+ return null;
+ }
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onFinished();
+ return null;
+ }
+ }).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+
+ voiceCommandChangesListener = new VoiceCommandUpdateOperation.VoiceCommandChangesListener() {
+ @Override
+ public void updateVoiceCommands(List<VoiceCommand> newCurrentVoiceCommands, HashMap<RPCRequest, String> errorObject) {
+ assertEquals(0, errorObject.size());
+ assertEquals(2, newCurrentVoiceCommands.size());
+ assertEquals(newCurrentVoiceCommands.get(0).getVoiceCommands().get(0), voiceCommand3.getVoiceCommands().get(0));
+ assertEquals(newCurrentVoiceCommands.get(1).getVoiceCommands().get(0), voiceCommand4.getVoiceCommands().get(0));
+ }
+ };
+
+ VoiceCommandUpdateOperation.VoiceCommandChangesListener listenerSpy = Mockito.spy(voiceCommandChangesListener);
+
+ voiceCommandUpdateOperation = new VoiceCommandUpdateOperation(internalInterface, deleteList, addList, listenerSpy);
+ voiceCommandUpdateOperation.onExecute();
+
+ verify(listenerSpy, times(1)).updateVoiceCommands(any(List.class), any(HashMap.class));
+ }
+
+ @Test
+ public void verifySendingAnEmptyListWillClearVoiceCommands() {
+ internalInterface = mock(ISdl.class);
+
+ doAnswer(new Answer<Void>() {
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ DeleteCommand deleteCommand = null;
+ AddCommand addCommand = null;
+
+ try {
+ deleteCommand = (DeleteCommand) ((List<Object>)invocation.getArguments()[0]).get(0);
+ } catch (Exception e) {
+ DebugTool.logInfo(TAG, "not DeleteCommands: " + e);
+ }
+
+ try {
+ addCommand = (AddCommand) ((List<Object>)invocation.getArguments()[0]).get(0);
+ } catch (Exception e) {
+ DebugTool.logInfo(TAG, "not AddCommands: " + e);
+ }
+
+ if (deleteCommand != null) {
+ DeleteCommandResponse successResponse = new DeleteCommandResponse();
+ successResponse.setSuccess(true);
+ List<DeleteCommand> deleteCommands = ((List<DeleteCommand>)invocation.getArguments()[0]);
+ for (DeleteCommand command : deleteCommands) {
+ successResponse.setCorrelationID(command.getCorrelationID());
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onResponse(command.getCorrelationID(), successResponse);
+ }
+ } else if (addCommand != null) {
+ AddCommandResponse successResponse = new AddCommandResponse();
+ successResponse.setSuccess(true);
+ List<AddCommand> addCommands = ((List<AddCommand>)invocation.getArguments()[0]);
+ for (AddCommand command : addCommands) {
+ successResponse.setCorrelationID(command.getCorrelationID());
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onResponse(command.getCorrelationID(), successResponse);
+ }
+ } else {
+ DebugTool.logInfo(TAG, "CallBacks failed");
+ return null;
+ }
+ ((OnMultipleRequestListener)invocation.getArguments()[1]).onFinished();
+ return null;
+ }
+ }).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+
+ voiceCommandChangesListener = new VoiceCommandUpdateOperation.VoiceCommandChangesListener() {
+ @Override
+ public void updateVoiceCommands(List<VoiceCommand> newCurrentVoiceCommands, HashMap<RPCRequest, String> errorObject) {
+ assertEquals(0, errorObject.size());
+ assertEquals(0, newCurrentVoiceCommands.size());
+ }
+ };
+
+ VoiceCommandUpdateOperation.VoiceCommandChangesListener listenerSpy = Mockito.spy(voiceCommandChangesListener);
+
+ voiceCommandUpdateOperation = new VoiceCommandUpdateOperation(internalInterface, deleteList, new ArrayList<VoiceCommand>(), listenerSpy);
+ voiceCommandUpdateOperation.onExecute();
+
+ verify(listenerSpy, times(1)).updateVoiceCommands(any(List.class), any(HashMap.class));
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/RPCStructTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/RPCStructTests.java
index 3cb7a1f3c..da539eba7 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/RPCStructTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/RPCStructTests.java
@@ -1,5 +1,6 @@
package com.smartdevicelink.test.proxy;
+import com.smartdevicelink.proxy.RPCMessage;
import com.smartdevicelink.proxy.RPCStruct;
import com.smartdevicelink.proxy.rpc.AirbagStatus;
import com.smartdevicelink.proxy.rpc.Choice;
@@ -183,4 +184,62 @@ public class RPCStructTests extends TestCase {
fail(e.getMessage());
}
}
+
+ public void testClone() {
+ Hashtable<String, Object> map = new Hashtable<>();
+ String key = "test";
+ Integer value = 42;
+ map.put(key, value);
+
+ RPCStruct rpcStruct = new RPCStruct(map);
+ rpcStruct.setPayloadProtected(true);
+ RPCStruct rpcClone = rpcStruct.clone();
+
+ assertEquals(rpcStruct.getStore().get("test"), rpcClone.getStore().get("test"));
+ assertTrue(rpcClone.isPayloadProtected());
+ }
+
+ public void testHashCode() {
+ Hashtable<String, Object> map = new Hashtable<>();
+ String key = "test";
+ Integer value = 42;
+ map.put(key, value);
+
+ RPCStruct rpcStruct = new RPCStruct(map);
+ RPCStruct rpcStruct2 = new RPCStruct(map);
+
+ Hashtable<String, Object> map2 = new Hashtable<>();
+ String key2 = "test2";
+ Integer value2 = 24;
+ map2.put(key2, value2);
+
+ RPCStruct rpcStruct3 = new RPCStruct(map2);
+
+ assertEquals(rpcStruct.hashCode(), rpcStruct2.hashCode());
+ assertNotSame(rpcStruct2.hashCode(), rpcStruct3.hashCode());
+ }
+
+ public void testEquals() {
+ Hashtable<String, Object> map = new Hashtable<>();
+ String key = "test";
+ Integer value = 42;
+ map.put(key, value);
+
+ RPCStruct rpcStruct = new RPCStruct(map);
+
+ RPCStruct rpcClone = rpcStruct.clone();
+
+ RPCStruct rpcNull = new RPCStruct(null);
+
+ RPCMessage nonStruct = new RPCMessage("TEST");
+
+ RPCStruct rpcStruct2 = new RPCStruct(map);
+
+ assertFalse(rpcStruct.equals(null));
+ assertTrue(rpcStruct.equals(rpcStruct));
+ assertFalse(rpcStruct.equals(nonStruct));
+ assertFalse(rpcStruct.equals(rpcNull));
+ assertTrue(rpcStruct.equals(rpcStruct2));
+ assertTrue(rpcStruct.equals(rpcClone));
+ }
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/RPCGenericTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/RPCGenericTests.java
index 178c7a125..e4a61b9ed 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/RPCGenericTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/RPCGenericTests.java
@@ -5,6 +5,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
@@ -45,6 +46,7 @@ import static junit.framework.TestCase.fail;
* - The enums have a value for every element in the spec and the names and deprecation status match the spec
*/
@RunWith(AndroidJUnit4.class)
+@Ignore //Remove this annotation before running these tests
public class RPCGenericTests {
private final String XML_FILE_NAME = "MOBILE_API.xml";
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/requests/SetMediaClockTimerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/requests/SetMediaClockTimerTests.java
index 7f22c0701..7cb73085f 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/requests/SetMediaClockTimerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/requests/SetMediaClockTimerTests.java
@@ -40,6 +40,7 @@ public class SetMediaClockTimerTests extends BaseRpcTests {
msg.setEndTime(TestValues.GENERAL_STARTTIME);
msg.setUpdateMode(TestValues.GENERAL_UPDATEMODE);
msg.setAudioStreamingIndicator(TestValues.GENERAL_AUDIO_STREAMING_INDICATOR);
+ msg.setCountRate(TestValues.GENERAL_FLOAT);
return msg;
}
@@ -63,6 +64,7 @@ public class SetMediaClockTimerTests extends BaseRpcTests {
result.put(SetMediaClockTimer.KEY_END_TIME, TestValues.JSON_STARTTIME);
result.put(SetMediaClockTimer.KEY_UPDATE_MODE, TestValues.GENERAL_UPDATEMODE);
result.put(SetMediaClockTimer.KEY_AUDIO_STREAMING_INDICATOR, TestValues.GENERAL_AUDIO_STREAMING_INDICATOR);
+ result.put(SetMediaClockTimer.KEY_COUNT_RATE, TestValues.GENERAL_FLOAT);
} catch (JSONException e) {
fail(TestValues.JSON_FAIL);
}
@@ -80,12 +82,14 @@ public class SetMediaClockTimerTests extends BaseRpcTests {
StartTime testEndTime = ((SetMediaClockTimer) msg).getEndTime();
UpdateMode testUpdateMode = ((SetMediaClockTimer) msg).getUpdateMode();
AudioStreamingIndicator testAudioStreamingIndicator = ((SetMediaClockTimer) msg).getAudioStreamingIndicator();
+ Float testCountRate = ((SetMediaClockTimer) msg).getCountRate();
// Valid Tests
assertEquals(TestValues.MATCH, TestValues.GENERAL_UPDATEMODE, testUpdateMode);
assertEquals(TestValues.MATCH, TestValues.GENERAL_AUDIO_STREAMING_INDICATOR, testAudioStreamingIndicator);
assertTrue(TestValues.TRUE, Validator.validateStartTime(TestValues.GENERAL_STARTTIME, testStartTime));
assertTrue(TestValues.TRUE, Validator.validateStartTime(TestValues.GENERAL_STARTTIME, testEndTime));
+ assertEquals(TestValues.MATCH, TestValues.GENERAL_FLOAT, testCountRate);
// Invalid/Null Tests
SetMediaClockTimer msg = new SetMediaClockTimer();
@@ -96,6 +100,7 @@ public class SetMediaClockTimerTests extends BaseRpcTests {
assertNull(TestValues.NULL, msg.getEndTime());
assertNull(TestValues.NULL, msg.getUpdateMode());
assertNull(TestValues.NULL, msg.getAudioStreamingIndicator());
+ assertNull(TestValues.NULL, msg.getCountRate());
}
/**
@@ -162,6 +167,9 @@ public class SetMediaClockTimerTests extends BaseRpcTests {
assertNull(TestValues.NULL, msg.getStartTime());
assertNull(TestValues.NULL, msg.getEndTime());
assertEquals(TestValues.MATCH, TestValues.GENERAL_AUDIO_STREAMING_INDICATOR, msg.getAudioStreamingIndicator());
+
+ msg = new SetMediaClockTimer().setCountRate(TestValues.GENERAL_FLOAT);
+ assertEquals(TestValues.GENERAL_FLOAT, msg.getCountRate());
}
/**
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompat.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompat.java
index db6c56fe5..6a69f84ba 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompat.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompat.java
@@ -32,39 +32,35 @@
package com.smartdevicelink.managers.audio;
import android.content.Context;
-import android.media.MediaCodec;
-import android.media.MediaFormat;
import android.net.Uri;
import androidx.annotation.NonNull;
+import com.livio.taskmaster.Queue;
import com.smartdevicelink.managers.audio.AudioStreamManager.SampleType;
-import com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
-import java.nio.ByteBuffer;
/**
* The audio decoder to decode a single audio file to PCM.
* This decoder supports phones with api < 21 but uses methods deprecated with api 21.
*/
public class AudioDecoderCompat extends BaseAudioDecoder {
- private static final String TAG = AudioDecoderCompat.class.getSimpleName();
- private static final int DEQUEUE_TIMEOUT = 3000;
- private static Runnable sRunnable;
- private Thread mThread;
+ private WeakReference<Queue> transactionQueue;
/**
* Creates a new object of AudioDecoder.
*
- * @param audioSource The audio source to decode.
- * @param context The context object to use to open the audio source.
- * @param sampleRate The desired sample rate for decoded audio data.
- * @param sampleType The desired sample type (8bit, 16bit, float).
- * @param listener A listener who receives the decoded audio.
+ * @param transactionQueue The operation queue that can be used to run operations in order.
+ * @param audioSource The audio source to decode.
+ * @param context The context object to use to open the audio source.
+ * @param sampleRate The desired sample rate for decoded audio data.
+ * @param sampleType The desired sample type (8bit, 16bit, float).
+ * @param listener A listener who receives the decoded audio.
*/
- AudioDecoderCompat(@NonNull Uri audioSource, @NonNull Context context, int sampleRate, @SampleType int sampleType, AudioDecoderListener listener) {
+ AudioDecoderCompat(@NonNull Queue transactionQueue, @NonNull Uri audioSource, @NonNull Context context, int sampleRate, @SampleType int sampleType, AudioDecoderListener listener) {
super(audioSource, context, sampleRate, sampleType, listener);
+ this.transactionQueue = new WeakReference<>(transactionQueue);
}
/**
@@ -74,8 +70,11 @@ public class AudioDecoderCompat extends BaseAudioDecoder {
try {
initMediaComponents();
decoder.start();
- mThread = new Thread(new DecoderRunnable(AudioDecoderCompat.this));
- mThread.start();
+
+ if (transactionQueue != null && transactionQueue.get() != null) {
+ AudioDecoderCompatOperation operation = new AudioDecoderCompatOperation(this);
+ transactionQueue.get().add(operation, false);
+ }
} catch (Exception e) {
e.printStackTrace();
@@ -86,84 +85,4 @@ public class AudioDecoderCompat extends BaseAudioDecoder {
stop();
}
}
-
-
- /**
- * Runnable to decode audio data
- */
- private static class DecoderRunnable implements Runnable {
- final WeakReference<AudioDecoderCompat> weakReference;
-
- /**
- * Decodes all audio data from source
- *
- * @param audioDecoderCompat instance of this class
- */
- DecoderRunnable(@NonNull AudioDecoderCompat audioDecoderCompat) {
- weakReference = new WeakReference<>(audioDecoderCompat);
-
- }
-
- @Override
- public void run() {
- final AudioDecoderCompat reference = weakReference.get();
- if (reference == null) {
- DebugTool.logWarning(TAG, "AudioDecoderCompat reference was null");
- return;
- }
- final ByteBuffer[] inputBuffersArray = reference.decoder.getInputBuffers();
- final ByteBuffer[] outputBuffersArray = reference.decoder.getOutputBuffers();
- MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
- MediaCodec.BufferInfo inputBufferInfo;
- ByteBuffer inputBuffer, outputBuffer;
- SampleBuffer sampleBuffer;
-
- while (reference != null && !reference.mThread.isInterrupted()) {
- int inputBuffersArrayIndex = 0;
- while (inputBuffersArrayIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
- inputBuffersArrayIndex = reference.decoder.dequeueInputBuffer(DEQUEUE_TIMEOUT);
- if (inputBuffersArrayIndex >= 0) {
- inputBuffer = inputBuffersArray[inputBuffersArrayIndex];
- inputBufferInfo = reference.onInputBufferAvailable(reference.extractor, inputBuffer);
- reference.decoder.queueInputBuffer(inputBuffersArrayIndex, inputBufferInfo.offset, inputBufferInfo.size, inputBufferInfo.presentationTimeUs, inputBufferInfo.flags);
- }
- }
-
- int outputBuffersArrayIndex = 0;
- while (outputBuffersArrayIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
- outputBuffersArrayIndex = reference.decoder.dequeueOutputBuffer(outputBufferInfo, DEQUEUE_TIMEOUT);
- if (outputBuffersArrayIndex >= 0) {
- outputBuffer = outputBuffersArray[outputBuffersArrayIndex];
- if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 && outputBufferInfo.size != 0) {
- reference.decoder.releaseOutputBuffer(outputBuffersArrayIndex, false);
- } else if (outputBuffer.limit() > 0) {
- sampleBuffer = reference.onOutputBufferAvailable(outputBuffer);
- if (reference.listener != null) {
- reference.listener.onAudioDataAvailable(sampleBuffer);
- }
- reference.decoder.releaseOutputBuffer(outputBuffersArrayIndex, false);
- }
- } else if (outputBuffersArrayIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- MediaFormat newFormat = reference.decoder.getOutputFormat();
- reference.onOutputFormatChanged(newFormat);
- }
- }
-
- if (outputBufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
- if (reference.listener != null) {
- reference.listener.onDecoderFinish(true);
- }
- reference.stop();
- try {
- reference.mThread.interrupt();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- reference.mThread = null;
- break;
- }
- }
- }
- }
- }
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompatOperation.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompatOperation.java
new file mode 100644
index 000000000..1371f8474
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioDecoderCompatOperation.java
@@ -0,0 +1,84 @@
+package com.smartdevicelink.managers.audio;
+
+import android.media.MediaCodec;
+import android.media.MediaFormat;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+
+class AudioDecoderCompatOperation extends Task {
+ private static final String TAG = "AudioDecoderCompatOperation";
+ private final WeakReference<AudioDecoderCompat> audioDecoderCompatWeakReference;
+ private static final int DEQUEUE_TIMEOUT = 3000;
+
+ AudioDecoderCompatOperation(AudioDecoderCompat audioDecoderCompat) {
+ super("AudioDecoderCompatOperation");
+ this.audioDecoderCompatWeakReference = new WeakReference<>(audioDecoderCompat);
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ final AudioDecoderCompat audioDecoderCompat = audioDecoderCompatWeakReference.get();
+ if (audioDecoderCompat == null) {
+ DebugTool.logWarning(TAG, "AudioDecoderCompat reference was null");
+ return;
+ }
+ final ByteBuffer[] inputBuffersArray = audioDecoderCompat.decoder.getInputBuffers();
+ final ByteBuffer[] outputBuffersArray = audioDecoderCompat.decoder.getOutputBuffers();
+ MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
+ MediaCodec.BufferInfo inputBufferInfo;
+ ByteBuffer inputBuffer, outputBuffer;
+ SampleBuffer sampleBuffer;
+
+ while (true) {
+ int inputBuffersArrayIndex = 0;
+ while (inputBuffersArrayIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ inputBuffersArrayIndex = audioDecoderCompat.decoder.dequeueInputBuffer(DEQUEUE_TIMEOUT);
+ if (inputBuffersArrayIndex >= 0) {
+ inputBuffer = inputBuffersArray[inputBuffersArrayIndex];
+ inputBufferInfo = audioDecoderCompat.onInputBufferAvailable(audioDecoderCompat.extractor, inputBuffer);
+ audioDecoderCompat.decoder.queueInputBuffer(inputBuffersArrayIndex, inputBufferInfo.offset, inputBufferInfo.size, inputBufferInfo.presentationTimeUs, inputBufferInfo.flags);
+ }
+ }
+
+ int outputBuffersArrayIndex = 0;
+ while (outputBuffersArrayIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ outputBuffersArrayIndex = audioDecoderCompat.decoder.dequeueOutputBuffer(outputBufferInfo, DEQUEUE_TIMEOUT);
+ if (outputBuffersArrayIndex >= 0) {
+ outputBuffer = outputBuffersArray[outputBuffersArrayIndex];
+ if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 && outputBufferInfo.size != 0) {
+ audioDecoderCompat.decoder.releaseOutputBuffer(outputBuffersArrayIndex, false);
+ } else if (outputBuffer.limit() > 0) {
+ sampleBuffer = audioDecoderCompat.onOutputBufferAvailable(outputBuffer);
+ if (audioDecoderCompat.listener != null) {
+ audioDecoderCompat.listener.onAudioDataAvailable(sampleBuffer);
+ }
+ audioDecoderCompat.decoder.releaseOutputBuffer(outputBuffersArrayIndex, false);
+ }
+ } else if (outputBuffersArrayIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat newFormat = audioDecoderCompat.decoder.getOutputFormat();
+ audioDecoderCompat.onOutputFormatChanged(newFormat);
+ }
+ }
+
+ if (outputBufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
+ if (audioDecoderCompat.listener != null) {
+ audioDecoderCompat.listener.onDecoderFinish(true);
+ }
+ audioDecoderCompat.stop();
+ break;
+ }
+ }
+ }
+}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java
index bb7269e57..667522372 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/audio/AudioStreamManager.java
@@ -101,6 +101,7 @@ public class AudioStreamManager extends BaseAudioStreamManager {
private StreamPacketizer audioPacketizer;
private SdlSession sdlSession = null;
private SessionType sessionType = null;
+ private com.livio.taskmaster.Queue transactionQueue;
private final Runnable serviceCompletionTimeoutCallback = new Runnable() {
@Override
@@ -437,7 +438,7 @@ public class AudioStreamManager extends BaseAudioStreamManager {
decoder = new AudioDecoder(audioSource, context.get(), sdlSampleRate, sdlSampleType, decoderListener);
} else {
// this BaseAudioDecoder subclass uses methods deprecated with api 21
- decoder = new AudioDecoderCompat(audioSource, context.get(), sdlSampleRate, sdlSampleType, decoderListener);
+ decoder = new AudioDecoderCompat(getTransactionQueue(), audioSource, context.get(), sdlSampleRate, sdlSampleType, decoderListener);
}
synchronized (queue) {
@@ -449,6 +450,13 @@ public class AudioStreamManager extends BaseAudioStreamManager {
}
}
+ private com.livio.taskmaster.Queue getTransactionQueue() {
+ if (transactionQueue == null && internalInterface != null && internalInterface.getTaskmaster() != null) {
+ transactionQueue = internalInterface.getTaskmaster().createQueue("AudioDecoderCompat", 6, false);
+ }
+ return transactionQueue;
+ }
+
/**
* Pushes raw audio data to SDL Core.
* The audio file will be played immediately. If another audio file is currently playing,
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 252ee09f2..0ffeba849 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
@@ -34,34 +34,23 @@ package com.smartdevicelink.managers.file;
import android.content.Context;
import android.content.res.Resources;
-import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.filetypes.SdlFile;
-import com.smartdevicelink.proxy.rpc.PutFile;
import com.smartdevicelink.util.DebugTool;
-import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
/**
* <strong>FileManager</strong> <br>
- * <p>
* Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br>
- * <p>
- * The SDLFileManager uploads files and keeps track of all the uploaded files names during a session. <br>
- * <p>
- * We need to add the following struct: SDLFile<br>
- * <p>
- * It is broken down to these areas: <br>
- * <p>
- * 1. Getters <br>
- * 2. Deletion methods <br>
- * 3. Uploading Files / Artwork
+ * The FileManager uploads files and keeps track of all the uploaded files names during a session. <br>
*/
public class FileManager extends BaseFileManager {
@@ -81,100 +70,33 @@ public class FileManager extends BaseFileManager {
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
- */
@Override
- PutFile createPutFile(@NonNull final SdlFile file) {
- PutFile putFile = new PutFile();
- if (file.getName() == null) {
- throw new IllegalArgumentException("You must specify an file name in the SdlFile");
- } else {
- putFile.setSdlFileName(file.getName());
+ InputStream openInputStreamWithFile(@NonNull SdlFile file) {
+ InputStream inputStream = null;
+
+ if (context.get() == null) {
+ DebugTool.logError(TAG, "Context is null. Cannot open file input stream!");
+ return null;
}
if (file.getResourceId() > 0) {
- // Use resource id to upload file
- byte[] contents = contentsOfResource(file.getResourceId());
- if (contents != null) {
- putFile.setFileData(contents);
- } else {
- throw new IllegalArgumentException("Resource file id was empty");
+ try {
+ inputStream = context.get().getResources().openRawResource(file.getResourceId());
+ } catch (Resources.NotFoundException e) {
+ DebugTool.logError(TAG, "File cannot be found.");
}
} else if (file.getUri() != null) {
- // Use URI to upload file
- byte[] contents = contentsOfUri(file.getUri());
- if (contents != null) {
- putFile.setFileData(contents);
- } else {
- throw new IllegalArgumentException("Uri was empty");
+ try {
+ inputStream = context.get().getContentResolver().openInputStream(file.getUri());
+ } catch (FileNotFoundException e) {
+ DebugTool.logError(TAG, String.format("File at %s cannot be found.", file.getUri()));
}
} else if (file.getFileData() != null) {
- // Use file data (raw bytes) to upload file
- putFile.setFileData(file.getFileData());
+ inputStream = new ByteArrayInputStream(file.getFileData());
} else {
- throw new IllegalArgumentException("The SdlFile to upload does " +
- "not specify its resourceId, Uri, or file data");
- }
-
- if (file.getType() != null) {
- putFile.setFileType(file.getType());
- }
- putFile.setPersistentFile(file.isPersistent());
-
- return putFile;
- }
-
- /**
- * Helper method to take resource files and turn them into byte arrays
- *
- * @param resource Resource file id
- * @return Resulting byte array
- */
- private byte[] contentsOfResource(int resource) {
- InputStream is = null;
- try {
- is = context.get().getResources().openRawResource(resource);
- return contentsOfInputStream(is);
- } catch (Resources.NotFoundException e) {
- DebugTool.logError(TAG, "Can't read from resource", e);
- return null;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
+ DebugTool.logError(TAG, "The SdlFile to upload does not specify its resourceId, Uri, or file data");
}
- }
- /**
- * Helper method to take Uri and turn it into byte array
- *
- * @param uri Uri for desired file
- * @return Resulting byte array
- */
- private byte[] contentsOfUri(Uri uri) {
- InputStream is = null;
- try {
- is = context.get().getContentResolver().openInputStream(uri);
- return contentsOfInputStream(is);
- } catch (IOException e) {
- DebugTool.logError(TAG, "Can't read from Uri", e);
- return null;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
+ return inputStream;
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java
index 1c665da8c..71f5af634 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java
@@ -56,10 +56,7 @@ class TextAndGraphicManager extends BaseTextAndGraphicManager {
@Override
SdlArtwork getBlankArtwork() {
if (blankArtwork == null) {
- blankArtwork = new SdlArtwork();
- blankArtwork.setType(FileType.GRAPHIC_PNG);
- blankArtwork.setName("blankArtwork");
- blankArtwork.setResourceId(R.drawable.transparent);
+ blankArtwork = new SdlArtwork("blankArtwork", FileType.GRAPHIC_PNG, R.drawable.transparent, true);
}
return blankArtwork;
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java
index 0348cc1e7..261525b29 100644
--- a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java
@@ -77,7 +77,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
abstract class BaseSdlManager {
- static final String TAG = "BaseSubManager";
+ static final String TAG = "BaseSdlManager";
final Object STATE_LOCK = new Object();
int state = -1;
String appId, appName, shortAppName, resumeHash;
diff --git a/base/src/main/java/com/smartdevicelink/managers/ISdl.java b/base/src/main/java/com/smartdevicelink/managers/ISdl.java
index 6d9a3c50f..e12880561 100644
--- a/base/src/main/java/com/smartdevicelink/managers/ISdl.java
+++ b/base/src/main/java/com/smartdevicelink/managers/ISdl.java
@@ -213,6 +213,13 @@ public interface ISdl {
Version getProtocolVersion();
/**
+ * Get the max payload size for a packet to be sent to the module
+ *
+ * @return the max transfer unit
+ */
+ long getMtu(SessionType serviceType);
+
+ /**
* Start encrypted RPC service
*/
void startRPCEncryption();
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
index 77ba2ef1e..d0b72eb1f 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
@@ -29,64 +29,47 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
-
package com.smartdevicelink.managers.file;
-
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
+import com.livio.taskmaster.Queue;
import com.smartdevicelink.managers.BaseSubManager;
import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
import com.smartdevicelink.managers.file.filetypes.SdlFile;
-import com.smartdevicelink.proxy.RPCRequest;
-import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.DeleteFile;
-import com.smartdevicelink.proxy.rpc.DeleteFileResponse;
-import com.smartdevicelink.proxy.rpc.ListFiles;
-import com.smartdevicelink.proxy.rpc.ListFilesResponse;
-import com.smartdevicelink.proxy.rpc.PutFile;
-import com.smartdevicelink.proxy.rpc.PutFileResponse;
-import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.Result;
-import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
-import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.Version;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* <strong>FileManager</strong> <br>
- * <p>
* Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br>
- * <p>
- * The SDLFileManager uploads files and keeps track of all the uploaded files names during a session. <br>
- * <p>
- * We need to add the following struct: SDLFile<br>
- * <p>
- * It is broken down to these areas: <br>
- * <p>
- * 1. Getters <br>
- * 2. Deletion methods <br>
- * 3. Uploading Files / Artwork
+ * The FileManager uploads files and keeps track of all the uploaded files names during a session. <br>
*/
abstract class BaseFileManager extends BaseSubManager {
final static String TAG = "FileManager";
final static int SPACE_AVAILABLE_MAX_VALUE = 2000000000;
- private List<String> remoteFiles;
- private final List<String> uploadedEphemeralFileNames;
- private int bytesAvailable = SPACE_AVAILABLE_MAX_VALUE;
- private final FileManagerConfig fileManagerConfig;
- private final HashMap<String, Integer> failedFileUploadsIndex;
+
+ final Set<String> mutableRemoteFileNames;
+ private final Set<String> uploadedEphemeralFileNames;
+ private int bytesAvailable;
+ private Queue transactionQueue;
+ private HashMap<String, Integer> failedFileUploadsCount;
+ private final int maxFileUploadAttempts;
+ private final int maxArtworkUploadAttempts;
/**
* Constructor for BaseFileManager
@@ -95,23 +78,47 @@ abstract class BaseFileManager extends BaseSubManager {
* @param fileManagerConfig FileManagerConfig
*/
BaseFileManager(ISdl internalInterface, FileManagerConfig fileManagerConfig) {
-
- // setup
super(internalInterface);
- uploadedEphemeralFileNames = new ArrayList<>();
- this.fileManagerConfig = fileManagerConfig;
- failedFileUploadsIndex = new HashMap<>();
+ this.bytesAvailable = 0;
+
+ this.mutableRemoteFileNames = new HashSet<>();
+ this.transactionQueue = internalInterface.getTaskmaster().createQueue("FileManager", 5, false);
+ this.uploadedEphemeralFileNames = new HashSet<>();
+
+ this.failedFileUploadsCount = new HashMap<>();
+ this.maxFileUploadAttempts = fileManagerConfig.getFileRetryCount() + 1;
+ this.maxArtworkUploadAttempts = fileManagerConfig.getArtworkRetryCount() + 1;
}
@Override
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void start(CompletionListener listener) {
- // prepare manager - don't set state to ready until we have list of files
+ // Prepare manager - don't set state to ready until we have list of files
retrieveRemoteFiles();
super.start(listener);
}
- // GETTERS
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ // Cancel the operations
+ if (transactionQueue != null) {
+ transactionQueue.close();
+ transactionQueue = null;
+ }
+
+ if (mutableRemoteFileNames != null) {
+ mutableRemoteFileNames.clear();
+ }
+
+ bytesAvailable = 0;
+
+ // Clear the failed uploads tracking so failed files can be uploaded again when a new connection has been established with Core
+ if (failedFileUploadsCount != null) {
+ failedFileUploadsCount.clear();
+ }
+ }
/**
* Returns a list of file names currently residing on core
@@ -119,12 +126,7 @@ abstract class BaseFileManager extends BaseSubManager {
* @return List<String> of remote file names
*/
public List<String> getRemoteFileNames() {
- if (getState() != BaseSubManager.READY) {
- // error and don't return list
- throw new IllegalArgumentException("FileManager is not READY");
- }
- // return list (this is synchronous at this point)
- return remoteFiles;
+ return new ArrayList<>(mutableRemoteFileNames);
}
/**
@@ -133,37 +135,46 @@ abstract class BaseFileManager extends BaseSubManager {
* @return int value representing The number of bytes still available
*/
public int getBytesAvailable() {
- return bytesAvailable;
+ return this.bytesAvailable;
}
private void retrieveRemoteFiles() {
- remoteFiles = new ArrayList<>();
- // hold list in remoteFiles class var
- ListFiles listFiles = new ListFiles();
- listFiles.setOnRPCResponseListener(new OnRPCResponseListener() {
+ listRemoteFilesWithCompletionListener(new FileManagerCompletionListener() {
@Override
- public void onResponse(int correlationId, RPCResponse response) {
- ListFilesResponse listFilesResponse = (ListFilesResponse) response;
- if (listFilesResponse.getSuccess()) {
- bytesAvailable = listFilesResponse.getSpaceAvailable() != null ? listFilesResponse.getSpaceAvailable() : SPACE_AVAILABLE_MAX_VALUE;
-
- if (listFilesResponse.getFilenames() != null) {
- remoteFiles.addAll(listFilesResponse.getFilenames());
- }
- // on callback set manager to ready state
- transitionToState(BaseSubManager.READY);
- } else {
- // file list could not be received. assume that setting can work and allow SDLManager to start
- DebugTool.logError(TAG, "File Manager could not list files");
- bytesAvailable = SPACE_AVAILABLE_MAX_VALUE;
- transitionToState(BaseSubManager.READY);
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (errorMessage != null) {
+ // HAX: In the case we are DISALLOWED we still want to transition to a ready state.
+ // Some head units return DISALLOWED for this RPC but otherwise work.
+ DebugTool.logWarning(TAG, "ListFiles is disallowed. Certain file manager APIs may not work properly.");
+ transitionToState(READY);
+ return;
}
+
+ // If no error, make sure we're in the ready state
+ transitionToState(READY);
}
});
- internalInterface.sendRPC(listFiles);
}
- // DELETION
+ private void listRemoteFilesWithCompletionListener(final FileManagerCompletionListener completionListener) {
+ ListFilesOperation operation = new ListFilesOperation(internalInterface, new FileManagerCompletionListener() {
+ @Override
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (errorMessage != null || !success) {
+ completionListener.onComplete(success, bytesAvailable, fileNames, errorMessage);
+ return;
+ }
+
+ // If there was no error, set our properties and call back to the completion listener
+ BaseFileManager.this.mutableRemoteFileNames.addAll(fileNames);
+ BaseFileManager.this.bytesAvailable = bytesAvailable;
+
+ completionListener.onComplete(success, bytesAvailable, fileNames, errorMessage);
+ }
+ });
+
+ transactionQueue.add(operation, false);
+ }
/**
* Attempts to delete the desired file from core, calls listener with indication of success/failure
@@ -172,24 +183,37 @@ abstract class BaseFileManager extends BaseSubManager {
* @param listener callback that is called on response from core
*/
public void deleteRemoteFileWithName(@NonNull final String fileName, final CompletionListener listener) {
- DeleteFile deleteFile = new DeleteFile();
- deleteFile.setSdlFileName(fileName);
- deleteFile.setOnRPCResponseListener(new OnRPCResponseListener() {
+ deleteRemoteFileWithNamePrivate(fileName, new FileManagerCompletionListener() {
@Override
- public void onResponse(int correlationId, RPCResponse response) {
- DeleteFileResponse deleteFileResponse = (DeleteFileResponse) response;
- if (deleteFileResponse.getSuccess()) {
- bytesAvailable = deleteFileResponse.getSpaceAvailable() != null ? deleteFileResponse.getSpaceAvailable() : SPACE_AVAILABLE_MAX_VALUE;
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (listener != null) {
+ listener.onComplete(success);
+ }
+ }
+ });
+ }
- remoteFiles.remove(fileName);
- uploadedEphemeralFileNames.remove(fileName);
+ private void deleteRemoteFileWithNamePrivate(@NonNull final String fileName, final FileManagerCompletionListener listener) {
+ if (!mutableRemoteFileNames.contains(fileName) && listener != null) {
+ String errorMessage = "No such remote file is currently known";
+ listener.onComplete(false, bytesAvailable, mutableRemoteFileNames, errorMessage);
+ return;
+ }
+
+ DeleteFileOperation operation = new DeleteFileOperation(internalInterface, fileName, new FileManagerCompletionListener() {
+ @Override
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (success) {
+ BaseFileManager.this.bytesAvailable = bytesAvailable;
+ BaseFileManager.this.mutableRemoteFileNames.remove(fileName);
}
+
if (listener != null) {
- listener.onComplete(deleteFileResponse.getSuccess());
+ listener.onComplete(success, bytesAvailable, mutableRemoteFileNames, errorMessage);
}
}
});
- internalInterface.sendRPC(deleteFile);
+ transactionQueue.add(operation, false);
}
/**
@@ -200,117 +224,129 @@ abstract class BaseFileManager extends BaseSubManager {
*/
public void deleteRemoteFilesWithNames(@NonNull List<String> fileNames, final MultipleFileCompletionListener listener) {
if (fileNames.isEmpty()) {
- return;
+ throw new IllegalArgumentException("This request requires that the array of files not be empty");
}
- final List<DeleteFile> deleteFileRequests = new ArrayList<>();
- for (String fileName : fileNames) {
- DeleteFile deleteFile = new DeleteFile();
- deleteFile.setSdlFileName(fileName);
- deleteFileRequests.add(deleteFile);
+
+ final Map<String, String> failedDeletes = new HashMap<>();
+
+ final DispatchGroup deleteFilesTask = new DispatchGroup();
+ deleteFilesTask.enter();
+
+ for (final String name : fileNames) {
+ deleteFilesTask.enter();
+ deleteRemoteFileWithNamePrivate(name, new FileManagerCompletionListener() {
+ @Override
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (!success) {
+ failedDeletes.put(name, errorMessage);
+ }
+ deleteFilesTask.leave();
+ }
+ });
}
- final Map<String, String> errors = new HashMap<>();
- sendMultipleFileOperations(deleteFileRequests, listener, errors);
- }
- // UPLOAD FILES / ARTWORK
+ deleteFilesTask.leave();
+
+ // Wait for all files to be deleted
+ deleteFilesTask.notify(new Runnable() {
+ @Override
+ public void run() {
+ if (listener == null) {
+ return;
+ }
+ if (failedDeletes.size() > 0) {
+ listener.onComplete(failedDeletes);
+ return;
+ }
+ listener.onComplete(null);
+ }
+ });
+ }
/**
- * Creates and returns a PutFile request that would upload a given SdlFile
+ * Check if an SdlFile has been uploaded to core
*
- * @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
+ * @param file SdlFile
+ * @return boolean that tells whether file has been uploaded to core (true) or not (false)
*/
- abstract PutFile createPutFile(@NonNull final SdlFile file);
+ public boolean hasUploadedFile(@NonNull SdlFile file) {
+ // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core had a bug where list files would cache incorrectly.
+ if (file.isPersistent() && mutableRemoteFileNames != null && mutableRemoteFileNames.contains(file.getName())) {
+ // If it's a persistent file, the bug won't present itself; just check if it's on the remote system
+ return true;
+ } else if (!file.isPersistent() && mutableRemoteFileNames != null && mutableRemoteFileNames.contains(file.getName()) && uploadedEphemeralFileNames.contains(file.getName())) {
+ // If it's an ephemeral file, the bug will present itself; check that it's a remote file AND that we've uploaded it this session
+ return true;
+ }
+ return false;
+ }
/**
- * Sends list of provided requests (strictly PutFile or DeleteFile) asynchronously through internalInterface,
- * calls listener on conclusion of sending requests.
+ * Check if an SdlFile needs to be uploaded to Core or not.
+ * It is different from hasUploadedFile() because it takes isStaticIcon and overwrite properties into consideration.
+ * ie, if the file is static icon, the method always returns false.
+ * If the file is dynamic, it returns true in one of these situations:
+ * 1) the file has the overwrite property set to true
+ * 2) the file hasn't been uploaded to Core before.
*
- * @param requests Non-empty list of PutFile or DeleteFile requests
- * @param listener MultipleFileCompletionListener that is called upon conclusion of sending requests
- * @param errors a hashMap that keeps track of RPCRequest that have failed to upload and returns to developer if listener is not null
+ * @param file the SdlFile that needs to be checked
+ * @return boolean that tells whether file needs to be uploaded to Core or not
*/
- private void sendMultipleFileOperations(final List<? extends RPCRequest> requests, final MultipleFileCompletionListener listener, final Map<String, String> errors) {
- final HashMap<Integer, RPCRequest> requestMap = new HashMap<>();
- final List<RPCRequest> requestsToResend = new ArrayList<>();
- final boolean deletionOperation;
- if (requests.get(0) instanceof PutFile) {
- deletionOperation = false;
- } else if (requests.get(0) instanceof DeleteFile) {
- deletionOperation = true;
- } else {
- return;
+ public boolean fileNeedsUpload(@NonNull SdlFile file) {
+ if (file != null && !file.isStaticIcon()) {
+ return file.getOverwrite() || !hasUploadedFile(file);
}
+ return false;
+ }
- OnMultipleRequestListener onMultipleRequestListener = new OnMultipleRequestListener() {
- int fileNum = 0;
+ /**
+ * Attempts to upload a list of SdlFiles to core
+ *
+ * @param files list of SdlFiles with file name and one of A) fileData, B) Uri, or C) resourceID set
+ * @param listener callback that is called once core responds to all upload requests
+ */
+ public void uploadFiles(@NonNull List<? extends SdlFile> files, final MultipleFileCompletionListener listener) {
+ if (files.isEmpty()) {
+ throw new IllegalArgumentException("This request requires that the array of files not be empty.");
+ }
- @Override
- public void addCorrelationId(int correlationId) {
- super.addCorrelationId(correlationId);
- requestMap.put(correlationId, requests.get(fileNum++));
- }
+ final Map<String, String> failedUploads = new HashMap<>();
+ final DispatchGroup uploadFilesTask = new DispatchGroup();
+ uploadFilesTask.enter();
+ // Wait for all files to be uploaded
+ uploadFilesTask.notify(new Runnable() {
@Override
- public void onUpdate(int remainingRequests) {
- }
+ public void run() {
+ if (listener == null) {
+ return;
+ }
- @Override
- public void onFinished() {
- if (!deletionOperation) {
- if (!requestsToResend.isEmpty()) {
- sendMultipleFileOperations(requestsToResend, listener, errors);
- } else if (listener != null) {
- listener.onComplete(errors.isEmpty() ? null : errors);
- }
- } else {
- if (listener != null) {
- listener.onComplete(errors.isEmpty() ? null : errors);
- }
+ if (failedUploads.size() > 0) {
+ listener.onComplete(failedUploads);
+ return;
}
+
+ listener.onComplete(null);
}
+ });
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- if (response instanceof PutFileResponse) {
- PutFileResponse putFileResponse = (PutFileResponse) response;
- bytesAvailable = putFileResponse.getSpaceAvailable() != null ? putFileResponse.getSpaceAvailable() : SPACE_AVAILABLE_MAX_VALUE;
-
- PutFile putFile = ((PutFile) requestMap.get(correlationId));
- if (putFile != null) {
- remoteFiles.add(putFile.getSdlFileName());
- uploadedEphemeralFileNames.add(putFile.getSdlFileName());
- }
-
- } else if (response instanceof DeleteFileResponse) {
- DeleteFileResponse deleteFileResponse = (DeleteFileResponse) response;
- bytesAvailable = deleteFileResponse.getSpaceAvailable() != null ? deleteFileResponse.getSpaceAvailable() : SPACE_AVAILABLE_MAX_VALUE;
-
- DeleteFile deleteFile = (DeleteFile) requestMap.get(correlationId);
- if (deleteFile != null) {
- remoteFiles.remove(deleteFile.getSdlFileName());
- uploadedEphemeralFileNames.remove(deleteFile.getSdlFileName());
- }
- }
- } else {
- final RPCRequest request = requestMap.get(correlationId);
- if (request != null) {
- if (!deletionOperation) {
- if (shouldReUploadFile(((PutFile) request).getSdlFileName(), ((PutFile) request).getFileType())) {
- request.setOnRPCResponseListener(null);
- requestsToResend.add(request);
- } else {
- errors.put(((PutFile) request).getSdlFileName(), buildErrorString(response.getResultCode(), response.getInfo()));
- }
- } else {
- errors.put(((DeleteFile) request).getSdlFileName(), buildErrorString(response.getResultCode(), response.getInfo()));
- }
+ for (int i = 0; i < files.size(); i++) {
+ final SdlFile file = files.get(i);
+ uploadFilesTask.enter();
+
+ uploadFilePrivate(file, new FileManagerCompletionListener() {
+ @Override
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (!success) {
+ failedUploads.put(file.getName(), errorMessage);
}
+
+ uploadFilesTask.leave();
}
- }
- };
- internalInterface.sendRPCs(requests, onMultipleRequestListener);
+ });
+ }
+ uploadFilesTask.leave();
}
/**
@@ -320,94 +356,101 @@ abstract class BaseFileManager extends BaseSubManager {
* @param listener called when core responds to the attempt to upload the file
*/
public void uploadFile(@NonNull final SdlFile file, final CompletionListener listener) {
- if (file.isStaticIcon()) {
- DebugTool.logWarning(TAG, String.format("%s is a static icon and doesn't need to be uploaded", file.getName()));
- listener.onComplete(true);
- return;
- }
- if (!file.getOverwrite() && hasUploadedFile(file)) {
- DebugTool.logWarning(TAG, String.format("%s has already been uploaded and the overwrite property is set to false. It will not be uploaded again", file.getName()));
- listener.onComplete(true);
- return;
- }
- PutFile putFile = createPutFile(file);
- putFile.setOnRPCResponseListener(new OnRPCResponseListener() {
+ uploadFilePrivate(file, new FileManagerCompletionListener() {
@Override
- public void onResponse(int correlationId, RPCResponse response) {
- PutFileResponse putFileResponse = (PutFileResponse) response;
- if (putFileResponse.getSuccess()) {
- bytesAvailable = putFileResponse.getSpaceAvailable() != null ? putFileResponse.getSpaceAvailable() : SPACE_AVAILABLE_MAX_VALUE;
- remoteFiles.add(file.getName());
- uploadedEphemeralFileNames.add(file.getName());
- if (listener != null) {
- listener.onComplete(true);
- }
- } else {
- if (shouldReUploadFile(file.getName(), file.getType())) {
- uploadFile(file, listener);
- } else if (listener != null) {
- listener.onComplete(false);
- }
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (!success && errorMessage != null) {
+ DebugTool.logWarning(TAG, errorMessage);
+ }
+
+ if (listener != null) {
+ listener.onComplete(success);
}
}
});
- internalInterface.sendRPC(putFile);
}
- /**
- * Check to see if file can be re-uploaded
- *
- * @param fileName a String that represents an SdlFile's name
- * @param fileType an instances of FileType that represents a type of File
- * @return true or false depending on if file with given type and name can be re-uploaded
- */
- private boolean shouldReUploadFile(String fileName, FileType fileType) {
- if (!failedFileUploadsIndex.containsKey(fileName)) {
- if (FileType.GRAPHIC_JPEG.equals(fileType) ||
- FileType.GRAPHIC_BMP.equals(fileType) ||
- FileType.GRAPHIC_PNG.equals(fileType)) {
- failedFileUploadsIndex.put(fileName, fileManagerConfig.getArtworkRetryCount());
- } else {
- failedFileUploadsIndex.put(fileName, fileManagerConfig.getFileRetryCount());
+ private void uploadFilePrivate(@NonNull final SdlFile file, final FileManagerCompletionListener listener) {
+ if (file == null) {
+ if (listener != null) {
+ listener.onComplete(false, bytesAvailable, null, "The file upload was canceled. The data for the file is missing.");
}
+ return;
}
- Integer fileRetryValue = failedFileUploadsIndex.get(fileName);
- if (fileRetryValue != null && fileRetryValue > 0) {
- failedFileUploadsIndex.put(fileName, fileRetryValue - 1);
- return true;
- }
- return false;
- }
- /**
- * Attempts to upload a list of SdlFiles to core
- *
- * @param files list of SdlFiles with file name and one of A) fileData, B) Uri, or C) resourceID set
- * @param listener callback that is called once core responds to all upload requests
- */
- public void uploadFiles(@NonNull List<? extends SdlFile> files, final MultipleFileCompletionListener listener) {
- if (files.isEmpty()) {
+ if (file.getName() == null) {
+ if (listener != null) {
+ listener.onComplete(false, bytesAvailable, null, "You must specify an file name in the SdlFile.");
+ }
return;
}
- final List<PutFile> putFileRequests = new ArrayList<>();
- for (SdlFile file : files) {
- if (file.isStaticIcon()) {
- DebugTool.logWarning(TAG, String.format("%s is a static icon and doesn't need to be uploaded", file.getName()));
- continue;
+
+ if (file.isStaticIcon()) {
+ if (listener != null) {
+ listener.onComplete(false, bytesAvailable, null, "The file upload was canceled. The file is a static icon, which cannot be uploaded.");
}
- if (!file.getOverwrite() && hasUploadedFile(file)) {
- DebugTool.logWarning(TAG, String.format("%s has already been uploaded and the overwrite property is set to false. It will not be uploaded again", file.getName()));
- continue;
+ return;
+ }
+
+ if (getState() != READY) {
+ if (listener != null) {
+ listener.onComplete(false, bytesAvailable, null, "The file manager was unable to send this file. This could be because the file manager has not started, or the head unit does not support files.");
}
- putFileRequests.add(createPutFile(file));
+ return;
}
- // if all files are static icons we complete listener with no errors
- if (putFileRequests.isEmpty()) {
- listener.onComplete(null);
- } else {
- final Map<String, String> errors = new HashMap<>();
- sendMultipleFileOperations(putFileRequests, listener, errors);
+
+ // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core
+ // had a bug where list files would cache incorrectly. This led to attempted uploads failing
+ // due to the system thinking they were already there when they were not. This is only needed
+ // if connecting to Core v4.3.1 or less which corresponds to RPC v4.3.1 or less
+ Version rpcVersion = new Version(internalInterface.getSdlMsgVersion());
+ if (!file.isPersistent() && !hasUploadedFile(file) && new Version(4, 4, 0).isNewerThan(rpcVersion) == 1) {
+ file.setOverwrite(true);
}
+
+ // Check our overwrite settings and error out if it would overwrite
+ if (!file.getOverwrite() && mutableRemoteFileNames.contains(file.getName())) {
+ String errorMessage = "Cannot overwrite remote file. The remote file system already has a file of this name, and the file manager is set to not automatically overwrite files.";
+ DebugTool.logWarning(TAG, errorMessage);
+ if (listener != null) {
+ listener.onComplete(true, bytesAvailable, null, errorMessage);
+ }
+ return;
+ }
+
+ // If we didn't error out over the overwrite, then continue on
+ sdl_uploadFilePrivate(file, listener);
+ }
+
+ private void sdl_uploadFilePrivate(@NonNull final SdlFile file, final FileManagerCompletionListener listener) {
+ final String fileName = file.getName();
+
+ SdlFileWrapper fileWrapper = new SdlFileWrapper(file, new FileManagerCompletionListener() {
+ @Override
+ public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
+ if (success) {
+ BaseFileManager.this.bytesAvailable = bytesAvailable;
+ BaseFileManager.this.mutableRemoteFileNames.add(fileName);
+ BaseFileManager.this.uploadedEphemeralFileNames.add(fileName);
+ } else {
+ incrementFailedUploadCountForFileName(file.getName(), BaseFileManager.this.failedFileUploadsCount);
+
+ int maxUploadCount = file instanceof SdlArtwork ? maxArtworkUploadAttempts : maxFileUploadAttempts;
+ if (canFileBeUploadedAgain(file, maxUploadCount, failedFileUploadsCount)) {
+ DebugTool.logInfo(TAG, String.format("Attempting to resend file with name %s after a failed upload attempt", file.getName()));
+ sdl_uploadFilePrivate(file, listener);
+ return;
+ }
+ }
+
+ if (listener != null) {
+ listener.onComplete(success, bytesAvailable, null, errorMessage);
+ }
+ }
+ });
+
+ UploadFileOperation operation = new UploadFileOperation(internalInterface, this, fileWrapper);
+ transactionQueue.add(operation, false);
}
/**
@@ -431,22 +474,60 @@ abstract class BaseFileManager extends BaseSubManager {
}
/**
- * Check if an SdlFile has been uploaded to core
+ * Checks if an artwork needs to be uploaded to Core. The artwork should not be sent to Core if
+ * the artwork is already on Core or if the artwork is not on Core after the maximum number of
+ * repeated upload attempts has been reached.
*
- * @param file SdlFile
- * @return boolean that tells whether file has been uploaded to core (true) or not (false)
+ * @param file The file to be uploaded to Core
+ * @param maxUploadCount The max number of times the file is allowed to be uploaded to Core
+ * @param failedFileUploadsCount
+ * @return True if the file still needs to be (re)sent to Core; false if not.
*/
- public boolean hasUploadedFile(@NonNull SdlFile file) {
- if (file.isPersistent() && remoteFiles != null && remoteFiles.contains(file.getName())) {
- return true;
- } else if (!file.isPersistent() && remoteFiles != null && remoteFiles.contains(file.getName())
- && uploadedEphemeralFileNames.contains(file.getName())) {
- return true;
+ private boolean canFileBeUploadedAgain(SdlFile file, int maxUploadCount, HashMap<String, Integer> failedFileUploadsCount) {
+ if (getState() != READY) {
+ DebugTool.logWarning(TAG, String.format("File named %s failed to upload. The file manager has shutdown so the file upload will not retry.", file.getName()));
+ return false;
}
- return false;
+
+ if (file == null) {
+ DebugTool.logError(TAG, "File can not be uploaded because it is not a valid file.");
+ return false;
+ }
+
+ if (hasUploadedFile(file)) {
+ DebugTool.logInfo(TAG, String.format("File named %s has already been uploaded.", file.getName()));
+ return false;
+ }
+
+ Integer failedUploadCount = failedFileUploadsCount.get(file.getName());
+ boolean canFileBeUploadedAgain = (failedUploadCount == null) || (failedUploadCount < maxUploadCount);
+ if (!canFileBeUploadedAgain) {
+ DebugTool.logError(TAG, String.format("File named %s failed to upload. Max number of upload attempts reached.", file.getName()));
+ }
+
+ return canFileBeUploadedAgain;
}
- // HELPERS
+ /**
+ * Increments the number of upload attempts for a file name by 1.
+ *
+ * @param name The name used to upload the file to Core
+ * @param failedFileUploadsCount
+ * @return
+ */
+ private void incrementFailedUploadCountForFileName(String name, HashMap<String, Integer> failedFileUploadsCount) {
+ Integer currentFailedUploadCount = failedFileUploadsCount.get(name);
+ Integer newFailedUploadCount = (currentFailedUploadCount != null) ? (currentFailedUploadCount + 1) : 1;
+ failedFileUploadsCount.put(name, newFailedUploadCount);
+ DebugTool.logWarning(TAG, String.format("File with name %s failed to upload %s times", name, newFailedUploadCount));
+ }
+
+ /**
+ * Opens a socket for reading data.
+ *
+ * @param file The file containing the data or the file url of the data
+ */
+ abstract InputStream openInputStreamWithFile(@NonNull SdlFile file);
/**
* Builds an error string for a given Result and info string
@@ -455,33 +536,8 @@ abstract class BaseFileManager extends BaseSubManager {
* @param info String returned from OnRPCRequestListener.onError()
* @return Error string
*/
+ @Deprecated
static public String buildErrorString(Result resultCode, String info) {
return resultCode.toString() + " : " + info;
}
-
- /**
- * Helper method to take InputStream and turn it into byte array
- *
- * @param is valid InputStream
- * @return Resulting byte array
- */
- byte[] contentsOfInputStream(InputStream is) {
- if (is == null) {
- return null;
- }
- try {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- final int bufferSize = 4096;
- final byte[] buffer = new byte[bufferSize];
- int available;
- while ((available = is.read(buffer)) >= 0) {
- os.write(buffer, 0, available);
- }
- return os.toByteArray();
- } catch (IOException e) {
- DebugTool.logError(TAG, "Can't read from InputStream", e);
- return null;
- }
- }
-
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java b/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
new file mode 100644
index 000000000..21df7f7c4
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
@@ -0,0 +1,103 @@
+/*
+ * 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.managers.file;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.DeleteFile;
+import com.smartdevicelink.proxy.rpc.DeleteFileResponse;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by Bilal Alsharifi on 12/1/20.
+ */
+class DeleteFileOperation extends Task {
+ private static final String TAG = "DeleteFileOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private String fileName;
+ private FileManagerCompletionListener completionListener;
+
+ DeleteFileOperation(ISdl internalInterface, String fileName, FileManagerCompletionListener completionListener) {
+ super("DeleteFileOperation");
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.fileName = fileName;
+ this.completionListener = completionListener;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ deleteFile();
+ }
+
+ private void deleteFile() {
+ DeleteFile deleteFile = new DeleteFile(fileName);
+ deleteFile.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ DeleteFileResponse deleteFileResponse = (DeleteFileResponse) response;
+ boolean success = deleteFileResponse.getSuccess();
+
+ // If spaceAvailable is null, set it to the max value
+ int bytesAvailable = deleteFileResponse.getSpaceAvailable() != null ? deleteFileResponse.getSpaceAvailable() : BaseFileManager.SPACE_AVAILABLE_MAX_VALUE;
+
+ if (completionListener != null) {
+ String errorMessage = success ? null : response.getInfo() + ": " + response.getResultCode();
+ completionListener.onComplete(success, bytesAvailable, null, errorMessage);
+ }
+
+ onFinished();
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(deleteFile);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return super.getName() + " - " + fileName;
+ }
+}
+
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/DispatchGroup.java b/base/src/main/java/com/smartdevicelink/managers/file/DispatchGroup.java
new file mode 100644
index 000000000..307a4bac4
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/DispatchGroup.java
@@ -0,0 +1,65 @@
+/*
+ * 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.managers.file;
+
+/**
+ * Created by Bilal Alsharifi on 12/2/20.
+ */
+class DispatchGroup {
+ private int count;
+ private Runnable runnable;
+
+ DispatchGroup() {
+ count = 0;
+ }
+
+ synchronized void enter() {
+ count++;
+ }
+
+ synchronized void leave() {
+ count--;
+ run();
+ }
+
+ void notify(Runnable runnable) {
+ this.runnable = runnable;
+ run();
+ }
+
+ private void run() {
+ if (count <= 0 && runnable != null) {
+ runnable.run();
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/FileManagerCompletionListener.java b/base/src/main/java/com/smartdevicelink/managers/file/FileManagerCompletionListener.java
new file mode 100644
index 000000000..db75aed6b
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/FileManagerCompletionListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.managers.file;
+
+import java.util.Collection;
+
+/**
+ * Created by Bilal Alsharifi on 12/1/20.
+ */
+interface FileManagerCompletionListener {
+ void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage);
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/ListFilesOperation.java b/base/src/main/java/com/smartdevicelink/managers/file/ListFilesOperation.java
new file mode 100644
index 000000000..794870459
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/ListFilesOperation.java
@@ -0,0 +1,111 @@
+/*
+ * 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.managers.file;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.ListFiles;
+import com.smartdevicelink.proxy.rpc.ListFilesResponse;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Created by Bilal Alsharifi on 12/1/20.
+ */
+class ListFilesOperation extends Task {
+ private static final String TAG = "ListFilesOperation";
+ private UUID operationId;
+ private final WeakReference<ISdl> internalInterface;
+ private FileManagerCompletionListener completionListener;
+
+ ListFilesOperation(ISdl internalInterface, FileManagerCompletionListener completionListener) {
+ super("ListFilesOperation");
+ this.operationId = UUID.randomUUID();
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.completionListener = completionListener;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ listFiles();
+ }
+
+ private void listFiles() {
+ ListFiles listFiles = new ListFiles();
+ listFiles.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ ListFilesResponse listFilesResponse = (ListFilesResponse) response;
+ boolean success = listFilesResponse.getSuccess();
+
+ List<String> fileNames = new ArrayList<>();
+ if (listFilesResponse.getFilenames() != null) {
+ fileNames.addAll(listFilesResponse.getFilenames());
+ }
+
+ // If spaceAvailable is null, set it to the max value
+ int bytesAvailable = listFilesResponse.getSpaceAvailable() != null ? listFilesResponse.getSpaceAvailable() : BaseFileManager.SPACE_AVAILABLE_MAX_VALUE;
+
+ if (completionListener != null) {
+ String errorMessage = success ? null : response.getInfo() + ": " + response.getResultCode();
+ completionListener.onComplete(success, bytesAvailable, fileNames, errorMessage);
+ }
+
+ onFinished();
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(listFiles);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return super.getName() + " - " + operationId;
+ }
+}
+
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/SdlFileWrapper.java b/base/src/main/java/com/smartdevicelink/managers/file/SdlFileWrapper.java
new file mode 100644
index 000000000..bdb7b9a62
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/SdlFileWrapper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.managers.file;
+
+import com.smartdevicelink.managers.file.filetypes.SdlFile;
+
+/**
+ * Created by Bilal Alsharifi on 12/1/20.
+ */
+class SdlFileWrapper {
+ private final SdlFile file;
+ private final FileManagerCompletionListener completionListener;
+
+ SdlFileWrapper(SdlFile file, FileManagerCompletionListener completionListener) {
+ this.file = file;
+ this.completionListener = completionListener;
+ }
+
+ SdlFile getFile() {
+ return file;
+ }
+
+ FileManagerCompletionListener getCompletionListener() {
+ return completionListener;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java b/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
new file mode 100644
index 000000000..c05aac5d0
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
@@ -0,0 +1,377 @@
+/*
+ * 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.managers.file;
+
+import androidx.annotation.NonNull;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.managers.file.filetypes.SdlFile;
+import com.smartdevicelink.protocol.SdlProtocolBase;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.PutFile;
+import com.smartdevicelink.proxy.rpc.PutFileResponse;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by Bilal Alsharifi on 12/1/20.
+ */
+class UploadFileOperation extends Task {
+ private static final String TAG = "UploadFileOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private final WeakReference<BaseFileManager> fileManager;
+ private SdlFileWrapper fileWrapper;
+ private InputStream inputStream;
+ private int fileSize;
+ private String streamError;
+ private int bytesAvailable;
+ private int highestCorrelationIDReceived;
+
+ UploadFileOperation(ISdl internalInterface, BaseFileManager fileManager, SdlFileWrapper fileWrapper) {
+ super("UploadFileOperation");
+
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ this.fileWrapper = fileWrapper;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ int mtuSize = 0;
+ if (internalInterface.get() != null) {
+ mtuSize = (int) internalInterface.get().getMtu(SessionType.RPC);
+ }
+ sendFile(this.fileWrapper.getFile(), mtuSize, this.fileWrapper.getCompletionListener());
+ }
+
+ /**
+ * Sends data asynchronously to the SDL Core by breaking the data into smaller packets, each of which is
+ * sent via a PutFile. If the SDL Core receives all the PutFile successfully, a success response with
+ * the amount of free storage space left on the SDL Core is returned. Otherwise the error returned by
+ * the SDL Core is passed along.
+ *
+ * @param file The file containing the data to be sent to the SDL Core
+ * @param mtuSize The maximum packet size allowed
+ * @param completionListener listener returning whether or not the upload was a success
+ */
+ private void sendFile(SdlFile file, int mtuSize, final FileManagerCompletionListener completionListener) {
+ streamError = null;
+ bytesAvailable = BaseFileManager.SPACE_AVAILABLE_MAX_VALUE;
+ highestCorrelationIDReceived = -1;
+
+ if (getState() == Task.CANCELED) {
+ String errorMessage = "The file upload transaction was canceled before it could be completed.";
+ completionListener.onComplete(false, bytesAvailable, null, errorMessage);
+ onFinished();
+ return;
+ }
+
+ if (file == null) {
+ String errorMessage = "The file manager was unable to send the file. This could be because the file does not exist at the specified file path or that passed data is invalid.";
+ completionListener.onComplete(false, bytesAvailable, null, errorMessage);
+ onFinished();
+ return;
+ }
+
+ if (fileManager.get() != null) {
+ this.inputStream = fileManager.get().openInputStreamWithFile(file);
+ this.fileSize = getFileSizeFromInputStream(inputStream);
+ }
+
+ int maxBulkDataSize = getMaxBulkDataSize(mtuSize, file, fileSize);
+
+ // If the file does not exist or the passed data is null, return an error
+ if (inputStream == null || fileSize == 0) {
+ closeInputStream();
+
+ String errorMessage = "The file manager was unable to send the file. This could be because the file does not exist at the specified file path or that passed data is invalid.";
+ completionListener.onComplete(false, bytesAvailable, null, errorMessage);
+ onFinished();
+ return;
+ }
+
+ final DispatchGroup putFileGroup = new DispatchGroup();
+ putFileGroup.enter();
+
+ // Wait for all packets be sent before returning whether or not the upload was a success
+ putFileGroup.notify(new Runnable() {
+ @Override
+ public void run() {
+ closeInputStream();
+
+ if (streamError != null || getState() == Task.CANCELED) {
+ completionListener.onComplete(false, bytesAvailable, null, streamError);
+ } else {
+ completionListener.onComplete(true, bytesAvailable, null, null);
+ }
+
+ onFinished();
+ }
+ });
+
+ // Break the data into small pieces, each of which will be sent in a separate PutFile
+ int currentOffset = 0;
+ int numberOfPieces = ((fileSize - 1) / maxBulkDataSize) + 1;
+ for (int i = 0; i < numberOfPieces; i++) {
+ putFileGroup.enter();
+
+ // Get a chunk of data from the input stream
+ int putFileLength = getPutFileLengthForOffset(currentOffset, fileSize, maxBulkDataSize);
+ int putFileBulkDataSize = getDataSizeForOffset(currentOffset, fileSize, maxBulkDataSize);
+ byte[] putFileBulkData = getDataChunkWithSize(putFileBulkDataSize, this.inputStream);
+
+ final PutFile putFile = new PutFile(file.getName(), file.getType())
+ .setPersistentFile(file.isPersistent())
+ .setSystemFile(false)
+ .setOffset(currentOffset)
+ .setLength(putFileLength);
+ putFile.setBulkData(putFileBulkData);
+ putFile.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ PutFileResponse putFileResponse = (PutFileResponse) response;
+
+ // Check if the upload process has been cancelled by another packet. If so, stop the upload process.
+ if (getState() == Task.CANCELED) {
+ putFileGroup.leave();
+ return;
+ }
+
+ // If the SDL Core returned an error, cancel the upload the process in the future
+ if (!response.getSuccess() || getState() == Task.CANCELED) {
+ streamError = response.getInfo() + ": " + response.getResultCode();
+ putFileGroup.leave();
+ cancelTask();
+ return;
+ }
+
+ // If no errors, watch for a response containing the amount of storage left on the SDL Core
+ if (newHighestCorrelationID(correlationId, highestCorrelationIDReceived)) {
+ highestCorrelationIDReceived = correlationId;
+
+ // If spaceAvailable is null, set it to the max value
+ bytesAvailable = putFileResponse.getSpaceAvailable() != null ? putFileResponse.getSpaceAvailable() : BaseFileManager.SPACE_AVAILABLE_MAX_VALUE;
+ }
+
+ putFileGroup.leave();
+ }
+ });
+
+ currentOffset += putFileBulkDataSize;
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(putFile);
+ }
+ }
+
+ putFileGroup.leave();
+ }
+
+ /**
+ * Close the input stream once all the data has been read
+ */
+ private void closeInputStream() {
+ if (this.inputStream == null) {
+ return;
+ }
+ try {
+ this.inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Returns the max possible size for the JSON data in each of the PutFile pieces.
+ *
+ * @param file The file containing the data to be sent to the SDL Core
+ * @param fileSize The size of the file
+ * @return max possible size for the JSON data
+ */
+ private int getMaxJSONSize(@NonNull SdlFile file, int fileSize) {
+ int maxJSONSize = 0;
+
+ final PutFile putFile = new PutFile(file.getName(), file.getType())
+ .setPersistentFile(file.isPersistent())
+ .setSystemFile(false)
+ .setOffset(fileSize)
+ .setLength(fileSize);
+
+ if (putFile != null && putFile.getStore() != null) {
+ maxJSONSize = putFile.getStore().toString().getBytes().length;
+ }
+ return maxJSONSize;
+ }
+
+ /**
+ * Returns the max size of bulk data that we can load into each PutFile to guarantee that the
+ * packet size do not exceed the max MTU size allowed by the SDL Core.
+ *
+ * @param mtuSize The maximum packet size allowed
+ * @param file The file containing the data to be sent to the SDL Core
+ * @param fileSize The size of the file
+ * @return max size of bulk data that we can load into each PutFile
+ */
+ private int getMaxBulkDataSize(int mtuSize, @NonNull SdlFile file, int fileSize) {
+ // Each RPC packet contains : frame header + payload (binary header + JSON data + bulk data)
+ // To make sure that packets do not exceed MTU size, the bulk data size for each packet should not exceed:
+ // mtuSize - (frameHeaderSize + binaryHeaderSize + maxJSONSize)
+
+ int frameHeaderSize = SdlProtocolBase.V2_HEADER_SIZE;
+ int binaryHeaderSize = 12;
+ int maxJSONSize = getMaxJSONSize(file, fileSize);
+ return mtuSize - (frameHeaderSize + binaryHeaderSize + maxJSONSize);
+ }
+
+ /**
+ * Returns the length of the data being sent in the PutFile. The first PutFile's length is unique in
+ * that it sends the full size of the data. For the rest of the PutFiles, the length parameter is equal
+ * to the size of the chunk of data being sent in the PutFile.
+ *
+ * @param currentOffset The current position in the file
+ * @param fileSize The size of the file
+ * @param maxBulkDataSize The max size of bulk data that we can load into each PutFile
+ * @return The length of the data being sent in the PutFile
+ */
+ private int getPutFileLengthForOffset(int currentOffset, int fileSize, int maxBulkDataSize) {
+ int putFileLength;
+ if (currentOffset == 0) {
+ // The first PutFile sends the full file size
+ putFileLength = fileSize;
+ } else if ((fileSize - currentOffset) < maxBulkDataSize) {
+ // The last PutFile sends the size of the remaining data
+ putFileLength = fileSize - currentOffset;
+ } else {
+ // All other PutFiles send the maximum bulk data size
+ putFileLength = maxBulkDataSize;
+ }
+ return putFileLength;
+ }
+
+ /**
+ * Gets the size of the data to be sent in a packet.
+ * Packet size can not be greater than the max MTU size allowed by the SDL Core.
+ *
+ * @param currentOffset The position in the file where to start reading data
+ * @param fileSize he size of the file
+ * @param maxBulkDataSize The max size of bulk data that we can load into each PutFile
+ * @return The size of the data to be sent in the packet.
+ */
+ private int getDataSizeForOffset(int currentOffset, int fileSize, int maxBulkDataSize) {
+ int dataSize;
+ int fileSizeRemaining = fileSize - currentOffset;
+ if (fileSizeRemaining < maxBulkDataSize) {
+ dataSize = fileSizeRemaining;
+ } else {
+ dataSize = maxBulkDataSize;
+ }
+ return dataSize;
+ }
+
+ /**
+ * Reads a chunk of data from input stream.
+ *
+ * @param size The amount of data to read from the input stream
+ * @param inputStream The stream from which to read the data
+ * @return The data read from the socket
+ */
+ private byte[] getDataChunkWithSize(int size, InputStream inputStream) {
+ if (size < 0) {
+ return null;
+ }
+
+ int bytesRead = 0;
+ byte[] buffer = new byte[size];
+ try {
+ bytesRead = inputStream.read(buffer, 0, size);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (bytesRead > 0) {
+ return buffer;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * One of the responses returned by the SDL Core will contain the correct remaining free storage
+ * size on the SDL Core. Since communication with the SDL Core is asynchronous, there is no way
+ * to predict which response contains the correct bytes available other than to watch for the
+ * largest correlation id, since that will be the last response sent by the SDL Core.
+ *
+ * @param correlationID The correlationID for the newest response returned by the SDL Core for a PutFile
+ * @param highestCorrelationIDReceived The largest currently received correlation id
+ * @return Whether or not the newest request contains the highest correlationId
+ */
+ private boolean newHighestCorrelationID(int correlationID, int highestCorrelationIDReceived) {
+ return correlationID > highestCorrelationIDReceived;
+ }
+
+ /**
+ * Gets the size of the data.
+ *
+ * @return The size of the data.
+ */
+ private int getFileSizeFromInputStream(InputStream inputStream) {
+ int size = 0;
+ if (inputStream != null) {
+ try {
+ size = inputStream.available();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return size;
+ }
+
+ @Override
+ public String getName() {
+ return super.getName() + " - " + fileWrapper.getFile().getName();
+ }
+}
+
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
index e63bda22c..619e48081 100644
--- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
@@ -97,7 +97,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
abstract class BaseLifecycleManager {
static final String TAG = "Lifecycle Manager";
- public static final Version MAX_SUPPORTED_RPC_VERSION = new Version(7, 0, 0);
+ public static final Version MAX_SUPPORTED_RPC_VERSION = new Version(7, 1, 0);
// Protected Correlation IDs
private final int REGISTER_APP_INTERFACE_CORRELATION_ID = 65529,
@@ -169,7 +169,12 @@ abstract class BaseLifecycleManager {
Taskmaster getTaskmaster() {
if (taskmaster == null) {
Taskmaster.Builder builder = new Taskmaster.Builder();
- builder.setThreadCount(2);
+ int threadCount = 2;
+ // Give NAVIGATION & PROJECTION apps an extra thread to handle audio/video streaming operations
+ if (appConfig != null && appConfig.appType != null && (appConfig.appType.contains(AppHMIType.NAVIGATION) || appConfig.appType.contains(AppHMIType.PROJECTION))) {
+ threadCount = 3;
+ }
+ builder.setThreadCount(threadCount);
builder.shouldBeDaemon(true);
taskmaster = builder.build();
taskmaster.start();
@@ -1042,6 +1047,11 @@ abstract class BaseLifecycleManager {
}
@Override
+ public long getMtu(SessionType serviceType) {
+ return BaseLifecycleManager.this.session.getMtu(serviceType);
+ }
+
+ @Override
public void startRPCEncryption() {
BaseLifecycleManager.this.startRPCEncryption();
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
index 10d24c765..c2c9cc52e 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
@@ -152,7 +152,7 @@ abstract class BaseSoftButtonManager extends BaseSubManager {
// Auto-send an updated Show if we have new capabilities
if (softButtonObjects != null && !softButtonObjects.isEmpty() && softButtonCapabilities != null && !softButtonCapabilitiesEquals(oldSoftButtonCapabilities, softButtonCapabilities)) {
- SoftButtonReplaceOperation operation = new SoftButtonReplaceOperation(internalInterface, fileManager, softButtonCapabilities, softButtonObjects, currentMainField1);
+ SoftButtonReplaceOperation operation = new SoftButtonReplaceOperation(internalInterface, fileManager, softButtonCapabilities, softButtonObjects, getCurrentMainField1());
transactionQueue.add(operation, false);
}
}
@@ -311,7 +311,7 @@ abstract class BaseSoftButtonManager extends BaseSubManager {
this.softButtonObjects = softButtonObjects;
// We only need to pass the first softButtonCapabilities in the array due to the fact that all soft button capabilities are the same (i.e. there is no way to assign a softButtonCapabilities to a specific soft button).
- SoftButtonReplaceOperation operation = new SoftButtonReplaceOperation(internalInterface, fileManager.get(), softButtonCapabilities, softButtonObjects, currentMainField1);
+ SoftButtonReplaceOperation operation = new SoftButtonReplaceOperation(internalInterface, fileManager.get(), softButtonCapabilities, softButtonObjects, getCurrentMainField1());
if (batchUpdates) {
batchQueue.clear();
@@ -384,7 +384,7 @@ abstract class BaseSoftButtonManager extends BaseSubManager {
}
private void transitionSoftButton() {
- SoftButtonTransitionOperation operation = new SoftButtonTransitionOperation(internalInterface, softButtonObjects, currentMainField1);
+ SoftButtonTransitionOperation operation = new SoftButtonTransitionOperation(internalInterface, softButtonObjects, getCurrentMainField1());
if (batchUpdates) {
for (Task task : batchQueue) {
@@ -467,10 +467,10 @@ abstract class BaseSoftButtonManager extends BaseSubManager {
for (Task task : transactionQueue.getTasksAsList()) {
if (task instanceof SoftButtonReplaceOperation) {
SoftButtonReplaceOperation operation = (SoftButtonReplaceOperation) task;
- operation.setCurrentMainField1(currentMainField1);
+ operation.setCurrentMainField1(getCurrentMainField1());
} else if (task instanceof SoftButtonTransitionOperation) {
SoftButtonTransitionOperation operation = (SoftButtonTransitionOperation) task;
- operation.setCurrentMainField1(currentMainField1);
+ operation.setCurrentMainField1(getCurrentMainField1());
}
}
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonReplaceOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonReplaceOperation.java
index 73658b257..e2611a959 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonReplaceOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonReplaceOperation.java
@@ -116,7 +116,7 @@ class SoftButtonReplaceOperation extends Task {
List<SdlArtwork> initialStatesToBeUploaded = new ArrayList<>();
for (SoftButtonObject softButtonObject : softButtonObjects) {
SoftButtonState softButtonState = softButtonObject.getCurrentState();
- if (softButtonState != null && artworkNeedsUpload(softButtonState.getArtwork())) {
+ if (softButtonState != null && fileManager.get()!= null && fileManager.get().fileNeedsUpload(softButtonState.getArtwork()) && supportsSoftButtonImages()) {
initialStatesToBeUploaded.add(softButtonState.getArtwork());
}
}
@@ -163,7 +163,7 @@ class SoftButtonReplaceOperation extends Task {
if (softButtonState.getName().equals(softButtonObject.getCurrentState().getName())) {
continue;
}
- if (artworkNeedsUpload(softButtonState.getArtwork())) {
+ if (fileManager.get() != null && fileManager.get().fileNeedsUpload(softButtonState.getArtwork()) && supportsSoftButtonImages()) {
otherStatesToBeUploaded.add(softButtonState.getArtwork());
}
}
@@ -284,14 +284,6 @@ class SoftButtonReplaceOperation extends Task {
}
}
- private boolean artworkNeedsUpload(SdlArtwork artwork) {
- return (artwork != null
- && fileManager.get() != null
- && !fileManager.get().hasUploadedFile(artwork)
- && softButtonCapabilities.getImageSupported()
- && !artwork.isStaticIcon());
- }
-
private boolean currentStateHasImages() {
for (SoftButtonObject softButtonObject : softButtonObjects) {
if (softButtonObject.getCurrentState().getArtwork() != null) {
@@ -304,7 +296,7 @@ class SoftButtonReplaceOperation extends Task {
private boolean allCurrentStateImagesAreUploaded() {
for (SoftButtonObject softButtonObject : softButtonObjects) {
SdlArtwork artwork = softButtonObject.getCurrentState().getArtwork();
- if (artworkNeedsUpload(artwork)) {
+ if (fileManager.get() != null && fileManager.get().fileNeedsUpload(artwork) && supportsSoftButtonImages()) {
return false;
}
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperation.java
index 2d0390e32..683820ee5 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicUpdateOperation.java
@@ -103,7 +103,7 @@ class TextAndGraphicUpdateOperation extends Task {
}
});
- } else if (!sdlArtworkNeedsUpload(updatedState.getPrimaryGraphic()) && !sdlArtworkNeedsUpload(updatedState.getSecondaryGraphic())) {
+ } else if (fileManager.get() != null && !fileManager.get().fileNeedsUpload(updatedState.getPrimaryGraphic()) && !fileManager.get().fileNeedsUpload(updatedState.getSecondaryGraphic())) {
DebugTool.logInfo(TAG, "Images already uploaded, sending full update");
// The files to be updated are already uploaded, send the full show immediately
sendShow(show, new CompletionListener() {
@@ -260,8 +260,8 @@ class TextAndGraphicUpdateOperation extends Task {
Show createImageOnlyShowWithPrimaryArtwork(SdlArtwork primaryArtwork, SdlArtwork secondaryArtwork) {
Show newShow = new Show();
- newShow.setGraphic((primaryArtwork != null && !(sdlArtworkNeedsUpload(primaryArtwork))) ? primaryArtwork.getImageRPC() : null);
- newShow.setSecondaryGraphic((secondaryArtwork != null && !(sdlArtworkNeedsUpload(secondaryArtwork))) ? secondaryArtwork.getImageRPC() : null);
+ newShow.setGraphic(shouldRPCIncludeImage(primaryArtwork) ? primaryArtwork.getImageRPC() : null);
+ newShow.setSecondaryGraphic(shouldRPCIncludeImage(secondaryArtwork) ? secondaryArtwork.getImageRPC() : null);
if (newShow.getGraphic() == null && newShow.getSecondaryGraphic() == null) {
DebugTool.logInfo(TAG, "No graphics to upload");
return null;
@@ -645,10 +645,9 @@ class TextAndGraphicUpdateOperation extends Task {
return array;
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private boolean sdlArtworkNeedsUpload(SdlArtwork artwork) {
- if (fileManager.get() != null) {
- return artwork != null && !fileManager.get().hasUploadedFile(artwork) && !artwork.isStaticIcon();
+ private boolean shouldRPCIncludeImage(SdlArtwork artwork) {
+ if (artwork != null) {
+ return artwork.isStaticIcon() || (fileManager.get() != null && fileManager.get().hasUploadedFile(artwork));
}
return false;
}
@@ -664,9 +663,10 @@ class TextAndGraphicUpdateOperation extends Task {
String currentScreenDataPrimaryGraphicName = (currentScreenData != null && currentScreenData.getPrimaryGraphic() != null) ? currentScreenData.getPrimaryGraphic().getName() : null;
String primaryGraphicName = updatedState.getPrimaryGraphic() != null ? updatedState.getPrimaryGraphic().getName() : null;
- boolean graphicMatchesExisting = CompareUtils.areStringsEqual(currentScreenDataPrimaryGraphicName, primaryGraphicName, true, true);
+ boolean graphicNameMatchesExisting = CompareUtils.areStringsEqual(currentScreenDataPrimaryGraphicName, primaryGraphicName, true, true);
+ boolean shouldOverwriteGraphic = updatedState.getPrimaryGraphic() != null && updatedState.getPrimaryGraphic().getOverwrite();
- return templateSupportsPrimaryArtwork && !graphicMatchesExisting;
+ return templateSupportsPrimaryArtwork && (shouldOverwriteGraphic || !graphicNameMatchesExisting);
}
/**
@@ -680,13 +680,14 @@ class TextAndGraphicUpdateOperation extends Task {
String currentScreenDataSecondaryGraphicName = (currentScreenData != null && currentScreenData.getSecondaryGraphic() != null) ? currentScreenData.getSecondaryGraphic().getName() : null;
String secondaryGraphicName = updatedState.getSecondaryGraphic() != null ? updatedState.getSecondaryGraphic().getName() : null;
- boolean graphicMatchesExisting = CompareUtils.areStringsEqual(currentScreenDataSecondaryGraphicName, secondaryGraphicName, true, true);
+ boolean graphicNameMatchesExisting = CompareUtils.areStringsEqual(currentScreenDataSecondaryGraphicName, secondaryGraphicName, true, true);
+ boolean shouldOverwriteGraphic = updatedState.getSecondaryGraphic() != null && updatedState.getSecondaryGraphic().getOverwrite();
// Cannot detect if there is a secondary image below v5.0, so we'll just try to detect if the primary image is allowed and allow the secondary image if it is.
if (internalInterface.get() != null && internalInterface.get().getSdlMsgVersion().getMajorVersion() >= 5) {
- return templateSupportsSecondaryArtwork && !graphicMatchesExisting;
+ return templateSupportsSecondaryArtwork && (shouldOverwriteGraphic || !graphicNameMatchesExisting);
} else {
- return templateSupportsImageField(ImageFieldName.graphic) && !graphicMatchesExisting;
+ return templateSupportsImageField(ImageFieldName.graphic) && (shouldOverwriteGraphic || !graphicNameMatchesExisting);
}
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java
index 6bf0b7d25..c857f0878 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java
@@ -261,20 +261,13 @@ class PreloadChoicesOperation extends Task {
List<SdlArtwork> artworksToUpload() {
List<SdlArtwork> artworksToUpload = new ArrayList<>();
for (ChoiceCell cell : cellsToUpload) {
- if (shouldSendChoicePrimaryImage() && artworkNeedsUpload(cell.getArtwork())) {
+ if (shouldSendChoicePrimaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getArtwork())) {
artworksToUpload.add(cell.getArtwork());
}
- if (shouldSendChoiceSecondaryImage() && artworkNeedsUpload(cell.getSecondaryArtwork())) {
+ if (shouldSendChoiceSecondaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
artworksToUpload.add(cell.getSecondaryArtwork());
}
}
return artworksToUpload;
}
-
- boolean artworkNeedsUpload(SdlArtwork artwork) {
- if (fileManager.get() != null) {
- return (artwork != null && !fileManager.get().hasUploadedFile(artwork) && !artwork.isStaticIcon());
- }
- return false;
- }
} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java
index 8a502e26c..d0fb36bd1 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java
@@ -344,6 +344,7 @@ class PresentChoiceSetOperation extends Task {
@Override
public void onUpdatedAutoCompleteList(List<String> updatedAutoCompleteList) {
keyboardProperties.setAutoCompleteList(updatedAutoCompleteList != null ? updatedAutoCompleteList : new ArrayList<String>());
+ keyboardProperties.setAutoCompleteText(updatedAutoCompleteList != null && !updatedAutoCompleteList.isEmpty() ? updatedAutoCompleteList.get(0) : null);
updateKeyboardProperties(null);
}
});
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
index dbf9c211c..f44e0c2e3 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
@@ -759,7 +759,7 @@ abstract class BaseMenuManager extends BaseSubManager {
List<SdlArtwork> artworks = new ArrayList<>();
for (MenuCell cell : cells) {
- if (artworkNeedsUpload(cell.getIcon())) {
+ if (fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getIcon())) {
artworks.add(cell.getIcon());
}
if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
@@ -770,18 +770,23 @@ abstract class BaseMenuManager extends BaseSubManager {
return artworks;
}
+ private boolean shouldRPCsIncludeImages(List<MenuCell> cells) {
+ for (MenuCell cell : cells) {
+ SdlArtwork artwork = cell.getIcon();
+ if (artwork != null && !artwork.isStaticIcon() && fileManager.get() != null && !fileManager.get().hasUploadedFile(artwork)) {
+ return false;
+ } else if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
+ return shouldRPCsIncludeImages(cell.getSubCells());
+ }
+ }
+ return true;
+ }
+
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean supportsImages() {
return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName(defaultMainWindowCapability, ImageFieldName.cmdIcon);
}
- private boolean artworkNeedsUpload(SdlArtwork artwork) {
- if (fileManager.get() != null) {
- return (artwork != null && !fileManager.get().hasUploadedFile(artwork) && !artwork.isStaticIcon());
- }
- return false;
- }
-
// IDs
private void updateIdsOnDynamicCells(List<MenuCell> dynamicCells) {
@@ -1132,7 +1137,7 @@ abstract class BaseMenuManager extends BaseSubManager {
List<RPCRequest> mainMenuCommands;
final List<RPCRequest> subMenuCommands;
- if (findAllArtworksToBeUploadedFromCells(menu).size() > 0 || !supportsImages()) {
+ if (!shouldRPCsIncludeImages(menu) || !supportsImages()) {
// Send artwork-less menu
mainMenuCommands = mainMenuCommandsForCells(menu, false);
subMenuCommands = subMenuCommandsForCells(menu, false);
@@ -1237,7 +1242,7 @@ abstract class BaseMenuManager extends BaseSubManager {
List<RPCRequest> mainMenuCommands;
- if (findAllArtworksToBeUploadedFromCells(adds).size() > 0 || !supportsImages()) {
+ if (!shouldRPCsIncludeImages(adds) || !supportsImages()) {
// Send artwork-less menu
mainMenuCommands = createCommandsForDynamicSubCells(newMenu, adds, false);
} else {
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java
index 4c449ff61..3e5fa6dbe 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java
@@ -34,47 +34,48 @@ package com.smartdevicelink.managers.screen.menu;
import androidx.annotation.NonNull;
+import com.livio.taskmaster.Queue;
+import com.livio.taskmaster.Task;
import com.smartdevicelink.managers.BaseSubManager;
import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.protocol.enums.FunctionID;
import com.smartdevicelink.proxy.RPCNotification;
-import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.AddCommand;
-import com.smartdevicelink.proxy.rpc.DeleteCommand;
+import com.smartdevicelink.proxy.RPCRequest;
import com.smartdevicelink.proxy.rpc.OnCommand;
import com.smartdevicelink.proxy.rpc.OnHMIStatus;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
import com.smartdevicelink.proxy.rpc.enums.PredefinedWindows;
-import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
import com.smartdevicelink.util.DebugTool;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
abstract class BaseVoiceCommandManager extends BaseSubManager {
private static final String TAG = "BaseVoiceCommandManager";
- List<VoiceCommand> voiceCommands, oldVoiceCommands;
-
- List<AddCommand> inProgressUpdate;
+ List<VoiceCommand> voiceCommands, currentVoiceCommands;
int lastVoiceCommandId;
private static final int voiceCommandIdMin = 1900000000;
- boolean waitingOnHMIUpdate;
- boolean hasQueuedUpdate;
-
HMILevel currentHMILevel;
OnRPCNotificationListener hmiListener;
OnRPCNotificationListener commandListener;
+ Queue transactionQueue;
+ VoiceCommandUpdateOperation updateOperation;
+
+
// CONSTRUCTORS
BaseVoiceCommandManager(@NonNull ISdl internalInterface) {
super(internalInterface);
- currentHMILevel = HMILevel.HMI_NONE;
+ this.transactionQueue = newTransactionQueue();
+
+ currentHMILevel = null;
addListeners();
lastVoiceCommandId = voiceCommandIdMin;
}
@@ -90,12 +91,16 @@ abstract class BaseVoiceCommandManager extends BaseSubManager {
lastVoiceCommandId = voiceCommandIdMin;
voiceCommands = null;
- oldVoiceCommands = null;
+ currentVoiceCommands = null;
- waitingOnHMIUpdate = false;
currentHMILevel = null;
- inProgressUpdate = null;
- hasQueuedUpdate = false;
+
+ if (transactionQueue != null) {
+ transactionQueue.close();
+ }
+ transactionQueue = null;
+
+ updateOperation = null;
// remove listeners
internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
@@ -104,172 +109,75 @@ abstract class BaseVoiceCommandManager extends BaseSubManager {
super.dispose();
}
- // SETTERS
-
- public void setVoiceCommands(List<VoiceCommand> voiceCommands) {
-
- // we actually need voice commands to set.
- if (voiceCommands == null || voiceCommands.size() == 0) {
- DebugTool.logInfo(TAG, "Trying to set empty list of voice commands, returning");
- return;
- }
-
- // make sure hmi is not none
- if (currentHMILevel == null || currentHMILevel == HMILevel.HMI_NONE) {
- // Trying to send on HMI_NONE, waiting for full
- this.voiceCommands = new ArrayList<>(voiceCommands);
- waitingOnHMIUpdate = true;
- return;
- }
-
- waitingOnHMIUpdate = false;
- lastVoiceCommandId = voiceCommandIdMin;
- updateIdsOnVoiceCommands(voiceCommands);
- this.oldVoiceCommands = new ArrayList<>();
- if (this.voiceCommands != null && !this.voiceCommands.isEmpty()) {
- this.oldVoiceCommands.addAll(this.voiceCommands);
- }
- this.voiceCommands = new ArrayList<>(voiceCommands);
-
- update();
- }
-
- public List<VoiceCommand> getVoiceCommands() {
- return voiceCommands;
+ private Queue newTransactionQueue() {
+ Queue queue = internalInterface.getTaskmaster().createQueue("VoiceCommandManager", 4, false);
+ queue.pause();
+ return queue;
}
- // UPDATING SYSTEM
-
- private void update() {
-
- if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE)) {
- waitingOnHMIUpdate = true;
- return;
+ // If the HMI level is NONE since we want to delay sending RPCs until we're in non-NONE
+ private void updateTransactionQueueSuspended() {
+ if (HMILevel.HMI_NONE.equals(currentHMILevel)) {
+ DebugTool.logInfo(TAG, "Suspending the transaction queue. Current HMI level is NONE");
+ transactionQueue.pause();
+ } else {
+ DebugTool.logInfo(TAG, "Starting the transaction queue");
+ transactionQueue.resume();
}
-
- if (inProgressUpdate != null) {
- // There's an in-progress update, put this on hold
- hasQueuedUpdate = true;
- return;
- }
-
- sendDeleteCurrentVoiceCommands(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- // we don't care about errors from deleting, send new add commands
- sendCurrentVoiceCommands(new CompletionListener() {
- @Override
- public void onComplete(boolean success2) {
- inProgressUpdate = null;
-
- if (hasQueuedUpdate) {
- update();
- hasQueuedUpdate = false;
- }
-
- if (!success2) {
- DebugTool.logError(TAG, "Error sending voice commands");
- }
- }
- });
- }
- });
-
}
- // DELETING OLD MENU ITEMS
+ // SETTERS
- private void sendDeleteCurrentVoiceCommands(final CompletionListener listener) {
+ public void setVoiceCommands(List<VoiceCommand> voiceCommands) {
- if (oldVoiceCommands == null || oldVoiceCommands.size() == 0) {
- if (listener != null) {
- listener.onComplete(true);
- }
+ // we actually need voice commands to set.
+ if (voiceCommands == null || voiceCommands.equals(this.voiceCommands)) {
+ DebugTool.logInfo(TAG, "Voice commands list was null or matches the current voice commands");
return;
}
- List<DeleteCommand> deleteVoiceCommands = deleteCommandsForVoiceCommands(oldVoiceCommands);
- oldVoiceCommands.clear();
- internalInterface.sendRPCs(deleteVoiceCommands, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
-
- }
+ updateIdsOnVoiceCommands(voiceCommands);
+ this.voiceCommands = new ArrayList<>(voiceCommands);
+ cleanTransactionQueue();
+ updateOperation = new VoiceCommandUpdateOperation(internalInterface, currentVoiceCommands, voiceCommands, new VoiceCommandUpdateOperation.VoiceCommandChangesListener() {
@Override
- public void onFinished() {
- DebugTool.logInfo(TAG, "Successfully deleted old voice commands");
- if (listener != null) {
- listener.onComplete(true);
+ public void updateVoiceCommands(List<VoiceCommand> newCurrentVoiceCommands, HashMap<RPCRequest, String> errorObject) {
+ DebugTool.logInfo(TAG, "The updated list of VoiceCommands: " + newCurrentVoiceCommands);
+ if (!errorObject.isEmpty()) {
+ DebugTool.logError(TAG, "The failed Add and Delete Commands: " + errorObject);
}
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
+ currentVoiceCommands = newCurrentVoiceCommands;
+ updatePendingOperations(newCurrentVoiceCommands);
+ updateOperation = null;
}
});
-
+ transactionQueue.add(updateOperation, false);
}
- // SEND NEW MENU ITEMS
-
- private void sendCurrentVoiceCommands(final CompletionListener listener) {
-
- if (voiceCommands == null || voiceCommands.size() == 0) {
- if (listener != null) {
- listener.onComplete(true); // no voice commands to send doesnt mean that its an error
- }
- return;
- }
-
- inProgressUpdate = addCommandsForVoiceCommands(voiceCommands);
-
- internalInterface.sendRPCs(inProgressUpdate, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
-
- }
-
- @Override
- public void onFinished() {
- DebugTool.logInfo(TAG, "Sending Voice Commands Complete");
- if (listener != null) {
- listener.onComplete(true);
- }
- oldVoiceCommands = voiceCommands;
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- }
- });
+ public List<VoiceCommand> getVoiceCommands() {
+ return voiceCommands;
}
- // DELETES
-
- List<DeleteCommand> deleteCommandsForVoiceCommands(List<VoiceCommand> voiceCommands) {
- List<DeleteCommand> deleteCommandList = new ArrayList<>();
- for (VoiceCommand command : voiceCommands) {
- DeleteCommand delete = new DeleteCommand(command.getCommandId());
- deleteCommandList.add(delete);
+ private void cleanTransactionQueue() {
+ if (transactionQueue != null) {
+ transactionQueue.clear();
}
- return deleteCommandList;
- }
-
- // COMMANDS
- List<AddCommand> addCommandsForVoiceCommands(List<VoiceCommand> voiceCommands) {
- List<AddCommand> addCommandList = new ArrayList<>();
- for (VoiceCommand command : voiceCommands) {
- addCommandList.add(commandForVoiceCommand(command));
+ if (updateOperation != null) {
+ updateOperation.cancelTask();
+ updateOperation = null;
}
- return addCommandList;
}
- private AddCommand commandForVoiceCommand(VoiceCommand voiceCommand) {
- AddCommand command = new AddCommand(voiceCommand.getCommandId());
- command.setVrCommands(voiceCommand.getVoiceCommands());
- return command;
+ private void updatePendingOperations(List<VoiceCommand> newCurrentVoiceCommands) {
+ for (Task operation : transactionQueue.getTasksAsList()) {
+ if (operation.getState() == Task.IN_PROGRESS) {
+ continue;
+ }
+ VoiceCommandUpdateOperation vcOperation = (VoiceCommandUpdateOperation) operation;
+ vcOperation.oldVoiceCommands = newCurrentVoiceCommands;
+ }
}
// HELPERS
@@ -292,14 +200,8 @@ abstract class BaseVoiceCommandManager extends BaseSubManager {
if (onHMIStatus.getWindowID() != null && onHMIStatus.getWindowID() != PredefinedWindows.DEFAULT_WINDOW.getValue()) {
return;
}
- HMILevel oldHMILevel = currentHMILevel;
currentHMILevel = onHMIStatus.getHmiLevel();
- // Auto-send an update if we were in NONE and now we are not
- if (oldHMILevel == HMILevel.HMI_NONE && currentHMILevel != HMILevel.HMI_NONE) {
- if (waitingOnHMIUpdate) {
- setVoiceCommands(voiceCommands);
- }
- }
+ updateTransactionQueueSuspended();
}
};
internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java
index 2d1b875cc..15d66b09e 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java
@@ -35,6 +35,7 @@ package com.smartdevicelink.managers.screen.menu;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+
import java.util.List;
public class VoiceCommand {
@@ -124,4 +125,36 @@ public class VoiceCommand {
int getCommandId() {
return commandId;
}
+
+ /**
+ * Used to compile hashcode for VoiceCommand for use to compare in equals method
+ *
+ * @return Custom hashcode of VoiceCommand variables
+ */
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result += Integer.rotateLeft(getCommandId(), 1);
+ for (int i = 0; i < this.getVoiceCommands().size(); i++) {
+ result += ((getVoiceCommands().get(i) == null) ? 0 : Integer.rotateLeft(getVoiceCommands().get(i).hashCode(), i + 2));
+ }
+ return result;
+ }
+
+ /**
+ * Uses our custom hashCode for VoiceCommand 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 SoftButtonObject, not the same
+ if (!(o instanceof VoiceCommand)) return false;
+ // return comparison
+ return hashCode() == o.hashCode();
+ }
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java
new file mode 100644
index 000000000..b755abd7d
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java
@@ -0,0 +1,230 @@
+package com.smartdevicelink.managers.screen.menu;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.AddCommand;
+import com.smartdevicelink.proxy.rpc.DeleteCommand;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+class VoiceCommandUpdateOperation extends Task {
+ private static final String TAG = "VoiceCommandReplaceOperation";
+ private final WeakReference<ISdl> internalInterface;
+ List<VoiceCommand> oldVoiceCommands;
+ private List<VoiceCommand> pendingVoiceCommands;
+ private List<DeleteCommand> deleteVoiceCommands;
+ private List<AddCommand> addCommandsToSend;
+ private VoiceCommandChangesListener voiceCommandListener;
+ private List<VoiceCommand> currentVoiceCommands;
+ private HashMap<RPCRequest, String> errorObject;
+
+ interface VoiceCommandChangesListener {
+ void updateVoiceCommands(List<VoiceCommand> newCurrentVoiceCommands, HashMap<RPCRequest, String> errorObject);
+ }
+
+ VoiceCommandUpdateOperation(ISdl internalInterface, List<VoiceCommand> oldVoiceCommands, List<VoiceCommand> pendingVoiceCommands, VoiceCommandChangesListener voiceCommandListener) {
+ super("VoiceCommandReplaceOperation");
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.oldVoiceCommands = oldVoiceCommands;
+ this.pendingVoiceCommands = pendingVoiceCommands;
+ this.currentVoiceCommands = new ArrayList<>();
+ if (oldVoiceCommands != null) {
+ this.currentVoiceCommands.addAll(oldVoiceCommands);
+ }
+ this.voiceCommandListener = voiceCommandListener;
+ this.errorObject = new HashMap<>();
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ onFinished();
+ return;
+ }
+
+ sendDeleteCurrentVoiceCommands(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ onFinished();
+ return;
+ }
+ // we don't care about errors from deleting, send new add commands
+ sendCurrentVoiceCommands(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success2) {
+ if (!success2) {
+ DebugTool.logError(TAG, "Error sending voice commands");
+ }
+ onFinished();
+ if (voiceCommandListener != null) {
+ voiceCommandListener.updateVoiceCommands(currentVoiceCommands, errorObject);
+ }
+ }
+ });
+ }
+ });
+ }
+
+ // Send DeleteCommandList
+
+ private void sendDeleteCurrentVoiceCommands(final CompletionListener listener) {
+
+ if (oldVoiceCommands == null || oldVoiceCommands.isEmpty()) {
+ if (listener != null) {
+ listener.onComplete(true);
+ }
+ return;
+ }
+
+ deleteVoiceCommands = deleteCommandsForVoiceCommands(oldVoiceCommands);
+
+ internalInterface.get().sendRPCs(deleteVoiceCommands, new OnMultipleRequestListener() {
+ @Override
+ public void onUpdate(int remainingRequests) {
+ }
+
+ @Override
+ public void onFinished() {
+ if (listener != null) {
+ if (errorObject.isEmpty()) {
+ DebugTool.logInfo(TAG, "Successfully deleted old voice commands");
+ listener.onComplete(true);
+ } else {
+ DebugTool.logInfo(TAG, "Unable to deleted some old voice commands");
+ listener.onComplete(false);
+ }
+ }
+ }
+
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ DeleteCommand foundDeleteCommand = null;
+ for (DeleteCommand deleteCommand : deleteVoiceCommands) {
+ if (correlationId == deleteCommand.getCorrelationID()) {
+ foundDeleteCommand = deleteCommand;
+ break;
+ }
+ }
+
+ if (!response.getSuccess()) {
+ errorObject.put(foundDeleteCommand, response.getInfo());
+ } else {
+ if (foundDeleteCommand == null) {
+ return;
+ }
+ removeCurrentVoiceCommand(foundDeleteCommand.getCmdID());
+ }
+ }
+ });
+
+ }
+
+ // Create DeleteCommand List
+
+ List<DeleteCommand> deleteCommandsForVoiceCommands(List<VoiceCommand> voiceCommands) {
+ List<DeleteCommand> deleteCommandList = new ArrayList<>();
+ for (VoiceCommand command : voiceCommands) {
+ DeleteCommand delete = new DeleteCommand(command.getCommandId());
+ deleteCommandList.add(delete);
+ }
+ return deleteCommandList;
+ }
+
+ private void removeCurrentVoiceCommand(Integer commandId) {
+ for (VoiceCommand voiceCommand : oldVoiceCommands) {
+ if (commandId == voiceCommand.getCommandId()) {
+ currentVoiceCommands.remove(voiceCommand);
+ return;
+ }
+ }
+ }
+
+ // SEND NEW MENU ITEMS
+
+ private void sendCurrentVoiceCommands(final CompletionListener listener) {
+
+ if (pendingVoiceCommands == null || pendingVoiceCommands.size() == 0) {
+ if (listener != null) {
+ listener.onComplete(true); // no voice commands to send doesnt mean that its an error
+ }
+ return;
+ }
+
+ addCommandsToSend = addCommandsForVoiceCommands(pendingVoiceCommands);
+
+ internalInterface.get().sendRPCs(addCommandsToSend, new OnMultipleRequestListener() {
+ @Override
+ public void onUpdate(int remainingRequests) {
+ }
+
+ @Override
+ public void onFinished() {
+ if (listener != null) {
+ if (errorObject.isEmpty()) {
+ DebugTool.logInfo(TAG, "Sending Voice Commands Complete");
+ listener.onComplete(true);
+ } else {
+ DebugTool.logInfo(TAG, "Sending Voice Commands Complete with errors");
+ listener.onComplete(false);
+ }
+ }
+ }
+
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ AddCommand foundAddCommand = null;
+ for (AddCommand addCommand : addCommandsToSend) {
+ if (correlationId == addCommand.getCorrelationID()) {
+ foundAddCommand = addCommand;
+ break;
+ }
+ }
+ if (!response.getSuccess()) {
+ errorObject.put(foundAddCommand, response.getInfo());
+ } else {
+ if (foundAddCommand == null) {
+ return;
+ }
+ VoiceCommand foundVoiceCommand = pendingVoiceCommand(foundAddCommand.getCmdID());
+ if (foundVoiceCommand != null) {
+ currentVoiceCommands.add(foundVoiceCommand);
+ }
+ }
+ }
+ });
+ }
+
+ // Create AddCommand List
+
+ List<AddCommand> addCommandsForVoiceCommands(List<VoiceCommand> voiceCommands) {
+ List<AddCommand> addCommandList = new ArrayList<>();
+ for (VoiceCommand command : voiceCommands) {
+ AddCommand addCommand = new AddCommand(command.getCommandId());
+ addCommand.setVrCommands(command.getVoiceCommands());
+ addCommandList.add(addCommand);
+ }
+ return addCommandList;
+ }
+
+ private VoiceCommand pendingVoiceCommand(Integer commandId) {
+ for (VoiceCommand voiceCommand : pendingVoiceCommands) {
+ if (commandId == voiceCommand.getCommandId()) {
+ return voiceCommand;
+ }
+ }
+ return null;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
index bb5e9cb31..0b42fcc15 100644
--- a/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
+++ b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
@@ -103,6 +103,7 @@ public class SdlProtocolBase {
private final Hashtable<Byte, Object> _messageLocks = new Hashtable<>();
private final HashMap<SessionType, Long> mtus = new HashMap<>();
private final HashMap<SessionType, TransportRecord> activeTransports = new HashMap<>();
+ private final HashMap<SessionType, Boolean> serviceStartedOnTransport = new HashMap<>();
private final Map<TransportType, List<ISecondaryTransportListener>> secondaryTransportListeners = new HashMap<>();
@@ -212,6 +213,7 @@ public class SdlProtocolBase {
messageID = 0;
headerSize = V1_HEADER_SIZE;
this.activeTransports.clear();
+ this.serviceStartedOnTransport.clear();
this.mtus.clear();
mtus.put(SessionType.RPC, (long) (V1_V2_MTU_SIZE - headerSize));
this.secondaryTransportParams = null;
@@ -708,6 +710,7 @@ public class SdlProtocolBase {
public void startService(SessionType serviceType, byte sessionID, boolean isEncrypted) {
final SdlPacket header = SdlPacketFactory.createStartSession(serviceType, 0x00, (byte) protocolVersion.getMajor(), sessionID, isEncrypted);
+ serviceStartedOnTransport.put(serviceType, true);
if (SessionType.RPC.equals(serviceType)) {
if (connectedPrimaryTransport != null) {
header.setTransportRecord(connectedPrimaryTransport);
@@ -1193,12 +1196,18 @@ public class SdlProtocolBase {
//a single transport record per transport.
//TransportType type = disconnectedTransport.getType();
if (getTransportForSession(SessionType.NAV) != null && disconnectedTransport.equals(getTransportForSession(SessionType.NAV))) {
- iSdlProtocol.onServiceError(null, SessionType.NAV, iSdlProtocol.getSessionId(), "Transport disconnected");
- activeTransports.remove(SessionType.NAV);
+ if (serviceStartedOnTransport.get(SessionType.NAV) != null && serviceStartedOnTransport.get(SessionType.NAV)) {
+ iSdlProtocol.onServiceError(null, SessionType.NAV, iSdlProtocol.getSessionId(), "Transport disconnected");
+ activeTransports.remove(SessionType.NAV);
+ serviceStartedOnTransport.remove(SessionType.NAV);
+ }
}
if (getTransportForSession(SessionType.PCM) != null && disconnectedTransport.equals(getTransportForSession(SessionType.PCM))) {
- iSdlProtocol.onServiceError(null, SessionType.PCM, iSdlProtocol.getSessionId(), "Transport disconnected");
- activeTransports.remove(SessionType.PCM);
+ if (serviceStartedOnTransport.get(SessionType.PCM) != null && serviceStartedOnTransport.get(SessionType.PCM)) {
+ iSdlProtocol.onServiceError(null, SessionType.PCM, iSdlProtocol.getSessionId(), "Transport disconnected");
+ activeTransports.remove(SessionType.PCM);
+ serviceStartedOnTransport.remove(SessionType.PCM);
+ }
}
if ((getTransportForSession(SessionType.RPC) != null && disconnectedTransport.equals(getTransportForSession(SessionType.RPC))) || disconnectedTransport.equals(connectedPrimaryTransport)) {
@@ -1255,6 +1264,7 @@ public class SdlProtocolBase {
requestedSession = false;
activeTransports.clear();
+ serviceStartedOnTransport.clear();
iSdlProtocol.onTransportDisconnected(info, primaryTransportAvailable, transportConfig);
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java b/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java
index e99eba1d7..c0dd495dd 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java
@@ -31,7 +31,10 @@
*/
package com.smartdevicelink.proxy;
+import androidx.annotation.Nullable;
+
import com.smartdevicelink.marshal.JsonRPCMarshaller;
+import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.SdlDataTypeConverter;
import com.smartdevicelink.util.Version;
@@ -46,7 +49,7 @@ import java.util.Hashtable;
import java.util.List;
import java.util.Set;
-public class RPCStruct {
+public class RPCStruct implements Cloneable {
public static final String KEY_BULK_DATA = "bulkData";
public static final String KEY_PROTECTED = "protected";
@@ -375,4 +378,60 @@ public class RPCStruct {
}
return null;
}
+
+ /**
+ * Creates a deep copy of the object
+ *
+ * @return deep copy of the object, null if an exception occurred
+ */
+ @Override
+ public RPCStruct clone() {
+ try {
+ RPCStruct clone = (RPCStruct) super.clone();
+ clone.setPayloadProtected(protectedPayload);
+ clone.setBulkData(_bulkData);
+ clone.store = (Hashtable) store.clone();
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ DebugTool.logError("RPCStruct", "Failed to clone: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Uses the RPCStruct store for RPCStruct objects
+ *
+ * @param obj - The object to compare
+ * @return boolean of whether the objects are the same or not
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ // if this is the same memory address, its the same
+ if (this == obj) {
+ return true;
+ }
+ // if this is not an instance of the same class, not the same
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ // return comparison of store
+ return isEqualToRPC((RPCStruct) obj);
+ }
+
+ private boolean isEqualToRPC(RPCStruct rpc) {
+ return store.equals(rpc.store);
+ }
+
+ /**
+ * Used to compile hashcode for RPCStruct
+ *
+ * @return Custom hashcode of RPCStruct
+ */
+ @Override
+ public int hashCode() {
+ return store.hashCode();
+ }
}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
index cba5d0445..4e7b113a1 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
@@ -38,6 +38,7 @@ import com.smartdevicelink.protocol.enums.FunctionID;
import com.smartdevicelink.proxy.RPCRequest;
import com.smartdevicelink.proxy.rpc.enums.AudioStreamingIndicator;
import com.smartdevicelink.proxy.rpc.enums.UpdateMode;
+import com.smartdevicelink.util.SdlDataTypeConverter;
import java.util.Hashtable;
@@ -94,6 +95,17 @@ import java.util.Hashtable;
* <td></td>
* <td>SmartDeviceLink 5.0</td>
* </tr>
+ * <tr>
+ * <td>countRate</td>
+ * <td>Float</td>
+ * <td>The value of this parameter is the amount that the media clock timer will advance per 1.0 seconds of real time. Values less than 1.0 will therefore advance the timer slower than real-time, while values greater than 1.0 will advance the timer faster than real-time.e.g. If this parameter is set to `0.5`, the timer will advance one second per two seconds real-time, or at 50% speed. If this parameter is set to `2.0`, the timer will advance two seconds per one second real-time, or at 200% speed.</td>
+
+ * <td>N</td>
+ * <td>{"num_min_value": 0.1, "num_max_value": 100.0}</td>
+ * <td>
+ * @since SmartDeviceLink 7.1.0
+ * </td>
+ * </tr>
*
* </table>
*
@@ -117,6 +129,7 @@ public class SetMediaClockTimer extends RPCRequest {
public static final String KEY_END_TIME = "endTime";
public static final String KEY_UPDATE_MODE = "updateMode";
public static final String KEY_AUDIO_STREAMING_INDICATOR = "audioStreamingIndicator";
+ public static final String KEY_COUNT_RATE = "countRate";
/**
* Constructs a new SetMediaClockTimer object
@@ -350,4 +363,38 @@ public class SetMediaClockTimer extends RPCRequest {
setParameters(KEY_AUDIO_STREAMING_INDICATOR, audioStreamingIndicator);
return this;
}
+
+ /**
+ * Sets the countRate.
+ *
+ * @param countRate The value of this parameter is the amount that the media clock timer will advance per 1.0
+ * seconds of real time. Values less than 1.0 will therefore advance the timer slower than
+ * real-time, while values greater than 1.0 will advance the timer faster than real-time.
+ * e.g. If this parameter is set to `0.5`, the timer will advance one second per two seconds
+ * real-time, or at 50% speed. If this parameter is set to `2.0`, the timer will advance two
+ * seconds per one second real-time, or at 200% speed.
+ * {"num_min_value": 0.1, "num_max_value": 100.0}
+ * @since SmartDeviceLink 7.1.0
+ */
+ public SetMediaClockTimer setCountRate(Float countRate) {
+ setParameters(KEY_COUNT_RATE, countRate);
+ return this;
+ }
+
+ /**
+ * Gets the countRate.
+ *
+ * @return Float The value of this parameter is the amount that the media clock timer will advance per 1.0
+ * seconds of real time. Values less than 1.0 will therefore advance the timer slower than
+ * real-time, while values greater than 1.0 will advance the timer faster than real-time.
+ * e.g. If this parameter is set to `0.5`, the timer will advance one second per two seconds
+ * real-time, or at 50% speed. If this parameter is set to `2.0`, the timer will advance two
+ * seconds per one second real-time, or at 200% speed.
+ * {"num_min_value": 0.1, "num_max_value": 100.0}
+ * @since SmartDeviceLink 7.1.0
+ */
+ public Float getCountRate() {
+ Object object = getParameters(KEY_COUNT_RATE);
+ return SdlDataTypeConverter.objectToFloat(object);
+ }
}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/ShowConstantTbt.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/ShowConstantTbt.java
index d0bbd0222..22b85b5ff 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/ShowConstantTbt.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/ShowConstantTbt.java
@@ -203,11 +203,11 @@ public class ShowConstantTbt extends RPCRequest {
}
/**
- * Sets a Fraction of distance till next maneuver
+ * Sets the distanceToManeuver.
*
- * @param distanceToManeuver a Double value representing a Fraction of distance till next maneuver
- * <p></p>
- * <b>Notes: </b>Minvalue=0; Maxvalue=1000000000
+ * @param distanceToManeuver Distance (in meters) until next maneuver. May be used to calculate progress bar.
+ * {"num_min_value": 0.0, "num_max_value": 1000000000.0}
+ * @since SmartDeviceLink 2.0.0
*/
public ShowConstantTbt setDistanceToManeuver(Double distanceToManeuver) {
setParameters(KEY_MANEUVER_DISTANCE, distanceToManeuver);
@@ -215,20 +215,23 @@ public class ShowConstantTbt extends RPCRequest {
}
/**
- * Gets a Fraction of distance till next maneuver
+ * Gets the distanceToManeuver.
*
- * @return Double -a Double value representing a Fraction of distance till next maneuver
+ * @return Float Distance (in meters) until next maneuver. May be used to calculate progress bar.
+ * {"num_min_value": 0.0, "num_max_value": 1000000000.0}
+ * @since SmartDeviceLink 2.0.0
*/
public Double getDistanceToManeuver() {
return getDouble(KEY_MANEUVER_DISTANCE);
}
/**
- * Sets a Distance till next maneuver (starting from) from previous maneuver
+ * Sets the distanceToManeuverScale.
*
- * @param distanceToManeuverScale a Double value representing a Distance till next maneuver (starting from) from previous maneuver
- * <p></p>
- * <b>Notes: </b>Minvalue=0; Maxvalue=1000000000
+ * @param distanceToManeuverScale Distance (in meters) from previous maneuver to next maneuver. May be used to calculate
+ * progress bar.
+ * {"num_min_value": 0.0, "num_max_value": 1000000000.0}
+ * @since SmartDeviceLink 2.0.0
*/
public ShowConstantTbt setDistanceToManeuverScale(Double distanceToManeuverScale) {
setParameters(KEY_MANEUVER_DISTANCE_SCALE, distanceToManeuverScale);
@@ -236,9 +239,12 @@ public class ShowConstantTbt extends RPCRequest {
}
/**
- * Gets a Distance till next maneuver (starting from) from previous maneuver
+ * Gets the distanceToManeuverScale.
*
- * @return Double -a Double value representing a Distance till next maneuver (starting from) from previous maneuver
+ * @return Float Distance (in meters) from previous maneuver to next maneuver. May be used to calculate
+ * progress bar.
+ * {"num_min_value": 0.0, "num_max_value": 1000000000.0}
+ * @since SmartDeviceLink 2.0.0
*/
public Double getDistanceToManeuverScale() {
return getDouble(KEY_MANEUVER_DISTANCE_SCALE);
diff --git a/base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java b/base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java
index f9467aee4..6bc34b836 100644
--- a/base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java
+++ b/base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java
@@ -132,9 +132,6 @@ public class VideoStreamingParameters {
if (params.interval > 0) {
this.interval = params.interval;
}
- if (this.resolution == null) {
- this.resolution = new ImageResolution();
- }
if (params.resolution != null) {
if (params.resolution.getResolutionHeight() != null && params.resolution.getResolutionHeight() > 0) {
this.resolution.setResolutionHeight(params.resolution.getResolutionHeight());
@@ -161,7 +158,7 @@ public class VideoStreamingParameters {
*/
public void update(VideoStreamingCapability capability, String vehicleMake) {
if (capability.getMaxBitrate() != null) {
- this.bitrate = Math.min(this.bitrate, capability.getMaxBitrate() * 1000);
+ this.bitrate = capability.getMaxBitrate() * 1000;
} // NOTE: the unit of maxBitrate in getSystemCapability is kbps.
double scale = DEFAULT_SCALE;
// For resolution and scale, the capability values should be taken rather than parameters specified by developers.
@@ -185,7 +182,7 @@ public class VideoStreamingParameters {
}
}
if (capability.getPreferredFPS() != null) {
- this.frameRate = Math.min(this.frameRate, capability.getPreferredFPS());
+ this.frameRate = capability.getPreferredFPS();
}
// This should be the last call as it will return out once a suitable format is found
diff --git a/javaEE/javaEE/build.gradle b/javaEE/javaEE/build.gradle
index d2c90a1b4..436ff8f9f 100644
--- a/javaEE/javaEE/build.gradle
+++ b/javaEE/javaEE/build.gradle
@@ -31,7 +31,7 @@ dependencies {
extraLibs 'org.mongodb:bson:4.0.5'
extraLibs 'androidx.annotation:annotation:1.1.0'
extraLibs 'org.java-websocket:Java-WebSocket:1.3.9'
- extraLibs 'com.livio.taskmaster:taskmaster:0.3.0'
+ extraLibs 'com.livio.taskmaster:taskmaster:0.4.0'
configurations.api.extendsFrom(configurations.extraLibs)
}
diff --git a/javaSE/javaSE/build.gradle b/javaSE/javaSE/build.gradle
index 09da6d5a6..3bf55e8c9 100644
--- a/javaSE/javaSE/build.gradle
+++ b/javaSE/javaSE/build.gradle
@@ -32,7 +32,7 @@ dependencies {
extraLibs 'org.mongodb:bson:4.0.5'
extraLibs 'androidx.annotation:annotation:1.1.0'
extraLibs 'org.java-websocket:Java-WebSocket:1.3.9'
- extraLibs 'com.livio.taskmaster:taskmaster:0.3.0'
+ extraLibs 'com.livio.taskmaster:taskmaster:0.4.0'
configurations.api.extendsFrom(configurations.extraLibs)
}
diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/FileManager.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/FileManager.java
index a2a464c11..b53a734d5 100644
--- a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/FileManager.java
+++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/FileManager.java
@@ -34,31 +34,16 @@ package com.smartdevicelink.managers.file;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
-
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.filetypes.SdlFile;
-import com.smartdevicelink.proxy.rpc.PutFile;
import com.smartdevicelink.util.DebugTool;
-import com.smartdevicelink.util.FileUtls;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
+import java.io.*;
/**
* <strong>FileManager</strong> <br>
- * <p>
* Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br>
- * <p>
- * The SDLFileManager uploads files and keeps track of all the uploaded files names during a session. <br>
- * <p>
- * We need to add the following struct: SDLFile<br>
- * <p>
- * It is broken down to these areas: <br>
- * <p>
- * 1. Getters <br>
- * 2. Deletion methods <br>
- * 3. Uploading Files / Artwork
+ * The FileManager uploads files and keeps track of all the uploaded files names during a session. <br>
*/
public class FileManager extends BaseFileManager {
@@ -74,76 +59,28 @@ public class FileManager extends BaseFileManager {
super(internalInterface, fileManagerConfig);
}
- /**
- * 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
- */
@Override
- PutFile createPutFile(@NonNull final SdlFile file) {
- PutFile putFile = new PutFile();
- if (file.getName() == null) {
- throw new IllegalArgumentException("You must specify an file name in the SdlFile");
- } else {
- putFile.setSdlFileName(file.getName());
- }
+ InputStream openInputStreamWithFile(@NonNull SdlFile file) {
+ InputStream inputStream = null;
if (file.getFilePath() != null) {
- //Attempt to access the file via a path
- byte[] data = FileUtls.getFileData(file.getFilePath());
- if (data != null) {
- putFile.setFileData(data);
- } else {
- throw new IllegalArgumentException("File at path was empty");
+ try {
+ inputStream = new FileInputStream(file.getFilePath());
+ } catch (FileNotFoundException e) {
+ DebugTool.logError(TAG, String.format("File at %s cannot be found.", file.getFilePath()));
}
} else if (file.getURI() != null) {
- // Use URI to upload file
- byte[] data = contentsOfUri(file.getURI());
- if (data != null) {
- putFile.setFileData(data);
- } else {
- throw new IllegalArgumentException("Uri was empty");
+ try {
+ inputStream = file.getURI().toURL().openStream();
+ } catch (IOException e) {
+ DebugTool.logError(TAG, String.format("File at %s cannot be found.", file.getURI()));
}
} else if (file.getFileData() != null) {
- // Use file data (raw bytes) to upload file
- putFile.setFileData(file.getFileData());
+ inputStream = new ByteArrayInputStream(file.getFileData());
} else {
- throw new IllegalArgumentException("The SdlFile to upload does " +
- "not specify its resourceId, Uri, or file data");
- }
-
- if (file.getType() != null) {
- putFile.setFileType(file.getType());
+ DebugTool.logError(TAG, "The SdlFile to upload does not specify its path, URI, or file data");
}
- putFile.setPersistentFile(file.isPersistent());
- return putFile;
- }
-
-
- /**
- * Helper method to take Uri and turn it into byte array
- *
- * @param uri Uri for desired file
- * @return Resulting byte array
- */
- private byte[] contentsOfUri(URI uri) {
- InputStream is = null;
- try {
- is = uri.toURL().openStream();
- return contentsOfInputStream(is);
- } catch (IOException e) {
- DebugTool.logError(TAG, "Can't read from URI", e);
- return null;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
+ return inputStream;
}
}
diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java
index b479e3ba5..575809538 100644
--- a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java
+++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/TextAndGraphicManager.java
@@ -54,10 +54,7 @@ class TextAndGraphicManager extends BaseTextAndGraphicManager {
@Override
SdlArtwork getBlankArtwork() {
if (blankArtwork == null) {
- blankArtwork = new SdlArtwork();
- blankArtwork.setType(FileType.GRAPHIC_PNG);
- blankArtwork.setName("blankArtwork");
- blankArtwork.setFileData(new byte[50]);
+ blankArtwork = new SdlArtwork("blankArtwork", FileType.GRAPHIC_PNG, new byte[50], true);
}
return blankArtwork;
}