summaryrefslogtreecommitdiff
path: root/base/src
diff options
context:
space:
mode:
Diffstat (limited to 'base/src')
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java47
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java20
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java22
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java309
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java35
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java74
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java274
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java927
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java374
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java1477
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java134
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateRunScore.java (renamed from base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java)62
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java37
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java32
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java144
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManagerCompletionListener.java (renamed from base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java)42
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java558
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java424
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java91
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/SendingRPCsCompletionListener.java46
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java32
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java5
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java5
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java20
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java157
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java61
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java105
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java41
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java6
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java6
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java4
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java320
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java13
-rw-r--r--base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java85
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/TransportConstants.java1
-rw-r--r--base/src/main/java/com/smartdevicelink/util/BitConverter.java2
42 files changed, 3590 insertions, 2432 deletions
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 9412fb122..da6bae4e6 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
@@ -70,6 +70,8 @@ abstract class BaseFileManager extends BaseSubManager {
private HashMap<String, Integer> failedFileUploadsCount;
private final int maxFileUploadAttempts;
private final int maxArtworkUploadAttempts;
+ final String fileManagerCannotOverwriteError = "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.";
+
/**
* Constructor for BaseFileManager
@@ -194,13 +196,7 @@ abstract class BaseFileManager extends BaseSubManager {
}
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() {
+ DeleteFileOperation operation = new DeleteFileOperation(internalInterface, fileName, mutableRemoteFileNames, new FileManagerCompletionListener() {
@Override
public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
if (success) {
@@ -405,46 +401,29 @@ abstract class BaseFileManager extends BaseSubManager {
return;
}
- // 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();
+ final SdlFile fileClone = file.clone();
- SdlFileWrapper fileWrapper = new SdlFileWrapper(file, new FileManagerCompletionListener() {
+ SdlFileWrapper fileWrapper = new SdlFileWrapper(fileClone, 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);
+ BaseFileManager.this.mutableRemoteFileNames.add(fileClone.getName());
+ if (!file.isPersistent()) {
+ BaseFileManager.this.uploadedEphemeralFileNames.add(fileClone.getName());
+ }
} else {
- incrementFailedUploadCountForFileName(file.getName(), BaseFileManager.this.failedFileUploadsCount);
+ incrementFailedUploadCountForFileName(fileClone.getName(), BaseFileManager.this.failedFileUploadsCount);
- int maxUploadCount = file instanceof SdlArtwork ? maxArtworkUploadAttempts : maxFileUploadAttempts;
- if (canFileBeUploadedAgain(file, maxUploadCount, failedFileUploadsCount)) {
+ int maxUploadCount = fileClone instanceof SdlArtwork ? maxArtworkUploadAttempts : maxFileUploadAttempts;
+ if (canFileBeUploadedAgain(fileClone, 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);
+ sdl_uploadFilePrivate(fileClone, listener);
return;
}
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java b/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
index 21df7f7c4..522c5c7e9 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
@@ -38,8 +38,10 @@ 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 com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
+import java.util.Set;
/**
* Created by Bilal Alsharifi on 12/1/20.
@@ -49,12 +51,14 @@ class DeleteFileOperation extends Task {
private final WeakReference<ISdl> internalInterface;
private String fileName;
private FileManagerCompletionListener completionListener;
+ private Set<String> mutableRemoteFileNames;
- DeleteFileOperation(ISdl internalInterface, String fileName, FileManagerCompletionListener completionListener) {
+ DeleteFileOperation(ISdl internalInterface, String fileName, Set<String> mutableRemoteFileNames, FileManagerCompletionListener completionListener) {
super("DeleteFileOperation");
this.internalInterface = new WeakReference<>(internalInterface);
this.fileName = fileName;
this.completionListener = completionListener;
+ this.mutableRemoteFileNames = mutableRemoteFileNames;
}
@Override
@@ -66,6 +70,15 @@ class DeleteFileOperation extends Task {
if (getState() == Task.CANCELED) {
return;
}
+ if (!mutableRemoteFileNames.contains(fileName)) {
+ if (completionListener != null) {
+ String errorMessage = "File to delete is no longer on the head unit, aborting operation";
+ // Returning BaseFileManager.SPACE_AVAILABLE_MAX_VALUE for bytesAvaialble as a placeHolder, it will not get updated in BaseFileManager as long as success returned is false.
+ completionListener.onComplete(false, BaseFileManager.SPACE_AVAILABLE_MAX_VALUE, mutableRemoteFileNames, errorMessage);
+ }
+ onFinished();
+ return;
+ }
deleteFile();
}
@@ -77,12 +90,15 @@ class DeleteFileOperation extends Task {
public void onResponse(int correlationId, RPCResponse response) {
DeleteFileResponse deleteFileResponse = (DeleteFileResponse) response;
boolean success = deleteFileResponse.getSuccess();
+ String errorMessage = success ? null : response.getInfo() + ": " + response.getResultCode();
+ if (errorMessage != null) {
+ DebugTool.logInfo(TAG, "Error deleting file: " + errorMessage);
+ }
// 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);
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java b/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
index 4a9b785c6..aab50a280 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
@@ -44,6 +44,7 @@ import com.smartdevicelink.proxy.rpc.PutFile;
import com.smartdevicelink.proxy.rpc.PutFileResponse;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.Version;
import java.io.IOException;
import java.io.InputStream;
@@ -81,6 +82,27 @@ class UploadFileOperation extends Task {
return;
}
+ SdlFile file = fileWrapper.getFile();
+ // 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
+ if (internalInterface.get() != null && fileManager.get() != null) {
+ Version rpcVersion = new Version(internalInterface.get().getSdlMsgVersion());
+ if (!file.isPersistent() && !fileManager.get().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() && fileManager.get().mutableRemoteFileNames.contains(file.getName())) {
+ DebugTool.logWarning(TAG, fileManager.get().fileManagerCannotOverwriteError);
+ if (this.fileWrapper.getCompletionListener() != null) {
+ this.fileWrapper.getCompletionListener().onComplete(true, bytesAvailable, null, fileManager.get().fileManagerCannotOverwriteError);
+ }
+ onFinished();
+ return;
+ }
+ }
+
int mtuSize = 0;
if (internalInterface.get() != null) {
mtuSize = (int) internalInterface.get().getMtu(SessionType.RPC);
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 9cf72f7cd..6602e7085 100644
--- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
@@ -90,6 +90,7 @@ import com.smartdevicelink.session.ISdlSessionListener;
import com.smartdevicelink.session.SdlSession;
import com.smartdevicelink.streaming.video.VideoStreamingParameters;
import com.smartdevicelink.transport.BaseTransportConfig;
+import com.smartdevicelink.transport.utl.TransportRecord;
import com.smartdevicelink.util.CorrelationIdGenerator;
import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.FileUtls;
@@ -104,7 +105,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, 1, 0);
+ public static final Version MAX_SUPPORTED_RPC_VERSION = new Version(8, 0, 0);
// Protected Correlation IDs
private final int REGISTER_APP_INTERFACE_CORRELATION_ID = 65529,
@@ -398,6 +399,7 @@ abstract class BaseLifecycleManager {
VehicleType vehicleType = raiResponse.getVehicleType();
String systemSoftwareVersion = raiResponse.getSystemSoftwareVersion();
if (vehicleType != null || systemSoftwareVersion != null) {
+ saveVehicleType(session.getActiveTransports(), vehicleType);
SystemInfo systemInfo = new SystemInfo(vehicleType, systemSoftwareVersion, null);
boolean validSystemInfo = lifecycleListener.onSystemInfoReceived(systemInfo);
if (!validSystemInfo) {
@@ -946,6 +948,7 @@ abstract class BaseLifecycleManager {
if (systemInfo != null && lifecycleListener != null) {
didCheckSystemInfo = true;
+ saveVehicleType(session.getActiveTransports(), systemInfo.getVehicleType());
boolean validSystemInfo = lifecycleListener.onSystemInfoReceived(systemInfo);
if (!validSystemInfo) {
DebugTool.logWarning(TAG, "Disconnecting from head unit, the system info was not accepted.");
@@ -1325,6 +1328,12 @@ abstract class BaseLifecycleManager {
abstract void cycle(SdlDisconnectedReason disconnectedReason);
+ void saveVehicleType(String address, VehicleType type){
+ }
+
+ void saveVehicleType(List<TransportRecord> activeTransports, VehicleType type) {
+ }
+
void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig) {
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java
index 54a343043..33a778e58 100644
--- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java
@@ -135,12 +135,15 @@ abstract class BaseSystemCapabilityManager {
// HAX: Issue #1705, Ford Sync bug returning incorrect template name for "NON-MEDIA" (https://github.com/smartdevicelink/sdl_java_suite/issues/1705).
List<String> templatesAvailable = display.getTemplatesAvailable();
- for (int i = 0; i < templatesAvailable.size(); i++) {
- if (templatesAvailable.get(i).equals("NON_MEDIA")) {
- templatesAvailable.set(i, "NON-MEDIA");
- break;
+ if (templatesAvailable != null) {
+ for (int i = 0; i < templatesAvailable.size(); i++) {
+ if ("NON_MEDIA".equals(templatesAvailable.get(i))) {
+ templatesAvailable.set(i, "NON-MEDIA");
+ break;
+ }
}
}
+
// copy all available display capabilities
defaultWindowCapability.setTemplatesAvailable(templatesAvailable);
defaultWindowCapability.setNumCustomPresetsAvailable(display.getNumCustomPresetsAvailable());
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
index 56aec5c19..10db9c54c 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
@@ -182,7 +182,7 @@ abstract class BaseAlertManager extends BaseSubManager {
private Queue newTransactionQueue() {
- Queue queue = internalInterface.getTaskmaster().createQueue("AlertManager", 6, false);
+ Queue queue = internalInterface.getTaskmaster().createQueue("AlertManager", 8, false);
queue.pause();
return queue;
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
index 893909718..0aae71751 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
@@ -42,7 +42,6 @@ import com.livio.taskmaster.Task;
import com.smartdevicelink.managers.BaseSubManager;
import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
-import com.smartdevicelink.managers.ManagerUtility;
import com.smartdevicelink.managers.file.FileManager;
import com.smartdevicelink.managers.lifecycle.OnSystemCapabilityListener;
import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager;
@@ -55,7 +54,6 @@ import com.smartdevicelink.proxy.rpc.KeyboardProperties;
import com.smartdevicelink.proxy.rpc.OnHMIStatus;
import com.smartdevicelink.proxy.rpc.WindowCapability;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
import com.smartdevicelink.proxy.rpc.enums.KeyboardLayout;
import com.smartdevicelink.proxy.rpc.enums.KeypressMode;
@@ -63,14 +61,12 @@ import com.smartdevicelink.proxy.rpc.enums.Language;
import com.smartdevicelink.proxy.rpc.enums.PredefinedWindows;
import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import com.smartdevicelink.proxy.rpc.enums.SystemContext;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
import com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
@@ -94,18 +90,14 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
WindowCapability defaultMainWindowCapability;
String displayName;
SystemContext currentSystemContext;
- HashSet<ChoiceCell> preloadedChoices, pendingPreloadChoices;
- ChoiceSet pendingPresentationSet;
+ HashSet<ChoiceCell> preloadedChoices;
// We will pass operations into this to be completed
final Queue transactionQueue;
- Task pendingPresentOperation;
PresentKeyboardOperation currentlyPresentedKeyboardOperation;
- int nextChoiceId;
int nextCancelId;
- final int choiceCellIdMin = 1;
private final int choiceCellCancelIdMin = 101;
private final int choiceCellCancelIdMax = 200;
boolean isVROptional;
@@ -126,8 +118,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
// setting/instantiating class vars
this.fileManager = new WeakReference<>(fileManager);
preloadedChoices = new HashSet<>();
- pendingPreloadChoices = new HashSet<>();
- nextChoiceId = choiceCellIdMin;
nextCancelId = choiceCellCancelIdMin;
isVROptional = false;
@@ -151,10 +141,8 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
currentSystemContext = null;
defaultMainWindowCapability = null;
- pendingPresentationSet = null;
- pendingPresentOperation = null;
+ preloadedChoices = null;
isVROptional = false;
- nextChoiceId = choiceCellIdMin;
nextCancelId = choiceCellCancelIdMin;
// remove listeners
@@ -191,6 +179,10 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
transactionQueue.add(checkChoiceVR, false);
}
+ interface ChoicesOperationCompletionListener {
+ void onComplete(boolean success, HashSet<ChoiceCell> loadedChoiceCells);
+ }
+
/**
* Preload choices to improve performance while presenting a choice set at a later time
*
@@ -203,12 +195,7 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- LinkedHashSet<ChoiceCell> mutableChoicesToUpload = getChoicesToBeUploadedWithArray(choices);
-
- mutableChoicesToUpload.removeAll(preloadedChoices);
- mutableChoicesToUpload.removeAll(pendingPreloadChoices);
-
- final LinkedHashSet<ChoiceCell> choicesToUpload = (LinkedHashSet<ChoiceCell>) mutableChoicesToUpload.clone();
+ final LinkedHashSet<ChoiceCell> choicesToUpload = new LinkedHashSet<>(choices);
if (choicesToUpload.size() == 0) {
if (listener != null) {
@@ -217,26 +204,17 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- updateIdsOnChoices(choicesToUpload);
-
- // Add the preload cells to the pending preload choices
- pendingPreloadChoices.addAll(choicesToUpload);
-
if (fileManager.get() != null) {
- PreloadChoicesOperation preloadChoicesOperation = new PreloadChoicesOperation(internalInterface, fileManager.get(), displayName, defaultMainWindowCapability, isVROptional, choicesToUpload, new CompletionListener() {
+ PreloadPresentChoicesOperation preloadChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager.get(), displayName, defaultMainWindowCapability, isVROptional, choicesToUpload, this.preloadedChoices, new ChoicesOperationCompletionListener() {
@Override
- public void onComplete(boolean success) {
- if (success) {
- preloadedChoices.addAll(choicesToUpload);
- pendingPreloadChoices.removeAll(choicesToUpload);
- if (listener != null) {
- listener.onComplete(true);
- }
- } else {
- DebugTool.logError(TAG, "There was an error pre loading choice cells");
- if (listener != null) {
- listener.onComplete(false);
+ public void onComplete(boolean success, HashSet<ChoiceCell> loadedCells) {
+ preloadedChoices = loadedCells;
+ updatePendingTasksWithCurrentPreloads();
+ if (listener != null) {
+ if (!success) {
+ DebugTool.logError(TAG, "There was an error pre loading choice cells");
}
+ listener.onComplete(success);
}
}
});
@@ -259,45 +237,15 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- // Find cells to be deleted that are already uploaded or are pending upload
- final HashSet<ChoiceCell> cellsToBeDeleted = choicesToBeDeletedWithArray(choices);
- HashSet<ChoiceCell> cellsToBeRemovedFromPending = choicesToBeRemovedFromPendingWithArray(choices);
- // If choices are deleted that are already uploaded or pending and are used by a pending presentation, cancel it and send an error
- HashSet<ChoiceCell> pendingPresentationChoices = new HashSet<>();
- if (pendingPresentationSet != null && pendingPresentationSet.getChoices() != null) {
- pendingPresentationChoices.addAll(pendingPresentationSet.getChoices());
- }
-
- if (pendingPresentOperation != null && pendingPresentOperation.getState() != Task.CANCELED && pendingPresentOperation.getState() != Task.FINISHED && (cellsToBeDeleted.retainAll(pendingPresentationChoices) || cellsToBeRemovedFromPending.retainAll(pendingPresentationChoices))) {
- pendingPresentOperation.cancelTask();
- DebugTool.logWarning(TAG, "Attempting to delete choice cells while there is a pending presentation operation. Pending presentation cancelled.");
- pendingPresentOperation = null;
- }
-
- // Remove cells from pending and delete choices
- pendingPresentationChoices.removeAll(cellsToBeRemovedFromPending);
- for (Task operation : transactionQueue.getTasksAsList()) {
- if (!(operation instanceof PreloadChoicesOperation)) {
- continue;
- }
- ((PreloadChoicesOperation) operation).removeChoicesFromUpload(cellsToBeRemovedFromPending);
- }
-
- // Find Choices to delete
- if (cellsToBeDeleted.size() == 0) {
- DebugTool.logInfo(TAG, "Cells to be deleted size == 0");
- return;
- }
- findIdsOnChoices(cellsToBeDeleted);
-
- DeleteChoicesOperation deleteChoicesOperation = new DeleteChoicesOperation(internalInterface, cellsToBeDeleted, new CompletionListener() {
+ DeleteChoicesOperation deleteChoicesOperation = new DeleteChoicesOperation(internalInterface, new HashSet<>(choices), preloadedChoices, new ChoicesOperationCompletionListener() {
@Override
- public void onComplete(boolean success) {
+ public void onComplete(boolean success, HashSet<ChoiceCell> updatedLoadedChoiceCells) {
if (!success) {
DebugTool.logError(TAG, "Failed to delete choices");
return;
}
- preloadedChoices.removeAll(cellsToBeDeleted);
+ preloadedChoices = updatedLoadedChoiceCells;
+ updatePendingTasksWithCurrentPreloads();
}
});
transactionQueue.add(deleteChoicesOperation, false);
@@ -322,63 +270,56 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- if (this.pendingPresentationSet != null && pendingPresentOperation != null) {
- pendingPresentOperation.cancelTask();
- DebugTool.logWarning(TAG, "Presenting a choice set while one is currently presented. Cancelling previous and continuing");
- }
-
- this.pendingPresentationSet = choiceSet;
- preloadChoices(this.pendingPresentationSet.getChoices(), new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- if (!success) {
- choiceSet.getChoiceSetSelectionListener().onError("There was an error pre-loading choice set choices");
- } else {
- sendPresentOperation(keyboardListener, mode);
- }
- }
- });
+ sendPresentOperation(choiceSet, keyboardListener, mode);
}
- private void sendPresentOperation(KeyboardListener keyboardListener, InteractionMode mode) {
+ private void sendPresentOperation(final ChoiceSet choiceSet, KeyboardListener keyboardListener, InteractionMode mode) {
if (mode == null) {
mode = InteractionMode.MANUAL_ONLY;
}
- findIdsOnChoiceSet(pendingPresentationSet);
-
// Pass back the information to the developer
- ChoiceSetSelectionListener privateChoiceListener = new ChoiceSetSelectionListener() {
+ ChoiceSetSelectionListener listener = new ChoiceSetSelectionListener() {
@Override
public void onChoiceSelected(ChoiceCell choiceCell, TriggerSource triggerSource, int rowIndex) {
- if (pendingPresentationSet != null && pendingPresentationSet.getChoiceSetSelectionListener() != null) {
- pendingPresentationSet.getChoiceSetSelectionListener().onChoiceSelected(choiceCell, triggerSource, rowIndex);
+ if (choiceSet != null && choiceSet.getChoiceSetSelectionListener() != null) {
+ choiceSet.getChoiceSetSelectionListener().onChoiceSelected(choiceCell, triggerSource, rowIndex);
}
}
@Override
public void onError(String error) {
- if (pendingPresentationSet != null && pendingPresentationSet.getChoiceSetSelectionListener() != null) {
- pendingPresentationSet.getChoiceSetSelectionListener().onError(error);
+ if (choiceSet != null && choiceSet.getChoiceSetSelectionListener() != null) {
+ if (error != null) {
+ choiceSet.getChoiceSetSelectionListener().onError(error);
+ } else if (getState() == SHUTDOWN) {
+ choiceSet.getChoiceSetSelectionListener().onError("Incorrect State");
+ } else {
+ DebugTool.logError(TAG, "Present finished but an unhandled state occurred and callback failed");
+ choiceSet.getChoiceSetSelectionListener().onError("callback failed");
+ }
}
}
};
- PresentChoiceSetOperation presentOp;
+ PreloadPresentChoicesOperation presentOp;
- if (keyboardListener == null) {
- // Non-searchable choice set
- DebugTool.logInfo(TAG, "Creating non-searchable choice set");
- presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, null, null, privateChoiceListener, getNextCancelId());
+ if (fileManager.get() != null) {
+ if (keyboardListener == null) {
+ // Non-searchable choice set
+ DebugTool.logInfo(TAG, "Creating non-searchable choice set");
+ presentOp = new PreloadPresentChoicesOperation(internalInterface, fileManager.get(), choiceSet, mode, null, null, getNextCancelId(), displayName, defaultMainWindowCapability, isVROptional, this.preloadedChoices, null, listener);
+ } else {
+ // Searchable choice set
+ DebugTool.logInfo(TAG, "Creating searchable choice set");
+ presentOp = new PreloadPresentChoicesOperation(internalInterface, this.fileManager.get(), choiceSet, mode, keyboardConfiguration, keyboardListener, getNextCancelId(), displayName, defaultMainWindowCapability, isVROptional, this.preloadedChoices, null, listener);
+ }
+
+ transactionQueue.add(presentOp, false);
} else {
- // Searchable choice set
- DebugTool.logInfo(TAG, "Creating searchable choice set");
- presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, keyboardConfiguration, keyboardListener, privateChoiceListener, getNextCancelId());
+ DebugTool.logError(TAG, "File Manager was null in preload choice operation");
}
-
- transactionQueue.add(presentOp, false);
- pendingPresentOperation = presentOp;
}
/**
@@ -400,12 +341,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return null;
}
- if (pendingPresentationSet != null && pendingPresentOperation != null) {
- pendingPresentOperation.cancelTask();
- pendingPresentationSet = null;
- DebugTool.logWarning(TAG, "There is a current or pending choice set, cancelling and continuing.");
- }
-
customKeyboardConfig = createValidKeyboardConfigurationBasedOnKeyboardCapabilitiesFromConfiguration(customKeyboardConfig);
if (customKeyboardConfig == null) {
if (this.keyboardConfiguration != null) {
@@ -421,7 +356,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
PresentKeyboardOperation keyboardOp = new PresentKeyboardOperation(internalInterface, keyboardConfiguration, initialText, customKeyboardConfig, listener, keyboardCancelID);
currentlyPresentedKeyboardOperation = keyboardOp;
transactionQueue.add(keyboardOp, false);
- pendingPresentOperation = keyboardOp;
return keyboardCancelID;
}
@@ -527,63 +461,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
// CHOICE SET MANAGEMENT HELPERS
- HashSet<ChoiceCell> choicesToBeDeletedWithArray(List<ChoiceCell> choices) {
- HashSet<ChoiceCell> choicesSet = new HashSet<>(choices);
- choicesSet.retainAll(this.preloadedChoices);
- return choicesSet;
- }
-
- HashSet<ChoiceCell> choicesToBeRemovedFromPendingWithArray(List<ChoiceCell> choices) {
- HashSet<ChoiceCell> choicesSet = new HashSet<>(choices);
- choicesSet.retainAll(this.pendingPreloadChoices);
- return choicesSet;
- }
-
- /**
- * Checks if 2 or more cells have the same text/title. In case this condition is true, this function will handle the presented issue by adding "(count)".
- * E.g. Choices param contains 2 cells with text/title "Address" will be handled by updating the uniqueText/uniqueTitle of the second cell to "Address (2)".
- * @param choices The list of choiceCells to be uploaded.
- */
- void addUniqueNamesToCells(List<ChoiceCell> choices) {
- HashMap<String, Integer> dictCounter = new HashMap<>();
-
- for (ChoiceCell cell : choices) {
- String cellName = cell.getText();
- Integer counter = dictCounter.get(cellName);
-
- if (counter != null) {
- dictCounter.put(cellName, ++counter);
- cell.setUniqueText(cell.getText() + " (" + counter + ")");
- } else {
- dictCounter.put(cellName, 1);
- }
- }
- }
-
- void addUniqueNamesBasedOnStrippedCells(List<ChoiceCell> strippedCells, List<ChoiceCell> unstrippedCells) {
- if (strippedCells == null || unstrippedCells == null || strippedCells.size() != unstrippedCells.size()) {
- return;
- }
- // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
- HashMap<ChoiceCell, Integer> dictCounter = new HashMap<>();
- for (int i = 0; i < strippedCells.size(); i++) {
- ChoiceCell cell = strippedCells.get(i);
- Integer counter = dictCounter.get(cell);
- if (counter != null) {
- counter++;
- dictCounter.put(cell, counter);
- } else {
- dictCounter.put(cell, 1);
- }
-
- counter = dictCounter.get(cell);
-
- if (counter > 1) {
- unstrippedCells.get(i).setUniqueText(unstrippedCells.get(i).getText() + " (" + counter + ")");
- }
- }
- }
-
private List<ChoiceCell> cloneChoiceCellList(List<ChoiceCell> originalList) {
if (originalList == null) {
return null;
@@ -596,71 +473,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return clone;
}
- private LinkedHashSet<ChoiceCell> getChoicesToBeUploadedWithArray(List<ChoiceCell> choices) {
- // Clone choices
- List<ChoiceCell> choicesClone = cloneChoiceCellList(choices);
- if (choices != null && internalInterface.getSdlMsgVersion() != null
- && (internalInterface.getSdlMsgVersion().getMajorVersion() < 7
- || (internalInterface.getSdlMsgVersion().getMajorVersion() == 7 && internalInterface.getSdlMsgVersion().getMinorVersion() == 0))) {
- // If we're on < RPC 7.1, all primary texts need to be unique, so we don't need to check removed properties and duplicate cells
- addUniqueNamesToCells(choicesClone);
- } else {
- List<ChoiceCell> strippedCellsClone = removeUnusedProperties(choicesClone);
- addUniqueNamesBasedOnStrippedCells(strippedCellsClone, choicesClone);
- }
- LinkedHashSet<ChoiceCell> choiceCloneLinkedHash = new LinkedHashSet<>(choicesClone);
- choiceCloneLinkedHash.removeAll(preloadedChoices);
- return choiceCloneLinkedHash;
- }
-
- List<ChoiceCell> removeUnusedProperties(List<ChoiceCell> choiceCells) {
- List<ChoiceCell> strippedCellsClone = cloneChoiceCellList(choiceCells);
- //Clone Cells
- for (ChoiceCell cell : strippedCellsClone) {
- // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
- cell.setVoiceCommands(null);
-
- if (!hasImageFieldOfName(ImageFieldName.choiceImage)) {
- cell.setArtwork(null);
- }
- if (!hasTextFieldOfName(TextFieldName.secondaryText)) {
- cell.setSecondaryText(null);
- }
- if (!hasTextFieldOfName(TextFieldName.tertiaryText)) {
- cell.setTertiaryText(null);
- }
- if (!hasImageFieldOfName(ImageFieldName.choiceSecondaryImage)) {
- cell.setSecondaryArtwork(null);
- }
- }
- return strippedCellsClone;
- }
-
- void updateIdsOnChoices(LinkedHashSet<ChoiceCell> choices) {
- for (ChoiceCell cell : choices) {
- cell.setChoiceId(this.nextChoiceId);
- this.nextChoiceId++;
- }
- }
-
- private void findIdsOnChoiceSet(ChoiceSet choiceSet) {
- findIdsOnChoices(new HashSet<>(choiceSet.getChoices()));
- }
-
- private void findIdsOnChoices(HashSet<ChoiceCell> choices) {
- for (ChoiceCell cell : choices) {
- ChoiceCell uploadCell = null;
- if (pendingPreloadChoices.contains(cell)) {
- uploadCell = findIfPresent(cell, pendingPreloadChoices);
- } else if (preloadedChoices.contains(cell)) {
- uploadCell = findIfPresent(cell, preloadedChoices);
- }
- if (uploadCell != null) {
- cell.setChoiceId(uploadCell.getChoiceId());
- }
- }
- }
-
ChoiceCell findIfPresent(ChoiceCell cell, HashSet<ChoiceCell> set) {
if (set.contains(cell)) {
for (ChoiceCell setCell : set) {
@@ -671,6 +483,22 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return null;
}
+ private void updatePendingTasksWithCurrentPreloads() {
+ for (Task task : this.transactionQueue.getTasksAsList()) {
+ if (task.getState() == Task.IN_PROGRESS || task.getState() == Task.CANCELED) {
+ continue;
+ }
+
+ if (task instanceof PreloadPresentChoicesOperation) {
+ PreloadPresentChoicesOperation preloadOp = (PreloadPresentChoicesOperation) task;
+ preloadOp.setLoadedCells(this.preloadedChoices);
+ } else if (task instanceof DeleteChoicesOperation) {
+ DeleteChoicesOperation deleteOp = (DeleteChoicesOperation) task;
+ deleteOp.setLoadedCells(this.preloadedChoices);
+ }
+ }
+ }
+
// LISTENERS
private void addListeners() {
@@ -740,14 +568,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
// ADDITIONAL HELPERS
- private boolean hasImageFieldOfName(ImageFieldName imageFieldName) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName(defaultMainWindowCapability, imageFieldName);
- }
-
- private boolean hasTextFieldOfName(TextFieldName textFieldName) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName(defaultMainWindowCapability, textFieldName);
- }
-
boolean setUpChoiceSet(ChoiceSet choiceSet) {
List<ChoiceCell> choices = choiceSet.getChoices();
@@ -765,8 +585,9 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
}
}
- HashSet<ChoiceCell> uniqueChoiceCells = new HashSet<>();
HashSet<String> uniqueVoiceCommands = new HashSet<>();
+ HashSet<ChoiceCell> uniqueChoiceCells = new HashSet<>();
+
int allVoiceCommandsCount = 0;
int choiceCellWithVoiceCommandCount = 0;
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java
index 7d59bf555..46d478000 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java
@@ -41,10 +41,10 @@ import java.util.ArrayList;
import java.util.List;
public class ChoiceCell implements Cloneable{
- private String text, secondaryText, tertiaryText, uniqueText;
+ private String text, secondaryText, tertiaryText;
private List<String> voiceCommands;
private SdlArtwork artwork, secondaryArtwork;
- private Integer choiceId;
+ private Integer choiceId, uniqueTextId;
/**
* MAX ID for cells - Cannot use Integer.MAX_INT as the value is too high.
@@ -58,7 +58,7 @@ public class ChoiceCell implements Cloneable{
*/
public ChoiceCell(@NonNull String text) {
setText(text);
- setUniqueText(text);
+ setUniqueTextId(1);
setChoiceId(MAX_ID);
}
@@ -71,7 +71,7 @@ public class ChoiceCell implements Cloneable{
*/
public ChoiceCell(@NonNull String text, List<String> voiceCommands, SdlArtwork artwork) {
setText(text);
- setUniqueText(text);
+ setUniqueTextId(1);
setVoiceCommands(voiceCommands);
setArtwork(artwork);
setChoiceId(MAX_ID);
@@ -91,7 +91,7 @@ public class ChoiceCell implements Cloneable{
setText(text);
setSecondaryText(secondaryText);
setTertiaryText(tertiaryText);
- setUniqueText(text);
+ setUniqueTextId(1);
setVoiceCommands(voiceCommands);
setArtwork(artwork);
setSecondaryArtwork(secondaryArtwork);
@@ -238,20 +238,28 @@ public class ChoiceCell implements Cloneable{
* Attempting to use cells that are exactly the same (all text and artwork fields are the same)
* will not cause this to be used. This will not be used when connected to modules supporting RPC 7.1+.
*
- * @param uniqueText - the uniqueText to be used in place of primaryText when core does not support identical names for ChoiceSets
+ * @param uniqueTextId - the uniqueTextId to be appended to primaryText when core does not support identical names for ChoiceSets
*/
- void setUniqueText(String uniqueText) {
- this.uniqueText = uniqueText;
+ void setUniqueTextId(Integer uniqueTextId) {
+ this.uniqueTextId = uniqueTextId;
}
/**
* NOTE: USED INTERNALLY
- * Get the uniqueText that was used in place of primaryText
+ * Get the uniqueTextId that was used to append to primaryText
*
- * @return the uniqueText for this Choice Cell
+ * @return the uniqueTextId for this Choice Cell
*/
+ Integer getUniqueTextId() {
+ return uniqueTextId;
+ }
+
String getUniqueText() {
- return uniqueText;
+ if (this.uniqueTextId != 1) {
+ return this.text + " (" + this.uniqueTextId + ")";
+ } else {
+ return this.text;
+ }
}
@Override
@@ -292,7 +300,7 @@ public class ChoiceCell implements Cloneable{
@NonNull
public String toString() {
return "ChoiceCell: ID: " + this.choiceId + " Text: " + text + " - Secondary Text: " + secondaryText + " - Tertiary Text: " + tertiaryText + " " +
- (text.equals(uniqueText) ? "" : "| Unique Text: " + uniqueText) + " | Artwork Names: " + ((getArtwork() == null || getArtwork().getName() == null) ? "Primary Art null" : getArtwork().getName())
+ (uniqueTextId == 1 ? "" : "| Unique Text: " + uniqueTextId) + " | Artwork Names: " + ((getArtwork() == null || getArtwork().getName() == null) ? "Primary Art null" : getArtwork().getName())
+ " Secondary Art - " + ((getSecondaryArtwork() == null || getSecondaryArtwork().getName() == null) ? "Secondary Art null" : getSecondaryArtwork().getName()) +
" | Voice Commands Size: " + ((getVoiceCommands() == null) ? 0 : getVoiceCommands().size());
}
@@ -315,6 +323,9 @@ public class ChoiceCell implements Cloneable{
if (this.voiceCommands != null) {
clone.voiceCommands = new ArrayList<>(voiceCommands);
}
+ if (this.uniqueTextId != null) {
+ clone.uniqueTextId = this.uniqueTextId;
+ }
return clone;
} catch (CloneNotSupportedException e) {
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java
index 53f209eae..a3814c1d2 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java
@@ -36,7 +36,6 @@
package com.smartdevicelink.managers.screen.choiceset;
import com.livio.taskmaster.Task;
-import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.proxy.RPCResponse;
import com.smartdevicelink.proxy.rpc.DeleteInteractionChoiceSet;
@@ -51,25 +50,37 @@ import java.util.List;
class DeleteChoicesOperation extends Task {
private static final String TAG = "DeleteChoicesOperation";
private final WeakReference<ISdl> internalInterface;
- private final HashSet<ChoiceCell> cellsToDelete;
- private final CompletionListener completionListener;
+ private HashSet<ChoiceCell> cellsToDelete;
+ private final BaseChoiceSetManager.ChoicesOperationCompletionListener completionListener;
+ private HashSet<ChoiceCell> loadedCells;
+ private List<DeleteInteractionChoiceSet> deleteChoices;
+ private boolean completionSuccess = false;
- DeleteChoicesOperation(ISdl internalInterface, HashSet<ChoiceCell> cellsToDelete, CompletionListener completionListener) {
+ DeleteChoicesOperation(ISdl internalInterface, HashSet<ChoiceCell> cellsToDelete, HashSet<ChoiceCell> loadedCells, BaseChoiceSetManager.ChoicesOperationCompletionListener completionListener) {
super("DeleteChoicesOperation");
this.internalInterface = new WeakReference<>(internalInterface);
this.cellsToDelete = cellsToDelete;
this.completionListener = completionListener;
+ this.loadedCells = loadedCells == null ? new HashSet<ChoiceCell>() : new HashSet<>(loadedCells);
}
@Override
public void onExecute() {
DebugTool.logInfo(TAG, "Choice Operation: Executing delete choices operation");
+ updateCellsToDelete();
+ if (this.cellsToDelete == null || this.cellsToDelete.isEmpty()) {
+ if (completionListener != null) {
+ completionListener.onComplete(true, loadedCells);
+ DebugTool.logInfo(TAG, "No cells were provided to delete");
+ }
+ DeleteChoicesOperation.super.onFinished();
+ }
sendDeletions();
}
private void sendDeletions() {
- List<DeleteInteractionChoiceSet> deleteChoices = createDeleteSets();
+ deleteChoices = createDeleteSets();
if (deleteChoices.size() > 0) {
@@ -82,7 +93,7 @@ class DeleteChoicesOperation extends Task {
@Override
public void onFinished() {
if (completionListener != null) {
- completionListener.onComplete(true);
+ completionListener.onComplete(true, loadedCells);
}
DebugTool.logInfo(TAG, "Successfully deleted choices");
@@ -93,21 +104,68 @@ class DeleteChoicesOperation extends Task {
public void onResponse(int correlationId, RPCResponse response) {
if (!response.getSuccess()) {
if (completionListener != null) {
- completionListener.onComplete(false);
+ completionListener.onComplete(false, loadedCells);
}
DebugTool.logError(TAG, "Failed to delete choice: " + response.getInfo() + " | Corr ID: " + correlationId);
DeleteChoicesOperation.super.onFinished();
+ } else {
+ if (loadedCells != null) {
+ loadedCells.remove(loadedCellFromCorrelationId(deleteChoices, correlationId));
+ }
}
}
});
}
} else {
if (completionListener != null) {
- completionListener.onComplete(true);
+ completionListener.onComplete(true, this.loadedCells);
}
DebugTool.logInfo(TAG, "No Choices to delete, continue");
+ DeleteChoicesOperation.super.onFinished();
+ }
+ }
+
+ public void setLoadedCells(HashSet<ChoiceCell> loadedCells) {
+ this.loadedCells = new HashSet<>(loadedCells);
+ }
+
+ private void updateCellsToDelete() {
+ HashSet<ChoiceCell> updatedCellsToDelete = new HashSet<>(this.cellsToDelete);
+ updatedCellsToDelete.retainAll(loadedCells);
+
+ for (ChoiceCell cell : updatedCellsToDelete) {
+ for (ChoiceCell loadedCell : this.loadedCells) {
+ if (loadedCell.equals(cell)) {
+ cell.setChoiceId(loadedCell.getChoiceId());
+ }
+ }
+ }
+
+ this.cellsToDelete = updatedCellsToDelete;
+ }
+
+ private ChoiceCell loadedCellFromCorrelationId(List<DeleteInteractionChoiceSet> deleteRpcs, int correlationId) {
+
+ Integer choiceId = null;
+
+ for (DeleteInteractionChoiceSet rpc : deleteRpcs) {
+ if (rpc.getCorrelationID() == correlationId) {
+ choiceId = rpc.getInteractionChoiceSetID();
+ }
}
+
+ if (choiceId == null) {
+ return null;
+ }
+
+ for (ChoiceCell cell : this.loadedCells) {
+ if (cell.getChoiceId() == choiceId) {
+ return cell;
+ }
+ }
+
+ return null;
}
List<DeleteInteractionChoiceSet> createDeleteSets() {
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
deleted file mode 100644
index 8f47a2880..000000000
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (c) 2019 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.
- *
- * Created by brettywhite on 6/12/19 1:52 PM
- *
- */
-
-package com.smartdevicelink.managers.screen.choiceset;
-
-import androidx.annotation.NonNull;
-
-import com.livio.taskmaster.Task;
-import com.smartdevicelink.managers.CompletionListener;
-import com.smartdevicelink.managers.ISdl;
-import com.smartdevicelink.managers.ManagerUtility;
-import com.smartdevicelink.managers.file.FileManager;
-import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
-import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
-import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.Choice;
-import com.smartdevicelink.proxy.rpc.CreateInteractionChoiceSet;
-import com.smartdevicelink.proxy.rpc.Image;
-import com.smartdevicelink.proxy.rpc.WindowCapability;
-import com.smartdevicelink.proxy.rpc.enums.DisplayType;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
-import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
-import com.smartdevicelink.util.DebugTool;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-
-class PreloadChoicesOperation extends Task {
- private static final String TAG = "PreloadChoicesOperation";
- private final WeakReference<ISdl> internalInterface;
- private final WeakReference<FileManager> fileManager;
- private final WindowCapability defaultMainWindowCapability;
- private final String displayName;
- private final HashSet<ChoiceCell> cellsToUpload;
- private final CompletionListener completionListener;
- private boolean isRunning;
- private final boolean isVROptional;
- private boolean choiceError = false;
-
- PreloadChoicesOperation(ISdl internalInterface, FileManager fileManager, String displayName, WindowCapability defaultMainWindowCapability,
- Boolean isVROptional, LinkedHashSet<ChoiceCell> cellsToPreload, CompletionListener listener) {
- super("PreloadChoicesOperation");
- this.internalInterface = new WeakReference<>(internalInterface);
- this.fileManager = new WeakReference<>(fileManager);
- this.displayName = displayName;
- this.defaultMainWindowCapability = defaultMainWindowCapability;
- this.isVROptional = isVROptional;
- this.cellsToUpload = cellsToPreload;
- this.completionListener = listener;
- }
-
- @Override
- public void onExecute() {
- DebugTool.logInfo(TAG, "Choice Operation: Executing preload choices operation");
- preloadCellArtworks(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- preloadCells();
- }
- });
- }
-
- void removeChoicesFromUpload(HashSet<ChoiceCell> choices) {
- if (isRunning) {
- return;
- }
- cellsToUpload.removeAll(choices);
- }
-
- private void preloadCellArtworks(@NonNull final CompletionListener listener) {
- isRunning = true;
-
- List<SdlArtwork> artworksToUpload = artworksToUpload();
-
- if (artworksToUpload.size() == 0) {
- DebugTool.logInfo(TAG, "Choice Preload: No Choice Artworks to upload");
- listener.onComplete(true);
- isRunning = false;
- return;
- }
-
- if (fileManager.get() != null) {
- fileManager.get().uploadArtworks(artworksToUpload, new MultipleFileCompletionListener() {
- @Override
- public void onComplete(Map<String, String> errors) {
- if (errors != null && errors.size() > 0) {
- DebugTool.logError(TAG, "Error uploading choice cell Artworks: " + errors.toString());
- listener.onComplete(false);
- isRunning = false;
- } else {
- DebugTool.logInfo(TAG, "Choice Artworks Uploaded");
- listener.onComplete(true);
- isRunning = false;
- }
- }
- });
- } else {
- DebugTool.logError(TAG, "File manager is null in choice preload operation");
- listener.onComplete(false);
- isRunning = false;
- }
- }
-
- private void preloadCells() {
- isRunning = true;
- List<CreateInteractionChoiceSet> choiceRPCs = new ArrayList<>(cellsToUpload.size());
- for (ChoiceCell cell : cellsToUpload) {
- CreateInteractionChoiceSet csCell = choiceFromCell(cell);
- if (csCell != null) {
- choiceRPCs.add(csCell);
- }
- }
-
- if (choiceRPCs.size() == 0) {
- DebugTool.logError(TAG, " All Choice cells to send are null, so the choice set will not be shown");
- completionListener.onComplete(true);
- isRunning = false;
- return;
- }
-
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPCs(choiceRPCs, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
-
- }
-
- @Override
- public void onFinished() {
- isRunning = false;
- DebugTool.logInfo(TAG, "Finished pre loading choice cells");
- completionListener.onComplete(!choiceError);
- choiceError = false;
- PreloadChoicesOperation.super.onFinished();
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (!response.getSuccess()) {
- DebugTool.logError(TAG, "There was an error uploading a choice cell: " + response.getInfo() + " resultCode: " + response.getResultCode());
- choiceError = true;
- }
- }
- });
- } else {
- DebugTool.logError(TAG, "Internal Interface null in preload choice operation");
- isRunning = false;
- completionListener.onComplete(false);
- }
- }
-
- private CreateInteractionChoiceSet choiceFromCell(ChoiceCell cell) {
-
- List<String> vrCommands;
- if (cell.getVoiceCommands() == null) {
- vrCommands = isVROptional ? null : Collections.singletonList(String.valueOf(cell.getChoiceId()));
- } else {
- vrCommands = cell.getVoiceCommands();
- }
-
- String menuName = shouldSendChoiceText() ? cell.getUniqueText() : null;
-
- if (menuName == null) {
- DebugTool.logError(TAG, "Could not convert Choice Cell to CreateInteractionChoiceSet. It will not be shown. Cell: " + cell.toString());
- return null;
- }
-
- String secondaryText = shouldSendChoiceSecondaryText() ? cell.getSecondaryText() : null;
- String tertiaryText = shouldSendChoiceTertiaryText() ? cell.getTertiaryText() : null;
-
- Image image = shouldSendChoicePrimaryImage() && cell.getArtwork() != null ? cell.getArtwork().getImageRPC() : null;
- Image secondaryImage = shouldSendChoiceSecondaryImage() && cell.getSecondaryArtwork() != null ? cell.getSecondaryArtwork().getImageRPC() : null;
-
- Choice choice = new Choice(cell.getChoiceId(), menuName);
- choice.setVrCommands(vrCommands);
- choice.setSecondaryText(secondaryText);
- choice.setTertiaryText(tertiaryText);
- choice.setIgnoreAddingVRItems(true);
-
- if (fileManager.get() != null) {
- if (image != null && (cell.getArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getArtwork()))) {
- choice.setImage(image);
- }
- if (secondaryImage != null && (cell.getSecondaryArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getSecondaryArtwork()))) {
- choice.setSecondaryImage(secondaryImage);
- }
- }
-
- return new CreateInteractionChoiceSet(choice.getChoiceID(), Collections.singletonList(choice));
- }
-
- // HELPERS
- boolean shouldSendChoiceText() {
- if (this.displayName != null && this.displayName.equals(DisplayType.GEN3_8_INCH.toString())) {
- return true;
- }
- return templateSupportsTextField(TextFieldName.menuName);
- }
-
- boolean shouldSendChoiceSecondaryText() {
- return templateSupportsTextField(TextFieldName.secondaryText);
- }
-
- boolean shouldSendChoiceTertiaryText() {
- return templateSupportsTextField(TextFieldName.tertiaryText);
- }
-
- boolean shouldSendChoicePrimaryImage() {
- return templateSupportsImageField(ImageFieldName.choiceImage);
- }
-
- boolean shouldSendChoiceSecondaryImage() {
- return templateSupportsImageField(ImageFieldName.choiceSecondaryImage);
- }
-
- boolean templateSupportsTextField(TextFieldName name) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName(defaultMainWindowCapability, name);
- }
-
- boolean templateSupportsImageField(ImageFieldName name) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName(defaultMainWindowCapability, name);
- }
-
- List<SdlArtwork> artworksToUpload() {
- List<SdlArtwork> artworksToUpload = new ArrayList<>();
- for (ChoiceCell cell : cellsToUpload) {
- if (shouldSendChoicePrimaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getArtwork())) {
- artworksToUpload.add(cell.getArtwork());
- }
- if (shouldSendChoiceSecondaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
- artworksToUpload.add(cell.getSecondaryArtwork());
- }
- }
- return artworksToUpload;
- }
-} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java
new file mode 100644
index 000000000..8c6ebca6f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java
@@ -0,0 +1,927 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * Created by rhenigan on 8/9/21 1:52 PM
+ *
+ */
+
+package com.smartdevicelink.managers.screen.choiceset;
+
+import androidx.annotation.NonNull;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.ISdl;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCNotification;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.CancelInteraction;
+import com.smartdevicelink.proxy.rpc.Choice;
+import com.smartdevicelink.proxy.rpc.CreateInteractionChoiceSet;
+import com.smartdevicelink.proxy.rpc.Image;
+import com.smartdevicelink.proxy.rpc.KeyboardProperties;
+import com.smartdevicelink.proxy.rpc.OnKeyboardInput;
+import com.smartdevicelink.proxy.rpc.PerformInteraction;
+import com.smartdevicelink.proxy.rpc.PerformInteractionResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.DisplayType;
+import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
+import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
+import com.smartdevicelink.proxy.rpc.enums.KeyboardEvent;
+import com.smartdevicelink.proxy.rpc.enums.LayoutMode;
+import com.smartdevicelink.proxy.rpc.enums.Result;
+import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
+import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+class PreloadPresentChoicesOperation extends Task {
+
+ private static final String TAG = "PreloadPresentChoicesOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private final WeakReference<FileManager> fileManager;
+ private final WindowCapability defaultMainWindowCapability;
+ private final String displayName;
+ private final ArrayList<ChoiceCell> cellsToUpload;
+ private final BaseChoiceSetManager.ChoicesOperationCompletionListener completionListener;
+ private final ChoiceSetSelectionListener selectionListener;
+ private final boolean isVROptional;
+ private boolean choiceError = false;
+ private HashSet<ChoiceCell> loadedCells;
+ private final ChoiceSet choiceSet;
+ // Start choiceId at 1 to ensure all HMIs handle it https://github.com/smartdevicelink/generic_hmi/commit/b292fbbec095b9ce11b520d47ec95b6fcff8e247
+ private static Integer choiceId = 1;
+ private static Boolean reachedMaxIds = false;
+ private static final int MAX_CHOICE_ID = 65535;
+ private final Integer cancelID;
+ private final InteractionMode presentationMode;
+ private final KeyboardProperties originalKeyboardProperties;
+ private KeyboardProperties keyboardProperties;
+ private ChoiceCell selectedCell;
+ private TriggerSource selectedTriggerSource;
+ private boolean updatedKeyboardProperties;
+ private OnRPCNotificationListener keyboardRPCListener;
+ Integer selectedCellRow;
+ KeyboardListener keyboardListener;
+ final SdlMsgVersion sdlMsgVersion;
+ private enum SDLPreloadPresentChoicesOperationState {
+ NOT_STARTED,
+ UPLOADING_IMAGES,
+ UPLOADING_CHOICES,
+ UPDATING_KEYBOARD_PROPERTIES,
+ PRESENTING_CHOICES,
+ CANCELLING_PRESENT_CHOICES,
+ RESETTING_KEYBOARD_PROPERTIES,
+ FINISHING
+ }
+ private SDLPreloadPresentChoicesOperationState currentState;
+
+ PreloadPresentChoicesOperation(ISdl internalInterface, FileManager fileManager, String displayName, WindowCapability defaultWindowCapability,
+ Boolean isVROptional, LinkedHashSet<ChoiceCell> cellsToPreload, HashSet<ChoiceCell> loadedCells, BaseChoiceSetManager.ChoicesOperationCompletionListener listener) {
+ super("PreloadPresentChoiceOperation");
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ this.displayName = displayName;
+ this.defaultMainWindowCapability = defaultWindowCapability;
+ this.isVROptional = isVROptional;
+ this.cellsToUpload = new ArrayList<>(cellsToPreload);
+ this.completionListener = listener;
+ this.keyboardListener = null;
+ this.choiceSet = null;
+ this.presentationMode = null;
+ this.cancelID = null;
+ this.originalKeyboardProperties = null;
+ this.keyboardProperties = null;
+ this.selectedCellRow = null;
+ this.sdlMsgVersion = internalInterface.getSdlMsgVersion();
+ this.loadedCells = loadedCells;
+ this.currentState = SDLPreloadPresentChoicesOperationState.NOT_STARTED;
+ this.selectionListener = null;
+ }
+
+ PreloadPresentChoicesOperation(ISdl internalInterface, FileManager fileManager, ChoiceSet choiceSet, InteractionMode mode,
+ KeyboardProperties originalKeyboardProperties, KeyboardListener keyboardListener, Integer cancelID, String displayName, WindowCapability windowCapability,
+ Boolean isVROptional, HashSet<ChoiceCell> loadedCells, BaseChoiceSetManager.ChoicesOperationCompletionListener preloadListener, ChoiceSetSelectionListener listener) {
+ super("PreloadPresentChoiceOperation");
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.keyboardListener = keyboardListener;
+ this.choiceSet = choiceSet;
+ this.choiceSet.canceledListener = new ChoiceSetCanceledListener() {
+ @Override
+ public void onChoiceSetCanceled() {
+ cancelInteraction();
+ }
+ };
+ this.presentationMode = mode;
+ this.cancelID = cancelID;
+ this.originalKeyboardProperties = originalKeyboardProperties;
+ this.keyboardProperties = originalKeyboardProperties;
+ this.selectedCellRow = null;
+ this.sdlMsgVersion = internalInterface.getSdlMsgVersion();
+ this.fileManager = new WeakReference<>(fileManager);
+ this.displayName = displayName;
+ this.defaultMainWindowCapability = windowCapability;
+ this.isVROptional = isVROptional;
+ this.cellsToUpload = new ArrayList<>(choiceSet.getChoices());
+ this.completionListener = preloadListener;
+ this.selectionListener = listener;
+ this.loadedCells = loadedCells;
+ this.currentState = SDLPreloadPresentChoicesOperationState.NOT_STARTED;
+ }
+
+ @Override
+ public void onExecute() {
+ if (this.getState() == CANCELED) {
+ return;
+ }
+
+ if (this.loadedCells == null || this.loadedCells.isEmpty()) {
+ choiceId = 1;
+ reachedMaxIds = false;
+ }
+
+ DebugTool.logInfo(TAG, "Choice Operation: Executing preload choices operation");
+ // Enforce unique cells and remove cells that are already loaded
+ this.cellsToUpload.removeAll(loadedCells);
+
+ if ((this.loadedCells.size() == MAX_CHOICE_ID) && this.cellsToUpload.size() > 0) {
+ DebugTool.logError(TAG, "Choice Cells to upload exceed maximum");
+ finishOperation(false);
+ }
+
+ assignIdsToCells(this.cellsToUpload);
+ makeCellsToUploadUnique(this.cellsToUpload);
+
+ if (this.choiceSet != null) {
+ updateChoiceSet(this.choiceSet, this.loadedCells, new HashSet<>(this.cellsToUpload));
+ }
+ // Start uploading cell artworks, then cells themselves, then determine if we want to present, then update keyboard properties if necessary, then present the choice set, then revert keyboard properties if necessary
+ preloadCellArtworks(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ // If some artworks failed to upload, we are still going to try to load the cells
+ if (getState()==CANCELED || !success) {
+ finishOperation(false);
+ return;
+ }
+ preloadCells(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState()==CANCELED || !success) {
+ finishOperation(false);
+ return;
+ }
+
+ if (choiceSet == null) {
+ finishOperation(true);
+ return;
+ }
+ DebugTool.logInfo(TAG, "Choice Operation: Executing present choice set operation");
+ updateKeyboardProperties(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState()==CANCELED || !success) {
+ finishOperation(false);
+ return;
+ }
+ presentChoiceSet(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ resetKeyboardProperties(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ finishOperation(success);
+ return;
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ // Preload operation methods
+ private void preloadCellArtworks(@NonNull final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.UPLOADING_IMAGES;
+
+ List<SdlArtwork> artworksToUpload = new ArrayList<>(artworksToUpload());
+
+ if (artworksToUpload.size() == 0) {
+ DebugTool.logInfo(TAG, "Choice Preload: No Choice Artworks to upload");
+ listener.onComplete(true);
+ return;
+ }
+
+ if (fileManager.get() != null) {
+ fileManager.get().uploadArtworks(artworksToUpload, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(Map<String, String> errors) {
+ if (errors != null && errors.size() > 0) {
+ DebugTool.logError(TAG, "Error uploading choice cell Artworks: " + errors.toString());
+ listener.onComplete(false);
+ } else {
+ DebugTool.logInfo(TAG, "Choice Artworks Uploaded");
+ listener.onComplete(true);
+ }
+ }
+ });
+ } else {
+ DebugTool.logError(TAG, "File manager is null in choice preload operation");
+ listener.onComplete(false);
+ }
+ }
+
+ private void preloadCells(@NonNull final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.UPLOADING_CHOICES;
+
+ final List<CreateInteractionChoiceSet> choiceRPCs = new ArrayList<>(cellsToUpload.size());
+ for (ChoiceCell cell : cellsToUpload) {
+ CreateInteractionChoiceSet csCell = choiceFromCell(cell);
+ if (csCell != null) {
+ choiceRPCs.add(csCell);
+ }
+ }
+
+ if (choiceRPCs.size() == 0) {
+ if (choiceSet == null) {
+ DebugTool.logError(TAG, " All Choice cells to send are null, so the choice set will not be shown");
+ }
+ listener.onComplete(true);
+ return;
+ }
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPCs(choiceRPCs, new OnMultipleRequestListener() {
+ @Override
+ public void onUpdate(int remainingRequests) {
+
+ }
+
+ @Override
+ public void onFinished() {
+ DebugTool.logInfo(TAG, "Finished pre loading choice cells");
+ listener.onComplete(!choiceError);
+ choiceError = false;
+ }
+
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (!response.getSuccess()) {
+ DebugTool.logError(TAG, "There was an error uploading a choice cell: " + response.getInfo() + " resultCode: " + response.getResultCode());
+ choiceError = true;
+ } else {
+ for (CreateInteractionChoiceSet rpc : choiceRPCs) {
+ if (correlationId == rpc.getCorrelationID()) {
+ loadedCells.add(cellFromChoiceId(rpc.getChoiceSet().get(0).getChoiceID()));
+ }
+ }
+ }
+ }
+ });
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null in preload choice operation");
+ listener.onComplete(!choiceError);
+ }
+ }
+
+ private void updateKeyboardProperties(final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.UPDATING_KEYBOARD_PROPERTIES;
+ if (keyboardListener == null) {
+ if (listener != null) {
+ listener.onComplete(true);
+ }
+ return;
+ }
+
+ addListeners();
+
+ if (keyboardListener != null && choiceSet.getCustomKeyboardConfiguration() != null) {
+ keyboardProperties = choiceSet.getCustomKeyboardConfiguration();
+ updatedKeyboardProperties = true;
+ }
+
+ SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
+ setGlobalProperties.setKeyboardProperties(keyboardProperties);
+ setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+
+ if (!response.getSuccess()) {
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ DebugTool.logError(TAG, "Error Setting keyboard properties in present choice set operation");
+ return;
+ }
+
+ updatedKeyboardProperties = true;
+
+ if (listener != null) {
+ listener.onComplete(true);
+ }
+ DebugTool.logInfo(TAG, "Success Setting keyboard properties in present choice set operation");
+ }
+ });
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setGlobalProperties);
+ } else {
+ DebugTool.logError(TAG, "Internal interface null - present choice set op - choice");
+ listener.onComplete(false);
+ }
+ }
+
+ void resetKeyboardProperties(final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.RESETTING_KEYBOARD_PROPERTIES;
+ if (this.keyboardListener == null || this.originalKeyboardProperties == null) {
+ if(listener != null) {
+ listener.onComplete(true);
+ finishOperation(true);
+ return;
+ }
+ }
+ SetGlobalProperties setProperties = new SetGlobalProperties();
+ setProperties.setKeyboardProperties(this.originalKeyboardProperties);
+ setProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ updatedKeyboardProperties = false;
+ DebugTool.logInfo(TAG, "Successfully reset choice keyboard properties to original config");
+ } else {
+ DebugTool.logError(TAG, "Failed to reset choice keyboard properties to original config " + response.getResultCode() + ", " + response.getInfo());
+ }
+ if (listener != null) {
+ listener.onComplete(response.getSuccess());
+ if (response.getSuccess()) {
+ finishOperation(true);
+ }
+ }
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setProperties);
+ internalInterface.get().removeOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null when finishing choice keyboard reset");
+ listener.onComplete(false);
+ }
+ }
+
+ private void presentChoiceSet(final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.PRESENTING_CHOICES;
+ PerformInteraction pi = getPerformInteraction();
+ pi.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (!response.getSuccess()) {
+ DebugTool.logError(TAG, "Presenting Choice set failed: " + response.getInfo());
+
+ if (selectionListener != null) {
+ selectionListener.onError(response.getInfo());
+ }
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ finishOperation(false);
+ return;
+ }
+
+ PerformInteractionResponse performInteractionResponse = (PerformInteractionResponse) response;
+ setSelectedCellWithId(performInteractionResponse.getChoiceID());
+ selectedTriggerSource = performInteractionResponse.getTriggerSource();
+
+ if (selectionListener != null && selectedCell != null && selectedTriggerSource != null && selectedCellRow != null) {
+ selectionListener.onChoiceSelected(selectedCell, selectedTriggerSource, selectedCellRow);
+ if (listener != null) {
+ listener.onComplete(true);
+ }
+ } else {
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ }
+ }
+ });
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(pi);
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null when presenting choice set in operation");
+ if (selectionListener != null) {
+ selectionListener.onError("Internal Interface null");
+ }
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ }
+ }
+
+ private void cancelInteraction() {
+ if ((getState() == Task.FINISHED)) {
+ DebugTool.logInfo(TAG, "This operation has already finished so it can not be canceled.");
+ return;
+ } else if (getState() == Task.CANCELED) {
+ DebugTool.logInfo(TAG, "This operation has already been canceled. It will be finished at some point during the operation.");
+ return;
+ } else if ((getState() == Task.IN_PROGRESS)) {
+ if (this.currentState != SDLPreloadPresentChoicesOperationState.PRESENTING_CHOICES) {
+ DebugTool.logInfo(TAG, "Canceling the operation before a present.");
+ this.cancelTask();
+ return;
+ }else if (sdlMsgVersion.getMajorVersion() < 6) {
+ DebugTool.logWarning(TAG, "Canceling a presented choice set is not supported on this head unit");
+ this.cancelTask();
+ return;
+ }
+
+ DebugTool.logInfo(TAG, "Canceling the presented choice set interaction.");
+
+ CancelInteraction cancelInteraction = new CancelInteraction(FunctionID.PERFORM_INTERACTION.getId(), cancelID);
+ cancelInteraction.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ DebugTool.logInfo(TAG, "Canceled the presented choice set " + ((response.getResultCode() == Result.SUCCESS) ? "successfully" : "unsuccessfully"));
+ } else {
+ DebugTool.logError(TAG, "Error canceling the presented choice set: " + correlationId + " with error: " + response.getInfo());
+ }
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(cancelInteraction);
+ } else {
+ DebugTool.logError(TAG, "Internal interface null - could not send cancel interaction for choice set");
+ }
+ } else {
+ DebugTool.logInfo(TAG, "Canceling a choice set that has not yet been sent to Core");
+ this.cancelTask();
+ }
+ }
+
+ // Present Helpers
+ void setSelectedCellWithId(Integer cellId) {
+ if (choiceSet.getChoices() != null && cellId != null) {
+ List<ChoiceCell> cells = choiceSet.getChoices();
+ for (int i = 0; i < cells.size(); i++) {
+ if (cells.get(i).getChoiceId() == cellId) {
+ selectedCell = cells.get(i);
+ selectedCellRow = i;
+ return;
+ }
+ }
+ }
+ }
+
+ PerformInteraction getPerformInteraction() {
+ if (this.choiceSet == null) {
+ return new PerformInteraction();
+ }
+ PerformInteraction pi = new PerformInteraction(choiceSet.getTitle(), presentationMode, getChoiceIds());
+ pi.setInitialPrompt(choiceSet.getInitialPrompt());
+ pi.setHelpPrompt(choiceSet.getHelpPrompt());
+ pi.setTimeoutPrompt(choiceSet.getTimeoutPrompt());
+ pi.setVrHelp(choiceSet.getVrHelpList());
+ pi.setTimeout(choiceSet.getTimeout() * 1000);
+ pi.setInteractionLayout(getLayoutMode());
+ pi.setCancelID(cancelID);
+ return pi;
+ }
+
+ private void assignIdsToCells(ArrayList<ChoiceCell> cells) {
+ ArrayList<Integer> usedIds = new ArrayList<>();
+ for (ChoiceCell cell : loadedCells) {
+ usedIds.add(cell.getChoiceId());
+ }
+ Collections.sort(usedIds);
+ ArrayList<Integer> sortedUsedIds = (ArrayList<Integer>) usedIds.clone();
+
+ // Loop through the cells we need ids for. Get and assign those ids
+ for (int i = 0; i < cells.size(); i++) {
+ int cellId = nextChoiceIdBasedOnUsedIds(sortedUsedIds);
+ cells.get(i).setChoiceId(cellId);
+
+ // Insert the ids into the usedIds sorted array in the correct position
+ for (int j = 0; j < sortedUsedIds.size(); j++) {
+ if (sortedUsedIds.get(j) > cellId) {
+ sortedUsedIds.add(j, cellId);
+ break;
+ } else if (j == (sortedUsedIds.size() - 1)) {
+ sortedUsedIds.add(cellId);
+ break;
+ }
+ }
+ }
+ }
+
+ // Find the next available choice is. Takes into account the loaded cells to ensure there are not duplicates
+ // @param usedIds The already loaded cell ids
+ // @return The choice id between 0 - 65535, or Not Found if no cell ids were available
+ private int nextChoiceIdBasedOnUsedIds(ArrayList<Integer> usedIds) {
+ // Check if we are entirely full, or if we've advanced beyond the max value, loop back
+ if (choiceId == MAX_CHOICE_ID) {
+ choiceId = 1;
+ reachedMaxIds = true;
+ }
+
+ if (reachedMaxIds) {
+ // We've looped all the way around, so we need to check loaded cells
+ // Sort the set of cells by the choice id so that we can more easily check which slots are available
+ if (usedIds.size() >= (MAX_CHOICE_ID + 1)) {
+ //If we've maxed out our slots, return the max value
+ choiceId = MAX_CHOICE_ID;
+ return choiceId;
+ }
+
+ // If the last value isn't the max value, just keep grabbing towards the max value
+ int lastUsedId = usedIds.get(usedIds.size() - 1);
+ if (lastUsedId < MAX_CHOICE_ID) {
+ choiceId = lastUsedId + 1;
+ return choiceId;
+ }
+
+ // All our easy options are gone. Find and grab an empty slot from within the sorted list
+ for (int i = 0; i < usedIds.size(); i++) {
+ int loopId = usedIds.get(i);
+ if (i != loopId) {
+ // This slot is open because the cell id does not match an open sorted slot
+ choiceId = i;
+ return choiceId;
+ }
+ }
+
+ // This *shouldn't* be possible
+ choiceId = MAX_CHOICE_ID;
+ return choiceId;
+ } else {
+ // We haven't looped all the way around yet, so we'll just take the current number, then advance the item
+ return choiceId++;
+ }
+ }
+
+ // Choice Uniqueness
+ void makeCellsToUploadUnique(ArrayList<ChoiceCell> cellsToUpload) {
+ if (cellsToUpload.size() == 0) {
+ return;
+ }
+
+ ArrayList<ChoiceCell> strippedCellsToUpload = cloneChoiceCellList(cellsToUpload);
+ ArrayList<ChoiceCell> strippedLoadedCells = cloneChoiceCellList(new ArrayList<>(loadedCells));
+ boolean supportsChoiceUniqueness = !(sdlMsgVersion.getMajorVersion() < 7 || (sdlMsgVersion.getMajorVersion() == 7 && sdlMsgVersion.getMinorVersion() == 0));
+ if (supportsChoiceUniqueness) {
+ removeUnusedProperties(strippedCellsToUpload);
+ removeUnusedProperties(strippedLoadedCells);
+ }
+
+ addUniqueNamesToCells(strippedCellsToUpload, strippedLoadedCells, supportsChoiceUniqueness);
+ transferUniqueNamesFromCells(strippedCellsToUpload, cellsToUpload);
+ }
+
+ private void updateChoiceSet(ChoiceSet choiceSet, HashSet<ChoiceCell> loadedCells, HashSet<ChoiceCell> cellsToUpload) {
+ ArrayList<ChoiceCell> choiceSetCells = new ArrayList<>();
+ ArrayList<ChoiceCell> loadedCellsList = new ArrayList<>(loadedCells);
+ ArrayList<ChoiceCell> CellsToUploadList = new ArrayList<>(cellsToUpload);
+ for (ChoiceCell cell : choiceSet.getChoices()) {
+ if (loadedCells.contains(cell)) {
+ choiceSetCells.add(loadedCellsList.get(loadedCellsList.indexOf(cell)));
+ } else if (cellsToUpload.contains(cell)) {
+ choiceSetCells.add(CellsToUploadList.get(CellsToUploadList.indexOf(cell)));
+ }
+ }
+ this.choiceSet.setChoices((List<ChoiceCell>) choiceSetCells.clone());
+ }
+
+ private void transferUniqueNamesFromCells(ArrayList<ChoiceCell> fromCells, ArrayList<ChoiceCell> toCells) {
+ for (int i = 0; i< fromCells.size(); i++) {
+ toCells.get(i).setUniqueTextId(fromCells.get(i).getUniqueTextId());
+ }
+ }
+
+ void removeUnusedProperties(List<ChoiceCell> choiceCells) {
+ for (ChoiceCell cell : choiceCells) {
+ // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
+ cell.setVoiceCommands(null);
+
+ if (!shouldSendChoicePrimaryImage()) {
+ cell.setArtwork(null);
+ }
+ if (!shouldSendChoiceSecondaryText()) {
+ cell.setSecondaryText(null);
+ }
+ if (!shouldSendChoiceTertiaryText()) {
+ cell.setTertiaryText(null);
+ }
+ if (!shouldSendChoiceSecondaryImage()) {
+ cell.setSecondaryArtwork(null);
+ }
+ }
+ }
+
+ private ArrayList<ChoiceCell> cloneChoiceCellList(List<ChoiceCell> originalList) {
+ if (originalList == null) {
+ return null;
+ }
+ ArrayList<ChoiceCell> clone = new ArrayList<>();
+ for (ChoiceCell choiceCell : originalList) {
+ clone.add(choiceCell.clone());
+ }
+ return clone;
+ }
+
+ /**
+ * Checks if 2 or more cells have the same text/title. In case this condition is true, this function will handle the presented issue by adding "(count)".
+ * E.g. Choices param contains 2 cells with text/title "Address" will be handled by updating the uniqueText/uniqueTitle of the second cell to "Address (2)".
+ */
+ void addUniqueNamesToCells(List<ChoiceCell> cellsToUpload, List<ChoiceCell> loadedCells, boolean supportsChoiceUniqueness) {
+ HashMap<Object, ArrayList<Integer>> dictCounter = new HashMap<>();
+
+ for (ChoiceCell loadedCell : loadedCells) {
+ Object cellKey = supportsChoiceUniqueness ? loadedCell : loadedCell.getText();
+ int cellUniqueId = loadedCell.getUniqueTextId();
+ if (dictCounter.get(cellKey) == null) {
+ ArrayList<Integer> uniqueIds = new ArrayList<>();
+ uniqueIds.add(cellUniqueId);
+ dictCounter.put(cellKey, uniqueIds);
+ } else {
+ dictCounter.get(cellKey).add(cellUniqueId);
+ }
+ }
+
+ for (ChoiceCell cell : cellsToUpload) {
+ Object cellKey = supportsChoiceUniqueness ? cell : cell.getText();
+ if (dictCounter.get(cellKey) == null) {
+ ArrayList<Integer> uniqueTextIds = new ArrayList<>();
+ uniqueTextIds.add(cell.getUniqueTextId());
+ dictCounter.put(cellKey, uniqueTextIds);
+ } else {
+ ArrayList<Integer> uniqueIds = dictCounter.get(cellKey);
+ Integer lowestMissingUniqueId = uniqueIds.get(uniqueIds.size() - 1) + 1;
+ for (int i = 1; i < dictCounter.get(cellKey).size() + 1; i++) {
+ if (i != dictCounter.get(cellKey).get(i -1)) {
+ lowestMissingUniqueId = i;
+ break;
+ }
+ }
+
+ cell.setUniqueTextId(lowestMissingUniqueId);
+ dictCounter.get(cellKey).add(cell.getUniqueTextId() - 1, cell.getUniqueTextId());
+ }
+ }
+ }
+
+ // Finding Cells
+ private ChoiceCell cellFromChoiceId(int choiceId) {
+ for (ChoiceCell cell : this.cellsToUpload) {
+ if (cell.getChoiceId() == choiceId) {
+ return cell;
+ }
+ }
+ return null;
+ }
+
+ // Assembling Choice RPCs
+ private CreateInteractionChoiceSet choiceFromCell(ChoiceCell cell) {
+
+ List<String> vrCommands;
+ if (cell.getVoiceCommands() == null) {
+ vrCommands = isVROptional ? null : Collections.singletonList(String.valueOf(cell.getChoiceId()));
+ } else {
+ vrCommands = cell.getVoiceCommands();
+ }
+
+ String menuName = shouldSendChoiceText() ? cell.getUniqueText() : null;
+
+ if (menuName == null) {
+ DebugTool.logError(TAG, "Could not convert Choice Cell to CreateInteractionChoiceSet. It will not be shown. Cell: " + cell.toString());
+ return null;
+ }
+
+ String secondaryText = shouldSendChoiceSecondaryText() ? cell.getSecondaryText() : null;
+ String tertiaryText = shouldSendChoiceTertiaryText() ? cell.getTertiaryText() : null;
+
+ Image image = shouldSendChoicePrimaryImage() && cell.getArtwork() != null ? cell.getArtwork().getImageRPC() : null;
+ Image secondaryImage = shouldSendChoiceSecondaryImage() && cell.getSecondaryArtwork() != null ? cell.getSecondaryArtwork().getImageRPC() : null;
+
+ Choice choice = new Choice(cell.getChoiceId(), menuName);
+ choice.setVrCommands(vrCommands);
+ choice.setSecondaryText(secondaryText);
+ choice.setTertiaryText(tertiaryText);
+ choice.setIgnoreAddingVRItems(true);
+
+ if (fileManager.get() != null) {
+ if (image != null && (cell.getArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getArtwork()))) {
+ choice.setImage(image);
+ }
+ if (secondaryImage != null && (cell.getSecondaryArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getSecondaryArtwork()))) {
+ choice.setSecondaryImage(secondaryImage);
+ }
+ }
+
+ return new CreateInteractionChoiceSet(choice.getChoiceID(), Collections.singletonList(choice));
+ }
+
+ boolean shouldSendChoiceText() {
+ if (this.displayName != null && this.displayName.equals(DisplayType.GEN3_8_INCH.toString())) {
+ return true;
+ }
+ return defaultMainWindowCapability == null || hasTextFieldOfName(defaultMainWindowCapability, TextFieldName.menuName);
+ }
+
+ boolean shouldSendChoiceSecondaryText() {
+ return defaultMainWindowCapability == null || hasTextFieldOfName(defaultMainWindowCapability, TextFieldName.secondaryText);
+ }
+
+ boolean shouldSendChoiceTertiaryText() {
+ return defaultMainWindowCapability == null || hasTextFieldOfName(defaultMainWindowCapability, TextFieldName.tertiaryText);
+ }
+
+ boolean shouldSendChoicePrimaryImage() {
+ return defaultMainWindowCapability == null || hasImageFieldOfName(defaultMainWindowCapability, ImageFieldName.choiceImage);
+ }
+
+ boolean shouldSendChoiceSecondaryImage() {
+ return defaultMainWindowCapability == null || hasImageFieldOfName(defaultMainWindowCapability, ImageFieldName.choiceSecondaryImage);
+ }
+
+ // SDL Notifications
+ private void addListeners() {
+
+ keyboardRPCListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+ if (getState() == Task.CANCELED) {
+ finishOperation(false);
+ return;
+ }
+
+ if (keyboardListener == null) {
+ DebugTool.logError(TAG, "Received Keyboard Input But Listener is null");
+ return;
+ }
+
+ OnKeyboardInput onKeyboard = (OnKeyboardInput) notification;
+ keyboardListener.onKeyboardDidSendEvent(onKeyboard.getEvent(), onKeyboard.getData());
+
+ if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_VOICE) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_SUBMITTED)) {
+ // Submit Voice or Text
+ keyboardListener.onUserDidSubmitInput(onKeyboard.getData(), onKeyboard.getEvent());
+ } else if (onKeyboard.getEvent().equals(KeyboardEvent.KEYPRESS)) {
+ // Notify of Keypress
+ keyboardListener.updateAutocompleteWithInput(onKeyboard.getData(), new KeyboardAutocompleteCompletionListener() {
+ @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);
+ }
+ });
+
+ keyboardListener.updateCharacterSetWithInput(onKeyboard.getData(), new KeyboardCharacterSetCompletionListener() {
+ @Override
+ public void onUpdatedCharacterSet(List<String> updatedCharacterSet) {
+ keyboardProperties.setLimitedCharacterList(updatedCharacterSet);
+ updateKeyboardProperties(null);
+ }
+ });
+ } else if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_ABORTED) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_CANCELLED)) {
+ // Notify of abort / Cancellation
+ keyboardListener.onKeyboardDidAbortWithReason(onKeyboard.getEvent());
+ } else if (onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_ENABLED) || onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_DISABLED)) {
+ keyboardListener.onKeyboardDidUpdateInputMask(onKeyboard.getEvent());
+ }
+ }
+ };
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().addOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
+ } else {
+ DebugTool.logError(TAG, "Present Choice Set Keyboard Listener Not Added");
+ }
+ }
+
+ public void setLoadedCells(HashSet<ChoiceCell> loadedCells) {
+ this.loadedCells = loadedCells;
+ }
+
+ HashSet<SdlArtwork> artworksToUpload() {
+ HashSet<SdlArtwork> artworksToUpload = new HashSet<>();
+ for (ChoiceCell cell : cellsToUpload) {
+ if (shouldSendChoicePrimaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getArtwork())) {
+ artworksToUpload.add(cell.getArtwork());
+ }
+ if (shouldSendChoiceSecondaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
+ artworksToUpload.add(cell.getSecondaryArtwork());
+ }
+ }
+ return artworksToUpload;
+ }
+
+ LayoutMode getLayoutMode() {
+ switch (choiceSet.getLayout()) {
+ case CHOICE_SET_LAYOUT_LIST:
+ return keyboardListener != null ? LayoutMode.LIST_WITH_SEARCH : LayoutMode.LIST_ONLY;
+ case CHOICE_SET_LAYOUT_TILES:
+ return keyboardListener != null ? LayoutMode.ICON_WITH_SEARCH : LayoutMode.ICON_ONLY;
+ }
+ return LayoutMode.LIST_ONLY; // default
+ }
+
+ private List<Integer> getChoiceIds() {
+
+ List<Integer> choiceIds = new ArrayList<>(choiceSet.getChoices().size());
+ for (ChoiceCell cell : choiceSet.getChoices()) {
+ choiceIds.add(cell.getChoiceId());
+ }
+ return choiceIds;
+ }
+
+ void finishOperation(boolean success) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.FINISHING;
+
+ if (this.completionListener != null) {
+ this.completionListener.onComplete(success, loadedCells);
+ }
+
+ if (this.choiceSet == null || this.choiceSet.getChoiceSetSelectionListener() == null) {
+ DebugTool.logWarning(TAG, "");
+ }
+
+
+ if (updatedKeyboardProperties) {
+ // We need to reset the keyboard properties
+ SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
+ setGlobalProperties.setKeyboardProperties(originalKeyboardProperties);
+ setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ updatedKeyboardProperties = false;
+ DebugTool.logInfo(TAG, "Successfully reset choice keyboard properties to original config");
+ } else {
+ DebugTool.logError(TAG, "Failed to reset choice keyboard properties to original config " + response.getResultCode() + ", " + response.getInfo());
+ }
+ onFinished();
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setGlobalProperties);
+ internalInterface.get().removeOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null when finishing choice keyboard reset");
+ }
+ } else {
+ onFinished();
+ }
+ }
+}
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
deleted file mode 100644
index b5d3945d1..000000000
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright (c) 2019 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.
- *
- * Created by brettywhite on 6/12/19 1:52 PM
- *
- */
-
-package com.smartdevicelink.managers.screen.choiceset;
-
-import com.livio.taskmaster.Task;
-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.CancelInteraction;
-import com.smartdevicelink.proxy.rpc.KeyboardProperties;
-import com.smartdevicelink.proxy.rpc.OnKeyboardInput;
-import com.smartdevicelink.proxy.rpc.PerformInteraction;
-import com.smartdevicelink.proxy.rpc.PerformInteractionResponse;
-import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
-import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
-import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
-import com.smartdevicelink.proxy.rpc.enums.KeyboardEvent;
-import com.smartdevicelink.proxy.rpc.enums.LayoutMode;
-import com.smartdevicelink.proxy.rpc.enums.Result;
-import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
-import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
-import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
-import com.smartdevicelink.util.DebugTool;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-class PresentChoiceSetOperation extends Task {
- private static final String TAG = "PresentChoiceSetOperation";
- private final WeakReference<ISdl> internalInterface;
- private final ChoiceSet choiceSet;
- private final Integer cancelID;
- private final InteractionMode presentationMode;
- private final KeyboardProperties originalKeyboardProperties;
- private KeyboardProperties keyboardProperties;
- private ChoiceCell selectedCell;
- private TriggerSource selectedTriggerSource;
- private boolean updatedKeyboardProperties;
- private OnRPCNotificationListener keyboardRPCListener;
- private final ChoiceSetSelectionListener choiceSetSelectionListener;
- Integer selectedCellRow;
- KeyboardListener keyboardListener;
- final SdlMsgVersion sdlMsgVersion;
-
- PresentChoiceSetOperation(ISdl internalInterface, ChoiceSet choiceSet, InteractionMode mode,
- KeyboardProperties originalKeyboardProperties, KeyboardListener keyboardListener, ChoiceSetSelectionListener choiceSetSelectionListener, Integer cancelID) {
- super("PresentChoiceSetOperation");
- this.internalInterface = new WeakReference<>(internalInterface);
- this.keyboardListener = keyboardListener;
- this.choiceSet = choiceSet;
- this.choiceSet.canceledListener = new ChoiceSetCanceledListener() {
- @Override
- public void onChoiceSetCanceled() {
- cancelInteraction();
- }
- };
- this.presentationMode = mode;
- this.cancelID = cancelID;
- this.originalKeyboardProperties = originalKeyboardProperties;
- this.keyboardProperties = originalKeyboardProperties;
- this.selectedCellRow = null;
- this.choiceSetSelectionListener = choiceSetSelectionListener;
- this.sdlMsgVersion = internalInterface.getSdlMsgVersion();
- }
-
- @Override
- public void onExecute() {
- DebugTool.logInfo(TAG, "Choice Operation: Executing present choice set operation");
- addListeners();
- start();
- }
-
- private void start() {
- if (getState() == Task.CANCELED) {
- finishOperation();
- return;
- }
-
- // Check if we're using a keyboard (searchable) choice set and setup keyboard properties if we need to
- if (keyboardListener != null && choiceSet.getCustomKeyboardConfiguration() != null) {
- keyboardProperties = choiceSet.getCustomKeyboardConfiguration();
- updatedKeyboardProperties = true;
- }
-
- updateKeyboardProperties(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- if (getState() == Task.CANCELED) {
- finishOperation();
- return;
- }
- presentChoiceSet();
- }
- });
- }
-
- // SENDING REQUESTS
-
- private void updateKeyboardProperties(final CompletionListener listener) {
- if (keyboardProperties == null) {
- if (listener != null) {
- listener.onComplete(false);
- }
- return;
- }
- SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
- setGlobalProperties.setKeyboardProperties(keyboardProperties);
- setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
-
- if (!response.getSuccess()) {
- if (listener != null) {
- listener.onComplete(false);
- }
- DebugTool.logError(TAG, "Error Setting keyboard properties in present choice set operation");
- return;
- }
-
- updatedKeyboardProperties = true;
-
- if (listener != null) {
- listener.onComplete(true);
- }
- DebugTool.logInfo(TAG, "Success Setting keyboard properties in present choice set operation");
- }
- });
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(setGlobalProperties);
- } else {
- DebugTool.logError(TAG, "Internal interface null - present choice set op - choice");
- }
- }
-
- private void presentChoiceSet() {
- PerformInteraction pi = getPerformInteraction();
- pi.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (!response.getSuccess()) {
- DebugTool.logError(TAG, "Presenting Choice set failed: " + response.getInfo());
-
- if (choiceSetSelectionListener != null) {
- choiceSetSelectionListener.onError(response.getInfo());
- }
- finishOperation();
- return;
- }
-
- PerformInteractionResponse performInteractionResponse = (PerformInteractionResponse) response;
- setSelectedCellWithId(performInteractionResponse.getChoiceID());
- selectedTriggerSource = performInteractionResponse.getTriggerSource();
-
- if (choiceSetSelectionListener != null && selectedCell != null && selectedTriggerSource != null && selectedCellRow != null) {
- choiceSetSelectionListener.onChoiceSelected(selectedCell, selectedTriggerSource, selectedCellRow);
- }
-
- finishOperation();
- }
- });
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(pi);
- } else {
- DebugTool.logError(TAG, "Internal Interface null when presenting choice set in operation");
- }
- }
-
- void finishOperation() {
- if (updatedKeyboardProperties) {
- // We need to reset the keyboard properties
- SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
- setGlobalProperties.setKeyboardProperties(originalKeyboardProperties);
- setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- updatedKeyboardProperties = false;
- DebugTool.logInfo(TAG, "Successfully reset choice keyboard properties to original config");
- } else {
- DebugTool.logError(TAG, "Failed to reset choice keyboard properties to original config " + response.getResultCode() + ", " + response.getInfo());
- }
- PresentChoiceSetOperation.super.onFinished();
- }
- });
-
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(setGlobalProperties);
- internalInterface.get().removeOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
- } else {
- DebugTool.logError(TAG, "Internal Interface null when finishing choice keyboard reset");
- }
- } else {
- PresentChoiceSetOperation.super.onFinished();
- }
- }
-
- /*
- * Cancels the choice set. If the choice set has not yet been sent to Core, it will not be sent. If the choice set is already presented on Core, the choice set will be dismissed using the `CancelInteraction` RPC.
- */
- private void cancelInteraction() {
- if ((getState() == Task.FINISHED)) {
- DebugTool.logInfo(TAG, "This operation has already finished so it can not be canceled.");
- return;
- } else if (getState() == Task.CANCELED) {
- DebugTool.logInfo(TAG, "This operation has already been canceled. It will be finished at some point during the operation.");
- return;
- } else if ((getState() == Task.IN_PROGRESS)) {
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Canceling a presented choice set is not supported on this head unit");
- return;
- }
-
- DebugTool.logInfo(TAG, "Canceling the presented choice set interaction.");
-
- CancelInteraction cancelInteraction = new CancelInteraction(FunctionID.PERFORM_INTERACTION.getId(), cancelID);
- cancelInteraction.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- DebugTool.logInfo(TAG, "Canceled the presented choice set " + ((response.getResultCode() == Result.SUCCESS) ? "successfully" : "unsuccessfully"));
- }
- });
-
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(cancelInteraction);
- } else {
- DebugTool.logError(TAG, "Internal interface null - could not send cancel interaction for choice set");
- }
- } else {
- DebugTool.logInfo(TAG, "Canceling a choice set that has not yet been sent to Core");
- this.cancelTask();
- }
- }
-
- // GETTERS
-
- PerformInteraction getPerformInteraction() {
- PerformInteraction pi = new PerformInteraction(choiceSet.getTitle(), presentationMode, getChoiceIds());
- pi.setInitialPrompt(choiceSet.getInitialPrompt());
- pi.setHelpPrompt(choiceSet.getHelpPrompt());
- pi.setTimeoutPrompt(choiceSet.getTimeoutPrompt());
- pi.setVrHelp(choiceSet.getVrHelpList());
- pi.setTimeout(choiceSet.getTimeout() * 1000);
- pi.setInteractionLayout(getLayoutMode());
- pi.setCancelID(cancelID);
- return pi;
- }
-
- LayoutMode getLayoutMode() {
- switch (choiceSet.getLayout()) {
- case CHOICE_SET_LAYOUT_LIST:
- return keyboardListener != null ? LayoutMode.LIST_WITH_SEARCH : LayoutMode.LIST_ONLY;
- case CHOICE_SET_LAYOUT_TILES:
- return keyboardListener != null ? LayoutMode.ICON_WITH_SEARCH : LayoutMode.ICON_ONLY;
- }
- return LayoutMode.LIST_ONLY; // default
- }
-
- private List<Integer> getChoiceIds() {
-
- List<Integer> choiceIds = new ArrayList<>(choiceSet.getChoices().size());
- for (ChoiceCell cell : choiceSet.getChoices()) {
- choiceIds.add(cell.getChoiceId());
- }
- return choiceIds;
- }
-
- // HELPERS
-
- void setSelectedCellWithId(Integer cellId) {
- if (choiceSet.getChoices() != null && cellId != null) {
- List<ChoiceCell> cells = choiceSet.getChoices();
- for (int i = 0; i < cells.size(); i++) {
- if (cells.get(i).getChoiceId() == cellId) {
- selectedCell = cells.get(i);
- selectedCellRow = i;
- return;
- }
- }
- }
- }
-
- // LISTENERS
-
- private void addListeners() {
-
- keyboardRPCListener = new OnRPCNotificationListener() {
- @Override
- public void onNotified(RPCNotification notification) {
- if (getState() == Task.CANCELED) {
- finishOperation();
- return;
- }
-
- if (keyboardListener == null) {
- DebugTool.logError(TAG, "Received Keyboard Input But Listener is null");
- return;
- }
-
- OnKeyboardInput onKeyboard = (OnKeyboardInput) notification;
- keyboardListener.onKeyboardDidSendEvent(onKeyboard.getEvent(), onKeyboard.getData());
-
- if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_VOICE) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_SUBMITTED)) {
- // Submit Voice or Text
- keyboardListener.onUserDidSubmitInput(onKeyboard.getData(), onKeyboard.getEvent());
- } else if (onKeyboard.getEvent().equals(KeyboardEvent.KEYPRESS)) {
- // Notify of Keypress
- keyboardListener.updateAutocompleteWithInput(onKeyboard.getData(), new KeyboardAutocompleteCompletionListener() {
- @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);
- }
- });
-
- keyboardListener.updateCharacterSetWithInput(onKeyboard.getData(), new KeyboardCharacterSetCompletionListener() {
- @Override
- public void onUpdatedCharacterSet(List<String> updatedCharacterSet) {
- keyboardProperties.setLimitedCharacterList(updatedCharacterSet);
- updateKeyboardProperties(null);
- }
- });
- } else if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_ABORTED) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_CANCELLED)) {
- // Notify of abort / Cancellation
- keyboardListener.onKeyboardDidAbortWithReason(onKeyboard.getEvent());
- } else if (onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_ENABLED) || onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_DISABLED)) {
- keyboardListener.onKeyboardDidUpdateInputMask(onKeyboard.getEvent());
- }
- }
- };
-
- if (internalInterface.get() != null) {
- internalInterface.get().addOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
- } else {
- DebugTool.logError(TAG, "Present Choice Set Keyboard Listener Not Added");
- }
- }
-} \ No newline at end of file
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 d19bae619..78d7a96e8 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
@@ -32,96 +32,67 @@
package com.smartdevicelink.managers.screen.menu;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.cloneMenuCellsList;
+
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.managers.ManagerUtility;
import com.smartdevicelink.managers.file.FileManager;
-import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
-import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
import com.smartdevicelink.managers.lifecycle.OnSystemCapabilityListener;
import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager;
import com.smartdevicelink.protocol.enums.FunctionID;
import com.smartdevicelink.proxy.RPCNotification;
-import com.smartdevicelink.proxy.RPCRequest;
-import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.AddCommand;
-import com.smartdevicelink.proxy.rpc.AddSubMenu;
-import com.smartdevicelink.proxy.rpc.DeleteCommand;
-import com.smartdevicelink.proxy.rpc.DeleteSubMenu;
import com.smartdevicelink.proxy.rpc.DisplayCapability;
-import com.smartdevicelink.proxy.rpc.MenuParams;
import com.smartdevicelink.proxy.rpc.OnCommand;
import com.smartdevicelink.proxy.rpc.OnHMIStatus;
-import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
-import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
-import com.smartdevicelink.proxy.rpc.ShowAppMenu;
import com.smartdevicelink.proxy.rpc.WindowCapability;
import com.smartdevicelink.proxy.rpc.enums.DisplayType;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
import com.smartdevicelink.proxy.rpc.enums.PredefinedWindows;
import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import com.smartdevicelink.proxy.rpc.enums.SystemContext;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
-import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
-import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
import com.smartdevicelink.util.DebugTool;
-import org.json.JSONException;
-
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
abstract class BaseMenuManager extends BaseSubManager {
private static final String TAG = "BaseMenuManager";
- private static final int KEEP = 0;
- private static final int MARKED_FOR_ADDITION = 1;
- private static final int MARKED_FOR_DELETION = 2;
-
- private final WeakReference<FileManager> fileManager;
+ static final int parentIdNotFound = 2000000000;
- List<MenuCell> menuCells, waitingUpdateMenuCells, oldMenuCells, keepsNew, keepsOld;
- List<RPCRequest> inProgressUpdate;
+ final WeakReference<FileManager> fileManager;
+ List<MenuCell> currentMenuCells;
+ List<MenuCell> menuCells;
DynamicMenuUpdatesMode dynamicMenuUpdatesMode;
+ MenuConfiguration currentMenuConfiguration;
MenuConfiguration menuConfiguration;
- SdlMsgVersion sdlMsgVersion;
private String displayType;
-
- boolean waitingOnHMIUpdate;
- private boolean hasQueuedUpdate;
HMILevel currentHMILevel;
-
- OnRPCNotificationListener hmiListener, commandListener;
- OnSystemCapabilityListener onDisplaysCapabilityListener;
- WindowCapability defaultMainWindowCapability;
-
- private static final int MAX_ID = 2000000000;
- private static final int parentIdNotFound = MAX_ID;
- private static final int menuCellIdMin = 1;
- int lastMenuId;
-
SystemContext currentSystemContext;
+ OnRPCNotificationListener hmiListener;
+ OnRPCNotificationListener commandListener;
+ OnSystemCapabilityListener onDisplaysCapabilityListener;
+ WindowCapability windowCapability;
+ Queue transactionQueue;
BaseMenuManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) {
-
super(internalInterface);
- // Set up some Vars
+ this.menuConfiguration = new MenuConfiguration(null, null);
+ this.menuCells = new ArrayList<>();
+ this.currentMenuCells = new ArrayList<>();
+ this.dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE;
+ this.transactionQueue = newTransactionQueue();
this.fileManager = new WeakReference<>(fileManager);
- currentSystemContext = SystemContext.SYSCTXT_MAIN;
- currentHMILevel = HMILevel.HMI_NONE;
- lastMenuId = menuCellIdMin;
- dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE;
- sdlMsgVersion = internalInterface.getSdlMsgVersion();
-
+ this.currentSystemContext = SystemContext.SYSCTXT_MAIN;
+ this.currentHMILevel = HMILevel.HMI_NONE;
addListeners();
}
@@ -133,24 +104,22 @@ abstract class BaseMenuManager extends BaseSubManager {
@Override
public void dispose() {
-
- lastMenuId = menuCellIdMin;
- menuCells = null;
- oldMenuCells = null;
- currentHMILevel = null;
+ menuCells = new ArrayList<>();
+ currentMenuCells = new ArrayList<>();
+ currentHMILevel = HMILevel.HMI_NONE;
currentSystemContext = SystemContext.SYSCTXT_MAIN;
dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE;
- defaultMainWindowCapability = null;
- inProgressUpdate = null;
- hasQueuedUpdate = false;
- waitingOnHMIUpdate = false;
- waitingUpdateMenuCells = null;
- keepsNew = null;
- keepsOld = null;
+ windowCapability = null;
menuConfiguration = null;
- sdlMsgVersion = null;
+ currentMenuConfiguration = null;
- // remove listeners
+ // Cancel the operations
+ if (transactionQueue != null) {
+ transactionQueue.close();
+ transactionQueue = null;
+ }
+
+ // Remove listeners
internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
internalInterface.removeOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener);
if (internalInterface.getSystemCapabilityManager() != null) {
@@ -160,103 +129,25 @@ abstract class BaseMenuManager extends BaseSubManager {
super.dispose();
}
- // SETTERS
-
- public void setDynamicUpdatesMode(@NonNull DynamicMenuUpdatesMode value) {
- this.dynamicMenuUpdatesMode = value;
+ private Queue newTransactionQueue() {
+ Queue queue = internalInterface.getTaskmaster().createQueue("MenuManager", 7, false);
+ queue.pause();
+ return queue;
}
- /**
- * Creates and sends all associated Menu RPCs
- *
- * @param cells - the menu cells that are to be sent to the head unit, including their sub-cells.
- */
- public void setMenuCells(@NonNull List<MenuCell> cells) {
-
- // Create a deep copy of the list so future changes by developers don't affect the algorithm logic
- List<MenuCell> clonedCells = cloneMenuCellsList(cells);
-
- // Check for cell lists with completely duplicate information, or any duplicate voiceCommands and return if it fails (logs are in the called method).
- if (clonedCells != null && !menuCellsAreUnique(clonedCells, new ArrayList<String>())) {
- return;
- }
-
- if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE) || currentSystemContext.equals(SystemContext.SYSCTXT_MENU)) {
- // We are in NONE or the menu is in use, bail out of here
- waitingOnHMIUpdate = true;
- waitingUpdateMenuCells = new ArrayList<>();
- if (clonedCells != null && !clonedCells.isEmpty()) {
- waitingUpdateMenuCells.addAll(clonedCells);
- }
- return;
- }
- waitingOnHMIUpdate = false;
-
- // If we're running on a connection < RPC 7.1, we need to de-duplicate cells because presenting them will fail if we have the same cell primary text.
- if (clonedCells != null && internalInterface.getSdlMsgVersion() != null
- && (internalInterface.getSdlMsgVersion().getMajorVersion() < 7
- || (internalInterface.getSdlMsgVersion().getMajorVersion() == 7 && internalInterface.getSdlMsgVersion().getMinorVersion() == 0))) {
- addUniqueNamesToCellsWithDuplicatePrimaryText(clonedCells);
- } else {
- // On > RPC 7.1, at this point all cells are unique when considering all properties,
- // but we also need to check if any cells will _appear_ as duplicates when displayed on the screen.
- // To check that, we'll remove properties from the set cells based on the system capabilities
- // (we probably don't need to consider them changing between now and when they're actually sent to the HU unless the menu layout changes)
- // and check for uniqueness again. Then we'll add unique identifiers to primary text if there are duplicates.
- // Then we transfer the primary text identifiers back to the main cells and add those to an operation to be sent.
- List<MenuCell> strippedCellsClone = removeUnusedProperties(clonedCells);
- addUniqueNamesBasedOnStrippedCells(strippedCellsClone, clonedCells);
-
- }
-
- // Update our Lists
- // set old list
- if (menuCells != null) {
- oldMenuCells = new ArrayList<>(menuCells);
- }
- // copy new list
- menuCells = new ArrayList<>();
- if (clonedCells != null && !clonedCells.isEmpty()) {
- menuCells.addAll(clonedCells);
- }
-
- // Upload the Artworks
- List<SdlArtwork> artworksToBeUploaded = findAllArtworksToBeUploadedFromCells(menuCells);
- if (artworksToBeUploaded.size() > 0 && fileManager.get() != null) {
- fileManager.get().uploadArtworks(artworksToBeUploaded, new MultipleFileCompletionListener() {
- @Override
- public void onComplete(Map<String, String> errors) {
-
- if (errors != null && errors.size() > 0) {
- DebugTool.logError(TAG, "Error uploading Menu Artworks: " + errors.toString());
- } else {
- DebugTool.logInfo(TAG, "Menu Artworks Uploaded");
- }
- // proceed
- updateMenuAndDetermineBestUpdateMethod();
- }
- });
+ // Suspend the queue if the HMI level is NONE since we want to delay sending RPCs until we're in non-NONE
+ private void updateTransactionQueueSuspended() {
+ if (currentHMILevel == HMILevel.HMI_NONE || currentSystemContext == SystemContext.SYSCTXT_MENU) {
+ DebugTool.logInfo(TAG, String.format("Suspending the transaction queue. Current HMI level is: %s, current system context is: %s", currentHMILevel, currentSystemContext));
+ transactionQueue.pause();
} else {
- // No Artworks to be uploaded, send off
- updateMenuAndDetermineBestUpdateMethod();
+ DebugTool.logInfo(TAG, "Starting the transaction queue");
+ transactionQueue.resume();
}
}
- /**
- * Returns current list of menu cells
- *
- * @return a List of Currently set menu cells
- */
- public List<MenuCell> getMenuCells() {
-
- if (menuCells != null) {
- return menuCells;
- } else if (waitingOnHMIUpdate && waitingUpdateMenuCells != null) {
- // this will keep from returning null if the menu list is set and we are pending HMI FULL
- return waitingUpdateMenuCells;
- } else {
- return null;
- }
+ public void setDynamicUpdatesMode(@NonNull DynamicMenuUpdatesMode value) {
+ this.dynamicMenuUpdatesMode = value;
}
/**
@@ -266,866 +157,167 @@ abstract class BaseMenuManager extends BaseSubManager {
return this.dynamicMenuUpdatesMode;
}
- // OPEN MENU RPCs
-
/**
- * Opens the Main Menu
- */
- public boolean openMenu() {
-
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Menu opening is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
- return false;
- }
-
- ShowAppMenu showAppMenu = new ShowAppMenu();
- showAppMenu.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- DebugTool.logInfo(TAG, "Open Main Menu Request Successful");
- } else {
- DebugTool.logError(TAG, "Open Main Menu Request Failed");
- }
- }
- });
- internalInterface.sendRPC(showAppMenu);
- return true;
- }
-
- /**
- * Opens a subMenu. The cell you pass in must be constructed with {@link MenuCell(String,SdlArtwork,List)}
- *
- * @param cell - A <Strong>SubMenu</Strong> cell whose sub menu you wish to open
- */
- public boolean openSubMenu(@NonNull MenuCell cell) {
-
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Sub menu opening is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
- return false;
- }
-
- if (oldMenuCells == null) {
- DebugTool.logError(TAG, "open sub menu called, but no Menu cells have been set");
- return false;
- }
- // We must see if we have a copy of this cell, since we clone the objects
- for (MenuCell clonedCell : oldMenuCells) {
- if (clonedCell.equals(cell) && clonedCell.getCellId() != MAX_ID) {
- // We've found the correct sub menu cell
- sendOpenSubMenu(clonedCell.getCellId());
- return true;
- }
- }
- return false;
- }
-
- private void sendOpenSubMenu(Integer id) {
-
- ShowAppMenu showAppMenu = new ShowAppMenu();
- showAppMenu.setMenuID(id);
- showAppMenu.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- DebugTool.logInfo(TAG, "Open Sub Menu Request Successful");
- } else {
- DebugTool.logError(TAG, "Open Sub Menu Request Failed");
- }
- }
- });
-
- internalInterface.sendRPC(showAppMenu);
- }
-
- // MENU CONFIG
-
- /**
- * This method is called via the screen manager to set the menuConfiguration.
- * This will be used when a menu item with sub-cells has a null value for menuConfiguration
+ * Creates and sends all associated Menu RPCs
*
- * @param menuConfiguration - The default menuConfiguration
+ * @param cells - the menu cells that are to be sent to the head unit, including their sub-cells.
*/
- public void setMenuConfiguration(@NonNull final MenuConfiguration menuConfiguration) {
-
- if (sdlMsgVersion == null) {
- DebugTool.logError(TAG, "SDL Message Version is null. Cannot set Menu Configuration");
- return;
- }
-
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Menu configurations is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
- return;
- }
-
- if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE) || currentSystemContext.equals(SystemContext.SYSCTXT_MENU)) {
- // We are in NONE or the menu is in use, bail out of here
- DebugTool.logError(TAG, "Could not set main menu configuration, HMI level: " + currentHMILevel + ", required: 'Not-NONE', system context: " + currentSystemContext + ", required: 'Not MENU'");
- return;
- }
-
- // In the future, when the manager is switched to use queues, the menuConfiguration should be set when SetGlobalProperties response is received
- this.menuConfiguration = menuConfiguration;
-
- if (menuConfiguration.getMenuLayout() != null) {
-
- SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
- setGlobalProperties.setMenuLayout(menuConfiguration.getMenuLayout());
- setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- DebugTool.logInfo(TAG, "Menu Configuration successfully set: " + menuConfiguration.toString());
- } else {
- DebugTool.logError(TAG, "onError: " + response.getResultCode() + " | Info: " + response.getInfo());
- }
- }
- });
- internalInterface.sendRPC(setGlobalProperties);
- } else {
- DebugTool.logInfo(TAG, "Menu Layout is null, not sending setGlobalProperties");
- }
- }
-
- public MenuConfiguration getMenuConfiguration() {
- return this.menuConfiguration;
- }
- // UPDATING SYSTEM
-
- // ROOT MENU
-
- private void updateMenuAndDetermineBestUpdateMethod() {
-
- if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE) || currentSystemContext.equals(SystemContext.SYSCTXT_MENU)) {
- // We are in NONE or the menu is in use, bail out of here
- DebugTool.logInfo(TAG, "HMI in None or System Context Menu, returning");
- waitingOnHMIUpdate = true;
- waitingUpdateMenuCells = menuCells;
- return;
- }
-
- if (inProgressUpdate != null && inProgressUpdate.size() > 0) {
- // there's an in-progress update so this needs to wait
- DebugTool.logInfo(TAG, "There is an in progress Menu Update, returning");
- hasQueuedUpdate = true;
- return;
- }
-
- // Checks against what the developer set for update mode and against the display type
- // to determine how the menu will be updated. This has the ability to be changed during
- // a session.
- if (checkUpdateMode(dynamicMenuUpdatesMode, displayType)) {
- // run the lists through the new algorithm
- RunScore rootScore = runMenuCompareAlgorithm(oldMenuCells, menuCells);
- if (rootScore == null) {
- // send initial menu (score will return null)
- // make a copy of our current cells
- DebugTool.logInfo(TAG, "Creating initial Menu");
- // Set the IDs if needed
- lastMenuId = menuCellIdMin;
- updateIdsOnMenuCells(menuCells, parentIdNotFound);
- this.oldMenuCells = new ArrayList<>(menuCells);
- createAndSendEntireMenu();
- } else {
- DebugTool.logInfo(TAG, "Dynamically Updating Menu");
- if (menuCells.size() == 0 && (oldMenuCells != null && oldMenuCells.size() > 0)) {
- // the dev wants to clear the menu. We have old cells and an empty array of new ones.
- deleteMenuWhenNewCellsEmpty();
- } else {
- // lets dynamically update the root menu
- dynamicallyUpdateRootMenu(rootScore);
- }
- }
- } else {
- // we are in compatibility mode
- DebugTool.logInfo(TAG, "Updating menus in compatibility mode");
- lastMenuId = menuCellIdMin;
- updateIdsOnMenuCells(menuCells, parentIdNotFound);
- // if the old cell array is not null, we want to delete the entire thing, else copy the new array
- if (oldMenuCells == null) {
- this.oldMenuCells = new ArrayList<>(menuCells);
- }
- createAndSendEntireMenu();
- }
- }
-
- private boolean checkUpdateMode(DynamicMenuUpdatesMode updateMode, String displayType) {
-
- if (updateMode.equals(DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE)) {
- if (displayType == null) {
- return true;
- }
- return (!displayType.equals(DisplayType.GEN3_8_INCH.toString()));
-
- } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_OFF)) {
- return false;
- } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_ON)) {
- return true;
- }
-
- return true;
- }
-
- private void deleteMenuWhenNewCellsEmpty() {
- sendDeleteRPCs(createDeleteRPCsForCells(oldMenuCells), new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- inProgressUpdate = null;
-
- if (!success) {
- DebugTool.logError(TAG, "Error Sending Current Menu");
- } else {
- DebugTool.logInfo(TAG, "Successfully Cleared Menu");
- }
- oldMenuCells = null;
- if (hasQueuedUpdate) {
- hasQueuedUpdate = false;
- }
- }
- });
- }
-
- private void dynamicallyUpdateRootMenu(RunScore bestRootScore) {
-
- // we need to run through the keeps and see if they have subCells, as they also need to be run
- // through the compare function.
- List<Integer> newIntArray = bestRootScore.getCurrentMenu();
- List<Integer> oldIntArray = bestRootScore.getOldMenu();
- List<RPCRequest> deleteCommands;
-
- // Set up deletes
- List<MenuCell> deletes = new ArrayList<>();
- keepsOld = new ArrayList<>();
- for (int x = 0; x < oldIntArray.size(); x++) {
- Integer old = oldIntArray.get(x);
- if (old.equals(MARKED_FOR_DELETION)) {
- // grab cell to send to function to create delete commands
- deletes.add(oldMenuCells.get(x));
- } else if (old.equals(KEEP)) {
- keepsOld.add(oldMenuCells.get(x));
- }
- }
- // create the delete commands
- deleteCommands = createDeleteRPCsForCells(deletes);
-
- // Set up the adds
- List<MenuCell> adds = new ArrayList<>();
- keepsNew = new ArrayList<>();
- for (int x = 0; x < newIntArray.size(); x++) {
- Integer newInt = newIntArray.get(x);
- if (newInt.equals(MARKED_FOR_ADDITION)) {
- // grab cell to send to function to create add commands
- adds.add(menuCells.get(x));
- } else if (newInt.equals(KEEP)) {
- keepsNew.add(menuCells.get(x));
- }
- }
- updateIdsOnDynamicCells(adds);
- // this is needed for the onCommands to still work
- transferIdsToKeptCells(keepsNew);
-
- if (adds.size() > 0) {
- DebugTool.logInfo(TAG, "Sending root menu updates");
- sendDynamicRootMenuRPCs(deleteCommands, adds);
- } else {
- DebugTool.logInfo(TAG, "All root menu items are kept. Check the sub menus");
- runSubMenuCompareAlgorithm();
- }
- }
-
- // OTHER
-
- private void sendDynamicRootMenuRPCs(List<RPCRequest> deleteCommands, final List<MenuCell> updatedCells) {
- sendDeleteRPCs(deleteCommands, new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- createAndSendMenuCellRPCs(updatedCells, new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- inProgressUpdate = null;
-
- if (!success) {
- DebugTool.logError(TAG, "Error Sending Current Menu");
- }
-
- if (hasQueuedUpdate) {
- //setMenuCells(waitingUpdateMenuCells);
- hasQueuedUpdate = false;
- }
- }
- });
- }
- });
- }
-
- // SUB MENUS
-
- // This is called in the listener in the sendMenu and sendSubMenuCommands Methods
- private void runSubMenuCompareAlgorithm() {
- // any cells that were re-added have their sub-cells added with them
- // at this point all we care about are the cells that were deemed equal and kept.
- if (keepsNew == null || keepsNew.size() == 0) {
+ public void setMenuCells(@NonNull List<MenuCell> cells) {
+ if (cells == null) {
+ DebugTool.logError(TAG, "Cells list is null. Skipping...");
return;
}
- List<SubCellCommandList> commandLists = new ArrayList<>();
-
- for (int i = 0; i < keepsNew.size(); i++) {
-
- MenuCell keptCell = keepsNew.get(i);
- MenuCell oldKeptCell = keepsOld.get(i);
-
- if (oldKeptCell.getSubCells() != null && oldKeptCell.getSubCells().size() > 0 && keptCell.getSubCells() != null && keptCell.getSubCells().size() > 0) {
- // ACTUAL LOGIC
- RunScore subScore = compareOldAndNewLists(oldKeptCell.getSubCells(), keptCell.getSubCells());
-
- if (subScore != null) {
- DebugTool.logInfo(TAG, "Sub menu Run Score: " + oldKeptCell.getTitle() + " Score: " + subScore.getScore());
- SubCellCommandList commandList = new SubCellCommandList(oldKeptCell.getTitle(), oldKeptCell.getCellId(), subScore, oldKeptCell.getSubCells(), keptCell.getSubCells());
- commandLists.add(commandList);
- }
- }
- }
- createSubMenuDynamicCommands(commandLists);
- }
-
- private void createSubMenuDynamicCommands(final List<SubCellCommandList> commandLists) {
-
- // break out
- if (commandLists.size() == 0) {
- if (inProgressUpdate != null) {
- inProgressUpdate = null;
- }
-
- if (hasQueuedUpdate) {
- DebugTool.logInfo(TAG, "Menu Manager has waiting updates, sending now");
- setMenuCells(waitingUpdateMenuCells);
- hasQueuedUpdate = false;
- }
- DebugTool.logInfo(TAG, "All menu updates, including sub menus - done.");
+ if (!menuCellsAreUnique(cells, new ArrayList<String>())) {
+ DebugTool.logError(TAG, "Not all set menu cells are unique, but that is required");
return;
}
- final SubCellCommandList commandList = commandLists.remove(0);
-
- DebugTool.logInfo(TAG, "Creating and Sending Dynamic Sub Commands For Root Menu Cell: " + commandList.getMenuTitle());
-
- // grab the scores
- RunScore score = commandList.getListsScore();
- List<Integer> newIntArray = score.getCurrentMenu();
- List<Integer> oldIntArray = score.getOldMenu();
-
- // Grab the sub-menus from the parent cell
- final List<MenuCell> oldCells = commandList.getOldList();
- final List<MenuCell> newCells = commandList.getNewList();
-
- // Create the list for the adds
- List<MenuCell> subCellKeepsNew = new ArrayList<>();
-
- List<RPCRequest> deleteCommands;
-
- // Set up deletes
- List<MenuCell> deletes = new ArrayList<>();
- for (int x = 0; x < oldIntArray.size(); x++) {
- Integer old = oldIntArray.get(x);
- if (old.equals(MARKED_FOR_DELETION)) {
- // grab cell to send to function to create delete commands
- deletes.add(oldCells.get(x));
- }
- }
- // create the delete commands
- deleteCommands = createDeleteRPCsForCells(deletes);
-
- // Set up the adds
- List<MenuCell> adds = new ArrayList<>();
- for (int x = 0; x < newIntArray.size(); x++) {
- Integer newInt = newIntArray.get(x);
- if (newInt.equals(MARKED_FOR_ADDITION)) {
- // grab cell to send to function to create add commands
- adds.add(newCells.get(x));
- } else if (newInt.equals(KEEP)) {
- subCellKeepsNew.add(newCells.get(x));
- }
- }
- final List<MenuCell> addsWithNewIds = updateIdsOnDynamicSubCells(oldCells, adds, commandList.getParentId());
- // this is needed for the onCommands to still work
- transferIdsToKeptSubCells(oldCells, subCellKeepsNew);
+ // Create a deep copy of the list so future changes by developers don't affect the algorithm logic
+ this.menuCells = cloneMenuCellsList(cells);
- sendDeleteRPCs(deleteCommands, new CompletionListener() {
+ boolean isDynamicMenuUpdateActive = isDynamicMenuUpdateActive(dynamicMenuUpdatesMode, displayType);
+ Task operation = new MenuReplaceOperation(internalInterface, fileManager.get(), windowCapability, currentMenuConfiguration, currentMenuCells, menuCells, isDynamicMenuUpdateActive, new MenuManagerCompletionListener() {
@Override
- public void onComplete(boolean success) {
- if (addsWithNewIds != null && addsWithNewIds.size() > 0) {
- createAndSendDynamicSubMenuRPCs(newCells, addsWithNewIds, new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- // recurse through next sub list
- DebugTool.logInfo(TAG, "Finished Sending Dynamic Sub Commands For Root Menu Cell: " + commandList.getMenuTitle());
- createSubMenuDynamicCommands(commandLists);
- }
- });
- } else {
- // no add commands to send, recurse through next sub list
- DebugTool.logInfo(TAG, "Finished Sending Dynamic Sub Commands For Root Menu Cell: " + commandList.getMenuTitle());
- createSubMenuDynamicCommands(commandLists);
- }
+ public void onComplete(boolean success, List<MenuCell> currentMenuCells) {
+ BaseMenuManager.this.currentMenuCells = currentMenuCells;
+ updateMenuReplaceOperationsWithNewCurrentMenu();
+ DebugTool.logInfo(TAG, "Finished updating menu");
}
});
- }
-
- // OTHER HELPER METHODS:
-
- // COMPARISONS
-
- RunScore runMenuCompareAlgorithm(List<MenuCell> oldCells, List<MenuCell> newCells) {
-
- if (oldCells == null || oldCells.size() == 0) {
- return null;
- }
-
- RunScore bestScore = compareOldAndNewLists(oldCells, newCells);
- DebugTool.logInfo(TAG, "Best menu run score: " + bestScore.getScore());
-
- return bestScore;
- }
-
- private RunScore compareOldAndNewLists(List<MenuCell> oldCells, List<MenuCell> newCells) {
-
- RunScore bestRunScore = null;
-
- // This first loop is for each 'run'
- for (int run = 0; run < oldCells.size(); run++) {
-
- List<Integer> oldArray = new ArrayList<>(oldCells.size());
- List<Integer> newArray = new ArrayList<>(newCells.size());
- // Set the statuses
- setDeleteStatus(oldCells.size(), oldArray);
- setAddStatus(newCells.size(), newArray);
-
- int startIndex = 0;
-
- // Keep items that appear in both lists
- for (int oldItems = run; oldItems < oldCells.size(); oldItems++) {
-
- for (int newItems = startIndex; newItems < newCells.size(); newItems++) {
-
- if (oldCells.get(oldItems).equals(newCells.get(newItems))) {
- oldArray.set(oldItems, KEEP);
- newArray.set(newItems, KEEP);
- // set the new start index
- startIndex = newItems + 1;
- break;
- }
- }
+ // Cancel previous MenuReplaceOperations
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ task.cancelTask();
}
-
- // Calculate number of adds, or the 'score' for this run
- int numberOfAdds = 0;
-
- for (int x = 0; x < newArray.size(); x++) {
- if (newArray.get(x).equals(MARKED_FOR_ADDITION)) {
- numberOfAdds++;
- }
- }
-
- // see if we have a new best score and set it if we do
- if (bestRunScore == null || numberOfAdds < bestRunScore.getScore()) {
- bestRunScore = new RunScore(numberOfAdds, oldArray, newArray);
- }
-
}
- return bestRunScore;
- }
-
- private void setDeleteStatus(Integer size, List<Integer> oldArray) {
- for (int i = 0; i < size; i++) {
- oldArray.add(MARKED_FOR_DELETION);
- }
- }
- private void setAddStatus(Integer size, List<Integer> newArray) {
- for (int i = 0; i < size; i++) {
- newArray.add(MARKED_FOR_ADDITION);
- }
+ transactionQueue.add(operation, false);
}
- // ARTWORKS
-
/**
- * Get an array of artwork that needs to be uploaded from a list of menu cells
- *
- * @param cells List of MenuCell's to check
- * @return List of artwork that needs to be uploaded
- */
- private List<SdlArtwork> findAllArtworksToBeUploadedFromCells(List<MenuCell> cells) {
- // Make sure we can use images in the menus
- if (!hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- return new ArrayList<>();
- }
-
- List<SdlArtwork> artworks = new ArrayList<>();
- for (MenuCell cell : cells) {
- if (fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getIcon())) {
- artworks.add(cell.getIcon());
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0 && hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage)) {
- if (fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
- artworks.add(cell.getSecondaryArtwork());
- }
- } else if ((cell.getSubCells() == null || cell.getSubCells().isEmpty()) && hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage)) {
- if (fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
- artworks.add(cell.getSecondaryArtwork());
- }
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- artworks.addAll(findAllArtworksToBeUploadedFromCells(cell.getSubCells()));
- }
- }
-
- return artworks;
- }
-
- /**
- * Determine if cells should or should not be uploaded to the head unit with artworks.
- *
- * No artworks will be uploaded if:
- *
- * 1. If any cell has a dynamic artwork that is not uploaded
- * 2. If any cell contains a secondary artwork may be used on the head unit, and the cell has a dynamic secondary artwork that is not uploaded
- * 3. If any cell's subcells fail check (1) or (2)
+ * Returns current list of menu cells
*
- * @param cells List of MenuCell's to check
- * @return True if the cells should be uploaded with artwork, false if they should not
+ * @return a List of Currently set menu cells
*/
- private boolean shouldRPCsIncludeImages(List<MenuCell> cells) {
- for (MenuCell cell : cells) {
- SdlArtwork artwork = cell.getIcon();
- SdlArtwork secondaryArtwork = cell.getSecondaryArtwork();
- if (artwork != null && !artwork.isStaticIcon() && fileManager.get() != null && !fileManager.get().hasUploadedFile(artwork)) {
- return false;
- } else if (cell.getSubCells() != null && cell.getSubCells().size() > 0 && hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage)) {
- if (secondaryArtwork != null && !secondaryArtwork.isStaticIcon() && fileManager.get() != null && !fileManager.get().hasUploadedFile(secondaryArtwork)) {
- return false;
- }
- } else if ((cell.getSubCells() == null || cell.getSubCells().isEmpty()) && hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage)) {
- if (secondaryArtwork != null && !secondaryArtwork.isStaticIcon() && fileManager.get() != null && !fileManager.get().hasUploadedFile(secondaryArtwork)) {
- return false;
- }
- } else if (cell.getSubCells() != null && cell.getSubCells().size() > 0 && !shouldRPCsIncludeImages(cell.getSubCells())) {
- return false;
- }
- }
- return true;
- }
-
- private boolean hasImageFieldOfName(ImageFieldName imageFieldName) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName(defaultMainWindowCapability, imageFieldName);
- }
-
- private boolean hasTextFieldOfName(TextFieldName textFieldName) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName(defaultMainWindowCapability, textFieldName);
+ public List<MenuCell> getMenuCells() {
+ return menuCells;
}
- // IDs
-
- private void updateIdsOnDynamicCells(List<MenuCell> dynamicCells) {
- if (menuCells != null && menuCells.size() > 0 && dynamicCells != null && dynamicCells.size() > 0) {
- for (int z = 0; z < menuCells.size(); z++) {
- MenuCell mainCell = menuCells.get(z);
- for (int i = 0; i < dynamicCells.size(); i++) {
- MenuCell dynamicCell = dynamicCells.get(i);
- if (mainCell.equals(dynamicCell)) {
- int newId = ++lastMenuId;
- menuCells.get(z).setCellId(newId);
- dynamicCells.get(i).setCellId(newId);
+ private boolean openMenuPrivate(MenuCell cell) {
+ MenuCell foundClonedCell = null;
- if (mainCell.getSubCells() != null && mainCell.getSubCells().size() > 0) {
- updateIdsOnMenuCells(mainCell.getSubCells(), mainCell.getCellId());
- }
- break;
- }
+ if (cell != null) {
+ // We must see if we have a copy of this cell, since we clone the objects
+ for (MenuCell clonedCell : currentMenuCells) {
+ if (clonedCell.equals(cell) && clonedCell.getCellId() != parentIdNotFound) {
+ // We've found the correct sub menu cell
+ foundClonedCell = clonedCell;
+ break;
}
}
}
- }
- private List<MenuCell> updateIdsOnDynamicSubCells(List<MenuCell> oldList, List<MenuCell> dynamicCells, Integer parentId) {
- if (oldList != null && oldList.size() > 0 && dynamicCells != null && dynamicCells.size() > 0) {
- for (int z = 0; z < oldList.size(); z++) {
- MenuCell mainCell = oldList.get(z);
- for (int i = 0; i < dynamicCells.size(); i++) {
- MenuCell dynamicCell = dynamicCells.get(i);
- if (mainCell.equals(dynamicCell)) {
- int newId = ++lastMenuId;
- oldList.get(z).setCellId(newId);
- dynamicCells.get(i).setParentCellId(parentId);
- dynamicCells.get(i).setCellId(newId);
- } else {
- int newId = ++lastMenuId;
- dynamicCells.get(i).setParentCellId(parentId);
- dynamicCells.get(i).setCellId(newId);
- }
- }
- }
- return dynamicCells;
+ if (cell != null && (!cell.isSubMenuCell())) {
+ DebugTool.logError(TAG, String.format("The cell %s does not contain any sub cells, so no submenu can be opened", cell.getTitle()));
+ return false;
+ } else if (cell != null && foundClonedCell == null) {
+ DebugTool.logError(TAG, "This cell has not been sent to the head unit, so no submenu can be opened. Make sure that the cell exists in the SDLManager.menu list");
+ return false;
+ } else if (internalInterface.getSdlMsgVersion().getMajorVersion() < 6) {
+ DebugTool.logWarning(TAG, "The openSubmenu method is not supported on this head unit.");
+ return false;
}
- return null;
- }
+ // Create the operation
+ MenuShowOperation operation = new MenuShowOperation(internalInterface, foundClonedCell);
- /**
- * Assign cell ids on an array of menu cells given a parent id (or no parent id)
- *
- * @param cells List of menu cells to update
- * @param parentId The parent id to assign if needed
- */
- private void updateIdsOnMenuCells(List<MenuCell> cells, int parentId) {
- for (MenuCell cell : cells) {
- int newId = ++lastMenuId;
- cell.setCellId(newId);
- cell.setParentCellId(parentId);
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- updateIdsOnMenuCells(cell.getSubCells(), cell.getCellId());
+ // Cancel previous open menu operations
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuShowOperation) {
+ task.cancelTask();
}
}
- }
- private void transferIdsToKeptCells(List<MenuCell> keeps) {
- for (int z = 0; z < oldMenuCells.size(); z++) {
- MenuCell oldCell = oldMenuCells.get(z);
- for (int i = 0; i < keeps.size(); i++) {
- MenuCell keptCell = keeps.get(i);
- if (oldCell.equals(keptCell)) {
- keptCell.setCellId(oldCell.getCellId());
- break;
- }
- }
- }
- }
+ transactionQueue.add(operation, false);
- private void transferIdsToKeptSubCells(List<MenuCell> old, List<MenuCell> keeps) {
- for (int z = 0; z < old.size(); z++) {
- MenuCell oldCell = old.get(z);
- for (int i = 0; i < keeps.size(); i++) {
- MenuCell keptCell = keeps.get(i);
- if (oldCell.equals(keptCell)) {
- keptCell.setCellId(oldCell.getCellId());
- break;
- }
- }
- }
+ return true;
}
- // DELETES
-
/**
- * Create an array of DeleteCommand and DeleteSubMenu RPCs from an ArrayList of menu cells
- *
- * @param cells List of menu cells to use
- * @return List of DeleteCommand and DeletedSubmenu RPCs
+ * Opens the Main Menu
*/
- private List<RPCRequest> createDeleteRPCsForCells(List<MenuCell> cells) {
- List<RPCRequest> deletes = new ArrayList<>();
- for (MenuCell cell : cells) {
- if (cell.getSubCells() == null) {
- DeleteCommand delete = new DeleteCommand(cell.getCellId());
- deletes.add(delete);
- } else {
- DeleteSubMenu delete = new DeleteSubMenu(cell.getCellId());
- deletes.add(delete);
- }
- }
- return deletes;
+ public boolean openMenu() {
+ return openMenuPrivate(null);
}
- // COMMANDS / SUBMENU RPCs
-
/**
- * This method will receive the cells to be added. It will then build an array of add commands using the correct index to position the new items in the correct location.
- * e.g. If the new menu array is [A, B, C, D] but only [C, D] are new we need to pass [A, B , C , D] so C and D can be added to index 2 and 3 respectively.
+ * Opens a subMenu.
*
- * @param cellsToAdd List of MenuCell's that will be added to the menu, this array must contain only cells that are not already in the menu.
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @return List of RPCRequest addCommands
+ * @param cell - A <Strong>SubMenu</Strong> cell whose sub menu you wish to open
*/
- private List<RPCRequest> mainMenuCommandsForCells(List<MenuCell> cellsToAdd, boolean shouldHaveArtwork) {
- List<RPCRequest> builtCommands = new ArrayList<>();
-
- // We need the index so we will use this type of loop
- for (int z = 0; z < menuCells.size(); z++) {
- MenuCell mainCell = menuCells.get(z);
- for (int i = 0; i < cellsToAdd.size(); i++) {
- MenuCell addCell = cellsToAdd.get(i);
- if (mainCell.equals(addCell)) {
- if (addCell.getSubCells() != null && addCell.getSubCells().size() > 0) {
- builtCommands.add(subMenuCommandForMenuCell(addCell, shouldHaveArtwork, z));
- } else {
- builtCommands.add(commandForMenuCell(addCell, shouldHaveArtwork, z));
- }
- break;
- }
- }
- }
- return builtCommands;
+ public boolean openSubMenu(@NonNull MenuCell cell) {
+ return openMenuPrivate(cell);
}
- /**
- * Creates AddSubMenu RPCs for the passed array of menu cells, AND all of those cells' subcell RPCs, both AddCommands and AddSubMenus
- *
- * @param cells List of MenuCell's to create RPCs for
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @return An List of RPCs of AddSubMenus and their associated subcell RPCs
- */
- private List<RPCRequest> subMenuCommandsForCells(List<MenuCell> cells, boolean shouldHaveArtwork) {
- List<RPCRequest> builtCommands = new ArrayList<>();
- for (MenuCell cell : cells) {
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- builtCommands.addAll(allCommandsForCells(cell.getSubCells(), shouldHaveArtwork));
- }
- }
- return builtCommands;
- }
/**
- * Creates AddCommand and AddSubMenu RPCs for a passed array of cells, AND all of those cells' subcell RPCs, both AddCommands and AddSubmenus
+ * This method is called via the screen manager to set the menuConfiguration.
+ * The menuConfiguration.SubMenuLayout value will be used when a menuCell with sub-cells has a null value for SubMenuLayout
*
- * @param cells List of MenuCell's to create RPCs for
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @return An List of RPCs of AddCommand and AddSubMenus for the array of menu cells and their subcells, recursively
+ * @param menuConfiguration - The default menuConfiguration
*/
- List<RPCRequest> allCommandsForCells(List<MenuCell> cells, boolean shouldHaveArtwork) {
- List<RPCRequest> builtCommands = new ArrayList<>();
-
- // We need the index so we will use this type of loop
- for (int i = 0; i < cells.size(); i++) {
- MenuCell cell = cells.get(i);
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- builtCommands.add(subMenuCommandForMenuCell(cell, shouldHaveArtwork, i));
- // recursively grab the commands for all the sub cells
- builtCommands.addAll(allCommandsForCells(cell.getSubCells(), shouldHaveArtwork));
- } else {
- builtCommands.add(commandForMenuCell(cell, shouldHaveArtwork, i));
- }
+ public void setMenuConfiguration(@NonNull final MenuConfiguration menuConfiguration) {
+ if (menuConfiguration.equals(this.menuConfiguration)) {
+ DebugTool.logInfo(TAG, "New menu configuration is equal to existing one, will not set new configuration");
+ return;
}
- return builtCommands;
- }
- private List<RPCRequest> createCommandsForDynamicSubCells(List<MenuCell> oldMenuCells, List<MenuCell> cells, boolean shouldHaveArtwork) {
- List<RPCRequest> builtCommands = new ArrayList<>();
- for (int z = 0; z < oldMenuCells.size(); z++) {
- MenuCell oldCell = oldMenuCells.get(z);
- for (int i = 0; i < cells.size(); i++) {
- MenuCell cell = cells.get(i);
- if (cell.equals(oldCell)) {
- builtCommands.add(commandForMenuCell(cell, shouldHaveArtwork, z));
- break;
+ this.menuConfiguration = menuConfiguration;
+
+ MenuConfigurationUpdateOperation operation = new MenuConfigurationUpdateOperation(internalInterface, windowCapability, menuConfiguration, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (!success) {
+ DebugTool.logError(TAG, "Error updating menu configuration.");
+ return;
}
+ BaseMenuManager.this.currentMenuConfiguration = menuConfiguration;
+ updateMenuReplaceOperationsWithNewMenuConfiguration();
}
- }
- return builtCommands;
- }
-
- /**
- * An individual AddCommand RPC for a given MenuCell
- *
- * @param cell MenuCell to create RPCs for
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @param position The position the AddCommand RPC should be given
- * @return The AddCommand RPC
- */
- private AddCommand commandForMenuCell(MenuCell cell, boolean shouldHaveArtwork, int position) {
-
- MenuParams params = new MenuParams(cell.getUniqueTitle());
- params.setSecondaryText((cell.getSecondaryText() != null && cell.getSecondaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuCommandSecondaryText)) ? cell.getSecondaryText() : null);
- params.setTertiaryText((cell.getTertiaryText() != null && cell.getTertiaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuCommandTertiaryText)) ? cell.getTertiaryText() : null);
- params.setParentID(cell.getParentCellId() != MAX_ID ? cell.getParentCellId() : null);
- params.setPosition(position);
+ });
- AddCommand command = new AddCommand(cell.getCellId());
- command.setMenuParams(params);
- if (cell.getVoiceCommands() != null && !cell.getVoiceCommands().isEmpty()) {
- command.setVrCommands(cell.getVoiceCommands());
- } else {
- command.setVrCommands(null);
+ // Cancel previous menu configuration operations
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuConfigurationUpdateOperation) {
+ task.cancelTask();
+ }
}
- command.setCmdIcon((cell.getIcon() != null && shouldHaveArtwork) ? cell.getIcon().getImageRPC() : null);
- command.setSecondaryImage((cell.getSecondaryArtwork() != null && shouldHaveArtwork && hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage) && !(fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork()))) ? cell.getSecondaryArtwork().getImageRPC() : null);
- return command;
+ transactionQueue.add(operation, false);
}
- /**
- * An individual AddSubMenu RPC for a given MenuCell
- *
- * @param cell The cell to create the RPC for
- * @param shouldHaveArtwork Whether artwork should be applied to the RPC
- * @param position The position the AddSubMenu RPC should be given
- * @return The AddSubMenu RPC
- */
- private AddSubMenu subMenuCommandForMenuCell(MenuCell cell, boolean shouldHaveArtwork, int position) {
- AddSubMenu subMenu = new AddSubMenu(cell.getCellId(), cell.getUniqueTitle());
- subMenu.setSecondaryText((cell.getSecondaryText() != null && cell.getSecondaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuSubMenuSecondaryText)) ? cell.getSecondaryText() : null);
- subMenu.setTertiaryText((cell.getTertiaryText() != null && cell.getTertiaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuSubMenuTertiaryText)) ? cell.getTertiaryText() : null);
- subMenu.setPosition(position);
- if (cell.getSubMenuLayout() != null) {
- subMenu.setMenuLayout(cell.getSubMenuLayout());
- } else if (menuConfiguration != null && menuConfiguration.getSubMenuLayout() != null) {
- subMenu.setMenuLayout(menuConfiguration.getSubMenuLayout());
- }
- subMenu.setMenuIcon((shouldHaveArtwork && (cell.getIcon() != null && cell.getIcon().getImageRPC() != null)) ? cell.getIcon().getImageRPC() : null);
- subMenu.setSecondaryImage((shouldHaveArtwork && hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage) && !(fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) && (cell.getSecondaryArtwork() != null && cell.getSecondaryArtwork().getImageRPC() != null)) ? cell.getSecondaryArtwork().getImageRPC() : null);
- return subMenu;
- }
-
- // CELL COMMAND HANDLING
-
-
- /**
- * Call a listener for a currently displayed MenuCell based on the incoming OnCommand notification
- *
- * @param cells List of MenuCell's to check (including their subcells)
- * @param command OnCommand notification retrieved
- * @return True if the handler was found, false if it was not found
- */
- private boolean callListenerForCells(List<MenuCell> cells, OnCommand command) {
- if (cells != null && cells.size() > 0 && command != null) {
- for (MenuCell cell : cells) {
-
- if (cell.getCellId() == command.getCmdID() && cell.getMenuSelectionListener() != null) {
- cell.getMenuSelectionListener().onTriggered(command.getTriggerSource());
- return true;
- }
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- // for each cell, if it has sub cells, recursively loop through those as well
- if (callListenerForCells(cell.getSubCells(), command)) {
- return true;
- }
- }
- }
- }
- return false;
+ public MenuConfiguration getMenuConfiguration() {
+ return this.menuConfiguration;
}
- // LISTENERS
-
private void addListeners() {
- // DISPLAY CAPABILITIES - via SCM
onDisplaysCapabilityListener = new OnSystemCapabilityListener() {
@Override
public void onCapabilityRetrieved(Object capability) {
// instead of using the parameter it's more safe to use the convenience method
List<DisplayCapability> capabilities = SystemCapabilityManager.convertToList(capability, DisplayCapability.class);
if (capabilities == null || capabilities.size() == 0) {
- DebugTool.logError(TAG, "SoftButton Manager - Capabilities sent here are null or empty");
+ DebugTool.logError(TAG, "Capabilities sent here are null or empty");
} else {
DisplayCapability display = capabilities.get(0);
displayType = display.getDisplayName();
for (WindowCapability windowCapability : display.getWindowCapabilities()) {
int currentWindowID = windowCapability.getWindowID() != null ? windowCapability.getWindowID() : PredefinedWindows.DEFAULT_WINDOW.getValue();
if (currentWindowID == PredefinedWindows.DEFAULT_WINDOW.getValue()) {
- defaultMainWindowCapability = windowCapability;
+ BaseMenuManager.this.windowCapability = windowCapability;
+ updateMenuReplaceOperationsWithNewWindowCapability();
}
}
}
@@ -1134,14 +326,13 @@ abstract class BaseMenuManager extends BaseSubManager {
@Override
public void onError(String info) {
DebugTool.logError(TAG, "Display Capability cannot be retrieved");
- defaultMainWindowCapability = null;
+ windowCapability = null;
}
};
if (internalInterface.getSystemCapabilityManager() != null) {
this.internalInterface.getSystemCapabilityManager().addOnSystemCapabilityListener(SystemCapabilityType.DISPLAYS, onDisplaysCapabilityListener);
}
- // HMI UPDATES
hmiListener = new OnRPCNotificationListener() {
@Override
public void onNotified(RPCNotification notification) {
@@ -1149,396 +340,82 @@ abstract class BaseMenuManager extends BaseSubManager {
if (onHMIStatus.getWindowID() != null && onHMIStatus.getWindowID() != PredefinedWindows.DEFAULT_WINDOW.getValue()) {
return;
}
- HMILevel oldHMILevel = currentHMILevel;
currentHMILevel = onHMIStatus.getHmiLevel();
-
- // Auto-send an updated menu if we were in NONE and now we are not, and we need an update
- if (oldHMILevel == HMILevel.HMI_NONE && currentHMILevel != HMILevel.HMI_NONE && currentSystemContext != SystemContext.SYSCTXT_MENU) {
- if (waitingOnHMIUpdate) {
- DebugTool.logInfo(TAG, "We now have proper HMI, sending waiting update");
- setMenuCells(waitingUpdateMenuCells);
- waitingUpdateMenuCells.clear();
- return;
- }
- }
-
- // If we don't check for this and only update when not in the menu, there can be IN_USE errors, especially with submenus.
- // We also don't want to encourage changing out the menu while the user is using it for usability reasons.
- SystemContext oldContext = currentSystemContext;
currentSystemContext = onHMIStatus.getSystemContext();
-
- if (oldContext == SystemContext.SYSCTXT_MENU && currentSystemContext != SystemContext.SYSCTXT_MENU && currentHMILevel != HMILevel.HMI_NONE) {
- if (waitingOnHMIUpdate) {
- DebugTool.logInfo(TAG, "We now have a proper system context, sending waiting update");
- setMenuCells(waitingUpdateMenuCells);
- waitingUpdateMenuCells.clear();
- }
- }
+ updateTransactionQueueSuspended();
}
};
internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
- // COMMANDS
commandListener = new OnRPCNotificationListener() {
@Override
public void onNotified(RPCNotification notification) {
OnCommand onCommand = (OnCommand) notification;
- callListenerForCells(menuCells, onCommand);
+ callListenerForCells(currentMenuCells, onCommand);
}
};
internalInterface.addOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener);
}
- // SEND NEW MENU ITEMS
-
- private void createAndSendEntireMenu() {
-
- if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE) || currentSystemContext.equals(SystemContext.SYSCTXT_MENU)) {
- // We are in NONE or the menu is in use, bail out of here
- DebugTool.logInfo(TAG, "HMI in None or System Context Menu, returning");
- waitingOnHMIUpdate = true;
- waitingUpdateMenuCells = menuCells;
- return;
- }
-
- if (inProgressUpdate != null && inProgressUpdate.size() > 0) {
- // there's an in-progress update so this needs to wait
- DebugTool.logInfo(TAG, "There is an in progress Menu Update, returning");
- hasQueuedUpdate = true;
- return;
- }
-
- deleteRootMenu(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- createAndSendMenuCellRPCs(menuCells, new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- inProgressUpdate = null;
-
- if (!success) {
- DebugTool.logError(TAG, "Error Sending Current Menu");
- }
-
- if (hasQueuedUpdate) {
- setMenuCells(waitingUpdateMenuCells);
- hasQueuedUpdate = false;
- }
- }
- });
- }
- });
- }
-
- private void createAndSendMenuCellRPCs(final List<MenuCell> menu, final CompletionListener listener) {
-
- if (menu.size() == 0) {
- if (listener != null) {
- // This can be considered a success if the user was clearing out their menu
- listener.onComplete(true);
- }
- return;
- }
-
- List<RPCRequest> mainMenuCommands;
- final List<RPCRequest> subMenuCommands;
-
- if (!shouldRPCsIncludeImages(menu) || !hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- // Send artwork-less menu
- mainMenuCommands = mainMenuCommandsForCells(menu, false);
- subMenuCommands = subMenuCommandsForCells(menu, false);
- } else {
- mainMenuCommands = mainMenuCommandsForCells(menu, true);
- subMenuCommands = subMenuCommandsForCells(menu, true);
+ private boolean callListenerForCells(List<MenuCell> cells, OnCommand command) {
+ if (cells == null || cells.isEmpty() || command == null) {
+ return false;
}
- // add all built commands to inProgressUpdate
- inProgressUpdate = new ArrayList<>(mainMenuCommands);
- inProgressUpdate.addAll(subMenuCommands);
-
- internalInterface.sendSequentialRPCs(mainMenuCommands, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
- // nothing here
- }
-
- @Override
- public void onFinished() {
-
- if (subMenuCommands.size() > 0) {
- sendSubMenuCommandRPCs(subMenuCommands, listener);
- DebugTool.logInfo(TAG, "Finished sending main menu commands. Sending sub menu commands.");
- } else {
-
- if (keepsNew != null && keepsNew.size() > 0) {
- runSubMenuCompareAlgorithm();
- } else {
- inProgressUpdate = null;
- DebugTool.logInfo(TAG, "Finished sending main menu commands.");
- }
- }
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- try {
- DebugTool.logInfo(TAG, "Main Menu response: " + response.serializeJSON().toString());
- } catch (JSONException e) {
- DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e);
- }
- } else {
- DebugTool.logError(TAG, "Result: " + response.getResultCode() + " Info: " + response.getInfo());
- }
- }
- });
- }
-
- private void sendSubMenuCommandRPCs(List<RPCRequest> commands, final CompletionListener listener) {
-
- internalInterface.sendSequentialRPCs(commands, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
-
- }
-
- @Override
- public void onFinished() {
-
- if (keepsNew != null && keepsNew.size() > 0) {
- runSubMenuCompareAlgorithm();
- } else {
- DebugTool.logInfo(TAG, "Finished Updating Menu");
- inProgressUpdate = null;
-
- if (listener != null) {
- listener.onComplete(true);
- }
- }
+ for (MenuCell cell : cells) {
+ if (cell.getCellId() == command.getCmdID() && cell.getMenuSelectionListener() != null) {
+ cell.getMenuSelectionListener().onTriggered(command.getTriggerSource());
+ return true;
}
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- try {
- DebugTool.logInfo(TAG, "Sub Menu response: " + response.serializeJSON().toString());
- } catch (JSONException e) {
- DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e);
- }
- } else {
- DebugTool.logError(TAG, "Failed to send sub menu commands: " + response.getInfo());
- if (listener != null) {
- listener.onComplete(false);
- }
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ // for each cell, if it has sub cells, recursively loop through those as well
+ boolean success = callListenerForCells(cell.getSubCells(), command);
+ if (success) {
+ return true;
}
}
- });
- }
-
- private void createAndSendDynamicSubMenuRPCs(List<MenuCell> newMenu, final List<MenuCell> adds, final CompletionListener listener) {
-
- if (adds.size() == 0) {
- if (listener != null) {
- // This can be considered a success if the user was clearing out their menu
- DebugTool.logError(TAG, "Called createAndSendDynamicSubMenuRPCs with empty menu");
- listener.onComplete(true);
- }
- return;
}
- List<RPCRequest> mainMenuCommands;
-
- if (!shouldRPCsIncludeImages(adds) || !hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- // Send artwork-less menu
- mainMenuCommands = createCommandsForDynamicSubCells(newMenu, adds, false);
- } else {
- mainMenuCommands = createCommandsForDynamicSubCells(newMenu, adds, true);
- }
-
- internalInterface.sendSequentialRPCs(mainMenuCommands, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
- // nothing here
- }
-
- @Override
- public void onFinished() {
-
- if (listener != null) {
- listener.onComplete(true);
- }
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- try {
- DebugTool.logInfo(TAG, "Dynamic Sub Menu response: " + response.serializeJSON().toString());
- } catch (JSONException e) {
- DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e);
- }
- } else {
- DebugTool.logError(TAG, "Result: " + response.getResultCode() + " Info: " + response.getInfo());
- }
- }
- });
+ return false;
}
- // DELETE OLD MENU ITEMS
-
- private void deleteRootMenu(final CompletionListener listener) {
-
- if (oldMenuCells == null || oldMenuCells.size() == 0) {
- if (listener != null) {
- // technically this method is successful if there's nothing to delete
- DebugTool.logInfo(TAG, "No old cells to delete, returning");
- listener.onComplete(true);
+ private void updateMenuReplaceOperationsWithNewCurrentMenu() {
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ ((MenuReplaceOperation) task).setCurrentMenu(this.currentMenuCells);
}
- } else {
- sendDeleteRPCs(createDeleteRPCsForCells(oldMenuCells), listener);
}
}
- private void sendDeleteRPCs(List<RPCRequest> deleteCommands, final CompletionListener listener) {
- if (oldMenuCells != null && oldMenuCells.size() == 0) {
- if (listener != null) {
- // technically this method is successful if there's nothing to delete
- DebugTool.logInfo(TAG, "No old cells to delete, returning");
- listener.onComplete(true);
+ private void updateMenuReplaceOperationsWithNewWindowCapability() {
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ ((MenuReplaceOperation) task).setWindowCapability(this.windowCapability);
}
- return;
}
-
- if (deleteCommands == null || deleteCommands.size() == 0) {
- // no dynamic deletes required. return
- if (listener != null) {
- // technically this method is successful if there's nothing to delete
- listener.onComplete(true);
- }
- return;
- }
-
- internalInterface.sendRPCs(deleteCommands, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
-
- }
-
- @Override
- public void onFinished() {
- DebugTool.logInfo(TAG, "Successfully deleted cells");
- if (listener != null) {
- listener.onComplete(true);
- }
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
-
- }
- });
- }
-
- private List<MenuCell> cloneMenuCellsList(List<MenuCell> originalList) {
- if (originalList == null) {
- return null;
- }
-
- List<MenuCell> clone = new ArrayList<>();
- for (MenuCell menuCell : originalList) {
- clone.add(menuCell.clone());
- }
- return clone;
}
- private void addUniqueNamesToCellsWithDuplicatePrimaryText(List<MenuCell> cells) {
- HashMap<String, Integer> dictCounter = new HashMap<>();
-
- for (MenuCell cell : cells) {
- String cellName = cell.getTitle();
- Integer counter = dictCounter.get(cellName);
-
- if (counter != null) {
- dictCounter.put(cellName, ++counter);
- cell.setUniqueTitle(cellName + " (" + counter + ")");
- } else {
- dictCounter.put(cellName, 1);
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- addUniqueNamesToCellsWithDuplicatePrimaryText(cell.getSubCells());
+ private void updateMenuReplaceOperationsWithNewMenuConfiguration() {
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ ((MenuReplaceOperation) task).setMenuConfiguration(currentMenuConfiguration);
}
}
}
- void addUniqueNamesBasedOnStrippedCells(List<MenuCell> strippedCells, List<MenuCell> unstrippedCells) {
- if (strippedCells == null || unstrippedCells == null || strippedCells.size() != unstrippedCells.size()) {
- return;
- }
- // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
- HashMap<MenuCell, Integer> dictCounter = new HashMap<>();
- for (int i = 0; i < strippedCells.size(); i++) {
- MenuCell cell = strippedCells.get(i);
- Integer counter = dictCounter.get(cell);
- if (counter != null) {
- counter = counter + 1;
- dictCounter.put(cell, counter);
- } else {
- dictCounter.put(cell, 1);
- }
- counter = dictCounter.get(cell);
- if (counter > 1) {
- unstrippedCells.get(i).setUniqueTitle(unstrippedCells.get(i).getTitle() + " (" + counter + ")");
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- addUniqueNamesBasedOnStrippedCells(cell.getSubCells(), unstrippedCells.get(i).getSubCells());
+ private boolean isDynamicMenuUpdateActive(DynamicMenuUpdatesMode updateMode, String displayType) {
+ if (updateMode.equals(DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE)) {
+ if (displayType == null) {
+ return true;
}
-
- }
-
-
- }
-
- List<MenuCell> removeUnusedProperties(List<MenuCell> menuCells) {
- if (menuCells == null) {
- return null;
+ return (!displayType.equals(DisplayType.GEN3_8_INCH.toString()));
+ } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_OFF)) {
+ return false;
+ } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_ON)) {
+ return true;
}
- List<MenuCell> removePropertiesClone = cloneMenuCellsList(menuCells);
- for (MenuCell cell : removePropertiesClone) {
- // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
- cell.setVoiceCommands(null);
- // Don't check ImageFieldName.subMenuIcon because it was added in 7.0 when the feature was added in 5.0.
- // Just assume that if cmdIcon is not available, the submenu icon is not either.
- if (!hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- cell.setIcon(null);
- }
- // Check for subMenu fields supported
- if (cell.getSubCells() != null) {
- if (!hasTextFieldOfName(TextFieldName.menuSubMenuSecondaryText)) {
- cell.setSecondaryText(null);
- }
- if (!hasTextFieldOfName(TextFieldName.menuSubMenuTertiaryText)) {
- cell.setTertiaryText(null);
- }
- if (!hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage)) {
- cell.setSecondaryArtwork(null);
- }
- cell.setSubCells(removeUnusedProperties(cell.getSubCells()));
- } else {
- if (!hasTextFieldOfName(TextFieldName.menuCommandSecondaryText)) {
- cell.setSecondaryText(null);
- }
- if (!hasTextFieldOfName(TextFieldName.menuCommandTertiaryText)) {
- cell.setTertiaryText(null);
- }
- if (!hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage)) {
- cell.setSecondaryArtwork(null);
- }
- }
- }
- return removePropertiesClone;
+ return true;
}
/**
@@ -1549,14 +426,14 @@ abstract class BaseMenuManager extends BaseSubManager {
* @return Boolean that indicates whether menuCells are unique or not
*/
private boolean menuCellsAreUnique(List<MenuCell> cells, ArrayList<String> allVoiceCommands) {
- //Check all voice commands for identical items and check each list of cells for identical cells
+ // Check all voice commands for identical items and check each list of cells for identical cells
HashSet<MenuCell> identicalCellsCheckSet = new HashSet<>();
for (MenuCell cell : cells) {
identicalCellsCheckSet.add(cell);
- // Recursively check the subcell lists to see if they are all unique as well. If anything is not, this will chain back up the list to return false.
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
+ // Recursively check the sub-cell lists to see if they are all unique as well. If anything is not, this will chain back up the list to return false.
+ if (cell.isSubMenuCell() && cell.getSubCells().size() > 0) {
boolean subCellsAreUnique = menuCellsAreUnique(cell.getSubCells(), allVoiceCommands);
if (!subCellsAreUnique) {
@@ -1566,16 +443,14 @@ abstract class BaseMenuManager extends BaseSubManager {
}
// Voice commands have to be identical across all lists
- if (cell.getVoiceCommands() == null) {
- continue;
+ if (cell.getVoiceCommands() != null) {
+ allVoiceCommands.addAll(cell.getVoiceCommands());
}
- allVoiceCommands.addAll(cell.getVoiceCommands());
}
-
// Check for duplicate cells
if (identicalCellsCheckSet.size() != cells.size()) {
- DebugTool.logError(TAG, "Not all cells are unique. The menu will not be set.");
+ DebugTool.logError(TAG, "Not all cells are unique. Cells in each list (such as main menu or sub cell list) must have some differentiating property other than the sub cells within a cell. The menu will not be set.");
return false;
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java
new file mode 100644
index 000000000..b6fa0266c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2021 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.screen.menu;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class DynamicMenuUpdateAlgorithm {
+ // Cell state that tells the menu manager what it should do with a given MenuCell
+ enum MenuCellState {
+ DELETE, // Marks the cell to be deleted
+ ADD, // Marks the cell to be added
+ KEEP // Marks the cell to be kept
+ }
+
+ static DynamicMenuUpdateRunScore compatibilityRunScoreWithOldMenuCells(List<MenuCell> oldMenuCells, List<MenuCell> updatedMenuCells) {
+ return new DynamicMenuUpdateRunScore(buildAllDeleteStatusesForMenu(oldMenuCells), buildAllAddStatusesForMenu(updatedMenuCells), updatedMenuCells.size());
+ }
+
+ static DynamicMenuUpdateRunScore dynamicRunScoreOldMenuCells(List<MenuCell> oldMenuCells, List<MenuCell> updatedMenuCells) {
+ if (!oldMenuCells.isEmpty() && updatedMenuCells.isEmpty()) {
+ // Deleting all cells
+ return new DynamicMenuUpdateRunScore(buildAllDeleteStatusesForMenu(oldMenuCells), new ArrayList<MenuCellState>(), 0);
+ } else if (oldMenuCells.isEmpty() && !updatedMenuCells.isEmpty()) {
+ // No cells to delete
+ return new DynamicMenuUpdateRunScore(new ArrayList<MenuCellState>(), buildAllAddStatusesForMenu(updatedMenuCells), updatedMenuCells.size());
+ } else if (oldMenuCells.isEmpty() && updatedMenuCells.isEmpty()) {
+ // Empty menu to empty menu
+ return new DynamicMenuUpdateRunScore(new ArrayList<MenuCellState>(), new ArrayList<MenuCellState>(), 0);
+ }
+ return startCompareAtRun(0, oldMenuCells, updatedMenuCells);
+ }
+
+ private static DynamicMenuUpdateRunScore startCompareAtRun(int startRun, List<MenuCell> oldMenuCells, List<MenuCell> updatedMenuCells) {
+ DynamicMenuUpdateRunScore bestScore = new DynamicMenuUpdateRunScore(new ArrayList<MenuCellState>(), new ArrayList<MenuCellState>(), 0);
+
+ for (int run = startRun; run < oldMenuCells.size(); run++) {
+ // Set the menu status as a 1-1 list, start off will oldMenus = all Deletes, newMenu = all Adds
+ List<MenuCellState> oldMenuStatus = buildAllDeleteStatusesForMenu(oldMenuCells);
+ List<MenuCellState> newMenuStatus = buildAllAddStatusesForMenu(updatedMenuCells);
+
+ int startIndex = 0;
+ for (int oldCellIndex = run; oldCellIndex < oldMenuCells.size(); oldCellIndex++) {
+ // For each old item, create inner loop to compare old cells to new cells to find a match
+ // if a match if found we mark the index at match for both the old and the new status to
+ // keep since we do not want to send RPCs for those cases
+ for (int newCellIndex = startIndex; newCellIndex < updatedMenuCells.size(); newCellIndex++) {
+ if (oldMenuCells.get(oldCellIndex).equalsWithUniqueTitle(updatedMenuCells.get(newCellIndex))) {
+ oldMenuStatus.set(oldCellIndex, MenuCellState.KEEP);
+ newMenuStatus.set(newCellIndex, MenuCellState.KEEP);
+ startIndex = newCellIndex + 1;
+ break;
+ }
+ }
+ }
+
+ // Add RPC are the biggest operation so we need to find the run with the least amount of Adds.
+ // We will reset the run we use each time a runScore is less than the current score.
+ int numberOfAdds = 0;
+ for (int status = 0; status < newMenuStatus.size(); status++) {
+ if (newMenuStatus.get(status).equals(MenuCellState.ADD)) {
+ numberOfAdds++;
+ }
+ }
+
+ // As soon as we a run that requires 0 Adds we will use it since we cant do better then 0
+ if (numberOfAdds == 0) {
+ bestScore = new DynamicMenuUpdateRunScore(oldMenuStatus, newMenuStatus, numberOfAdds);
+ return bestScore;
+ }
+
+ // if we haven't create the bestScore object or if the current score beats the old score then we will create a new bestScore
+ if (bestScore.isEmpty() || numberOfAdds < bestScore.getScore()) {
+ bestScore = new DynamicMenuUpdateRunScore(oldMenuStatus, newMenuStatus, numberOfAdds);
+ }
+
+ }
+ return bestScore;
+ }
+
+ /**
+ * Builds a 1-1 list of Deletes for every element in the array
+ * @param oldMenu The old menu list
+ */
+ static List<MenuCellState> buildAllDeleteStatusesForMenu (List<MenuCell> oldMenu){
+ List<MenuCellState> oldMenuStatus = new ArrayList<>(oldMenu.size());
+ for (int index = 0; index < oldMenu.size(); index++) {
+ oldMenuStatus.add(MenuCellState.DELETE);
+ }
+ return oldMenuStatus;
+ }
+
+ /**
+ * Builds a 1-1 list of Adds for every element in the list
+ * @param newMenu The new menu list
+ */
+ static List<MenuCellState> buildAllAddStatusesForMenu (List<MenuCell> newMenu){
+ List<MenuCellState> newMenuStatus = new ArrayList<>(newMenu.size());
+ for (int index = 0; index < newMenu.size(); index++) {
+ newMenuStatus.add(MenuCellState.ADD);
+ }
+ return newMenuStatus;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateRunScore.java
index 604d52b09..892fd1a8d 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateRunScore.java
@@ -32,60 +32,46 @@
package com.smartdevicelink.managers.screen.menu;
-import java.util.List;
-
-class SubCellCommandList {
-
- private RunScore listsScore;
- private String menuTitle;
- private Integer parentId;
- private List<MenuCell> oldList, newList;
-
- SubCellCommandList(String menuTitle, Integer parentId, RunScore listsScore, List<MenuCell> oldList, List<MenuCell> newList) {
- setMenuTitle(menuTitle);
- setParentId(parentId);
- setListsScore(listsScore);
- setOldList(oldList);
- setNewList(newList);
- }
+import com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState;
- private void setParentId(Integer parentId) {
- this.parentId = parentId;
- }
+import java.util.List;
- Integer getParentId() {
- return parentId;
- }
+class DynamicMenuUpdateRunScore {
+ private List<MenuCellState> oldStatus; // Will contain all the Deletes and Keeps
+ private List<MenuCellState> updatedStatus; // Will contain all the Adds and Keeps
+ private int score; // Will contain the score, number of total Adds that will need to be created
- private void setMenuTitle(String menuTitle) {
- this.menuTitle = menuTitle;
+ DynamicMenuUpdateRunScore(List<MenuCellState> oldStatus, List<MenuCellState> updatedStatus, int score) {
+ setOldStatus(oldStatus);
+ setUpdatedStatus(updatedStatus);
+ setScore(score);
}
- String getMenuTitle() {
- return menuTitle;
+ private void setUpdatedStatus(List<MenuCellState> updatedStatus) {
+ this.updatedStatus = updatedStatus;
}
- private void setListsScore(RunScore listsScore) {
- this.listsScore = listsScore;
+ List<MenuCellState> getUpdatedStatus() {
+ return updatedStatus;
}
- RunScore getListsScore() {
- return listsScore;
+ private void setOldStatus(List<MenuCellState> oldStatus) {
+ this.oldStatus = oldStatus;
}
- private void setOldList(List<MenuCell> oldList) {
- this.oldList = oldList;
+ List<MenuCellState> getOldStatus() {
+ return oldStatus;
}
- List<MenuCell> getOldList() {
- return oldList;
+ private void setScore(int score) {
+ this.score = score;
}
- private void setNewList(List<MenuCell> newList) {
- this.newList = newList;
+ public int getScore() {
+ return score;
}
- List<MenuCell> getNewList() {
- return newList;
+ boolean isEmpty() {
+ return oldStatus.size() == 0 && updatedStatus.size() == 0 && score == 0;
}
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java
index 652891181..eaf91ae76 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java
@@ -432,6 +432,10 @@ public class MenuCell implements Cloneable {
// HELPER
+ boolean isSubMenuCell() {
+ return getSubCells() != null;
+ }
+
/**
* Note: You should compare using the {@link #equals(Object)} method. <br>
* Hash the parameters of the object and return the result for comparison
@@ -446,17 +450,24 @@ public class MenuCell implements Cloneable {
public int hashCode() {
int result = 1;
result += ((getTitle() == null) ? 0 : Integer.rotateLeft(getTitle().hashCode(), 1));
- result += ((getIcon() == null) ? 0 : Integer.rotateLeft(getIcon().hashCode(), 2));
+ result += ((getIcon() == null || getIcon().getName() == null) ? 0 : Integer.rotateLeft(getIcon().getName().hashCode(), 2));
result += ((getVoiceCommands() == null) ? 0 : Integer.rotateLeft(getVoiceCommands().hashCode(), 3));
result += ((getSubCells() == null) ? 0 : Integer.rotateLeft(1, 4));
- result += ((getSecondaryText() == null) ? 0 : Integer.rotateLeft(getSecondaryText().hashCode(), 1));
- result += ((getTertiaryText() == null) ? 0 : Integer.rotateLeft(getTertiaryText().hashCode(), 1));
- result += ((getSecondaryArtwork() == null) ? 0 : Integer.rotateLeft(getSecondaryArtwork().hashCode(), 2));
+ result += ((getSecondaryText() == null) ? 0 : Integer.rotateLeft(getSecondaryText().hashCode(), 5));
+ result += ((getTertiaryText() == null) ? 0 : Integer.rotateLeft(getTertiaryText().hashCode(), 6));
+ result += ((getSecondaryArtwork() == null || getSecondaryArtwork().getName() == null) ? 0 : Integer.rotateLeft(getSecondaryArtwork().getName().hashCode(), 7));
+ result += ((getSubMenuLayout() == null) ? 0 : Integer.rotateLeft(getSubMenuLayout().hashCode(), 8));
+ return result;
+ }
+
+ private int hashCodeWithUniqueTitle() {
+ int result = hashCode();
+ result += ((getUniqueTitle() == null) ? 0 : Integer.rotateLeft(getUniqueTitle().hashCode(), 9));
return result;
}
/**
- * Uses our custom hashCode for MenuCell objects, but does <strong>NOT</strong> compare the listener objects
+ * Uses our custom hashCode for MenuCell objects
*
* @param o - The object to compare
* @return boolean of whether the objects are the same or not
@@ -475,6 +486,22 @@ public class MenuCell implements Cloneable {
}
/**
+ * Uses our custom hashCode for MenuCell objects. This method takes UniqueTitle into consideration when doing the equality check
+ *
+ * @param o - The object to compare
+ * @return boolean of whether the objects are the same or not
+ */
+ boolean equalsWithUniqueTitle(MenuCell o) {
+ if (o == null) {
+ return false;
+ }
+ // if this is the same memory address, its the same
+ if (this == o) return true;
+ // if we get to this point, create the hashes and compare them
+ return hashCodeWithUniqueTitle() == o.hashCodeWithUniqueTitle();
+ }
+
+ /**
* Creates a deep copy of the object
*
* @return deep copy of the object, null if an exception occurred
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java
index f478c80fc..9ae1ae28d 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java
@@ -101,4 +101,36 @@ public class MenuConfiguration {
return "MenuConfiguration: MenuLayout = " + this.mainMenuLayout + " | SubMenuLayout = " + this.submenuLayout;
}
+ /**
+ * Note: You should compare using the {@link #equals(Object)} method. <br>
+ * Hash the parameters of the object and return the result for comparison
+ * @return the hash code as an int
+ */
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result += ((getMenuLayout() == null) ? 0 : Integer.rotateLeft(getMenuLayout().hashCode(), 1));
+ result += ((getSubMenuLayout() == null) ? 0 : Integer.rotateLeft(getSubMenuLayout().hashCode(), 2));
+ return result;
+ }
+
+ /**
+ * Uses our custom hashCode for MenuConfiguration 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, its the same
+ if (this == o) return true;
+ // if this is not an instance of this class, not the same
+ if (!(o instanceof MenuConfiguration)) return false;
+ // if we get to this point, create the hashes and compare them
+ return hashCode() == o.hashCode();
+ }
+
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java
new file mode 100644
index 000000000..430509bea
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2021 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.screen.menu;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * Created by Bilal Alsharifi on 1/21/21.
+ */
+class MenuConfigurationUpdateOperation extends Task {
+ private static final String TAG = "MenuConfigurationUpdateOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private final List<MenuLayout> availableMenuLayouts;
+ private final MenuConfiguration updatedMenuConfiguration;
+ private final CompletionListener completionListener;
+
+ MenuConfigurationUpdateOperation(ISdl internalInterface, WindowCapability windowCapability, MenuConfiguration menuConfiguration, CompletionListener completionListener) {
+ super(TAG);
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.availableMenuLayouts = windowCapability != null ? windowCapability.getMenuLayoutsAvailable() : null;
+ this.updatedMenuConfiguration = menuConfiguration;
+ this.completionListener = completionListener;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ sendSetGlobalProperties(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ finishOperation(success);
+ }
+ });
+ }
+
+ private void sendSetGlobalProperties(final CompletionListener listener) {
+ if (internalInterface.get() == null) {
+ listener.onComplete(false);
+ return;
+ }
+
+ SdlMsgVersion sdlMsgVersion = internalInterface.get().getSdlMsgVersion();
+ if (sdlMsgVersion == null) {
+ DebugTool.logError(TAG, "SDL Message Version is null. Cannot set Menu Configuration");
+ listener.onComplete(false);
+ return;
+ }
+
+ if (sdlMsgVersion.getMajorVersion() < 6) {
+ DebugTool.logWarning(TAG, "Menu configurations is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
+ listener.onComplete(false);
+ return;
+ }
+
+ if (updatedMenuConfiguration.getMenuLayout() == null) {
+ DebugTool.logInfo(TAG, "Menu Layout is null, not sending setGlobalProperties");
+ listener.onComplete(false);
+ return;
+ }
+
+ if (availableMenuLayouts == null) {
+ DebugTool.logWarning(TAG, "Could not set the main menu configuration. Which menu layouts can be used is not available");
+ listener.onComplete(false);
+ return;
+ } else if (!availableMenuLayouts.contains(updatedMenuConfiguration.getMenuLayout()) || !availableMenuLayouts.contains(updatedMenuConfiguration.getSubMenuLayout())) {
+ DebugTool.logError(TAG, String.format("One or more of the set menu layouts are not available on this system. The menu configuration will not be set. Available menu layouts: %s, set menu layouts: %s", availableMenuLayouts, updatedMenuConfiguration));
+ listener.onComplete(false);
+ return;
+ }
+
+ SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
+ setGlobalProperties.setMenuLayout(updatedMenuConfiguration.getMenuLayout());
+ setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ DebugTool.logInfo(TAG, "Menu Configuration successfully set: " + updatedMenuConfiguration.toString());
+ } else {
+ DebugTool.logError(TAG, "onError: " + response.getResultCode() + " | Info: " + response.getInfo());
+ }
+ listener.onComplete(response.getSuccess());
+ }
+ });
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setGlobalProperties);
+ }
+ }
+
+ private void finishOperation(boolean success) {
+ if (completionListener != null) {
+ completionListener.onComplete(success);
+ }
+ onFinished();
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManagerCompletionListener.java
index 6d031bb0e..584fa90ca 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManagerCompletionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Livio, Inc.
+ * Copyright (c) 2021 Livio, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -34,39 +34,9 @@ package com.smartdevicelink.managers.screen.menu;
import java.util.List;
-class RunScore {
-
- private int score;
- private List<Integer> oldMenu, currentMenu;
-
- RunScore(int score, List<Integer> oldMenu, List<Integer> currentMenu) {
- setScore(score);
- setOldMenu(oldMenu);
- setCurrentMenu(currentMenu);
- }
-
- private void setCurrentMenu(List<Integer> currentMenu) {
- this.currentMenu = currentMenu;
- }
-
- List<Integer> getCurrentMenu() {
- return currentMenu;
- }
-
- private void setOldMenu(List<Integer> oldMenu) {
- this.oldMenu = oldMenu;
- }
-
- List<Integer> getOldMenu() {
- return oldMenu;
- }
-
- private void setScore(int score) {
- this.score = score;
- }
-
- public int getScore() {
- return score;
- }
-
+/**
+ * Created by Bilal Alsharifi on 1/21/21.
+ */
+interface MenuManagerCompletionListener {
+ void onComplete(boolean success, List<MenuCell> currentMenuCells);
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java
new file mode 100644
index 000000000..7db1a85b9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (c) 2021 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.screen.menu;
+
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName;
+import static com.smartdevicelink.managers.screen.menu.BaseMenuManager.parentIdNotFound;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.addCellWithCellId;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.addIdsToMenuCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.cloneMenuCellsList;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.commandIdForRPCRequest;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.deleteCommandsForCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.findAllArtworksToBeUploadedFromCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.mainMenuCommandsForCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.positionForRPCRequest;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.removeCellFromList;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.sendRPCs;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.subMenuCommandsForCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.transferCellIDsFromCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.transferCellListenersFromCells;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.CompletionListener;
+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.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
+import com.smartdevicelink.util.DebugTool;
+
+import org.json.JSONException;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Bilal Alsharifi on 1/20/21.
+ */
+class MenuReplaceOperation extends Task {
+ private static final String TAG = "MenuReplaceOperation";
+
+ private final WeakReference<ISdl> internalInterface;
+ private final WeakReference<FileManager> fileManager;
+ private WindowCapability windowCapability;
+ private List<MenuCell> currentMenu;
+ private final List<MenuCell> updatedMenu;
+ private final boolean isDynamicMenuUpdateActive;
+ private final MenuManagerCompletionListener operationCompletionListener;
+ private MenuConfiguration menuConfiguration;
+
+ MenuReplaceOperation(ISdl internalInterface, FileManager fileManager, WindowCapability windowCapability, MenuConfiguration menuConfiguration, List<MenuCell> currentMenu, List<MenuCell> updatedMenu, boolean isDynamicMenuUpdateActive, MenuManagerCompletionListener operationCompletionListener) {
+ super(TAG);
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ this.windowCapability = windowCapability;
+ this.menuConfiguration = menuConfiguration;
+ this.currentMenu = currentMenu;
+ this.updatedMenu = updatedMenu;
+ this.isDynamicMenuUpdateActive = isDynamicMenuUpdateActive;
+ this.operationCompletionListener = operationCompletionListener;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ updateMenuCells(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ finishOperation(success);
+ }
+ });
+ }
+
+ private void updateMenuCells(final CompletionListener listener) {
+ addIdsToMenuCells(updatedMenu, parentIdNotFound);
+
+ // Strip the "current menu" and the new menu of properties that are not displayed on the head unit
+ List<MenuCell> updatedStrippedMenu = cellsWithRemovedPropertiesFromCells(updatedMenu, windowCapability);
+ List<MenuCell> currentStrippedMenu = cellsWithRemovedPropertiesFromCells(currentMenu, windowCapability);
+
+ // Check if head unit supports cells with duplicate titles
+ SdlMsgVersion rpcVersion = internalInterface.get().getSdlMsgVersion();
+ boolean supportsMenuUniqueness = rpcVersion.getMajorVersion() > 7 || (rpcVersion.getMajorVersion() == 7 && rpcVersion.getMinorVersion() > 0);
+
+ // Generate unique names and ensure that all menus we are tracking have them so that we can properly compare when using the dynamic algorithm
+ generateUniqueNamesForCells(updatedStrippedMenu, supportsMenuUniqueness);
+ applyUniqueNamesOnCells(updatedStrippedMenu, updatedMenu);
+
+ DynamicMenuUpdateRunScore runScore;
+ if (!isDynamicMenuUpdateActive) {
+ DebugTool.logInfo(TAG, "Dynamic menu update inactive. Forcing the deletion of all old cells and adding all new ones, even if they're the same.");
+ runScore = DynamicMenuUpdateAlgorithm.compatibilityRunScoreWithOldMenuCells(currentStrippedMenu, updatedStrippedMenu);
+ } else {
+ DebugTool.logInfo(TAG, "Dynamic menu update active. Running the algorithm to find the best way to delete / add cells.");
+ runScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(currentStrippedMenu, updatedStrippedMenu);
+ }
+
+ // If both old and new menu cells are empty, nothing needs to be done.
+ if (runScore.isEmpty()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ List<MenuCellState> deleteMenuStatus = runScore.getOldStatus();
+ List<MenuCellState> addMenuStatus = runScore.getUpdatedStatus();
+
+ // Drop the cells into buckets based on the run score
+ final List<MenuCell> cellsToDelete = filterMenuCellsWithStatusList(currentMenu, deleteMenuStatus, MenuCellState.DELETE);
+ final List<MenuCell> cellsToAdd = filterMenuCellsWithStatusList(updatedMenu, addMenuStatus, MenuCellState.ADD);
+
+ // These lists should ONLY contain KEEPS. These will be used for SubMenu compares
+ final List<MenuCell> oldKeeps = filterMenuCellsWithStatusList(currentMenu, deleteMenuStatus, MenuCellState.KEEP);
+ final List<MenuCell> newKeeps = filterMenuCellsWithStatusList(updatedMenu, addMenuStatus, MenuCellState.KEEP);
+
+ // Old kept cells ids need to be moved to the new kept cells so that submenu changes have correct parent ids
+ transferCellIDsFromCells(oldKeeps, newKeeps);
+
+ // Transfer new cells' listeners to the old cells, which are stored in the current menu
+ transferCellListenersFromCells(newKeeps, oldKeeps);
+
+ // Upload the Artworks, then we will start updating the main menu
+ uploadMenuArtworks(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ return;
+ }
+
+ updateMenuWithCellsToDelete(cellsToDelete, cellsToAdd, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ return;
+ }
+
+ updateSubMenuWithOldKeptCells(oldKeeps, newKeeps, 0, listener);
+ }
+ });
+ }
+ });
+ }
+
+ private void uploadMenuArtworks(final CompletionListener listener) {
+ List<SdlArtwork> artworksToBeUploaded = new ArrayList<>(findAllArtworksToBeUploadedFromCells(updatedMenu, fileManager.get(), windowCapability));
+ if (artworksToBeUploaded.isEmpty()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ if (fileManager.get() == null) {
+ listener.onComplete(false);
+ return;
+ }
+
+ fileManager.get().uploadArtworks(artworksToBeUploaded, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(Map<String, String> errors) {
+ if (errors != null && !errors.isEmpty()) {
+ DebugTool.logError(TAG, "Error uploading Menu Artworks: " + errors.toString());
+ listener.onComplete(false);
+ } else {
+ DebugTool.logInfo(TAG, "Menu artwork upload completed, beginning upload of main menu");
+ listener.onComplete(true);
+ }
+ }
+ });
+ }
+
+ /**
+ * Takes the main menu cells to delete and add, and deletes the current menu cells, then adds the new menu cells in the correct locations
+ *
+ * @param deleteCells The cells that need to be deleted
+ * @param addCells The cells that need to be added
+ * @param listener A CompletionListener called when complete
+ */
+ private void updateMenuWithCellsToDelete(List<MenuCell> deleteCells, final List<MenuCell> addCells, final CompletionListener listener) {
+ sendDeleteMenuCells(deleteCells, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ sendAddMenuCells(addCells, updatedMenu, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (!success) {
+ DebugTool.logError(TAG, "Error Sending Current Menu");
+ }
+
+ listener.onComplete(success);
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Takes the submenu cells that are old keeps and new keeps and determines which cells need to be deleted or added
+ *
+ * @param oldKeptCells The old kept cells
+ * @param newKeptCells The new kept cells
+ * @param index The index of the main menu to use
+ * @param listener The listener to call when all submenu updates are complete
+ */
+ private void updateSubMenuWithOldKeptCells(final List<MenuCell> oldKeptCells, final List<MenuCell> newKeptCells, final int index, final CompletionListener listener) {
+ if (oldKeptCells.isEmpty() || index >= oldKeptCells.size()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ if (oldKeptCells.get(index) != null && oldKeptCells.get(index).isSubMenuCell() && !oldKeptCells.get(index).getSubCells().isEmpty()) {
+ DynamicMenuUpdateRunScore tempScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(oldKeptCells.get(index).getSubCells(), newKeptCells.get(index).getSubCells());
+
+ // If both old and new menu cells are empty. Then nothing needs to be done.
+ if (tempScore.isEmpty()) {
+ // After the first set of submenu cells were added and deleted we must find the next set of sub cells until we loop through all the elements
+ updateSubMenuWithOldKeptCells(oldKeptCells, newKeptCells, index + 1, listener);
+ return;
+ }
+
+ List<MenuCellState> deleteMenuStatus = tempScore.getOldStatus();
+ List<MenuCellState> addMenuStatus = tempScore.getUpdatedStatus();
+
+ final List<MenuCell> cellsToDelete = filterMenuCellsWithStatusList(oldKeptCells.get(index).getSubCells(), deleteMenuStatus, MenuCellState.DELETE);
+ final List<MenuCell> cellsToAdd = filterMenuCellsWithStatusList(newKeptCells.get(index).getSubCells(), addMenuStatus, MenuCellState.ADD);
+
+ final List<MenuCell> oldSubcellKeeps = filterMenuCellsWithStatusList(oldKeptCells.get(index).getSubCells(), deleteMenuStatus, MenuCellState.KEEP);
+ final List<MenuCell> newSubcellKeeps = filterMenuCellsWithStatusList(newKeptCells.get(index).getSubCells(), addMenuStatus, MenuCellState.KEEP);
+
+ transferCellListenersFromCells(newSubcellKeeps, oldSubcellKeeps);
+
+ sendDeleteMenuCells(cellsToDelete, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ }
+
+ sendAddMenuCells(cellsToAdd, newKeptCells.get(index).getSubCells(), new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ }
+
+ // After the first set of submenu cells were added and deleted we must find the next set of sub cells until we loop through all the elements
+ updateSubMenuWithOldKeptCells(oldKeptCells, newKeptCells, index + 1, listener);
+ }
+ });
+ }
+ });
+ } else {
+ // There are no sub cells, we can skip to the next index.
+ updateSubMenuWithOldKeptCells(oldKeptCells, newKeptCells, index + 1, listener);
+ }
+ }
+
+ /**
+ * Send Delete RPCs for given menu cells
+ *
+ * @param deleteMenuCells The menu cells to be deleted
+ * @param listener A CompletionListener called when the RPCs are finished with an error if any failed
+ */
+ private void sendDeleteMenuCells(List<MenuCell> deleteMenuCells, final CompletionListener listener) {
+ if (deleteMenuCells == null || deleteMenuCells.isEmpty()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ List<RPCRequest> deleteMenuCommands = deleteCommandsForCells(deleteMenuCells);
+ sendRPCs(deleteMenuCommands, internalInterface.get(), new SendingRPCsCompletionListener() {
+ @Override
+ public void onComplete(boolean success, Map<RPCRequest, String> errors) {
+ if (!success) {
+ DebugTool.logWarning(TAG, "Unable to delete all old menu commands. " + convertErrorsMapToString(errors));
+ } else {
+ DebugTool.logInfo(TAG, "Finished deleting old menu");
+ }
+ listener.onComplete(success);
+ }
+
+ @Override
+ public void onResponse(RPCRequest request, RPCResponse response) {
+ if (response.getSuccess()) {
+ // Find the id of the successful request and remove it from the current menu list wherever it may have been
+ int commandId = commandIdForRPCRequest(request);
+ removeCellFromList(currentMenu, commandId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Send Add RPCs for given new menu cells compared to old menu cells
+ *
+ * @param addMenuCells The new menu cells we want displayed
+ * @param fullMenu The full menu from which the addMenuCells come. This allows us to grab the positions from that menu for the new cells
+ * @param listener A CompletionListener called when the RPCs are finished with an error if any failed
+ */
+ private void sendAddMenuCells(final List<MenuCell> addMenuCells, final List<MenuCell> fullMenu, final CompletionListener listener) {
+ if (addMenuCells == null || addMenuCells.isEmpty()) {
+ DebugTool.logInfo(TAG, "There are no cells to update.");
+ listener.onComplete(true);
+ return;
+ }
+
+ MenuLayout defaultSubmenuLayout = menuConfiguration != null ? menuConfiguration.getSubMenuLayout() : null;
+
+ // RPCs for cells on the main menu level. They could be AddCommands or AddSubMenus depending on whether the cell has child cells or not.
+ final List<RPCRequest> mainMenuCommands = mainMenuCommandsForCells(addMenuCells, fileManager.get(), fullMenu, windowCapability, defaultSubmenuLayout);
+
+ // RPCs for cells on the second menu level (one level deep). They could be AddCommands or AddSubMenus.
+ final List<RPCRequest> subMenuCommands = subMenuCommandsForCells(addMenuCells, fileManager.get(), windowCapability, defaultSubmenuLayout);
+
+ sendRPCs(mainMenuCommands, internalInterface.get(), new SendingRPCsCompletionListener() {
+ @Override
+ public void onComplete(boolean success, Map<RPCRequest, String> errors) {
+ if (!success) {
+ DebugTool.logError(TAG, "Failed to send main menu commands. " + convertErrorsMapToString(errors));
+ listener.onComplete(false);
+ return;
+ } else if (subMenuCommands.isEmpty()) {
+ DebugTool.logInfo(TAG, "Finished sending new cells");
+ listener.onComplete(true);
+ return;
+ }
+
+ sendRPCs(subMenuCommands, internalInterface.get(), new SendingRPCsCompletionListener() {
+ @Override
+ public void onComplete(boolean success, Map<RPCRequest, String> errors) {
+ if (!success) {
+ DebugTool.logError(TAG, "Failed to send sub menu commands. " + convertErrorsMapToString(errors));
+ } else {
+ DebugTool.logInfo(TAG, "Finished sending new cells");
+ }
+ listener.onComplete(success);
+ }
+
+ @Override
+ public void onResponse(RPCRequest request, RPCResponse response) {
+ if (response.getSuccess()) {
+ // Find the id of the successful request and add it from the current menu list wherever it needs to be
+ int commandId = commandIdForRPCRequest(request);
+ int position = positionForRPCRequest(request);
+ addCellWithCellId(commandId, position, addMenuCells, currentMenu);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onResponse(RPCRequest request, RPCResponse response) {
+ if (response.getSuccess()) {
+ // Find the id of the successful request and add it from the current menu list wherever it needs to be
+ int commandId = commandIdForRPCRequest(request);
+ int position = positionForRPCRequest(request);
+ addCellWithCellId(commandId, position, addMenuCells, currentMenu);
+ }
+ }
+ });
+ }
+
+ private List<MenuCell> filterMenuCellsWithStatusList(List<MenuCell> menuCells, List<MenuCellState> statusList, MenuCellState menuCellState) {
+ List<MenuCell> filteredCells = new ArrayList<>();
+ for (int index = 0; index < statusList.size(); index++) {
+ if (statusList.get(index).equals(menuCellState)) {
+ filteredCells.add(menuCells.get(index));
+ }
+ }
+ return filteredCells;
+ }
+
+ private String convertErrorsMapToString(Map<RPCRequest, String> errors) {
+ if (errors == null) {
+ return null;
+ }
+ StringBuilder stringBuilder = new StringBuilder();
+ for (RPCRequest request : errors.keySet()) {
+ stringBuilder.append(errors.get(request));
+ stringBuilder.append(System.getProperty("line.separator"));
+ try {
+ stringBuilder.append(request.serializeJSON().toString(4));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ stringBuilder.append(System.getProperty("line.separator"));
+ }
+ return stringBuilder.toString();
+ }
+
+ List<MenuCell> cellsWithRemovedPropertiesFromCells(List<MenuCell> cells, WindowCapability windowCapability) {
+ if (cells == null) {
+ return null;
+ }
+
+ List<MenuCell> removePropertiesClone = cloneMenuCellsList(cells);
+
+ for (MenuCell cell : removePropertiesClone) {
+ // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
+ cell.setVoiceCommands(null);
+
+ // Don't check ImageFieldName.subMenuIcon because it was added in 7.0 when the feature was added in 5.0.
+ // Just assume that if cmdIcon is not available, the submenu icon is not either.
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.cmdIcon)) {
+ cell.setIcon(null);
+ }
+
+ // Check for subMenu fields supported
+ if (cell.isSubMenuCell()) {
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuSecondaryText)) {
+ cell.setSecondaryText(null);
+ }
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuTertiaryText)) {
+ cell.setTertiaryText(null);
+ }
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.menuSubMenuSecondaryImage)) {
+ cell.setSecondaryArtwork(null);
+ }
+ cell.setSubCells(cellsWithRemovedPropertiesFromCells(cell.getSubCells(), windowCapability));
+ } else {
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuCommandSecondaryText)) {
+ cell.setSecondaryText(null);
+ }
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuCommandTertiaryText)) {
+ cell.setTertiaryText(null);
+ }
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.menuCommandSecondaryImage)) {
+ cell.setSecondaryArtwork(null);
+ }
+ }
+ }
+ return removePropertiesClone;
+ }
+
+ private void generateUniqueNamesForCells(List<MenuCell> menuCells, boolean supportsMenuUniqueness) {
+ if (menuCells == null) {
+ return;
+ }
+
+ // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
+ HashMap<String, Integer> dictCounter = new HashMap<>();
+
+ for (MenuCell cell : menuCells) {
+ String key = supportsMenuUniqueness ? String.valueOf(cell.hashCode()) : cell.getTitle();
+ Integer counter = dictCounter.get(key);
+
+ if (counter != null) {
+ dictCounter.put(key, ++counter);
+ cell.setUniqueTitle(cell.getTitle() + " (" + counter + ")");
+ } else {
+ dictCounter.put(key, 1);
+ cell.setUniqueTitle(cell.getTitle());
+ }
+
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ generateUniqueNamesForCells(cell.getSubCells(), supportsMenuUniqueness);
+ }
+ }
+ }
+
+ private void applyUniqueNamesOnCells(List<MenuCell> fromMenuCells, List<MenuCell> toMenuCells) {
+ if (fromMenuCells.size() != toMenuCells.size()) {
+ return;
+ }
+
+ for (int i = 0; i < fromMenuCells.size(); i++) {
+ toMenuCells.get(i).setUniqueTitle(fromMenuCells.get(i).getUniqueTitle());
+ if (fromMenuCells.get(i).isSubMenuCell() && !fromMenuCells.get(i).getSubCells().isEmpty()) {
+ applyUniqueNamesOnCells(fromMenuCells.get(i).getSubCells(), toMenuCells.get(i).getSubCells());
+ }
+ }
+ }
+
+ void setMenuConfiguration(MenuConfiguration menuConfiguration) {
+ this.menuConfiguration = menuConfiguration;
+ }
+
+ void setCurrentMenu(List<MenuCell> currentMenuCells) {
+ this.currentMenu = currentMenuCells;
+ }
+
+ void setWindowCapability(WindowCapability windowCapability) {
+ this.windowCapability = windowCapability;
+ }
+
+ private void finishOperation(boolean success) {
+ if (operationCompletionListener != null) {
+ operationCompletionListener.onComplete(success, currentMenu);
+ }
+ onFinished();
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java
new file mode 100644
index 000000000..210b24ba9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2021 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.screen.menu;
+
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName;
+import static com.smartdevicelink.managers.screen.menu.BaseMenuManager.parentIdNotFound;
+
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.AddCommand;
+import com.smartdevicelink.proxy.rpc.AddSubMenu;
+import com.smartdevicelink.proxy.rpc.DeleteCommand;
+import com.smartdevicelink.proxy.rpc.DeleteSubMenu;
+import com.smartdevicelink.proxy.rpc.Image;
+import com.smartdevicelink.proxy.rpc.MenuParams;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Created by Bilal Alsharifi on 1/25/21.
+ */
+class MenuReplaceUtilities {
+ private static final String TAG = "MenuReplaceUtilities";
+ private static int menuId = 0;
+
+ static int getNextMenuId() {
+ return ++menuId;
+ }
+
+ /**
+ * Assign cell ids on a list of menu cells given a parent id (or no parent id)
+ *
+ * @param menuCells The list of menu cells to update
+ * @param parentId The parent id to assign if needed
+ */
+ static void addIdsToMenuCells(List<MenuCell> menuCells, int parentId) {
+ for (MenuCell cell : menuCells) {
+ cell.setCellId(getNextMenuId());
+ if (parentId != parentIdNotFound) {
+ cell.setParentCellId(parentId);
+ }
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ addIdsToMenuCells(cell.getSubCells(), cell.getCellId());
+ }
+ }
+ }
+
+ static void transferCellIDsFromCells(List<MenuCell> fromCells, List<MenuCell> toCells) {
+ if (fromCells == null || toCells == null || fromCells.isEmpty() || fromCells.size() != toCells.size()) {
+ return;
+ }
+ for (int i = 0; i < toCells.size(); i++) {
+ toCells.get(i).setCellId(fromCells.get(i).getCellId());
+ }
+
+ // Update parent ids
+ for (MenuCell cell : toCells) {
+ if (!cell.isSubMenuCell()) {
+ continue;
+ }
+
+ for (MenuCell subCell : cell.getSubCells()) {
+ subCell.setParentCellId(cell.getCellId());
+ }
+ }
+ }
+
+ static void transferCellListenersFromCells(List<MenuCell> fromCells, List<MenuCell> toCells) {
+ if (fromCells == null || toCells == null || fromCells.isEmpty() || fromCells.size() != toCells.size()) {
+ return;
+ }
+ for (int i = 0; i < fromCells.size(); i++) {
+ toCells.get(i).setMenuSelectionListener(fromCells.get(i).getMenuSelectionListener());
+ }
+ }
+
+ static Set<SdlArtwork> findAllArtworksToBeUploadedFromCells(List<MenuCell> cells, FileManager fileManager, WindowCapability windowCapability) {
+ // Make sure we can use images in the menus
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.cmdIcon)) {
+ return new HashSet<>();
+ }
+
+ Set<SdlArtwork> artworks = new HashSet<>();
+ for (MenuCell cell : cells) {
+ if (fileManager != null) {
+ if (fileManager.fileNeedsUpload(cell.getIcon())) {
+ artworks.add(cell.getIcon());
+ }
+ if (hasImageFieldOfName(windowCapability, ImageFieldName.menuCommandSecondaryImage) && fileManager.fileNeedsUpload(cell.getSecondaryArtwork())) {
+ artworks.add(cell.getSecondaryArtwork());
+ }
+ }
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ artworks.addAll(findAllArtworksToBeUploadedFromCells(cell.getSubCells(), fileManager, windowCapability));
+ }
+ }
+
+ return artworks;
+ }
+
+ // If there is an icon and the icon has been uploaded, or if the icon is a static icon, it should include the image
+ static boolean shouldCellIncludePrimaryImageFromCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability) {
+ boolean supportsImage = cell.isSubMenuCell() ? hasImageFieldOfName(windowCapability, ImageFieldName.subMenuIcon) : hasImageFieldOfName(windowCapability, ImageFieldName.cmdIcon);
+ return cell.getIcon() != null && supportsImage && (fileManager.hasUploadedFile(cell.getIcon()) || cell.getIcon().isStaticIcon());
+ }
+
+ // If there is an icon and the icon has been uploaded, or if the icon is a static icon, it should include the image
+ static boolean shouldCellIncludeSecondaryImageFromCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability) {
+ boolean supportsImage = cell.isSubMenuCell() ? hasImageFieldOfName(windowCapability, ImageFieldName.menuSubMenuSecondaryImage) : hasImageFieldOfName(windowCapability, ImageFieldName.menuCommandSecondaryImage);
+ return cell.getSecondaryArtwork() != null && supportsImage && (fileManager.hasUploadedFile(cell.getSecondaryArtwork()) || cell.getSecondaryArtwork().isStaticIcon());
+ }
+
+ static int commandIdForRPCRequest(RPCRequest request) {
+ int commandId = 0;
+ if (request instanceof AddCommand) {
+ commandId = ((AddCommand) request).getCmdID();
+ } else if (request instanceof AddSubMenu) {
+ commandId = ((AddSubMenu) request).getMenuID();
+ } else if (request instanceof DeleteCommand) {
+ commandId = ((DeleteCommand) request).getCmdID();
+ } else if (request instanceof DeleteSubMenu) {
+ commandId = ((DeleteSubMenu) request).getMenuID();
+ }
+ return commandId;
+ }
+
+ static int positionForRPCRequest(RPCRequest request) {
+ int position = 0;
+ if (request instanceof AddCommand) {
+ position = ((AddCommand) request).getMenuParams().getPosition();
+ } else if (request instanceof AddSubMenu) {
+ position = ((AddSubMenu) request).getPosition();
+ }
+ return position;
+ }
+
+ static List<RPCRequest> deleteCommandsForCells(List<MenuCell> cells) {
+ List<RPCRequest> deletes = new ArrayList<>();
+ for (MenuCell cell : cells) {
+ if (cell.isSubMenuCell()) {
+ DeleteSubMenu delete = new DeleteSubMenu(cell.getCellId());
+ deletes.add(delete);
+ } else {
+ DeleteCommand delete = new DeleteCommand(cell.getCellId());
+ deletes.add(delete);
+ }
+ }
+ return deletes;
+ }
+
+ static List<RPCRequest> mainMenuCommandsForCells(List<MenuCell> cells, FileManager fileManager, List<MenuCell> menu, WindowCapability windowCapability, MenuLayout defaultSubmenuLayout) {
+ List<RPCRequest> commands = new ArrayList<>();
+
+ // We need the index to use it as position so we will use this type of loop
+ for (int menuInteger = 0; menuInteger < menu.size(); menuInteger++) {
+ MenuCell mainCell = menu.get(menuInteger);
+ for (int updateCellsIndex = 0; updateCellsIndex < cells.size(); updateCellsIndex++) {
+ MenuCell addCell = cells.get(updateCellsIndex);
+ if (mainCell.equals(addCell)) {
+ if (addCell.isSubMenuCell()) {
+ commands.add(subMenuCommandForMenuCell(addCell, fileManager, windowCapability, menuInteger, defaultSubmenuLayout));
+ } else {
+ commands.add(commandForMenuCell(addCell, fileManager, windowCapability, menuInteger));
+ }
+ break;
+ }
+ }
+ }
+ return commands;
+ }
+
+ static List<RPCRequest> subMenuCommandsForCells(List<MenuCell> cells, FileManager fileManager, WindowCapability windowCapability, MenuLayout defaultSubmenuLayout) {
+ List<RPCRequest> commands = new ArrayList<>();
+ for (MenuCell cell : cells) {
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ commands.addAll(allCommandsForCells(cell.getSubCells(), fileManager, windowCapability, defaultSubmenuLayout));
+ }
+ }
+ return commands;
+ }
+
+ static List<RPCRequest> allCommandsForCells(List<MenuCell> cells, FileManager fileManager, WindowCapability windowCapability, MenuLayout defaultSubmenuLayout) {
+ List<RPCRequest> commands = new ArrayList<>();
+
+ for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
+ MenuCell cell = cells.get(cellIndex);
+ if (cell.isSubMenuCell()) {
+ commands.add(subMenuCommandForMenuCell(cell, fileManager, windowCapability, cellIndex, defaultSubmenuLayout));
+
+ // Recursively grab the commands for all the sub cells
+ if (!cell.getSubCells().isEmpty()) {
+ commands.addAll(allCommandsForCells(cell.getSubCells(), fileManager, windowCapability, defaultSubmenuLayout));
+ }
+ } else {
+ commands.add(commandForMenuCell(cell, fileManager, windowCapability, cellIndex));
+ }
+ }
+ return commands;
+ }
+
+ static AddCommand commandForMenuCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability, int position) {
+ AddCommand command = new AddCommand(cell.getCellId());
+
+ MenuParams params = new MenuParams(cell.getUniqueTitle());
+ params.setSecondaryText((cell.getSecondaryText() != null && !cell.getSecondaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuCommandSecondaryText)) ? cell.getSecondaryText() : null);
+ params.setTertiaryText((cell.getTertiaryText() != null && !cell.getTertiaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuCommandTertiaryText)) ? cell.getTertiaryText() : null);
+ params.setParentID(cell.getParentCellId() != parentIdNotFound ? cell.getParentCellId() : null);
+ params.setPosition(position);
+
+ command.setMenuParams(params);
+ if (cell.getVoiceCommands() != null && !cell.getVoiceCommands().isEmpty()) {
+ command.setVrCommands(cell.getVoiceCommands());
+ } else {
+ command.setVrCommands(null);
+ }
+ boolean shouldCellIncludePrimaryImage = cell.getIcon() != null && shouldCellIncludePrimaryImageFromCell(cell, fileManager, windowCapability);
+ command.setCmdIcon(shouldCellIncludePrimaryImage ? cell.getIcon().getImageRPC() : null);
+
+ boolean shouldCellIncludeSecondaryImage = cell.getSecondaryArtwork() != null && shouldCellIncludeSecondaryImageFromCell(cell, fileManager, windowCapability);
+ command.setSecondaryImage(shouldCellIncludeSecondaryImage ? cell.getSecondaryArtwork().getImageRPC() : null);
+
+ return command;
+ }
+
+ static AddSubMenu subMenuCommandForMenuCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability, int position, MenuLayout defaultSubmenuLayout) {
+ boolean shouldCellIncludePrimaryImage = cell.getIcon() != null && cell.getIcon().getImageRPC() != null && shouldCellIncludePrimaryImageFromCell(cell, fileManager, windowCapability);
+ Image icon = (shouldCellIncludePrimaryImage ? cell.getIcon().getImageRPC() : null);
+ boolean shouldCellIncludeSecondaryImage = cell.getSecondaryArtwork() != null && cell.getSecondaryArtwork().getImageRPC() != null && shouldCellIncludeSecondaryImageFromCell(cell, fileManager, windowCapability);
+ Image secondaryIcon = (shouldCellIncludeSecondaryImage ? cell.getSecondaryArtwork().getImageRPC() : null);
+
+ if (cell.getVoiceCommands() != null && !cell.getVoiceCommands().isEmpty()) {
+ DebugTool.logWarning(TAG, "Setting voice commands for submenu cells is not supported. The voice commands will not be set.");
+ }
+
+ MenuLayout submenuLayout;
+ List<MenuLayout> availableMenuLayouts = windowCapability != null ? windowCapability.getMenuLayoutsAvailable() : null;
+ if (cell.getSubMenuLayout() != null && availableMenuLayouts != null && availableMenuLayouts.contains(cell.getSubMenuLayout())) {
+ submenuLayout = cell.getSubMenuLayout();
+ } else {
+ submenuLayout = defaultSubmenuLayout;
+ }
+
+ return new AddSubMenu(cell.getCellId(), cell.getUniqueTitle())
+ .setParentID(cell.getParentCellId() != parentIdNotFound ? cell.getParentCellId() : null)
+ .setSecondaryText((cell.getSecondaryText() != null && !cell.getSecondaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuSecondaryText)) ? cell.getSecondaryText() : null)
+ .setTertiaryText((cell.getTertiaryText() != null && !cell.getTertiaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuTertiaryText)) ? cell.getTertiaryText() : null)
+ .setPosition(position)
+ .setMenuLayout(submenuLayout)
+ .setMenuIcon(icon)
+ .setSecondaryImage(secondaryIcon);
+ }
+
+ static boolean removeCellFromList(List<MenuCell> menuCellList, int commandId) {
+ for (MenuCell menuCell : menuCellList) {
+ if (menuCell.getCellId() == commandId) {
+ // If the cell id matches the command id, remove it from the list and return
+ menuCellList.remove(menuCell);
+ return true;
+ } else if (menuCell.isSubMenuCell() && !menuCell.getSubCells().isEmpty()) {
+ // If the menu cell has sub cells, we need to recurse and check the sub cells
+ List<MenuCell> newList = menuCell.getSubCells();
+ boolean foundAndRemovedItem = removeCellFromList(newList, commandId);
+ if (foundAndRemovedItem) {
+ menuCell.setSubCells(newList);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static boolean addCellWithCellId(int cellId, int position, List<MenuCell> newMenuList, List<MenuCell> mainMenuList) {
+ MenuCell addedCell = null;
+ for (MenuCell cell : newMenuList) {
+ if (cell.getCellId() == cellId) {
+ addedCell = cell;
+ break;
+ } else if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ boolean success = addCellWithCellId(cellId, position, cell.getSubCells(), mainMenuList);
+ if (success) {
+ return true;
+ }
+ }
+ }
+ if (addedCell != null) {
+ return addMenuCell(addedCell, mainMenuList, position);
+ }
+ return false;
+ }
+
+ private static boolean addMenuCell(MenuCell cell, List<MenuCell> menuCellList, int position) {
+ if (cell.getParentCellId() == parentIdNotFound) {
+ // The cell does not have a parent id, just insert it into the main menu
+ insertMenuCell(cell, menuCellList, position);
+ return true;
+ }
+
+ // If the cell has a parent id, we need to find the cell with a matching cell id and insert it into its submenu
+ for (MenuCell menuCell : menuCellList) {
+ if (menuCell.getCellId() == cell.getParentCellId()) {
+ if (menuCell.getSubCells() == null) {
+ menuCell.setSubCells(new ArrayList<MenuCell>());
+ }
+ // If we found the correct submenu, insert it into that submenu
+ insertMenuCell(cell, menuCell.getSubCells(), position);
+ return true;
+ } else if (menuCell.isSubMenuCell() && !menuCell.getSubCells().isEmpty()) {
+ // Check the sub cells of this cell to see if any of those have cell ids that match the parent cell id
+ List<MenuCell> newList = menuCell.getSubCells();
+ boolean foundAndAddedItem = addMenuCell(cell, newList, position);
+ if (foundAndAddedItem) {
+ menuCell.setSubCells(newList);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static void insertMenuCell(MenuCell cell, List<MenuCell> cellList, int position) {
+ MenuCell cellToInsert = cell;
+ if (cellToInsert.isSubMenuCell()) {
+ // We should not add the subCells automatically when adding a parent cell
+ cellToInsert = cell.clone();
+ cellToInsert.getSubCells().clear();
+ }
+ if (position > cellList.size()) {
+ cellList.add(cellToInsert);
+ } else {
+ cellList.add(position, cellToInsert);
+ }
+ }
+
+ static List<MenuCell> cloneMenuCellsList(List<MenuCell> originalList) {
+ if (originalList == null) {
+ return new ArrayList<>();
+ }
+
+ List<MenuCell> clone = new ArrayList<>();
+ for (MenuCell menuCell : originalList) {
+ clone.add(menuCell.clone());
+ }
+ return clone;
+ }
+
+ static void sendRPCs(final List<RPCRequest> requests, ISdl internalInterface, final SendingRPCsCompletionListener listener) {
+ final Map<RPCRequest, String> errors = new HashMap<>();
+ if (requests == null || requests.isEmpty()) {
+ listener.onComplete(true, errors);
+ return;
+ }
+
+ internalInterface.sendRPCs(requests, new OnMultipleRequestListener() {
+ @Override
+ public void onUpdate(int remainingRequests) {
+ }
+
+ @Override
+ public void onFinished() {
+ listener.onComplete(errors.isEmpty(), errors);
+ }
+
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ RPCRequest request = null;
+ for (RPCRequest r : requests) {
+ if (response.getCorrelationID().equals(r.getCorrelationID())) {
+ request = r;
+ break;
+ }
+ }
+ if (!response.getSuccess()) {
+ errors.put(request, "Failed to send RPC. Result: " + response.getResultCode() + ". Info: " + response.getInfo());
+ }
+ listener.onResponse(request, response);
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java
new file mode 100644
index 000000000..a685ca966
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2021 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.screen.menu;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.ShowAppMenu;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by Bilal Alsharifi on 1/21/21.
+ */
+class MenuShowOperation extends Task {
+ private static final String TAG = "MenuShowOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private final MenuCell submenuCell;
+
+ MenuShowOperation(ISdl internalInterface, MenuCell menuCell) {
+ super(TAG);
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.submenuCell = menuCell;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ Integer menuId = submenuCell != null ? submenuCell.getCellId() : null;
+ sendShowAppMenu(menuId);
+ }
+
+ private void sendShowAppMenu(Integer id) {
+ ShowAppMenu showAppMenu = new ShowAppMenu();
+ showAppMenu.setMenuID(id);
+ showAppMenu.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ DebugTool.logInfo(TAG, "Successfully opened application menu");
+ } else {
+ DebugTool.logError(TAG, "Open Menu Request Failed. Result code: " + response.getResultCode() + ". Info: " + response.getInfo());
+ }
+ onFinished();
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(showAppMenu);
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/SendingRPCsCompletionListener.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/SendingRPCsCompletionListener.java
new file mode 100644
index 000000000..1339df1eb
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/SendingRPCsCompletionListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2021 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.screen.menu;
+
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+
+import java.util.Map;
+
+/**
+ * Created by Bilal Alsharifi on 1/29/21.
+ */
+interface SendingRPCsCompletionListener {
+ void onComplete(boolean success, Map<RPCRequest, String> errors);
+ void onResponse(RPCRequest request, RPCResponse response);
+}
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
index 59caffbdb..0650ab388 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java
@@ -1,3 +1,35 @@
+/*
+ * Copyright (c) 2021 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.screen.menu;
import com.livio.taskmaster.Task;
diff --git a/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java b/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java
index 9694d3f06..fdbba048d 100644
--- a/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java
+++ b/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java
@@ -264,10 +264,15 @@ class BaseSdlPacket {
this.priorityCoefficient = priority;
}
+ @Deprecated
public int getPrioirtyCoefficient() {
return this.priorityCoefficient;
}
+ public int getPriorityCoefficient() {
+ return this.priorityCoefficient;
+ }
+
public void setTransportRecord(TransportRecord transportRecord) {
this.transportRecord = transportRecord;
}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java b/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
index 8a1968f36..9e2ab38d0 100644
--- a/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
+++ b/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
@@ -182,7 +182,12 @@ public class ProtocolMessage {
this.priorityCoefficient = priority;
}
+ @Deprecated
public int getPrioirtyCoefficient() {
return this.priorityCoefficient;
}
+
+ public int getPriorityCoefficient() {
+ return this.priorityCoefficient;
+ }
} // end-class \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
index 5cac1302c..f7dd797d0 100644
--- a/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
+++ b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
@@ -89,7 +89,7 @@ public class SdlProtocolBase {
public static final int V2_HEADER_SIZE = 12;
//If increasing MAX PROTOCOL VERSION major version, make sure to alter it in SdlPsm
- private static final Version MAX_PROTOCOL_VERSION = new Version(5, 4, 0);
+ private static final Version MAX_PROTOCOL_VERSION = new Version(5, 4, 1);
public static final int V1_V2_MTU_SIZE = 1500;
public static final int V3_V4_MTU_SIZE = 131072;
@@ -572,11 +572,7 @@ public class SdlProtocolBase {
if (sessionType.eq(SessionType.CONTROL)) {
final byte[] secureData = protocolMsg.getData().clone();
data = new byte[headerSize + secureData.length];
-
- final BinaryFrameHeader binFrameHeader =
- SdlPacketFactory.createBinaryFrameHeader(protocolMsg.getRPCType(), protocolMsg.getFunctionID(), protocolMsg.getCorrID(), 0);
- System.arraycopy(binFrameHeader.assembleHeaderBytes(), 0, data, 0, headerSize);
- System.arraycopy(secureData, 0, data, headerSize, secureData.length);
+ System.arraycopy(secureData, 0, data, 0, secureData.length);
} else if (protocolMsg.getBulkData() != null) {
data = new byte[12 + protocolMsg.getJsonSize() + protocolMsg.getBulkData().length];
sessionType = SessionType.BULK_DATA;
@@ -1566,5 +1562,17 @@ public class SdlProtocolBase {
} // end-method
} // end-class
+ /**
+ * This method will return copy of active transports
+ *
+ * @return a list of active transports
+ * */
+ public List<TransportRecord> getActiveTransports() {
+ List<TransportRecord> records = new ArrayList<>();
+ for (TransportRecord record : activeTransports.values()) {
+ records.add(new TransportRecord(record.getType(), record.getAddress()));
+ }
+ return records;
+ }
}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java b/base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java
new file mode 100644
index 000000000..c7bc1326d
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java
@@ -0,0 +1,157 @@
+package com.smartdevicelink.protocol;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.protocol.enums.SecurityQueryErrorCode;
+import com.smartdevicelink.protocol.enums.SecurityQueryID;
+import com.smartdevicelink.protocol.enums.SecurityQueryType;
+import com.smartdevicelink.util.BitConverter;
+import com.smartdevicelink.util.DebugTool;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryPayload {
+ private static final String TAG = "BinaryQueryHeader";
+
+ private SecurityQueryType _securityQueryType;
+ private SecurityQueryID _securityQueryID;
+ private int _correlationID;
+ private int _jsonSize;
+ private SecurityQueryErrorCode _errorCode;
+
+ private byte[] _jsonData = null;
+ private byte[] _bulkData = null;
+
+ private static final int SECURITY_QUERY_HEADER_SIZE = 12;
+
+ public SecurityQueryPayload() {
+ }
+
+ public static SecurityQueryPayload parseBinaryQueryHeader(byte[] binHeader) {
+ if (binHeader == null || binHeader.length < SECURITY_QUERY_HEADER_SIZE) {
+ DebugTool.logError(TAG, "Security Payload error: not enough data to form a Security Query Header. Data length: " + (binHeader != null ? binHeader.length : "null"));
+ return null;
+ }
+
+ SecurityQueryPayload msg = new SecurityQueryPayload();
+
+ //Set QueryType from the first 8 bits
+ byte QUERY_Type = (byte) (binHeader[0]);
+ msg.setQueryType(SecurityQueryType.valueOf(QUERY_Type));
+
+ //Set queryID from the last 24 bits of the first 32 bits
+ byte[] _queryID = new byte[3];
+ System.arraycopy(binHeader, 1, _queryID, 0, 3);
+ msg.setQueryID(SecurityQueryID.valueOf(_queryID));
+
+ //set correlationID from the 32 bits after the first 32 bits
+ int corrID = BitConverter.intFromByteArray(binHeader, 4);
+ msg.setCorrelationID(corrID);
+
+ //set jsonSize from the last 32 bits after the first 64 bits
+ int _jsonSize = BitConverter.intFromByteArray(binHeader, 8);
+ msg.setJsonSize(_jsonSize);
+
+ //If we get an error message we want the error code from the last 8 bits
+ if (msg.getQueryType() == SecurityQueryType.NOTIFICATION && msg.getQueryID() == SecurityQueryID.SEND_INTERNAL_ERROR) {
+ msg.setErrorCode(SecurityQueryErrorCode.valueOf(binHeader[binHeader.length - 1]));
+ }
+
+ try {
+ //Get the JsonData after the header (after 96 bits) based on the jsonData size
+ if (_jsonSize > 0 && _jsonSize <= (binHeader.length - SECURITY_QUERY_HEADER_SIZE)) {
+ byte[] _jsonData = new byte[_jsonSize];
+ System.arraycopy(binHeader, SECURITY_QUERY_HEADER_SIZE, _jsonData, 0, _jsonSize);
+ msg.setJsonData(_jsonData);
+ }
+
+ //Get the binaryData after the header (after 96 bits) and the jsonData size
+ if (binHeader.length - _jsonSize - SECURITY_QUERY_HEADER_SIZE > 0) {
+ byte[] _bulkData;
+ if (msg.getQueryType() == SecurityQueryType.NOTIFICATION && msg.getQueryID() == SecurityQueryID.SEND_INTERNAL_ERROR) {
+ _bulkData = new byte[binHeader.length - _jsonSize - SECURITY_QUERY_HEADER_SIZE - 1];
+ } else {
+ _bulkData = new byte[binHeader.length - _jsonSize - SECURITY_QUERY_HEADER_SIZE];
+ }
+ System.arraycopy(binHeader, SECURITY_QUERY_HEADER_SIZE + _jsonSize, _bulkData, 0, _bulkData.length);
+ msg.setBulkData(_bulkData);
+ }
+
+ } catch (OutOfMemoryError | ArrayIndexOutOfBoundsException e) {
+ DebugTool.logError(TAG, "Unable to process data to form header");
+ return null;
+ }
+
+ return msg;
+ }
+
+ public byte[] assembleHeaderBytes() {
+ // From the properties, create a data buffer
+ // Query Type - first 8 bits
+ // Query ID - next 24 bits
+ // Sequence Number - next 32 bits
+ // JSON size - next 32 bits
+ byte[] ret = new byte[SECURITY_QUERY_HEADER_SIZE];
+ ret[0] = _securityQueryType.getValue();
+ System.arraycopy(_securityQueryID.getValue(), 0, ret, 1, 3);
+ System.arraycopy(BitConverter.intToByteArray(_correlationID), 0, ret, 4, 4);
+ System.arraycopy(BitConverter.intToByteArray(_jsonSize), 0, ret, 8, 4);
+ return ret;
+ }
+
+ public SecurityQueryType getQueryType() {
+ return _securityQueryType;
+ }
+
+ public void setQueryType(SecurityQueryType _securityQueryType) {
+ this._securityQueryType = _securityQueryType;
+ }
+
+ public SecurityQueryID getQueryID() {
+ return _securityQueryID;
+ }
+
+ public void setQueryID(SecurityQueryID _securityQueryID) {
+ this._securityQueryID = _securityQueryID;
+ }
+
+ public int getCorrelationID() {
+ return _correlationID;
+ }
+
+ public void setCorrelationID(int _correlationID) {
+ this._correlationID = _correlationID;
+ }
+
+ public int getJsonSize() {
+ return _jsonSize;
+ }
+
+ public void setJsonSize(int _jsonSize) {
+ this._jsonSize = _jsonSize;
+ }
+
+ public SecurityQueryErrorCode getErrorCode() {
+ return _errorCode;
+ }
+
+ public void setErrorCode(SecurityQueryErrorCode _errorCode) {
+ this._errorCode = _errorCode;
+ }
+
+ public byte[] getJsonData() {
+ return _jsonData;
+ }
+
+ public void setJsonData(byte[] _jsonData) {
+ this._jsonData = new byte[this._jsonSize];
+ System.arraycopy(_jsonData, 0, this._jsonData, 0, _jsonSize);
+ }
+
+ public byte[] getBulkData() {
+ return _bulkData;
+ }
+
+ public void setBulkData(byte[] _bulkData) {
+ this._bulkData = _bulkData;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java
new file mode 100644
index 000000000..5601271d3
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java
@@ -0,0 +1,61 @@
+package com.smartdevicelink.protocol.enums;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.util.ByteEnumer;
+
+import java.util.Vector;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryErrorCode extends ByteEnumer {
+
+ private static final Vector<SecurityQueryErrorCode> theList = new Vector<>();
+
+ public static Vector<SecurityQueryErrorCode> getList() {
+ return theList;
+ }
+
+ protected SecurityQueryErrorCode(byte value, String name) {
+ super(value, name);
+ }
+
+ public final static SecurityQueryErrorCode ERROR_SUCCESS = new SecurityQueryErrorCode((byte) 0x00, "ERROR_SUCCESS");
+ public final static SecurityQueryErrorCode ERROR_INVALID_QUERY_SIZE = new SecurityQueryErrorCode((byte) 0x01, "ERROR_INVALID_QUERY_SIZE");
+ public final static SecurityQueryErrorCode ERROR_INVALID_QUERY_ID = new SecurityQueryErrorCode((byte) 0x02, "ERROR_INVALID_QUERY_ID");
+ public final static SecurityQueryErrorCode ERROR_NOT_SUPPORTED = new SecurityQueryErrorCode((byte) 0x03, "ERROR_NOT_SUPPORTED");
+ public final static SecurityQueryErrorCode ERROR_SERVICE_ALREADY_PROTECTED = new SecurityQueryErrorCode((byte) 0x04, "ERROR_SERVICE_ALREADY_PROTECTED");
+ public final static SecurityQueryErrorCode ERROR_SERVICE_NOT_PROTECTED = new SecurityQueryErrorCode((byte) 0x05, "ERROR_SERVICE_NOT_PROTECTED");
+ public final static SecurityQueryErrorCode ERROR_DECRYPTION_FAILED = new SecurityQueryErrorCode((byte) 0x06, "ERROR_DECRYPTION_FAILED");
+ public final static SecurityQueryErrorCode ERROR_ENCRYPTION_FAILED = new SecurityQueryErrorCode((byte) 0x07, "ERROR_ENCRYPTION_FAILED");
+ public final static SecurityQueryErrorCode ERROR_SSL_INVALID_DATA = new SecurityQueryErrorCode((byte) 0x08, "ERROR_SSL_INVALID_DATA");
+ public final static SecurityQueryErrorCode ERROR_HANDSHAKE_FAILED = new SecurityQueryErrorCode((byte) 0x09, "ERROR_HANDSHAKE_FAILED");
+ public final static SecurityQueryErrorCode INVALID_CERT = new SecurityQueryErrorCode((byte) 0x0A, "INVALID_CERT");
+ public final static SecurityQueryErrorCode EXPIRED_CERT = new SecurityQueryErrorCode((byte) 0x0B, "EXPIRED_CERT");
+ public final static SecurityQueryErrorCode ERROR_INTERNAL = new SecurityQueryErrorCode((byte) 0xFF, "ERROR_INTERNAL");
+ public final static SecurityQueryErrorCode ERROR_UNKNOWN_INTERNAL_ERROR = new SecurityQueryErrorCode((byte) 0xFE, "ERROR_UNKNOWN_INTERNAL_ERROR");
+
+ static {
+ theList.addElement(ERROR_SUCCESS);
+ theList.addElement(ERROR_INVALID_QUERY_SIZE);
+ theList.addElement(ERROR_INVALID_QUERY_ID);
+ theList.addElement(ERROR_NOT_SUPPORTED);
+ theList.addElement(ERROR_SERVICE_ALREADY_PROTECTED);
+ theList.addElement(ERROR_SERVICE_NOT_PROTECTED);
+ theList.addElement(ERROR_DECRYPTION_FAILED);
+ theList.addElement(ERROR_ENCRYPTION_FAILED);
+ theList.addElement(ERROR_SSL_INVALID_DATA);
+ theList.addElement(ERROR_HANDSHAKE_FAILED);
+ theList.addElement(INVALID_CERT);
+ theList.addElement(EXPIRED_CERT);
+ theList.addElement(ERROR_INTERNAL);
+ theList.addElement(ERROR_UNKNOWN_INTERNAL_ERROR);
+ }
+
+ public static SecurityQueryErrorCode valueOf(byte passedByte) {
+ return (SecurityQueryErrorCode) get(theList, passedByte);
+ }
+
+ public static SecurityQueryErrorCode[] values() {
+ return theList.toArray(new SecurityQueryErrorCode[theList.size()]);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java
new file mode 100644
index 000000000..c1b799ec9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java
@@ -0,0 +1,105 @@
+package com.smartdevicelink.protocol.enums;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.util.BitConverter;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Objects;
+import java.util.Vector;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryID {
+
+ private static final Vector<SecurityQueryID> theList = new Vector<>();
+
+ public static Vector<SecurityQueryID> getList() {
+ return theList;
+ }
+
+ private static final byte[] sendHandshakeDataByteArray = {(byte) 0x00, (byte) 0x00, (byte) 0x01};
+ private static final byte[] sendInternalErrorByteArray = {(byte) 0x00, (byte) 0x00, (byte) 0x02};
+ private static final byte[] invalidQueryIdByteArray = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
+ public final static SecurityQueryID SEND_HANDSHAKE_DATA = new SecurityQueryID(sendHandshakeDataByteArray, "SEND_HANDSHAKE_DATA");
+ public final static SecurityQueryID SEND_INTERNAL_ERROR = new SecurityQueryID(sendInternalErrorByteArray, "SEND_INTERNAL_ERROR");
+ public final static SecurityQueryID INVALID_QUERY_ID = new SecurityQueryID(invalidQueryIdByteArray, "INVALID_QUERY_ID");
+
+ static {
+ theList.addElement(SEND_HANDSHAKE_DATA);
+ theList.addElement(SEND_INTERNAL_ERROR);
+ theList.addElement(INVALID_QUERY_ID);
+ }
+
+ protected SecurityQueryID(byte[] value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ private final byte[] value;
+ private final String name;
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public int getIntValue() {
+ byte[] copy = new byte[4];
+ System.arraycopy(value, 0, copy, 1, 3);
+ return BitConverter.intFromByteArray(copy, 0);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean equals(SecurityQueryID other) {
+ return Objects.equals(name, other.getName());
+ }
+
+ public boolean eq(SecurityQueryID other) {
+ return equals(other);
+ }
+
+ public byte[] value() {
+ return value;
+ }
+
+ public static SecurityQueryID get(Vector<?> theList, byte[] value) {
+ Enumeration<?> enumer = theList.elements();
+ while (enumer.hasMoreElements()) {
+ try {
+ SecurityQueryID current = (SecurityQueryID) enumer.nextElement();
+ if (Arrays.equals(current.getValue(), value)) {
+ return current;
+ }
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public static SecurityQueryID get(Vector<?> theList, String name) {
+ Enumeration<?> enumer = theList.elements();
+ while (enumer.hasMoreElements()) {
+ try {
+ SecurityQueryID current = (SecurityQueryID) enumer.nextElement();
+ if (current.getName().equals(name)) {
+ return current;
+ }
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public static SecurityQueryID valueOf(byte[] passedByteArray) {
+ return (SecurityQueryID) get(theList, passedByteArray);
+ }
+
+ public static SecurityQueryID[] values() {
+ return theList.toArray(new SecurityQueryID[theList.size()]);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java
new file mode 100644
index 000000000..bb021e79c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java
@@ -0,0 +1,41 @@
+package com.smartdevicelink.protocol.enums;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.util.ByteEnumer;
+
+import java.util.Vector;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryType extends ByteEnumer {
+
+ private static final Vector<SecurityQueryType> theList = new Vector<>();
+
+ public static Vector<SecurityQueryType> getList() {
+ return theList;
+ }
+
+ protected SecurityQueryType(byte value, String name) {
+ super(value, name);
+ }
+
+ public final static SecurityQueryType REQUEST = new SecurityQueryType((byte) 0x00, "REQUEST");
+ public final static SecurityQueryType RESPONSE = new SecurityQueryType((byte) 0x10, "RESPONSE");
+ public final static SecurityQueryType NOTIFICATION = new SecurityQueryType((byte) 0x20, "NOTIFICATION");
+ public final static SecurityQueryType INVALID_QUERY_TYPE = new SecurityQueryType((byte) 0xFF, "INVALID_QUERY_TYPE");
+
+ static {
+ theList.addElement(REQUEST);
+ theList.addElement(RESPONSE);
+ theList.addElement(NOTIFICATION);
+ theList.addElement(INVALID_QUERY_TYPE);
+ }
+
+ public static SecurityQueryType valueOf(byte passedByte) {
+ return (SecurityQueryType) get(theList, passedByte);
+ }
+
+ public static SecurityQueryType[] values() {
+ return theList.toArray(new SecurityQueryType[theList.size()]);
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java
index 7ead85fef..edff66488 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java
@@ -64,7 +64,7 @@ public class AudioControlCapabilities extends RPCStruct {
/**
* Constructs a newly allocated AudioControlCapabilities object
*
- * @param moduleName short friendly name of the light control module.
+ * @param moduleName short friendly name of the audio control module.
*/
public AudioControlCapabilities(@NonNull String moduleName) {
this();
@@ -74,7 +74,7 @@ public class AudioControlCapabilities extends RPCStruct {
/**
* Sets the moduleName portion of the AudioControlCapabilities class
*
- * @param moduleName The short friendly name of the light control module. It should not be used to identify a module by mobile application.
+ * @param moduleName The short friendly name of the audio control module. It should not be used to identify a module by mobile application.
*/
public AudioControlCapabilities setModuleName(@NonNull String moduleName) {
setValue(KEY_MODULE_NAME, moduleName);
@@ -84,7 +84,7 @@ public class AudioControlCapabilities extends RPCStruct {
/**
* Gets the moduleName portion of the AudioControlCapabilities class
*
- * @return String - The short friendly name of the light control module. It should not be used to identify a module by mobile application.
+ * @return String - The short friendly name of the audio control module. It should not be used to identify a module by mobile application.
*/
public String getModuleName() {
return getString(KEY_MODULE_NAME);
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java
index 05dccbc88..a120e66bc 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java
@@ -68,7 +68,7 @@ public class RadioControlCapabilities extends RPCStruct {
/**
* Constructs a new RadioControlCapabilities object
*
- * @param moduleName The short friendly name of the climate control module.
+ * @param moduleName The short friendly name of the radio control module.
* It should not be used to identify a module by mobile application.
*/
public RadioControlCapabilities(@NonNull String moduleName) {
@@ -79,7 +79,7 @@ public class RadioControlCapabilities extends RPCStruct {
/**
* Sets the moduleName portion of the RadioControlCapabilities class
*
- * @param moduleName The short friendly name of the climate control module.
+ * @param moduleName The short friendly name of the radio control module.
* It should not be used to identify a module by mobile application.
*/
public RadioControlCapabilities setModuleName(@NonNull String moduleName) {
@@ -90,7 +90,7 @@ public class RadioControlCapabilities extends RPCStruct {
/**
* Gets the moduleName portion of the RadioControlCapabilities class
*
- * @return String - Short friendly name of the climate control module.
+ * @return String - Short friendly name of the radio control module.
*/
public String getModuleName() {
return getString(KEY_MODULE_NAME);
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java
index ef82a3686..cbb44fcdd 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java
@@ -75,7 +75,7 @@ public class SeatControlCapabilities extends RPCStruct {
/**
* Constructs a newly allocated SeatControlCapabilities object
*
- * @param moduleName short friendly name of the light control module.
+ * @param moduleName short friendly name of the seat control module.
*/
public SeatControlCapabilities(@NonNull String moduleName) {
this();
@@ -94,7 +94,7 @@ public class SeatControlCapabilities extends RPCStruct {
/**
* Sets the moduleName portion of the SeatControlCapabilities class
*
- * @param moduleName - The short friendly name of the light control module. It should not be used to identify a module by mobile application.
+ * @param moduleName - The short friendly name of the seat control module. It should not be used to identify a module by mobile application.
*/
public SeatControlCapabilities setModuleName(@NonNull String moduleName) {
setValue(KEY_MODULE_NAME, moduleName);
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 60b9c524f..099224638 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
@@ -72,7 +72,7 @@ import java.util.Hashtable;
* <td>endTime</td>
* <td>StartTime</td>
* <td> EndTime can be provided for "COUNTUP" and "COUNTDOWN"; to be used to calculate any visual progress bar (if not provided, this feature is ignored)
- * If endTime is greater then startTime for COUNTDOWN or less than startTime for COUNTUP, then the request will return an INVALID_DATA.
+ * If endTime is greater than startTime for COUNTDOWN or less than startTime for COUNTUP, then the request will return an INVALID_DATA.
* endTime will be ignored for "RESUME", and "CLEAR"
* endTime can be sent for "PAUSE", in which case it will update the paused endTime</td>
* <td>N</td>
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java
index 5962a04fd..e9bdcd905 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java
@@ -534,7 +534,7 @@ public class Show extends RPCRequest {
}
/**
- * Sets the the Soft buttons defined by the App
+ * Sets the Soft buttons defined by the App
*
* @param softButtons a List value representing the Soft buttons defined by the
* App
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java
index e930bf533..c39ef4703 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java
@@ -39,7 +39,7 @@ import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import java.util.Hashtable;
/**
- * Struct that indicates the a SystemCapabilityType and houses different structs to describe particular capabilities
+ * Struct that indicates the SystemCapabilityType and houses different structs to describe particular capabilities
*/
public class SystemCapability extends RPCStruct {
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java
index 5db768cb5..68c47cf50 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java
@@ -34,82 +34,131 @@ package com.smartdevicelink.proxy.rpc;
import androidx.annotation.NonNull;
import com.smartdevicelink.proxy.RPCStruct;
+import com.smartdevicelink.proxy.rpc.enums.ComponentVolumeStatus;
import com.smartdevicelink.proxy.rpc.enums.WarningLightStatus;
+import com.smartdevicelink.util.DebugTool;
import java.util.Hashtable;
/**
- * <p>The status and pressure of the tires.</p>
- * <p><b> Parameter List:</b></p>
+ * The status and pressure of the tires.
+ *
+ * <p><b>Parameter List</b></p>
*
* <table border="1" rules="all">
- * <tr>
- * <th>Param Name</th>
- * <th>Type</th>
- * <th>Description</th>
- * <th>Version</th>
- * </tr>
- * <tr>
- * <td>PressureTellTale</td>
- * <td>WarningLightStatus</td>
- * <td>Status of the Tire Pressure TellTale</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>LeftFront</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the left front tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>RightFront</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the right front tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>LeftRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the left rear tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>RightRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the right rear tire</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>InnerLeftRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the inner left rear tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>InnerRightRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the inner right rear tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * </table>
- * <p>
- * @since SmartDeviceLink 2.0
+ * <tr>
+ * <th>Param Name</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * <th>Required</th>
+ * <th>Notes</th>
+ * <th>Version Available</th>
+ * </tr>
+ * <tr>
+ * <td>pressureTelltale</td>
+ * <td>WarningLightStatus</td>
+ * <td>Status of the Tire Pressure Telltale. See WarningLightStatus.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>leftFront</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the left front tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ *
+ * </tr>
+ * <tr>
+ * <td>rightFront</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the right front tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>leftRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the left rear tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>rightRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the right rear tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>innerLeftRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the inner left rear.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>innerRightRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the inner right rear.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * </table>
*
- * @see WarningLightStatus
- * @see SingleTireStatus
- * @see GetVehicleData
- * @see OnVehicleData
+ * @since SmartDeviceLink 2.0.0
*/
-
public class TireStatus extends RPCStruct {
+ private static final String TAG = "TireStatus";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_PRESSURE_TELL_TALE = "pressureTelltale";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_LEFT_FRONT = "leftFront";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_RIGHT_FRONT = "rightFront";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_LEFT_REAR = "leftRear";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_INNER_LEFT_REAR = "innerLeftRear";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_INNER_RIGHT_REAR = "innerRightRear";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_RIGHT_REAR = "rightRear";
-
public TireStatus() {
}
@@ -125,7 +174,7 @@ public class TireStatus extends RPCStruct {
/**
* Constructs a new TireStatus object
*
- * @param pressureTellTale Status of the Tire Pressure TellTale
+ * @param pressureTelltale Status of the Tire Pressure TellTale
* @param leftFront The status of the left front tire.
* @param rightFront The status of the right front tire.
* @param leftRear The status of the left rear tire.
@@ -133,9 +182,9 @@ public class TireStatus extends RPCStruct {
* @param innerLeftRear The status of the inner left rear tire.
* @param innerRightRear The status of the inner right rear tire.
*/
- public TireStatus(@NonNull WarningLightStatus pressureTellTale, @NonNull SingleTireStatus leftFront, @NonNull SingleTireStatus rightFront, @NonNull SingleTireStatus leftRear, @NonNull SingleTireStatus rightRear, @NonNull SingleTireStatus innerLeftRear, @NonNull SingleTireStatus innerRightRear) {
+ public TireStatus(WarningLightStatus pressureTelltale, SingleTireStatus leftFront, SingleTireStatus rightFront, SingleTireStatus leftRear, SingleTireStatus rightRear, SingleTireStatus innerLeftRear, SingleTireStatus innerRightRear) {
this();
- setPressureTelltale(pressureTellTale);
+ setPressureTelltale(pressureTelltale);
setLeftFront(leftFront);
setRightFront(rightFront);
setLeftRear(leftRear);
@@ -176,60 +225,181 @@ public class TireStatus extends RPCStruct {
* @return the status of the tire pressure Telltale.
*/
public WarningLightStatus getPressureTelltale() {
- return (WarningLightStatus) getObject(WarningLightStatus.class, KEY_PRESSURE_TELL_TALE);
+ WarningLightStatus warningLightStatus = (WarningLightStatus) getObject(WarningLightStatus.class, KEY_PRESSURE_TELL_TALE);
+ if (warningLightStatus == null) {
+ WarningLightStatus newWarningLightStatus = WarningLightStatus.NOT_USED;
+ setValue(KEY_PRESSURE_TELL_TALE, newWarningLightStatus);
+ warningLightStatus = newWarningLightStatus;
+ DebugTool.logWarning(TAG, "TireStatus.pressureTelltale was null and will be set to .notUsed. In the future, this will change to be nullable.");
+ }
+ return warningLightStatus;
}
- public TireStatus setLeftFront(@NonNull SingleTireStatus leftFront) {
+ /**
+ * Sets the leftFront.
+ *
+ * @param leftFront The status of the left front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setLeftFront(SingleTireStatus leftFront) {
setValue(KEY_LEFT_FRONT, leftFront);
return this;
}
+ /**
+ * Gets the leftFront.
+ *
+ * @return SingleTireStatus The status of the left front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getLeftFront() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_FRONT);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_FRONT);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_LEFT_FRONT, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.leftFront was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setRightFront(@NonNull SingleTireStatus rightFront) {
+ /**
+ * Sets the rightFront.
+ *
+ * @param rightFront The status of the right front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setRightFront(SingleTireStatus rightFront) {
setValue(KEY_RIGHT_FRONT, rightFront);
return this;
}
+ /**
+ * Gets the rightFront.
+ *
+ * @return SingleTireStatus The status of the right front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getRightFront() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_FRONT);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_FRONT);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_RIGHT_FRONT, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.rightFront was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setLeftRear(@NonNull SingleTireStatus leftRear) {
+ /**
+ * Sets the leftRear.
+ *
+ * @param leftRear The status of the left rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setLeftRear(SingleTireStatus leftRear) {
setValue(KEY_LEFT_REAR, leftRear);
return this;
}
+ /**
+ * Gets the leftRear.
+ *
+ * @return SingleTireStatus The status of the left rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getLeftRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_LEFT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.leftRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setRightRear(@NonNull SingleTireStatus rightRear) {
+ /**
+ * Sets the rightRear.
+ *
+ * @param rightRear The status of the right rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setRightRear(SingleTireStatus rightRear) {
setValue(KEY_RIGHT_REAR, rightRear);
return this;
}
+ /**
+ * Gets the rightRear.
+ *
+ * @return SingleTireStatus The status of the right rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getRightRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_RIGHT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.rightRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setInnerLeftRear(@NonNull SingleTireStatus innerLeftRear) {
+ /**
+ * Sets the innerLeftRear.
+ *
+ * @param innerLeftRear The status of the inner left rear.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setInnerLeftRear(SingleTireStatus innerLeftRear) {
setValue(KEY_INNER_LEFT_REAR, innerLeftRear);
return this;
}
+ /**
+ * Gets the innerLeftRear.
+ *
+ * @return SingleTireStatus The status of the inner left rear.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getInnerLeftRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_LEFT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_LEFT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_INNER_LEFT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.innerLeftRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setInnerRightRear(@NonNull SingleTireStatus innerRightRear) {
+ /**
+ * Sets the innerRightRear.
+ *
+ * @param innerRightRear The status of the inner right rear.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setInnerRightRear(SingleTireStatus innerRightRear) {
setValue(KEY_INNER_RIGHT_REAR, innerRightRear);
return this;
}
+ /**
+ * Gets the innerRightRear.
+ *
+ * @return SingleTireStatus The status of the inner right rear.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getInnerRightRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_RIGHT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_RIGHT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_INNER_RIGHT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.innerRightRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java
index a05216661..8841126c3 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java
@@ -33,6 +33,7 @@ package com.smartdevicelink.proxy.rpc;
import com.smartdevicelink.proxy.RPCStruct;
+import java.util.HashMap;
import java.util.Hashtable;
/**
@@ -111,6 +112,18 @@ public class VehicleType extends RPCStruct {
}
/**
+ * <p>
+ * Constructs a new VehicleType object indicated by the Hashtable
+ * parameter
+ * </p>
+ *
+ * @param hash The Hashtable to use
+ */
+ public VehicleType(HashMap<String, Object> hash) {
+ super(new Hashtable<>(hash));
+ }
+
+ /**
* get the make of the vehicle
*
* @return the make of the vehicle
diff --git a/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java b/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java
index 8b35541b4..5fcc8d8ca 100644
--- a/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java
+++ b/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java
@@ -32,16 +32,20 @@
package com.smartdevicelink.session;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import com.smartdevicelink.exception.SdlException;
import com.smartdevicelink.managers.lifecycle.RpcConverter;
+import com.smartdevicelink.protocol.SecurityQueryPayload;
import com.smartdevicelink.protocol.ISdlProtocol;
import com.smartdevicelink.protocol.ISdlServiceListener;
import com.smartdevicelink.protocol.ProtocolMessage;
import com.smartdevicelink.protocol.SdlPacket;
import com.smartdevicelink.protocol.SdlProtocolBase;
import com.smartdevicelink.protocol.enums.ControlFrameTags;
+import com.smartdevicelink.protocol.enums.SecurityQueryID;
+import com.smartdevicelink.protocol.enums.SecurityQueryType;
import com.smartdevicelink.protocol.enums.SessionType;
import com.smartdevicelink.proxy.RPCMessage;
import com.smartdevicelink.proxy.rpc.VehicleType;
@@ -52,10 +56,12 @@ import com.smartdevicelink.security.SdlSecurityBase;
import com.smartdevicelink.streaming.video.VideoStreamingParameters;
import com.smartdevicelink.transport.BaseTransportConfig;
import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.TransportRecord;
import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.SystemInfo;
import com.smartdevicelink.util.Version;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
@@ -185,21 +191,76 @@ public abstract class BaseSdlSession implements ISdlProtocol, ISecurityInitializ
protected void processControlService(ProtocolMessage msg) {
- if (sdlSecurity == null)
+ if (sdlSecurity == null || msg.getData() == null)
return;
+
+
+ if (msg.getData().length < 12) {
+ DebugTool.logError(TAG, "Security message is malformed, less than 12 bytes. It does not have a security payload header.");
+ }
+ // Check the client's message header for any internal errors
+ // NOTE: Before Core v8.0.0, all these messages will be notifications. In Core v8.0.0 and later, received messages will have the proper query type. Therefore, we cannot do things based only on the query type being request or response.
+ SecurityQueryPayload receivedHeader = SecurityQueryPayload.parseBinaryQueryHeader(msg.getData().clone());
+ if (receivedHeader == null) {
+ DebugTool.logError(TAG, "Module Security Query could not convert to object.");
+ return;
+ }
+
int iLen = msg.getData().length - 12;
byte[] data = new byte[iLen];
System.arraycopy(msg.getData(), 12, data, 0, iLen);
byte[] dataToRead = new byte[4096];
- Integer iNumBytes = sdlSecurity.runHandshake(data, dataToRead);
+ Integer iNumBytes = null;
- if (iNumBytes == null || iNumBytes <= 0)
+ // If the query is of type `Notification` and the id represents a client internal error, we abort the response message and the encryptionManager will not be in state ready.
+ if (receivedHeader.getQueryID() == SecurityQueryID.SEND_INTERNAL_ERROR
+ && receivedHeader.getQueryType() == SecurityQueryType.NOTIFICATION) {
+ if (receivedHeader.getErrorCode() != null) {
+ DebugTool.logError(TAG, "Security Query module internal error: " + receivedHeader.getErrorCode().getName());
+ } else {
+ DebugTool.logError(TAG, "Security Query module error: No information provided");
+ }
return;
+ }
+
+ if (receivedHeader.getQueryID() != SecurityQueryID.SEND_HANDSHAKE_DATA) {
+ DebugTool.logError(TAG, "Security Query module error: Message is not a SEND_HANDSHAKE_DATA REQUEST");
+ return;
+ }
+
+ if (receivedHeader.getQueryType() == SecurityQueryType.RESPONSE) {
+ DebugTool.logError(TAG, "Security Query module error: Message is a response, which is not supported");
+ return;
+ }
+
+ iNumBytes = sdlSecurity.runHandshake(data, dataToRead);
+
+ // Assemble a security query payload header for our response
+ SecurityQueryPayload responseHeader = new SecurityQueryPayload();
+
+ byte[] returnBytes;
+ if (iNumBytes == null || iNumBytes <= 0) {
+ DebugTool.logError(TAG, "Internal Error processing control service");
+
+ responseHeader.setQueryID(SecurityQueryID.SEND_INTERNAL_ERROR);
+ responseHeader.setQueryType(SecurityQueryType.NOTIFICATION);
+ responseHeader.setCorrelationID(msg.getCorrID());
+ responseHeader.setJsonSize(0);
+ returnBytes = new byte[12];
+ } else {
+ responseHeader.setQueryID(SecurityQueryID.SEND_HANDSHAKE_DATA);
+ responseHeader.setQueryType(SecurityQueryType.RESPONSE);
+ responseHeader.setCorrelationID(msg.getCorrID());
+ responseHeader.setJsonSize(0);
+ returnBytes = new byte[iNumBytes + 12];
+ System.arraycopy(dataToRead, 0, returnBytes, 12, iNumBytes);
+ }
+
+
+ System.arraycopy(responseHeader.assembleHeaderBytes(), 0, returnBytes, 0, 12);
- byte[] returnBytes = new byte[iNumBytes];
- System.arraycopy(dataToRead, 0, returnBytes, 0, iNumBytes);
ProtocolMessage protocolMessage = new ProtocolMessage();
protocolMessage.setSessionType(SessionType.CONTROL);
protocolMessage.setData(returnBytes);
@@ -415,4 +476,18 @@ public abstract class BaseSdlSession implements ISdlProtocol, ISecurityInitializ
public boolean isTransportForServiceAvailable(SessionType sessionType) {
return sdlProtocol != null && sdlProtocol.isTransportForServiceAvailable(sessionType);
}
+
+ /**
+ * Retrieves list of the active transports
+ *
+ * @return a list of active transports
+ * */
+ @Nullable
+ public List<TransportRecord> getActiveTransports() {
+ if (this.sdlProtocol != null) {
+ return this.sdlProtocol.getActiveTransports();
+ } else {
+ return null;
+ }
+ }
}
diff --git a/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java b/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
index d2a45fcb9..79a055bc9 100644
--- a/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
+++ b/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
@@ -44,6 +44,7 @@ public class TransportConstants {
public static final String ROUTER_SERVICE_ACTION = "com.smartdevicelink.router.service";
public static final String FOREGROUND_EXTRA = "foreground";
public static final String CONFIRMED_SDL_DEVICE = "confirmed_sdl_device";
+ public static final String VEHICLE_INFO_EXTRA = "vehicle_info";
public static final String BIND_LOCATION_PACKAGE_NAME_EXTRA = "BIND_LOCATION_PACKAGE_NAME_EXTRA";
public static final String BIND_LOCATION_CLASS_NAME_EXTRA = "BIND_LOCATION_CLASS_NAME_EXTRA";
diff --git a/base/src/main/java/com/smartdevicelink/util/BitConverter.java b/base/src/main/java/com/smartdevicelink/util/BitConverter.java
index 94695802f..df507d6fe 100644
--- a/base/src/main/java/com/smartdevicelink/util/BitConverter.java
+++ b/base/src/main/java/com/smartdevicelink/util/BitConverter.java
@@ -163,7 +163,7 @@ public class BitConverter {
* Converts the byte array into a string of hex values.
*
* @param bytes byte array that will be converted to hex
- * @param end EXCLUSIVE so if it it receives 10 it will print 0-9
+ * @param end EXCLUSIVE so if it receives 10 it will print 0-9
* @return the String containing converted hex values or null if byte array is null
*/
public static String bytesToHex(byte[] bytes, int end) {