diff options
30 files changed, 2802 insertions, 114 deletions
diff --git a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java index 8767b70fa..e4119d16c 100755 --- a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java +++ b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java @@ -11,11 +11,13 @@ import android.os.Build; import android.os.IBinder; import android.util.Log; +import com.smartdevicelink.managers.AlertCompletionListener; import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.SdlManager; import com.smartdevicelink.managers.SdlManagerListener; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate; +import com.smartdevicelink.managers.screen.AlertView; import com.smartdevicelink.managers.screen.OnButtonListener; import com.smartdevicelink.managers.screen.choiceset.ChoiceCell; import com.smartdevicelink.managers.screen.choiceset.ChoiceSet; @@ -26,7 +28,6 @@ import com.smartdevicelink.managers.screen.menu.VoiceCommand; import com.smartdevicelink.managers.screen.menu.VoiceCommandSelectionListener; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.RPCNotification; -import com.smartdevicelink.proxy.rpc.Alert; import com.smartdevicelink.proxy.rpc.OnButtonEvent; import com.smartdevicelink.proxy.rpc.OnButtonPress; import com.smartdevicelink.proxy.rpc.OnHMIStatus; @@ -424,10 +425,16 @@ public class SdlService extends Service { } private void showAlert(String text) { - Alert alert = new Alert(); - alert.setAlertText1(text); - alert.setDuration(5000); - sdlManager.sendRPC(alert); + AlertView.Builder builder = new AlertView.Builder(); + builder.setText(text); + builder.setTimeout(5); + AlertView alertView = builder.build(); + sdlManager.getScreenManager().presentAlert(alertView, new AlertCompletionListener() { + @Override + public void onComplete(boolean success, Integer tryAgainTime) { + Log.i(TAG, "Alert presented: "+ success); + } + }); } // Choice Set diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/AlertViewTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/AlertViewTest.java new file mode 100644 index 000000000..041d0bbed --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/AlertViewTest.java @@ -0,0 +1,78 @@ +package com.smartdevicelink; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.screen.AlertAudioData; +import com.smartdevicelink.managers.screen.AlertView; +import com.smartdevicelink.managers.screen.SoftButtonObject; +import com.smartdevicelink.managers.screen.SoftButtonState; +import com.smartdevicelink.proxy.rpc.enums.FileType; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Collections; + +@RunWith(AndroidJUnit4.class) +public class AlertViewTest { + + @Test + public void testAlertView() { + SdlArtwork artwork1 = new SdlArtwork("test1", FileType.GRAPHIC_PNG, 1, true); + SdlArtwork artwork2 = new SdlArtwork("test2", FileType.GRAPHIC_PNG, 2, true); + + SoftButtonState softButtonState1 = new SoftButtonState("object1-state1", "o1s1", new SdlArtwork("image1", FileType.GRAPHIC_PNG, 3, true)); + SoftButtonObject softButtonObject1 = new SoftButtonObject("object1", Arrays.asList(softButtonState1), softButtonState1.getName(), null); + SoftButtonObject softButtonObject2 = new SoftButtonObject("object2", Arrays.asList(softButtonState1), softButtonState1.getName(), null); + + AlertAudioData alertAudioData = new AlertAudioData("hi"); + + AlertView.Builder builder = new AlertView.Builder(); + builder.setText("Test"); + builder.setTertiaryText("Test"); + builder.setSecondaryText("Test"); + builder.setTimeout(1); + builder.setIcon(artwork1); + builder.setSoftButtons(Collections.singletonList(softButtonObject1)); + builder.setDefaultTimeOut(3); + builder.setAudio(alertAudioData); + builder.setShowWaitIndicator(true); + AlertView alertView = builder.build(); + + assertEquals(alertView.getText(), "Test"); + assertEquals(alertView.getSecondaryText(), "Test"); + assertEquals(alertView.getTertiaryText(), "Test"); + assertTrue(alertView.getAudio().getAudioData().size() > 0); + assertEquals(alertView.getIcon().getName(), "test1"); + assertEquals(alertView.getSoftButtons().get(0).getName(), "object1"); + assertEquals(alertView.getDefaultTimeout(), 3); + assertEquals(alertView.getTimeout().intValue(), 3); + assertEquals(alertView.isShowWaitIndicator(), true); + + alertView.setText("Test2"); + alertView.setTertiaryText("Test2"); + alertView.setSecondaryText("Test2"); + alertView.setDefaultTimeout(6); + alertView.setTimeout(6); + alertView.setAudio(alertAudioData); + alertView.setIcon(artwork2); + alertView.setSoftButtons(Collections.singletonList(softButtonObject2)); + alertView.setShowWaitIndicator(false); + + assertEquals(alertView.getText(), "Test2"); + assertEquals(alertView.getSecondaryText(), "Test2"); + assertEquals(alertView.getTertiaryText(), "Test2"); + assertTrue(alertView.getAudio().getAudioData().size() > 0); + assertEquals(alertView.getIcon().getName(), "test2"); + assertEquals(alertView.getSoftButtons().get(0).getName(), "object2"); + assertEquals(alertView.getDefaultTimeout(), 6); + assertEquals(alertView.getTimeout().intValue(), 6); + assertEquals(alertView.isShowWaitIndicator(), false); + + } +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java index ca941fab6..db9ce11ac 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java @@ -7,6 +7,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.livio.taskmaster.Taskmaster; import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate; import com.smartdevicelink.managers.lockscreen.LockScreenConfig; +import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.RPCMessage; import com.smartdevicelink.proxy.RPCRequest; @@ -140,6 +141,10 @@ public class SdlManagerTests { // mock internalInterface and set it manually internalInterface = mock(ISdl.class); + manager.set_internalInterface(internalInterface); + PermissionManager permissionManager = mock(PermissionManager.class); + + when(internalInterface.getPermissionManager()).thenReturn(permissionManager); when(internalInterface.getTaskmaster()).thenReturn(new Taskmaster.Builder().build()); manager._internalInterface = internalInterface; diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java index 95977e857..64e4c1474 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java @@ -7,6 +7,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.livio.taskmaster.Taskmaster; import com.smartdevicelink.managers.ISdl; import com.smartdevicelink.managers.ManagerUtility; +import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.protocol.ISdlServiceListener; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.protocol.enums.SessionType; @@ -1066,5 +1067,10 @@ public class SystemCapabilityManagerTests { public SystemCapabilityManager getSystemCapabilityManager() { return null; } + + @Override + public PermissionManager getPermissionManager() { + return null; + } } } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/AlertAudioDataTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/AlertAudioDataTest.java new file mode 100644 index 000000000..3459c6e34 --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/AlertAudioDataTest.java @@ -0,0 +1,56 @@ +package com.smartdevicelink.managers.screen; + +import android.content.Context; +import android.net.Uri; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.smartdevicelink.managers.file.filetypes.SdlFile; +import com.smartdevicelink.proxy.rpc.enums.FileType; +import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.Collections; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class AlertAudioDataTest { + SdlFile testAudio; + + @Before + public void setUp() throws Exception { + Context mTestContext = getInstrumentation().getContext(); + Uri uri1 = Uri.parse("android.resource://" + mTestContext.getPackageName() + "/raw/test_audio_square_250hz_80amp_1s.mp3"); + testAudio = new SdlFile("TestAudioFile", FileType.AUDIO_MP3, uri1, false); + } + + @Test + public void testConstructors() { + AlertAudioData alertAudioData1 = new AlertAudioData("phoneticString", SpeechCapabilities.TEXT); + alertAudioData1.setPlayTone(true); + assertEquals("phoneticString", alertAudioData1.getAudioData().get(0).getText()); + assertTrue(alertAudioData1.isPlayTone()); + + AlertAudioData alertAudioData2 = new AlertAudioData("spokenString"); + assertEquals("spokenString", alertAudioData2.getAudioData().get(0).getText()); + + AlertAudioData alertAudioData3 = new AlertAudioData(testAudio); + assertEquals(alertAudioData3.getAudioData().get(0).getText(), testAudio.getName()); + } + + @Test + public void testAdd() { + AlertAudioData alertAudioData1 = new AlertAudioData("phoneticString", SpeechCapabilities.TEXT); + alertAudioData1.addAudioFiles(Collections.singletonList(testAudio)); + alertAudioData1.addPhoneticSpeechSynthesizerStrings(Collections.singletonList("addition"), SpeechCapabilities.TEXT); + alertAudioData1.addSpeechSynthesizerStrings(Collections.singletonList("addition2")); + alertAudioData1.addAudioFiles(Collections.singletonList(testAudio)); + assertEquals("phoneticString", alertAudioData1.getAudioData().get(0).getText()); + assertEquals(testAudio.getName(), alertAudioData1.getAudioData().get(1).getText()); + assertEquals("addition", alertAudioData1.getAudioData().get(2).getText()); + assertEquals("addition2", alertAudioData1.getAudioData().get(3).getText()); + } +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/AlertManagerTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/AlertManagerTest.java new file mode 100644 index 000000000..1655b3d08 --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/AlertManagerTest.java @@ -0,0 +1,166 @@ +package com.smartdevicelink.managers.screen; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.livio.taskmaster.Taskmaster; +import com.smartdevicelink.managers.AlertCompletionListener; +import com.smartdevicelink.managers.ISdl; +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.lifecycle.OnSystemCapabilityListener; +import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager; +import com.smartdevicelink.managers.permission.OnPermissionChangeListener; +import com.smartdevicelink.managers.permission.PermissionManager; +import com.smartdevicelink.managers.permission.PermissionStatus; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.rpc.DisplayCapability; +import com.smartdevicelink.proxy.rpc.ImageField; +import com.smartdevicelink.proxy.rpc.SoftButtonCapabilities; +import com.smartdevicelink.proxy.rpc.TextField; +import com.smartdevicelink.proxy.rpc.WindowCapability; +import com.smartdevicelink.proxy.rpc.enums.ImageFieldName; +import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.proxy.rpc.enums.TextFieldName; +import com.smartdevicelink.test.TestValues; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(AndroidJUnit4.class) +public class AlertManagerTest { + AlertManager alertManager; + + @Before + public void setUp() throws Exception { + // mock things + ISdl internalInterface = mock(ISdl.class); + FileManager fileManager = mock(FileManager.class); + PermissionManager permissionManager = mock(PermissionManager.class); + + when(internalInterface.getPermissionManager()).thenReturn(permissionManager); + + Taskmaster taskmaster = new Taskmaster.Builder().build(); + taskmaster.start(); + when(internalInterface.getTaskmaster()).thenReturn(taskmaster); + + Answer<Void> permissionAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + OnPermissionChangeListener onPermissionChangeListener = (OnPermissionChangeListener) args[2]; + Map<FunctionID, PermissionStatus > allowedPermissions = new HashMap<>(); + int permissionGroupStatus = PermissionManager.PERMISSION_GROUP_STATUS_DISALLOWED; + onPermissionChangeListener.onPermissionsChange(allowedPermissions,permissionGroupStatus); + return null; + } + }; + doAnswer(permissionAnswer).when(permissionManager).addListener(any(List.class), anyInt(), any(OnPermissionChangeListener.class)); + + Answer<Void> onSystemCapabilityAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + OnSystemCapabilityListener onSystemCapabilityListener = (OnSystemCapabilityListener) args[1]; + WindowCapability windowCapability = getWindowCapability(3); + DisplayCapability displayCapability = new DisplayCapability(); + displayCapability.setWindowCapabilities(Collections.singletonList(windowCapability)); + List<DisplayCapability> capabilities = Collections.singletonList(displayCapability); + onSystemCapabilityListener.onCapabilityRetrieved(capabilities); + return null; + } + }; + + SystemCapabilityManager systemCapabilityManager = mock(SystemCapabilityManager.class); + doAnswer(onSystemCapabilityAnswer).when(systemCapabilityManager).addOnSystemCapabilityListener(eq(SystemCapabilityType.DISPLAYS), any(OnSystemCapabilityListener.class)); + doReturn(systemCapabilityManager).when(internalInterface).getSystemCapabilityManager(); + + alertManager = new AlertManager(internalInterface, fileManager); + } + + @Test + public void testInstantiation() { + assertNotNull(alertManager.currentWindowCapability); + assertNotNull(alertManager.nextCancelId); + assertFalse(alertManager.isAlertRPCAllowed); + } + + @Test + public void testPresentAlert() { + AlertView.Builder builder = new AlertView.Builder(); + AlertView alertView = builder.build(); + alertManager.presentAlert(alertView, new AlertCompletionListener() { + @Override + public void onComplete(boolean success, Integer tryAgainTime) { + + } + }); + assertTrue(alertManager.transactionQueue.getTasksAsList().size() == 1); + } + + private WindowCapability getWindowCapability(int numberOfAlertFields) { + TextField alertText1 = new TextField(); + alertText1.setName(TextFieldName.alertText1); + TextField alertText2 = new TextField(); + alertText2.setName(TextFieldName.alertText2); + TextField alertText3 = new TextField(); + alertText3.setName(TextFieldName.alertText3); + TextField mainField4 = new TextField(); + mainField4.setName(TextFieldName.mainField4); + + List<TextField> textFieldList = new ArrayList<>(); + + textFieldList.add(alertText1); + textFieldList.add(alertText2); + textFieldList.add(alertText3); + + List<TextField> returnList = new ArrayList<>(); + + if (numberOfAlertFields > 0) { + for (int i = 0; i < numberOfAlertFields; i++) { + returnList.add(textFieldList.get(i)); + } + } + + WindowCapability windowCapability = new WindowCapability(); + windowCapability.setTextFields(returnList); + + ImageField imageField = new ImageField(); + imageField.setName(ImageFieldName.alertIcon); + List<ImageField> imageFieldList = new ArrayList<>(); + imageFieldList.add(imageField); + windowCapability.setImageFields(imageFieldList); + + windowCapability.setImageFields(imageFieldList); + + SoftButtonCapabilities softButtonCapabilities = new SoftButtonCapabilities(); + softButtonCapabilities.setImageSupported(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setShortPressAvailable(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setLongPressAvailable(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setUpDownAvailable(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setTextSupported(TestValues.GENERAL_BOOLEAN); + + windowCapability.setSoftButtonCapabilities(Collections.singletonList(softButtonCapabilities)); + return windowCapability; + } + +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/PresentAlertOperationTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/PresentAlertOperationTest.java new file mode 100644 index 000000000..1283992a2 --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/PresentAlertOperationTest.java @@ -0,0 +1,315 @@ +package com.smartdevicelink.managers.screen; + +import android.content.Context; +import android.net.Uri; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.livio.taskmaster.Task; +import com.smartdevicelink.managers.AlertCompletionListener; +import com.smartdevicelink.managers.ISdl; +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.file.MultipleFileCompletionListener; +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.file.filetypes.SdlFile; +import com.smartdevicelink.proxy.RPCRequest; +import com.smartdevicelink.proxy.rpc.Alert; +import com.smartdevicelink.proxy.rpc.AlertResponse; +import com.smartdevicelink.proxy.rpc.CancelInteraction; +import com.smartdevicelink.proxy.rpc.CancelInteractionResponse; +import com.smartdevicelink.proxy.rpc.ImageField; +import com.smartdevicelink.proxy.rpc.OnButtonEvent; +import com.smartdevicelink.proxy.rpc.OnButtonPress; +import com.smartdevicelink.proxy.rpc.SdlMsgVersion; +import com.smartdevicelink.proxy.rpc.SoftButtonCapabilities; +import com.smartdevicelink.proxy.rpc.TextField; +import com.smartdevicelink.proxy.rpc.WindowCapability; +import com.smartdevicelink.proxy.rpc.enums.FileType; +import com.smartdevicelink.proxy.rpc.enums.ImageFieldName; +import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities; +import com.smartdevicelink.proxy.rpc.enums.TextFieldName; +import com.smartdevicelink.test.TestValues; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(AndroidJUnit4.class) +public class PresentAlertOperationTest { + + private PresentAlertOperation presentAlertOperation; + private WindowCapability defaultMainWindowCapability; + private AlertView alertView; + private AlertAudioData alertAudioData; + SdlArtwork testAlertArtwork, testSoftButtonArtwork; + ISdl internalInterface; + FileManager fileManager; + SoftButtonState alertSoftButtonState; + SoftButtonObject alertSoftButtonObject; + private List<SpeechCapabilities> speechCapabilities; + SdlFile testAudio; + AlertCompletionListener alertCompletionListener; + BaseAlertManager.AlertSoftButtonClearListener alertSoftButtonClearListener; + + private Answer<Void> onArtworkUploadSuccess = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + MultipleFileCompletionListener listener = (MultipleFileCompletionListener) args[1]; + listener.onComplete(null); + return null; + } + }; + + private Answer<Void> onAlertSuccess = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + RPCRequest message = (RPCRequest) args[0]; + if (message instanceof Alert) { + int correlationId = message.getCorrelationID(); + AlertResponse alertResponse = new AlertResponse(); + alertResponse.setSuccess(true); + message.getOnRPCResponseListener().onResponse(correlationId, alertResponse); + } + return null; + } + }; + + private Answer<Void> onCancelAlert = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + RPCRequest message = (RPCRequest) args[0]; + if (message instanceof CancelInteraction) { + int correlationId = message.getCorrelationID(); + CancelInteractionResponse cancelInteraction = new CancelInteractionResponse(); + cancelInteraction.setSuccess(true); + message.getOnRPCResponseListener().onResponse(correlationId, cancelInteraction); + } + return null; + } + }; + + Task task; + @Before + public void setUp() throws Exception { + Context mTestContext = getInstrumentation().getContext(); + // mock things + internalInterface = mock(ISdl.class); + fileManager = mock(FileManager.class); + task = mock(Task.class); + + alertSoftButtonClearListener = new BaseAlertManager.AlertSoftButtonClearListener() { + @Override + public void onButtonClear(List<SoftButtonObject> softButtonObjects) { + + } + }; + testAlertArtwork = new SdlArtwork(); + testAlertArtwork.setName("testArtwork1"); + Uri uri1 = Uri.parse("android.resource://" + mTestContext.getPackageName() + "/drawable/ic_sdl"); + testAlertArtwork.setUri(uri1); + testAlertArtwork.setType(FileType.GRAPHIC_PNG); + + testSoftButtonArtwork = new SdlArtwork(); + Uri uri2 = Uri.parse("android.resource://" + mTestContext.getPackageName() + "drawable-hdpi/sdl_lockscreen_icon.png"); + testSoftButtonArtwork.setName("testArtwork2"); + testSoftButtonArtwork.setUri(uri2); + testSoftButtonArtwork.setType(FileType.GRAPHIC_PNG); + + Uri uri3 = Uri.parse("android.resource://" + mTestContext.getPackageName() + "/raw/test_audio_square_250hz_80amp_1s.mp3"); + testAudio = new SdlFile("TestAudioFile", FileType.AUDIO_MP3, uri3, false); + + alertAudioData = new AlertAudioData("Spoken Sting"); + alertAudioData.setPlayTone(true); + alertAudioData.addAudioFiles(Collections.singletonList(testAudio)); + + alertSoftButtonState = new SoftButtonState("state1", "State 1", testSoftButtonArtwork); + SoftButtonObject.OnEventListener onEventListener = new SoftButtonObject.OnEventListener() { + @Override + public void onPress(SoftButtonObject softButtonObject, OnButtonPress onButtonPress) { + + } + + @Override + public void onEvent(SoftButtonObject softButtonObject, OnButtonEvent onButtonEvent) { + + } + }; + alertSoftButtonObject = new SoftButtonObject("Soft button 1", alertSoftButtonState, onEventListener); + + AlertView.Builder builder = new AlertView.Builder(); + builder.setText("test"); + builder.setSecondaryText("secondaryText"); + builder.setTertiaryText("tertiaryText"); + builder.setAudio(alertAudioData); + builder.setIcon(testAlertArtwork); + builder.setDefaultTimeOut(10); + builder.setTimeout(5); + builder.setSoftButtons(Collections.singletonList(alertSoftButtonObject)); + builder.setShowWaitIndicator(true); + alertView = builder.build(); + + defaultMainWindowCapability = getWindowCapability(3); + speechCapabilities = new ArrayList<SpeechCapabilities>(); + speechCapabilities.add(SpeechCapabilities.FILE); + alertCompletionListener = new AlertCompletionListener() { + @Override + public void onComplete(boolean success, Integer tryAgainTime) { + + } + }; + presentAlertOperation = new PresentAlertOperation(internalInterface, alertView, defaultMainWindowCapability, speechCapabilities, fileManager, 1, alertCompletionListener, alertSoftButtonClearListener); + when(fileManager.fileNeedsUpload(any(SdlFile.class))).thenReturn(true); + } + + @Test + public void testPresentAlertTruncatedText() { + doAnswer(onAlertSuccess).when(internalInterface).sendRPC(any(Alert.class)); + // Same response works for uploading artworks as it does for files + + when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0)); + WindowCapability windowCapability = getWindowCapability(1); + PresentAlertOperation presentAlertOperation = new PresentAlertOperation(internalInterface, alertView, windowCapability, speechCapabilities, fileManager, 1, alertCompletionListener, alertSoftButtonClearListener); + Alert alert = presentAlertOperation.alertRpc(); + + assertEquals(alert.getAlertText1(), alertView.getText() + " - " + alertView.getSecondaryText() + " - " + alertView.getTertiaryText()); + + windowCapability = getWindowCapability(2); + + presentAlertOperation = new PresentAlertOperation(internalInterface, alertView, windowCapability, speechCapabilities, fileManager, 1, alertCompletionListener, alertSoftButtonClearListener); + alert = presentAlertOperation.alertRpc(); + assertEquals(alert.getAlertText1(), alertView.getText()); + assertEquals(alert.getAlertText2(),alertView.getSecondaryText() + " - " + alertView.getTertiaryText()); + } + + @Test + public void testPresentAlertHappyPath() { + doAnswer(onAlertSuccess).when(internalInterface).sendRPC(any(Alert.class)); + // Same response works for uploading artworks as it does for files + doAnswer(onArtworkUploadSuccess).when(fileManager).uploadArtworks(any(List.class), any(MultipleFileCompletionListener.class)); + doAnswer(onArtworkUploadSuccess).when(fileManager).uploadFiles(any(List.class), any(MultipleFileCompletionListener.class)); + when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0)); + + // Test Images need to be uploaded, sending text and uploading images + presentAlertOperation.onExecute(); + + // Verifies that uploadArtworks gets called only with the fist presentAlertOperation.onExecute call + verify(fileManager, times(1)).uploadArtworks(any(List.class), any(MultipleFileCompletionListener.class)); + + verify(fileManager, times(1)).uploadFiles(any(List.class), any(MultipleFileCompletionListener.class)); + + verify(internalInterface, times(1)).sendRPC(any(Alert.class)); + } + + @Test + public void testPresentAlertNoAudioAndArtwork() { + doAnswer(onAlertSuccess).when(internalInterface).sendRPC(any(Alert.class)); + + AlertView.Builder builder = new AlertView.Builder(); + builder.setText("Hi"); + builder.build(); + AlertView alertView1 = builder.build(); + + presentAlertOperation = new PresentAlertOperation(internalInterface, alertView1, defaultMainWindowCapability, speechCapabilities, fileManager, 2, alertCompletionListener, alertSoftButtonClearListener); + + when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0)); + + // Test Images need to be uploaded, sending text and uploading images + presentAlertOperation.onExecute(); + + // Verifies that uploadArtworks gets called only with the fist presentAlertOperation.onExecute call + verify(fileManager, times(0)).uploadArtworks(any(List.class), any(MultipleFileCompletionListener.class)); + verify(fileManager, times(0)).uploadFiles(any(List.class), any(MultipleFileCompletionListener.class)); + + verify(internalInterface, times(1)).sendRPC(any(Alert.class)); + } + + @Test + public void testPresentAlertNoImages() { + doAnswer(onAlertSuccess).when(internalInterface).sendRPC(any(Alert.class)); + // Same response works for uploading artworks as it does for files + doAnswer(onArtworkUploadSuccess).when(fileManager).uploadArtworks(any(List.class), any(MultipleFileCompletionListener.class)); + doAnswer(onArtworkUploadSuccess).when(fileManager).uploadFiles(any(List.class), any(MultipleFileCompletionListener.class)); + + when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0)); + + // Test Images need to be uploaded, sending text and uploading images + presentAlertOperation.onExecute(); + + // Verifies that uploadArtworks gets called only with the fist presentAlertOperation.onExecute call + verify(fileManager, times(1)).uploadArtworks(any(List.class), any(MultipleFileCompletionListener.class)); + verify(internalInterface, times(1)).sendRPC(any(Alert.class)); + } + + @Test + public void testCancelOperation() { + //Cancel right away + presentAlertOperation.cancelTask(); + presentAlertOperation.onExecute(); + verify(internalInterface, times(0)).sendRPC(any(Alert.class)); + } + + private WindowCapability getWindowCapability(int numberOfAlertFields) { + TextField alertText1 = new TextField(); + alertText1.setName(TextFieldName.alertText1); + TextField alertText2 = new TextField(); + alertText2.setName(TextFieldName.alertText2); + TextField alertText3 = new TextField(); + alertText3.setName(TextFieldName.alertText3); + TextField mainField4 = new TextField(); + mainField4.setName(TextFieldName.mainField4); + + List<TextField> textFieldList = new ArrayList<>(); + + textFieldList.add(alertText1); + textFieldList.add(alertText2); + textFieldList.add(alertText3); + + List<TextField> returnList = new ArrayList<>(); + + if (numberOfAlertFields > 0) { + for (int i = 0; i < numberOfAlertFields; i++) { + returnList.add(textFieldList.get(i)); + } + } + + WindowCapability windowCapability = new WindowCapability(); + windowCapability.setTextFields(returnList); + + ImageField imageField = new ImageField(); + imageField.setName(ImageFieldName.alertIcon); + List<ImageField> imageFieldList = new ArrayList<>(); + imageFieldList.add(imageField); + windowCapability.setImageFields(imageFieldList); + + windowCapability.setImageFields(imageFieldList); + + SoftButtonCapabilities softButtonCapabilities = new SoftButtonCapabilities(); + softButtonCapabilities.setImageSupported(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setShortPressAvailable(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setLongPressAvailable(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setUpDownAvailable(TestValues.GENERAL_BOOLEAN); + softButtonCapabilities.setTextSupported(TestValues.GENERAL_BOOLEAN); + + windowCapability.setSoftButtonCapabilities(Collections.singletonList(softButtonCapabilities)); + return windowCapability; + } +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java index 627a900f6..6ccf93aeb 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java @@ -7,6 +7,7 @@ import com.smartdevicelink.managers.BaseSubManager; import com.smartdevicelink.managers.ISdl; import com.smartdevicelink.managers.file.FileManager; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.managers.screen.menu.DynamicMenuUpdatesMode; import com.smartdevicelink.proxy.rpc.enums.FileType; import com.smartdevicelink.proxy.rpc.enums.MetadataType; @@ -18,9 +19,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; +import java.util.Collections; import java.util.List; import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; import static org.mockito.Mockito.mock; @@ -41,6 +44,9 @@ public class ScreenManagerTests { ISdl internalInterface = mock(ISdl.class); when(internalInterface.getTaskmaster()).thenReturn(new Taskmaster.Builder().build()); + PermissionManager permissionManager = mock(PermissionManager.class); + + when(internalInterface.getPermissionManager()).thenReturn(permissionManager); FileManager fileManager = mock(FileManager.class); screenManager = new ScreenManager(internalInterface, fileManager); screenManager.start(null); @@ -174,4 +180,91 @@ public class ScreenManagerTests { assertEquals(screenManager.getSoftButtonObjectById(200), softButtonObject2); } + @Test + public void testSettingSoftButtonId() { + // Create softButtonObject1 + SoftButtonState softButtonState1 = new SoftButtonState("object1-state1", "it is", testArtwork); + SoftButtonState softButtonState2 = new SoftButtonState("object1-state2", "Wed", testArtwork); + SoftButtonObject softButtonObject1 = new SoftButtonObject("object1", Arrays.asList(softButtonState1, softButtonState2), softButtonState1.getName(), null); + softButtonObject1.setButtonId(100); + + // Create softButtonObject2 + SoftButtonState softButtonState3 = new SoftButtonState("object2-state1", "my", testArtwork); + SoftButtonState softButtonState4 = new SoftButtonState("object2-state2", "dudes!", null); + SoftButtonObject softButtonObject2 = new SoftButtonObject("object2", Arrays.asList(softButtonState3, softButtonState4), softButtonState3.getName(), null); + softButtonObject2.setButtonId(200); + + List<SoftButtonObject> softButtonObjects = Arrays.asList(softButtonObject1, softButtonObject2); + assertTrue(screenManager.checkAndAssignButtonIds(softButtonObjects, BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER)); + screenManager.softButtonIDBySoftButtonManager.add(200); + assertFalse(screenManager.checkAndAssignButtonIds(softButtonObjects, BaseScreenManager.ManagerLocation.ALERT_MANAGER)); + screenManager.softButtonIDByAlertManager.add(100); + assertFalse(screenManager.checkAndAssignButtonIds(softButtonObjects, BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER)); + screenManager.softButtonIDByAlertManager.clear(); + screenManager.softButtonIDBySoftButtonManager.clear(); + assertTrue(screenManager.checkAndAssignButtonIds(softButtonObjects, BaseScreenManager.ManagerLocation.ALERT_MANAGER)); + softButtonObject1.setButtonId(400); + softButtonObject2.setButtonId(500); + assertTrue(screenManager.checkAndAssignButtonIds(softButtonObjects, BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER)); + SoftButtonObject softButtonObject3 = new SoftButtonObject("object1", Arrays.asList(softButtonState1, softButtonState2), softButtonState1.getName(), null); + SoftButtonObject softButtonObject4 = new SoftButtonObject("object2", Arrays.asList(softButtonState3, softButtonState4), softButtonState3.getName(), null); + assertTrue(screenManager.checkAndAssignButtonIds(softButtonObjects, BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER)); + + + + + + } + @Test + public void testAssigningIdsToSoftButtonObjects() { + SoftButtonObject sbo1, sbo2, sbo3, sbo4, sbo5; + + // Case 1 - don't set id for any button (Manager should set ids automatically starting from 1 and up) + sbo1 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo2 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo3 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo4 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo5 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + screenManager.checkAndAssignButtonIds(Arrays.asList(sbo1, sbo2, sbo3, sbo4, sbo5), BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER); + assertEquals("SoftButtonObject id doesn't match the expected value", 1, sbo1.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 2, sbo2.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 3, sbo3.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 4, sbo4.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 5, sbo5.getButtonId()); + + + // Case 2 - Set ids for all buttons (Manager shouldn't alter the ids set by developer) + sbo1 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo1.setButtonId(100); + sbo2 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo2.setButtonId(200); + sbo3 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo3.setButtonId(300); + sbo4 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo4.setButtonId(400); + sbo5 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo5.setButtonId(500); + screenManager.checkAndAssignButtonIds(Arrays.asList(sbo1, sbo2, sbo3, sbo4, sbo5), BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER); + assertEquals("SoftButtonObject id doesn't match the expected value", 100, sbo1.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 200, sbo2.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 300, sbo3.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 400, sbo4.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 500, sbo5.getButtonId()); + + + // Case 3 - Set ids for some buttons (Manager shouldn't alter the ids set by developer. And it should assign ids for the ones that don't have id) + sbo1 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo1.setButtonId(50); + sbo2 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo3 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo4 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + sbo4.setButtonId(100); + sbo5 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); + screenManager.checkAndAssignButtonIds(Arrays.asList(sbo1, sbo2, sbo3, sbo4, sbo5), BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER); + assertEquals("SoftButtonObject id doesn't match the expected value", 50, sbo1.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 101, sbo2.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 102, sbo3.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 100, sbo4.getButtonId()); + assertEquals("SoftButtonObject id doesn't match the expected value", 103, sbo5.getButtonId()); + } } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java index 7719e2996..becc01a74 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/SoftButtonManagerTests.java @@ -304,59 +304,6 @@ public class SoftButtonManagerTests { assertEquals(softButtonState1, softButtonObject1.getCurrentState()); } - @Test - public void testAssigningIdsToSoftButtonObjects() { - SoftButtonObject sbo1, sbo2, sbo3, sbo4, sbo5; - - // Case 1 - don't set id for any button (Manager should set ids automatically starting from 1 and up) - sbo1 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo2 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo3 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo4 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo5 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - softButtonManager.checkAndAssignButtonIds(Arrays.asList(sbo1, sbo2, sbo3, sbo4, sbo5)); - assertEquals("SoftButtonObject id doesn't match the expected value", 1, sbo1.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 2, sbo2.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 3, sbo3.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 4, sbo4.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 5, sbo5.getButtonId()); - - - // Case 2 - Set ids for all buttons (Manager shouldn't alter the ids set by developer) - sbo1 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo1.setButtonId(100); - sbo2 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo2.setButtonId(200); - sbo3 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo3.setButtonId(300); - sbo4 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo4.setButtonId(400); - sbo5 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo5.setButtonId(500); - softButtonManager.checkAndAssignButtonIds(Arrays.asList(sbo1, sbo2, sbo3, sbo4, sbo5)); - assertEquals("SoftButtonObject id doesn't match the expected value", 100, sbo1.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 200, sbo2.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 300, sbo3.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 400, sbo4.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 500, sbo5.getButtonId()); - - - // Case 3 - Set ids for some buttons (Manager shouldn't alter the ids set by developer. And it should assign ids for the ones that don't have id) - sbo1 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo1.setButtonId(50); - sbo2 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo3 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo4 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - sbo4.setButtonId(100); - sbo5 = new SoftButtonObject(null, Collections.EMPTY_LIST, null, null); - softButtonManager.checkAndAssignButtonIds(Arrays.asList(sbo1, sbo2, sbo3, sbo4, sbo5)); - assertEquals("SoftButtonObject id doesn't match the expected value", 50, sbo1.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 101, sbo2.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 102, sbo3.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 100, sbo4.getButtonId()); - assertEquals("SoftButtonObject id doesn't match the expected value", 103, sbo5.getButtonId()); - } - /** * Test custom overridden softButtonObject equals method */ diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java index 09adbbaa6..0c5c47cc9 100644 --- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java @@ -40,19 +40,33 @@ import android.os.Looper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.livio.taskmaster.Taskmaster; import com.smartdevicelink.managers.audio.AudioStreamManager; import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager; import com.smartdevicelink.managers.lockscreen.LockScreenConfig; import com.smartdevicelink.managers.lockscreen.LockScreenManager; import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.managers.screen.ScreenManager; import com.smartdevicelink.managers.video.VideoStreamManager; +import com.smartdevicelink.protocol.ISdlServiceListener; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.protocol.enums.SessionType; +import com.smartdevicelink.proxy.RPCMessage; +import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; +import com.smartdevicelink.proxy.rpc.SdlMsgVersion; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCRequestListener; +import com.smartdevicelink.streaming.video.VideoStreamingParameters; import com.smartdevicelink.transport.BaseTransportConfig; import com.smartdevicelink.transport.MultiplexTransportConfig; import com.smartdevicelink.transport.enums.TransportType; import com.smartdevicelink.transport.utl.TransportRecord; import com.smartdevicelink.util.DebugTool; +import com.smartdevicelink.util.Version; import java.util.List; @@ -374,4 +388,133 @@ public class SdlManager extends BaseSdlManager { return sdlManager; } } + + ISdl _internalInterface = new ISdl() { + @Override + public void start() { + lifecycleManager.getInternalInterface(SdlManager.this).start(); + } + + @Override + public void stop() { + lifecycleManager.getInternalInterface(SdlManager.this).start(); + } + + @Override + public boolean isConnected() { + return lifecycleManager.getInternalInterface(SdlManager.this).isConnected(); + } + + @Override + public void addServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { + lifecycleManager.getInternalInterface(SdlManager.this).addServiceListener(serviceType, sdlServiceListener); + } + + @Override + public void removeServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { + lifecycleManager.getInternalInterface(SdlManager.this).removeServiceListener(serviceType, sdlServiceListener); + } + + @Override + public void startVideoService(VideoStreamingParameters parameters, boolean encrypted) { + lifecycleManager.getInternalInterface(SdlManager.this).startVideoService(parameters,encrypted); + } + + @Override + public void startAudioService(boolean encrypted) { + lifecycleManager.getInternalInterface(SdlManager.this).startAudioService(encrypted); + } + + @Override + public void sendRPC(RPCMessage message) { + lifecycleManager.getInternalInterface(SdlManager.this).sendRPC(message); + } + + @Override + public void sendRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).sendRPCs(rpcs, listener); + } + + @Override + public void sendSequentialRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).sendSequentialRPCs(rpcs, listener); + } + + @Override + public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).addOnRPCNotificationListener(notificationId, listener); + } + + @Override + public boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + return lifecycleManager.getInternalInterface(SdlManager.this).removeOnRPCNotificationListener(notificationId, listener); + } + + @Override + public void addOnRPCRequestListener(FunctionID functionID, OnRPCRequestListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).addOnRPCRequestListener(functionID, listener); + } + + @Override + public boolean removeOnRPCRequestListener(FunctionID functionID, OnRPCRequestListener listener) { + return lifecycleManager.getInternalInterface(SdlManager.this).removeOnRPCRequestListener(functionID, listener); + } + + @Override + public void addOnRPCListener(FunctionID responseId, OnRPCListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).addOnRPCListener(responseId, listener); + } + + @Override + public boolean removeOnRPCListener(FunctionID responseId, OnRPCListener listener) { + return lifecycleManager.getInternalInterface(SdlManager.this).removeOnRPCListener(responseId, listener); + } + + @Override + public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() { + return lifecycleManager.getInternalInterface(SdlManager.this).getRegisterAppInterfaceResponse(); + } + + @Override + public boolean isTransportForServiceAvailable(SessionType serviceType) { + return lifecycleManager.getInternalInterface(SdlManager.this).isTransportForServiceAvailable(serviceType); + } + + @NonNull + @Override + public SdlMsgVersion getSdlMsgVersion() { + return lifecycleManager.getInternalInterface(SdlManager.this).getSdlMsgVersion(); + } + + @NonNull + @Override + public Version getProtocolVersion() { + return lifecycleManager.getInternalInterface(SdlManager.this).getProtocolVersion(); + } + + @Override + public long getMtu(SessionType serviceType) { + return lifecycleManager.getInternalInterface(SdlManager.this).getMtu(serviceType); + } + + @Override + public void startRPCEncryption() { + lifecycleManager.getInternalInterface(SdlManager.this).startRPCEncryption(); + } + + @Override + public Taskmaster getTaskmaster() { + return lifecycleManager.getInternalInterface(SdlManager.this).getTaskmaster(); + } + + @Override + public SystemCapabilityManager getSystemCapabilityManager() { + return lifecycleManager.getInternalInterface(SdlManager.this).getSystemCapabilityManager(); + } + + @Override + public PermissionManager getPermissionManager() { + return permissionManager; + } + }; } diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/AlertManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/AlertManager.java new file mode 100644 index 000000000..c0a5d92e2 --- /dev/null +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/AlertManager.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; + +import com.smartdevicelink.managers.ISdl; +import com.smartdevicelink.managers.file.FileManager; + +/** + * <strong>AlertManager</strong> <br> + * <p> + * Alert manager handles uploading images and audio needed by an alert, sending an alert and cancelling an alert.<br> + * Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br> + */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +class AlertManager extends BaseAlertManager { + + public AlertManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) { + super(internalInterface, fileManager); + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/AlertCompletionListener.java b/base/src/main/java/com/smartdevicelink/managers/AlertCompletionListener.java new file mode 100644 index 000000000..7801632ba --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/AlertCompletionListener.java @@ -0,0 +1,11 @@ +package com.smartdevicelink.managers; + +public interface AlertCompletionListener { + + /** + * Returns whether an Alert operation was successful or not along with tryAgainTime + * @param success - Boolean that is True if Operation was a success, False otherwise. + * @param tryAgainTime - Amount of time (in seconds) that an app must wait before resending an alert. + */ + void onComplete(boolean success, Integer tryAgainTime); +} diff --git a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java index 6730ee3eb..24b0d8455 100644 --- a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java @@ -32,6 +32,7 @@ package com.smartdevicelink.managers; import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; import com.smartdevicelink.managers.file.FileManager; import com.smartdevicelink.managers.file.FileManagerConfig; @@ -871,4 +872,9 @@ abstract class BaseSdlManager { lifecycleManager.startRPCEncryption(); } } + + @RestrictTo(RestrictTo.Scope.TESTS) + void set_internalInterface(ISdl _internalInterface) { + this._internalInterface = _internalInterface; + } } diff --git a/base/src/main/java/com/smartdevicelink/managers/ISdl.java b/base/src/main/java/com/smartdevicelink/managers/ISdl.java index e12880561..785b5649e 100644 --- a/base/src/main/java/com/smartdevicelink/managers/ISdl.java +++ b/base/src/main/java/com/smartdevicelink/managers/ISdl.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import com.livio.taskmaster.Taskmaster; import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager; +import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.protocol.ISdlServiceListener; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.protocol.enums.SessionType; @@ -227,4 +228,6 @@ public interface ISdl { Taskmaster getTaskmaster(); SystemCapabilityManager getSystemCapabilityManager(); + + PermissionManager getPermissionManager(); } diff --git a/base/src/main/java/com/smartdevicelink/managers/ManagerUtility.java b/base/src/main/java/com/smartdevicelink/managers/ManagerUtility.java index b5ad249bb..6b5297e21 100644 --- a/base/src/main/java/com/smartdevicelink/managers/ManagerUtility.java +++ b/base/src/main/java/com/smartdevicelink/managers/ManagerUtility.java @@ -108,6 +108,41 @@ public class ManagerUtility { } /** + * Method to get number of alert textFields allowed to be set according to WindowCapability + * + * @param windowCapability WindowCapability representing the capabilities of the desired window + * @return linesFound Number of alert textFields found in WindowCapability + */ + public static int getMaxNumberOfAlertFieldLines(final WindowCapability windowCapability) { + int highestFound = 0; + if (windowCapability != null && windowCapability.getTextFields() != null) { + for (TextField field : windowCapability.getTextFields()) { + int fieldNumber = 0; + if (field != null && field.getName() != null) { + switch (field.getName()) { + case alertText1: + fieldNumber = 1; + break; + case alertText2: + fieldNumber = 2; + break; + case alertText3: + fieldNumber = 3; + break; + } + } + if (fieldNumber > 0) { + highestFound = Math.max(highestFound, fieldNumber); + if (highestFound == 3) { + break; + } + } + } + } + return highestFound; + } + + /** * Method to get a list of all available text fields * * @return list of all available text fields with CID1SET Character Set 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 280043a8a..b3c59afb1 100644 --- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java @@ -40,6 +40,7 @@ import com.smartdevicelink.exception.SdlException; import com.smartdevicelink.managers.ISdl; import com.smartdevicelink.managers.SdlManager; import com.smartdevicelink.managers.ServiceEncryptionListener; +import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.marshal.JsonRPCMarshaller; import com.smartdevicelink.protocol.ISdlServiceListener; import com.smartdevicelink.protocol.ProtocolMessage; @@ -1099,6 +1100,11 @@ abstract class BaseLifecycleManager { public SystemCapabilityManager getSystemCapabilityManager() { return BaseLifecycleManager.this.systemCapabilityManager; } + + @Override + public PermissionManager getPermissionManager() { + return null; + } }; /* ******************************************************************************************************* diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/AlertAudioData.java b/base/src/main/java/com/smartdevicelink/managers/screen/AlertAudioData.java new file mode 100644 index 000000000..c8c250ca5 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/AlertAudioData.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +import androidx.annotation.NonNull; + +import com.smartdevicelink.managers.file.filetypes.SdlFile; +import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities; +import com.smartdevicelink.util.DebugTool; + +public class AlertAudioData extends AudioData implements Cloneable { + // Whether the alert tone should be played before the prompt (if any) is spoken. Defaults to false + private boolean playTone; + + public AlertAudioData(@NonNull SdlFile audioFile) { + super(audioFile); + } + + public AlertAudioData(@NonNull String spokenString) { + super(spokenString); + } + + public AlertAudioData(@NonNull String phoneticString, @NonNull SpeechCapabilities phoneticType) { + super(phoneticString, phoneticType); + } + + public boolean isPlayTone() { + return playTone; + } + + public void setPlayTone(boolean playTone) { + this.playTone = playTone; + } + + /** + * Creates a deep copy of the object + * + * @return deep copy of the object, null if an exception occurred + */ + @Override + public AlertAudioData clone() { + try { + AlertAudioData alertAudioData = (AlertAudioData) super.clone(); + return alertAudioData; + } catch (CloneNotSupportedException e) { + if (DebugTool.isDebugEnabled()) { + throw new RuntimeException("Clone not supported by super class"); + } + } + return null; + } +}
\ No newline at end of file diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/AlertCanceledListener.java b/base/src/main/java/com/smartdevicelink/managers/screen/AlertCanceledListener.java new file mode 100644 index 000000000..ac65c2035 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/AlertCanceledListener.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +public interface AlertCanceledListener { + /** + * Notifies the subscriber that the Alert should be cancelled. + */ + void onAlertCanceled(); +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/AlertView.java b/base/src/main/java/com/smartdevicelink/managers/screen/AlertView.java new file mode 100644 index 000000000..4e009147b --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/AlertView.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.util.DebugTool; + +import java.util.ArrayList; +import java.util.List; + +public class AlertView implements Cloneable { + + private static Integer defaultTimeout = 5; + private static final int TIMEOUT_MIN = 3; + private static final int TIMEOUT_MAX = 10; + private String text, secondaryText, tertiaryText; + private Integer timeout; + private AlertAudioData audio; + private boolean showWaitIndicator; + private List<SoftButtonObject> softButtons; + private SdlArtwork icon; + AlertCanceledListener canceledListener; + + + private AlertView() { + this.timeout = defaultTimeout; + } + + public static class Builder { + + AlertView alertView; + + public Builder() { + alertView = new AlertView(); + } + + /** + * The primary line of text for display on the alert. If fewer than three alert lines are available + * on the head unit, the screen manager will automatically concatenate some of the lines together. + */ + public Builder setText(String text) { + this.alertView.text = text; + return this; + } + + /** + * The secondary line of text for display on the alert. If fewer than three alert lines are available + * on the head unit, the screen manager will automatically concatenate some of the lines together. + */ + public Builder setSecondaryText(String secondaryText) { + alertView.secondaryText = secondaryText; + return this; + } + + /** + * The tertiary line of text for display on the alert. If fewer than three alert lines are available + * on the head unit, the screen manager will automatically concatenate some of the lines together. + */ + public Builder setTertiaryText(String tertiaryText) { + alertView.tertiaryText = tertiaryText; + return this; + } + + /** + * Timeout in seconds. Defaults to 0, which will use `defaultTimeout`. If this is set below the + * minimum, it will be capped at 3 seconds. Minimum 3 seconds, maximum 10 seconds. If this is + * set above the maximum, it will be capped at 10 seconds. Defaults to 0. + * + * Please note that if a button is added to the alert, the defaultTimeout and timeout values will be ignored. + */ + public Builder setTimeout(Integer timeout) { + alertView.timeout = timeout; + return this; + } + + /** + * If supported, the alert GUI will display some sort of indefinite waiting / refresh / loading + * indicator animation. Defaults to NO. + */ + public Builder setShowWaitIndicator(boolean showWaitIndicator) { + alertView.showWaitIndicator = showWaitIndicator; + return this; + } + + /** + * Soft buttons the user may select to perform actions. Only one `SoftButtonState` per object + * is supported; if any soft button object contains multiple states, an exception will be thrown. + */ + public Builder setSoftButtons(List<SoftButtonObject> softButtons) { + alertView.setSoftButtons(softButtons); + return this; + } + + /** + * Text spoken, file(s) played, and/or tone played when the alert appears + */ + public Builder setAudio(AlertAudioData audio) { + alertView.audio = audio; + return this; + } + + /** + * An artwork that will be displayed when the icon appears. This will be uploaded prior to the + * appearance of the alert if necessary. This will not be uploaded if the head unit does not + * declare support for alertIcon. + */ + public Builder setIcon(SdlArtwork icon) { + alertView.icon = icon; + return this; + } + + /** + * Set this to change the default timeout for all alerts. If a timeout is not set on an individual + * alert object (or if it is set to 0.0), then it will use this timeout instead. See `timeout` + * for more details. If this is not set by you, it will default to 5 seconds. The minimum is + * 3 seconds, the maximum is 10 seconds. If this is set below the minimum, it will be capped + * at 3 seconds. If this is set above the maximum, it will be capped at 10 seconds. + */ + public Builder setDefaultTimeOut(int defaultTimeOut) { + alertView.setDefaultTimeout(defaultTimeOut); + return this; + } + + public AlertView build() { + return alertView; + } + } + + // Notifies the subscriber that the alert should be cancelled. + public void cancel() { + if (canceledListener == null) { + return; + } + canceledListener.onAlertCanceled(); + } + + public Integer getTimeout() { + if (timeout == null) { + timeout = defaultTimeout; + } else if (timeout < TIMEOUT_MIN) { + return TIMEOUT_MIN; + } else if (timeout > TIMEOUT_MAX) { + return TIMEOUT_MAX; + } + return timeout; + } + + public AlertAudioData getAudio() { + return audio; + } + + public void setAudio(AlertAudioData audio) { + this.audio = audio; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public boolean isShowWaitIndicator() { + return showWaitIndicator; + } + + public void setShowWaitIndicator(boolean showWaitIndicator) { + this.showWaitIndicator = showWaitIndicator; + } + + public String getSecondaryText() { + return secondaryText; + } + + public void setSecondaryText(String secondaryText) { + this.secondaryText = secondaryText; + } + + public String getTertiaryText() { + return tertiaryText; + } + + public void setTertiaryText(String tertiaryText) { + this.tertiaryText = tertiaryText; + } + + public List<SoftButtonObject> getSoftButtons() { + return softButtons; + } + + public void setSoftButtons(List<SoftButtonObject> softButtons) { + for (SoftButtonObject softButtonObject : softButtons) { + if (softButtonObject.getStates().size() != 1) { + throw new IllegalArgumentException("Attempting create a soft button for an Alert with more than one state. Alerts only support soft buttons with one state"); + } + } + this.softButtons = softButtons; + } + + public SdlArtwork getIcon() { + return icon; + } + + public void setIcon(SdlArtwork icon) { + this.icon = icon; + } + + public int getDefaultTimeout() { + return defaultTimeout; + } + + public void setDefaultTimeout(int defaultTimeout) { + if (defaultTimeout <= TIMEOUT_MIN) { + AlertView.defaultTimeout = TIMEOUT_MIN; + return; + } else if (defaultTimeout >= TIMEOUT_MAX) { + AlertView.defaultTimeout = TIMEOUT_MAX; + return; + } + AlertView.defaultTimeout = defaultTimeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Creates a deep copy of the object + * + * @return deep copy of the object, null if an exception occurred + */ + @Override + public AlertView clone() { + try { + AlertView alertView = (AlertView) super.clone(); + if (alertView != null) { + if (alertView.getAudio() != null) { + alertView.audio = audio.clone(); + } + if (alertView.getSoftButtons() != null) { + List<SoftButtonObject> softButtonObjectList = new ArrayList<>(); + for (int i = 0; i < alertView.softButtons.size(); i++) { + SoftButtonObject cloneSoftButton = alertView.softButtons.get(i).clone(); + softButtonObjectList.add(cloneSoftButton); + } + alertView.softButtons = softButtonObjectList; + } + if (alertView.icon != null) { + alertView.icon = icon.clone(); + } + } + return alertView; + } catch (CloneNotSupportedException e) { + if (DebugTool.isDebugEnabled()) { + throw new RuntimeException("Clone not supported by super class"); + } + } + return null; + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/AudioData.java b/base/src/main/java/com/smartdevicelink/managers/screen/AudioData.java new file mode 100644 index 000000000..18aa110e3 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/AudioData.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +import androidx.annotation.NonNull; + +import com.smartdevicelink.managers.file.filetypes.SdlFile; +import com.smartdevicelink.proxy.rpc.TTSChunk; +import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +public class AudioData { + + // All audio data + private List<TTSChunk> audioData; + + // The audio files that will be uploaded. + private HashMap<String, SdlFile> audioFiles; + + public AudioData(@NonNull SdlFile audioFile) { + this.audioFiles = new HashMap<>(); + this.audioData = new ArrayList<>(); + audioFiles.put(audioFile.getName(), audioFile); + audioData.add(new TTSChunk(audioFile.getName(), SpeechCapabilities.FILE)); + } + + public AudioData(@NonNull String spokenString) { + this.audioData = new ArrayList<>(); + audioData.add(new TTSChunk(spokenString, SpeechCapabilities.TEXT)); + } + + public AudioData(@NonNull String phoneticString, @NonNull SpeechCapabilities phoneticType) { + if (!isValidPhoneticType(phoneticType)) { + return; + } + this.audioData = new ArrayList<>(); + audioData.add(new TTSChunk(phoneticString, phoneticType)); + } + + /** + * Checks if the phonetic type can be used to create a text-to-speech string. + * + * @param phoneticType The phonetic type of the text-to-speech string + * @return True if the phoneticType is of type `SAPI_PHONEMES`, `LHPLUS_PHONEMES`, `TEXT`, or `PRE_RECORDED`; false if not. + */ + boolean isValidPhoneticType(SpeechCapabilities phoneticType) { + if (!(phoneticType.equals(SpeechCapabilities.SAPI_PHONEMES) || phoneticType.equals(SpeechCapabilities.LHPLUS_PHONEMES) + || phoneticType.equals(SpeechCapabilities.TEXT) || phoneticType.equals(SpeechCapabilities.PRE_RECORDED))) { + return false; + } + return true; + } + + /** + * Add additional SDLFiles holding data or pointing to a file on the file system. When this object + * is passed to an `Alert` or `Speak`, the file will be uploaded if it is not already, then played + * if the system supports that feature. + * + * @param audioFiles A list of audio file to be played by the system + */ + public void addAudioFiles(@NonNull List<SdlFile> audioFiles) { + if (this.audioFiles == null) { + this.audioFiles = new HashMap<>(); + } + if (this.audioData == null) { + this.audioData = new ArrayList<>(); + } + for (SdlFile file : audioFiles) { + audioData.add(new TTSChunk(file.getName(), SpeechCapabilities.FILE)); + this.audioFiles.put(file.getName(), file); + } + } + + /** + * Create additional strings to be spoken by the system speech synthesizer. + * + * @param spokenString The strings to be spoken by the system speech synthesizer + */ + public void addSpeechSynthesizerStrings(@NonNull List<String> spokenString) { + if (spokenString.size() == 0) { + return; + } + List<TTSChunk> newPrompts = new ArrayList<>(); + for (String spoken : spokenString) { + if (spoken.length() == 0) { + continue; + } + newPrompts.add(new TTSChunk().setText(spoken).setType(SpeechCapabilities.TEXT)); + } + if (newPrompts.size() == 0) { + return; + } + if (audioData == null) { + this.audioData = newPrompts; + return; + } + audioData.addAll(newPrompts); + } + + /** + * Create additional strings to be spoken by the system speech synthesizer using a phonetic string. + * + * @param phoneticString The strings to be spoken by the system speech synthesizer + * @param phoneticType Must be one of `SAPI_PHONEMES`, `LHPLUS_PHONEMES`, `TEXT`, or `PRE_RECORDED` or no object will be created + */ + public void addPhoneticSpeechSynthesizerStrings(@NonNull List<String> phoneticString, @NonNull SpeechCapabilities phoneticType) { + if (!(isValidPhoneticType(phoneticType)) || phoneticString.size() == 0) { + return; + } + List<TTSChunk> newPrompts = new ArrayList<>(); + for (String phonetic : phoneticString) { + if (phonetic.length() == 0) { + continue; + } + newPrompts.add(new TTSChunk(phonetic, phoneticType)); + } + if (newPrompts.size() == 0) { + return; + } + if (audioData == null) { + this.audioData = newPrompts; + return; + } + audioData.addAll(newPrompts); + } + + HashMap<String, SdlFile> getAudioFiles() { + return audioFiles; + } + + public List<TTSChunk> getAudioData() { + return audioData; + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java new file mode 100644 index 000000000..56aec5c19 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +import androidx.annotation.NonNull; + +import com.livio.taskmaster.Queue; +import com.livio.taskmaster.Task; +import com.smartdevicelink.managers.AlertCompletionListener; +import com.smartdevicelink.managers.BaseSubManager; +import com.smartdevicelink.managers.CompletionListener; +import com.smartdevicelink.managers.ISdl; +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.lifecycle.OnSystemCapabilityListener; +import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager; +import com.smartdevicelink.managers.permission.OnPermissionChangeListener; +import com.smartdevicelink.managers.permission.PermissionElement; +import com.smartdevicelink.managers.permission.PermissionStatus; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.RPCNotification; +import com.smartdevicelink.proxy.rpc.DisplayCapability; +import com.smartdevicelink.proxy.rpc.OnButtonEvent; +import com.smartdevicelink.proxy.rpc.OnButtonPress; +import com.smartdevicelink.proxy.rpc.WindowCapability; +import com.smartdevicelink.proxy.rpc.enums.ButtonName; +import com.smartdevicelink.proxy.rpc.enums.PredefinedWindows; +import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities; +import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; +import com.smartdevicelink.util.DebugTool; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + + +abstract class BaseAlertManager extends BaseSubManager { + + private static final String TAG = "BaseAlertManager"; + Queue transactionQueue; + WindowCapability currentWindowCapability; + private OnSystemCapabilityListener onSpeechCapabilityListener, onDisplaysCapabilityListener; + List<SpeechCapabilities> speechCapabilities; + private UUID permissionListener; + boolean isAlertRPCAllowed = false; + private final WeakReference<FileManager> fileManager; + int nextCancelId; + private final int alertCancelIdMin = 1; + private final int alertCancelIdMax = 100; + private List<SoftButtonObject> softButtonObjects; + OnRPCNotificationListener onButtonPressListener, onButtonEventListener; + + + public BaseAlertManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) { + super(internalInterface); + this.transactionQueue = newTransactionQueue(); + this.fileManager = new WeakReference<>(fileManager); + nextCancelId = 0; + this.softButtonObjects = new ArrayList<>(); + addListeners(); + } + + /** + * Starts the manager + * @param listener CompletionListener that is called once the BaseSubManager's state is READY, LIMITED, or ERROR + */ + @Override + public void start(CompletionListener listener) { + transitionToState(READY); + super.start(listener); + } + + /** + * Clean up everything after the manager is no longer needed + */ + @Override + public void dispose() { + currentWindowCapability = null; + speechCapabilities = null; + isAlertRPCAllowed = false; + softButtonObjects = null; + + if (transactionQueue != null) { + transactionQueue.close(); + transactionQueue = null; + } + + // remove listeners + if (internalInterface.getSystemCapabilityManager() != null) { + internalInterface.getSystemCapabilityManager().removeOnSystemCapabilityListener(SystemCapabilityType.DISPLAYS, onDisplaysCapabilityListener); + internalInterface.getSystemCapabilityManager().removeOnSystemCapabilityListener(SystemCapabilityType.SPEECH, onSpeechCapabilityListener); + } + if (internalInterface.getPermissionManager() != null) { + internalInterface.getPermissionManager().removeListener(permissionListener); + } + internalInterface.removeOnRPCNotificationListener(FunctionID.ON_BUTTON_PRESS, onButtonPressListener); + internalInterface.removeOnRPCNotificationListener(FunctionID.ON_BUTTON_EVENT, onButtonEventListener); + super.dispose(); + } + + /** + * Creates a PresentAlertOperation and adds it to the transactionQueue + * + * @param alert - AlertView object that contains alert information + * @param listener - AlertCompletionListener that will notify the sender when Alert has completed + */ + public void presentAlert(AlertView alert, AlertCompletionListener listener) { + if (getState() == ERROR) { + DebugTool.logWarning(TAG, "Alert Manager In Error State"); + return; + } + + // Check for softButtons and assign them ID's, Behavior mimic SoftButtonManager, + // as in if invalid ID's are set, Alert will not show up. + // It's best if ID's are not set custom and allow the screenManager to set them. + if (alert.getSoftButtons() != null) { + if (!BaseScreenManager.checkAndAssignButtonIds(alert.getSoftButtons(), BaseScreenManager.ManagerLocation.ALERT_MANAGER)) { + DebugTool.logError(TAG, "Attempted to set soft button objects for Alert, but multiple buttons had the same id."); + return; + } + softButtonObjects.addAll(alert.getSoftButtons()); + } + + if (nextCancelId >= alertCancelIdMax) { + nextCancelId = alertCancelIdMin; + } else { + nextCancelId++; + } + + PresentAlertOperation operation = new PresentAlertOperation(internalInterface, alert, currentWindowCapability, speechCapabilities, fileManager.get(), nextCancelId, listener, new AlertSoftButtonClearListener() { + @Override + public void onButtonClear(List<SoftButtonObject> softButtonObjectList) { + // Stop keeping track of SoftButtonObject listeners as operation has finished + for (SoftButtonObject object : softButtonObjectList) { + softButtonObjects.remove(object); + } + } + }); + transactionQueue.add(operation, false); + } + + /** + * Interface that sends a list of SoftButtonObjects back from PresentAlertOperation, to allow BaseAlertManager + * to stop keeping track of them for their onButtonEventListener + */ + interface AlertSoftButtonClearListener { + void onButtonClear(List<SoftButtonObject> softButtonObjects); + } + + + private Queue newTransactionQueue() { + Queue queue = internalInterface.getTaskmaster().createQueue("AlertManager", 6, false); + queue.pause(); + return queue; + } + + /** + * Get the soft button objects list + * + * @return a List<SoftButtonObject> + */ + protected List<SoftButtonObject> getSoftButtonObjects() { + return softButtonObjects; + } + + /** + * Get the SoftButtonObject that has the provided buttonId + * + * @param buttonId a int value that represents the id of the button + * @return a SoftButtonObject + */ + protected SoftButtonObject getSoftButtonObjectById(int buttonId) { + for (SoftButtonObject softButtonObject : softButtonObjects) { + if (softButtonObject.getButtonId() == buttonId) { + return softButtonObject; + } + } + return null; + } + + // Suspend the queue if the WindowCapabilities are null + // OR if isAlertRPCAllowed is false + private void updateTransactionQueueSuspended() { + if (!isAlertRPCAllowed || currentWindowCapability == null) { + DebugTool.logInfo(TAG, String.format("Suspending the transaction queue. Current permission status is false: %b, window capabilities are null: %b", isAlertRPCAllowed, currentWindowCapability == null)); + transactionQueue.pause(); + } else { + DebugTool.logInfo(TAG, "Starting the transaction queue"); + transactionQueue.resume(); + } + } + + + private void addListeners() { + // Retrieves SpeechCapabilities of the system. + onSpeechCapabilityListener = new OnSystemCapabilityListener() { + @Override + public void onCapabilityRetrieved(Object capability) { + speechCapabilities = SystemCapabilityManager.convertToList(capability, SpeechCapabilities.class); + } + + @Override + public void onError(String info) { + + } + }; + if (internalInterface.getSystemCapabilityManager() != null) { + this.internalInterface.getSystemCapabilityManager().getCapability(SystemCapabilityType.SPEECH, onSpeechCapabilityListener, false); + } + // Retrieves WindowCapability of the system, if WindowCapability are null, queue pauses + 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) { + currentWindowCapability = null; + } else { + DisplayCapability display = capabilities.get(0); + for (WindowCapability windowCapability : display.getWindowCapabilities()) { + int currentWindowID = windowCapability.getWindowID() != null ? windowCapability.getWindowID() : PredefinedWindows.DEFAULT_WINDOW.getValue(); + if (currentWindowID != PredefinedWindows.DEFAULT_WINDOW.getValue()) { + continue; + } + currentWindowCapability = windowCapability; + updatePendingOperationsWithNewWindowCapability(); + break; + } + } + // Update the queue's suspend state + updateTransactionQueueSuspended(); + } + + @Override + public void onError(String info) { + DebugTool.logError(TAG, "Display Capability cannot be retrieved"); + currentWindowCapability = null; + updateTransactionQueueSuspended(); + } + }; + if (internalInterface.getSystemCapabilityManager() != null) { + this.internalInterface.getSystemCapabilityManager().addOnSystemCapabilityListener(SystemCapabilityType.DISPLAYS, onDisplaysCapabilityListener); + } + + // Subscribes to permission updates for the `Alert` RPC. If the alert is not allowed at the current HMI level, the queue is suspended. + // Any `Alert` RPCs added while the queue is suspended will be sent when the `Alert` RPC is allowed at the current HMI level and the queue is unsuspended. + PermissionElement alertPermissionElement = new PermissionElement(FunctionID.ALERT, null); + permissionListener = internalInterface.getPermissionManager().addListener(Collections.singletonList(alertPermissionElement), internalInterface.getPermissionManager().PERMISSION_GROUP_TYPE_ANY, new OnPermissionChangeListener() { + @Override + public void onPermissionsChange(@NonNull Map<FunctionID, PermissionStatus> allowedPermissions, int permissionGroupStatus) { + if (allowedPermissions.get(FunctionID.ALERT) != null) { + isAlertRPCAllowed = allowedPermissions.get(FunctionID.ALERT).getIsRPCAllowed(); + } else { + isAlertRPCAllowed = false; + } + updateTransactionQueueSuspended(); + } + }); + + this.onButtonPressListener = new OnRPCNotificationListener() { + @Override + public void onNotified(RPCNotification notification) { + OnButtonPress onButtonPress = (OnButtonPress) notification; + if (onButtonPress != null && onButtonPress.getButtonName() == ButtonName.CUSTOM_BUTTON) { + Integer buttonId = onButtonPress.getCustomButtonID(); + if (getSoftButtonObjects() != null) { + for (SoftButtonObject softButtonObject : getSoftButtonObjects()) { + if (softButtonObject.getButtonId() == buttonId && softButtonObject.getOnEventListener() != null) { + softButtonObject.getOnEventListener().onPress(getSoftButtonObjectById(buttonId), onButtonPress); + break; + } + } + } + } + } + }; + this.internalInterface.addOnRPCNotificationListener(FunctionID.ON_BUTTON_PRESS, onButtonPressListener); + + // Add OnButtonEventListener to notify SoftButtonObjects when there is a button event + this.onButtonEventListener = new OnRPCNotificationListener() { + @Override + public void onNotified(RPCNotification notification) { + OnButtonEvent onButtonEvent = (OnButtonEvent) notification; + if (onButtonEvent != null && onButtonEvent.getButtonName() == ButtonName.CUSTOM_BUTTON) { + Integer buttonId = onButtonEvent.getCustomButtonID(); + if (getSoftButtonObjects() != null) { + for (SoftButtonObject softButtonObject : getSoftButtonObjects()) { + if (softButtonObject.getButtonId() == buttonId && softButtonObject.getOnEventListener() != null) { + softButtonObject.getOnEventListener().onEvent(getSoftButtonObjectById(buttonId), onButtonEvent); + break; + } + } + } + } + } + }; + this.internalInterface.addOnRPCNotificationListener(FunctionID.ON_BUTTON_EVENT, onButtonEventListener); + } + + // Updates pending task with new DisplayCapabilities + void updatePendingOperationsWithNewWindowCapability() { + for (Task task : transactionQueue.getTasksAsList()) { + if (!(task instanceof PresentAlertOperation)) { + continue; + } + ((PresentAlertOperation) task).currentWindowCapability = currentWindowCapability; + } + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java index 8b976b2af..d24ca4d98 100644 --- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java @@ -31,10 +31,12 @@ */ package com.smartdevicelink.managers.screen; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; +import com.smartdevicelink.managers.AlertCompletionListener; import com.smartdevicelink.managers.BaseSubManager; import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.ISdl; @@ -58,6 +60,8 @@ import com.smartdevicelink.proxy.rpc.enums.MetadataType; import com.smartdevicelink.proxy.rpc.enums.TextAlignment; import com.smartdevicelink.util.DebugTool; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.List; @@ -77,6 +81,13 @@ abstract class BaseScreenManager extends BaseSubManager { private MenuManager menuManager; private ChoiceSetManager choiceSetManager; private SubscribeButtonManager subscribeButtonManager; + private AlertManager alertManager; + + static final int SOFT_BUTTON_ID_NOT_SET_VALUE = -1; + static final int SOFT_BUTTON_ID_MIN_VALUE = 0; + static final int SOFT_BUTTON_ID_MAX_VALUE = 10000; + static HashSet<Integer> softButtonIDBySoftButtonManager; + static HashSet<Integer> softButtonIDByAlertManager; // Sub manager listener private final CompletionListener subManagerListener = new CompletionListener() { @@ -85,15 +96,15 @@ abstract class BaseScreenManager extends BaseSubManager { public synchronized void onComplete(boolean success) { if (softButtonManager != null && textAndGraphicManager != null && voiceCommandManager != null && menuManager != null && choiceSetManager != null && subscribeButtonManager != null) { if (softButtonManager.getState() == BaseSubManager.READY && textAndGraphicManager.getState() == BaseSubManager.READY && voiceCommandManager.getState() == BaseSubManager.READY && menuManager.getState() == BaseSubManager.READY - && subscribeButtonManager.getState() == BaseSubManager.READY) { + && subscribeButtonManager.getState() == BaseSubManager.READY && alertManager.getState() == BaseSubManager.READY) { DebugTool.logInfo(TAG, "Starting screen manager, all sub managers are in ready state"); transitionToState(READY); } else if (softButtonManager.getState() == BaseSubManager.ERROR && textAndGraphicManager.getState() == BaseSubManager.ERROR && voiceCommandManager.getState() == BaseSubManager.ERROR && menuManager.getState() == BaseSubManager.ERROR - && choiceSetManager.getState() == BaseSubManager.ERROR && subscribeButtonManager.getState() == BaseSubManager.ERROR) { + && choiceSetManager.getState() == BaseSubManager.ERROR && subscribeButtonManager.getState() == BaseSubManager.ERROR && alertManager.getState() == BaseSubManager.ERROR) { DebugTool.logError(TAG, "ERROR starting screen manager, all sub managers are in error state"); transitionToState(ERROR); } else if (textAndGraphicManager.getState() == BaseSubManager.SETTING_UP || softButtonManager.getState() == BaseSubManager.SETTING_UP || voiceCommandManager.getState() == BaseSubManager.SETTING_UP || menuManager.getState() == BaseSubManager.SETTING_UP - || choiceSetManager.getState() == BaseSubManager.SETTING_UP || subscribeButtonManager.getState() == BaseSubManager.SETTING_UP) { + || choiceSetManager.getState() == BaseSubManager.SETTING_UP || subscribeButtonManager.getState() == BaseSubManager.SETTING_UP || alertManager.getState() == BaseSubManager.SETTING_UP) { DebugTool.logInfo(TAG, "SETTING UP screen manager, at least one sub manager is still setting up"); transitionToState(SETTING_UP); } else { @@ -111,6 +122,8 @@ abstract class BaseScreenManager extends BaseSubManager { BaseScreenManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) { super(internalInterface); this.fileManager = new WeakReference<>(fileManager); + softButtonIDBySoftButtonManager = new HashSet<>(); + softButtonIDByAlertManager = new HashSet<>(); initialize(); } @@ -124,6 +137,7 @@ abstract class BaseScreenManager extends BaseSubManager { this.menuManager.start(subManagerListener); this.choiceSetManager.start(subManagerListener); this.subscribeButtonManager.start(subManagerListener); + this.alertManager.start(subManagerListener); } private void initialize() { @@ -132,6 +146,7 @@ abstract class BaseScreenManager extends BaseSubManager { this.textAndGraphicManager = new TextAndGraphicManager(internalInterface, fileManager.get(), softButtonManager); this.menuManager = new MenuManager(internalInterface, fileManager.get()); this.choiceSetManager = new ChoiceSetManager(internalInterface, fileManager.get()); + this.alertManager = new AlertManager(internalInterface, fileManager.get()); } this.subscribeButtonManager = new SubscribeButtonManager(internalInterface); this.voiceCommandManager = new VoiceCommandManager(internalInterface); @@ -149,6 +164,10 @@ abstract class BaseScreenManager extends BaseSubManager { menuManager.dispose(); choiceSetManager.dispose(); subscribeButtonManager.dispose(); + alertManager.dispose(); + softButtonIDByAlertManager = null; + softButtonIDBySoftButtonManager = null; + super.dispose(); } @@ -703,4 +722,74 @@ abstract class BaseScreenManager extends BaseSubManager { public void removeButtonListener(@NonNull ButtonName buttonName, @NonNull OnButtonListener listener) { subscribeButtonManager.removeButtonListener(buttonName, listener); } + + public void presentAlert(AlertView alert, AlertCompletionListener listener) { + alertManager.presentAlert(alert, listener); + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ManagerLocation.SOFTBUTTON_MANAGER, ManagerLocation.ALERT_MANAGER}) + @interface ManagerLocation { + int SOFTBUTTON_MANAGER = 0; + int ALERT_MANAGER = 1; + } + + /** + * Used across managers to check and assign SoftButton ID's to prevent conflicts. + * + * @param softButtonObjects - list of SoftButtonObjects + * @param location - location from which the button came from, so if new buttons get set on screen, we can clear the old ID's out + * @return True if ButtonID's are good, False if not. + */ + static boolean checkAndAssignButtonIds(List<SoftButtonObject> softButtonObjects, @ManagerLocation int location) { + // Depending on location form which the softButtons came from, we will clear out the id list so they can be reset + if (location == ManagerLocation.ALERT_MANAGER) { + softButtonIDByAlertManager.clear(); + } else if (location == ManagerLocation.SOFTBUTTON_MANAGER) { + softButtonIDBySoftButtonManager.clear(); + } + // Check if multiple soft button objects have the same id + HashSet<Integer> buttonIdsSetHashSet = new HashSet<>(); + int currentSoftButtonId, numberOfButtonIdsSet = 0, maxButtonIdsSetByDev = SOFT_BUTTON_ID_MIN_VALUE; + + for (SoftButtonObject softButtonObject : softButtonObjects) { + currentSoftButtonId = softButtonObject.getButtonId(); + if (currentSoftButtonId != SOFT_BUTTON_ID_NOT_SET_VALUE) { + if (softButtonIDByAlertManager.contains(currentSoftButtonId) || softButtonIDBySoftButtonManager.contains(currentSoftButtonId)) { + return false; + } + numberOfButtonIdsSet++; + if (currentSoftButtonId > maxButtonIdsSetByDev) { + maxButtonIdsSetByDev = currentSoftButtonId; + } + buttonIdsSetHashSet.add(softButtonObject.getButtonId()); + } + } + if (numberOfButtonIdsSet != buttonIdsSetHashSet.size()) { + return false; + } + + // Set ids for soft button objects + int generatedSoftButtonId = maxButtonIdsSetByDev; + for (SoftButtonObject softButtonObject : softButtonObjects) { + // If the dev did not set the buttonId, the manager should set an id on the dev's behalf + currentSoftButtonId = softButtonObject.getButtonId(); + if (currentSoftButtonId == SOFT_BUTTON_ID_NOT_SET_VALUE) { + do { + if (generatedSoftButtonId >= SOFT_BUTTON_ID_MAX_VALUE) { + generatedSoftButtonId = SOFT_BUTTON_ID_MIN_VALUE; + } + generatedSoftButtonId++; + } while (buttonIdsSetHashSet.contains(generatedSoftButtonId)); + softButtonObject.setButtonId(generatedSoftButtonId); + buttonIdsSetHashSet.add(generatedSoftButtonId); + if (location == ManagerLocation.ALERT_MANAGER) { + softButtonIDByAlertManager.add(generatedSoftButtonId); + } else if (location == ManagerLocation.SOFTBUTTON_MANAGER) { + softButtonIDBySoftButtonManager.add(generatedSoftButtonId); + } + } + } + return true; + } } diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java index c2c9cc52e..4d93be501 100644 --- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java @@ -298,7 +298,7 @@ abstract class BaseSoftButtonManager extends BaseSubManager { return; } - if (!checkAndAssignButtonIds(softButtonObjects)) { + if (!BaseScreenManager.checkAndAssignButtonIds(softButtonObjects, BaseScreenManager.ManagerLocation.SOFTBUTTON_MANAGER)) { DebugTool.logError(TAG, "Attempted to set soft button objects, but multiple buttons had the same id"); return; } @@ -340,49 +340,6 @@ abstract class BaseSoftButtonManager extends BaseSubManager { return false; } - /** - * Check if there is a collision in the ids provided by the developer and assign ids to the SoftButtonObjects that do not have ids - * - * @param softButtonObjects the list of the SoftButtonObject values that should be displayed on the head unit - * @return boolean representing whether the ids are unique or not - */ - boolean checkAndAssignButtonIds(List<SoftButtonObject> softButtonObjects) { - // Check if multiple soft button objects have the same id - HashSet<Integer> buttonIdsSetByDevHashSet = new HashSet<>(); - int currentSoftButtonId, numberOfButtonIdsSetByDev = 0, maxButtonIdsSetByDev = SoftButtonObject.SOFT_BUTTON_ID_MIN_VALUE; - for (SoftButtonObject softButtonObject : softButtonObjects) { - currentSoftButtonId = softButtonObject.getButtonId(); - if (currentSoftButtonId != SoftButtonObject.SOFT_BUTTON_ID_NOT_SET_VALUE) { - numberOfButtonIdsSetByDev++; - if (currentSoftButtonId > maxButtonIdsSetByDev) { - maxButtonIdsSetByDev = currentSoftButtonId; - } - buttonIdsSetByDevHashSet.add(softButtonObject.getButtonId()); - } - } - if (numberOfButtonIdsSetByDev != buttonIdsSetByDevHashSet.size()) { - return false; - } - - - // Set ids for soft button objects - int generatedSoftButtonId = maxButtonIdsSetByDev; - for (SoftButtonObject softButtonObject : softButtonObjects) { - // If the dev did not set the buttonId, the manager should set an id on the dev's behalf - currentSoftButtonId = softButtonObject.getButtonId(); - if (currentSoftButtonId == SoftButtonObject.SOFT_BUTTON_ID_NOT_SET_VALUE) { - do { - if (generatedSoftButtonId >= SoftButtonObject.SOFT_BUTTON_ID_MAX_VALUE) { - generatedSoftButtonId = SoftButtonObject.SOFT_BUTTON_ID_MIN_VALUE; - } - generatedSoftButtonId++; - } while (buttonIdsSetByDevHashSet.contains(generatedSoftButtonId)); - softButtonObject.setButtonId(generatedSoftButtonId); - } - } - return true; - } - private void transitionSoftButton() { SoftButtonTransitionOperation operation = new SoftButtonTransitionOperation(internalInterface, softButtonObjects, getCurrentMainField1()); diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/DispatchGroup.java b/base/src/main/java/com/smartdevicelink/managers/screen/DispatchGroup.java new file mode 100644 index 000000000..f6dccb715 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/DispatchGroup.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +/** + * Created by Julian Kast on 12/10/20. + */ +class DispatchGroup { + private int count; + private Runnable runnable; + DispatchGroup() { + count = 0; + } + synchronized void enter() { + count++; + } + synchronized void leave() { + count--; + run(); + } + void notify(Runnable runnable) { + this.runnable = runnable; + run(); + } + private void run() { + if (count <= 0 && runnable != null) { + runnable.run(); + } + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/PresentAlertOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/PresentAlertOperation.java new file mode 100644 index 000000000..9957ad309 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/PresentAlertOperation.java @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2020 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen; + +import com.livio.taskmaster.Task; +import com.smartdevicelink.managers.AlertCompletionListener; +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.file.filetypes.SdlFile; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.RPCResponse; +import com.smartdevicelink.proxy.rpc.Alert; +import com.smartdevicelink.proxy.rpc.AlertResponse; +import com.smartdevicelink.proxy.rpc.CancelInteraction; +import com.smartdevicelink.proxy.rpc.SoftButton; +import com.smartdevicelink.proxy.rpc.SoftButtonCapabilities; +import com.smartdevicelink.proxy.rpc.TTSChunk; +import com.smartdevicelink.proxy.rpc.WindowCapability; +import com.smartdevicelink.proxy.rpc.enums.ImageFieldName; +import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities; +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; +import java.util.Map; + +/** + * Operation that handles uploading images and audio data needed by the alert and, once the data uploads are complete, sending the alert. + * + * Created by Julian Kast on 12/10/20. + */ +public class PresentAlertOperation extends Task { + private static final String TAG = "PresentAlertOperation"; + private AlertView alertView; + private AlertCompletionListener listener; + private final WeakReference<ISdl> internalInterface; + private final WeakReference<FileManager> fileManager; + WindowCapability currentWindowCapability; + private int cancelId; + private List<SpeechCapabilities> speechCapabilities; + boolean isAlertPresented; + static int SOFTBUTTON_COUNT = 4; + private BaseAlertManager.AlertSoftButtonClearListener alertSoftButtonClearListener; + + public PresentAlertOperation(ISdl internalInterface, AlertView alertView, WindowCapability currentCapabilities, List<SpeechCapabilities> speechCapabilities, FileManager fileManager, Integer cancelId, AlertCompletionListener listener, BaseAlertManager.AlertSoftButtonClearListener alertSoftButtonClearListener) { + super("PresentAlertOperation"); + this.internalInterface = new WeakReference<>(internalInterface); + this.fileManager = new WeakReference<>(fileManager); + this.currentWindowCapability = currentCapabilities; + this.speechCapabilities = speechCapabilities; + this.alertView = alertView.clone(); + this.listener = listener; + this.cancelId = cancelId; + this.isAlertPresented = false; + this.alertSoftButtonClearListener = alertSoftButtonClearListener; + + this.alertView.canceledListener = new AlertCanceledListener() { + @Override + public void onAlertCanceled() { + cancelAlert(); + } + }; + alertView.canceledListener = this.alertView.canceledListener; + } + + @Override + public void onExecute() { + DebugTool.logInfo(TAG, "Alert Operation: Executing present Alert operation"); + start(); + } + + private void start() { + if (getState() == Task.CANCELED) { + finishOperation(false, null); + return; + } + if (!isValidAlertViewData(alertView)) { + if (alertView.getAudio() != null && alertView.getAudio().getAudioData().size() > 0) { + DebugTool.logError(TAG, "The module does not support the use of only audio file data in an alert. " + + "The alert has no data and can not be sent to the module. " + + "The use of audio file data in an alert is only supported on modules supporting RPC Spec v5.0 or newer"); + } else { + DebugTool.logError(TAG, "The alert data is invalid." + + " At least either text, secondaryText or audio needs to be provided. " + + "Make sure to set at least the text, secondaryText or audio properties on the AlertView"); + } + finishOperation(false, null); + return; + } + final DispatchGroup uploadFilesTask = new DispatchGroup(); + + // Enter DispatchGroup twice for two tasks needing to be completed, One for uploading images and one for uploading audio files + uploadFilesTask.enter(); + uploadFilesTask.enter(); + + // DispatchGroup notify when all tasks are done + uploadFilesTask.notify(new Runnable() { + @Override + public void run() { + presentAlert(); + } + }); + + // DispatchGroup Task 1: uploading images + uploadImages(new CompletionListener() { + @Override + public void onComplete(boolean success) { + uploadFilesTask.leave(); + } + }); + + // DispatchGroup Task 2: uploading audio files + uploadAudioFiles(new CompletionListener() { + @Override + public void onComplete(boolean success) { + uploadFilesTask.leave(); + } + }); + } + + /** + * Checks the `AlertView` data to make sure it conforms to the RPC Spec, which says that at least either `alertText1`, `alertText2` or `TTSChunks` need to be provided. + * @param alertView - Alert data that needs to be presented + * @return true if AlertView data conforms to RPC Spec + */ + private boolean isValidAlertViewData(AlertView alertView) { + if (alertView.getText() != null && alertView.getText().length() > 0) { + return true; + } + if (alertView.getSecondaryText() != null && alertView.getSecondaryText().length() > 0) { + return true; + } + if (alertView.getAudio() != null && getTTSChunksForAlert(alertView) != null) { + return true; + } + return false; + } + + // Upload methods + + /** + * Upload the alert audio files. + * + * @param listener - CompletionListener called when all audio files have been uploaded + */ + private void uploadAudioFiles(final CompletionListener listener) { + if (!supportsAlertAudioFile()) { + DebugTool.logInfo(TAG, "Module does not support audio files for alerts, skipping upload of audio files"); + listener.onComplete(true); + return; + } + + if (alertView.getAudio() == null || alertView.getAudio().getAudioData() == null || alertView.getAudio().getAudioData().size() == 0) { + DebugTool.logInfo(TAG, "No audio files need to be uploaded for alert"); + listener.onComplete(true); + return; + } + + List<SdlFile> filesToBeUploaded = new ArrayList<>(); + for (TTSChunk ttsChunk : alertView.getAudio().getAudioData()) { + if(ttsChunk.getType() != SpeechCapabilities.FILE){ + continue; + } + SdlFile audioFile = alertView.getAudio().getAudioFiles().get(ttsChunk.getText()); + if (fileManager.get() == null || !fileManager.get().fileNeedsUpload(audioFile)) { + continue; + } + filesToBeUploaded.add(audioFile); + } + + if (filesToBeUploaded.size() == 0) { + DebugTool.logInfo(TAG, "No audio files need to be uploaded for alert"); + listener.onComplete(true); + return; + } + + DebugTool.logInfo(TAG, "Uploading audio files for alert"); + + if (fileManager.get() != null) { + fileManager.get().uploadFiles(filesToBeUploaded, new MultipleFileCompletionListener() { + @Override + public void onComplete(Map<String, String> errors) { + if (getState() == Task.CANCELED) { + finishOperation(false, null); + return; + } + if (errors != null) { + DebugTool.logError(TAG, "Error uploading alert audio files:" + errors.toString()); + } else { + DebugTool.logInfo(TAG, "All alert audio files uploaded"); + } + listener.onComplete(true); + } + }); + } + } + + /** + * Upload the alert icon and soft button images. + * + * @param listener - CompletionListener called when all images have been uploaded. + */ + private void uploadImages(final CompletionListener listener) { + List<SdlArtwork> artworksToBeUploaded = new ArrayList<>(); + + if (supportsAlertIcon() && fileManager.get() != null && fileManager.get().fileNeedsUpload(alertView.getIcon())) { + artworksToBeUploaded.add(alertView.getIcon()); + } + + if (alertView.getSoftButtons() != null) { + for (int i = 0; i < getSoftButtonCount(); i++) { + SoftButtonObject object = alertView.getSoftButtons().get(i); + if (supportsSoftButtonImages() && object.getCurrentState() != null && fileManager.get() != null && fileManager.get().fileNeedsUpload(object.getCurrentState().getArtwork())) { + artworksToBeUploaded.add(object.getCurrentState().getArtwork()); + } + } + } + + if (artworksToBeUploaded.size() == 0) { + DebugTool.logInfo(TAG, "No Images to upload for alert"); + listener.onComplete(true); + return; + } + + DebugTool.logInfo(TAG, "Uploading images for alert"); + + if (fileManager.get() != null) { + fileManager.get().uploadArtworks(artworksToBeUploaded, new MultipleFileCompletionListener() { + @Override + public void onComplete(Map<String, String> errors) { + if (getState() == Task.CANCELED) { + DebugTool.logInfo(TAG, "Operation canceled"); + finishOperation(false, null); + return; + } + + if (errors != null) { + DebugTool.logError(TAG, "AlertManager artwork failed to upload with error: " + errors.toString()); + } else { + DebugTool.logInfo(TAG, "All alert images uploaded"); + } + listener.onComplete(true); + } + }); + } + } + + /** + * Sends the alert RPC to the module. The operation is finished once a response has been received from the module. + */ + private void presentAlert() { + if (getState() == Task.CANCELED) { + DebugTool.logInfo(TAG, "Operation canceled"); + finishOperation(false, null); + return; + } + + isAlertPresented = true; + + Alert alert = alertRpc(); + + alert.setOnRPCResponseListener(new OnRPCResponseListener() { + @Override + public void onResponse(int correlationId, RPCResponse response) { + if (!response.getSuccess()) { + DebugTool.logError(TAG, "There was an error presenting the alert: " + response.getInfo()); + } else { + DebugTool.logInfo(TAG, "Alert finished presenting"); + } + finishOperation(response.getSuccess(), ((AlertResponse) response).getTryAgainTime()); + } + }); + internalInterface.get().sendRPC(alert); + } + + /** + * Cancels the alert. If the alert has not yet been sent to the module, it will not be sent. + * If the alert is already presented on the module, the alert will be immediately dismissed. + * Canceling an already presented alert will only work if connected to modules supporting RPC spec versions 6.0+. + * On older versions alert will not be dismissed. + */ + private void cancelAlert() { + 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 (internalInterface.get() != null && internalInterface.get().getSdlMsgVersion() != null && internalInterface.get().getSdlMsgVersion().getMajorVersion() < 6) { + DebugTool.logError(TAG, "Attempting to cancel this operation in-progress; if the alert is already presented on the module, it cannot be dismissed."); + this.cancelTask(); + return; + } else if (!isAlertPresented) { + DebugTool.logError(TAG, "Alert has not yet been sent to the module. Alert will not be shown.."); + this.cancelTask(); + return; + } + + DebugTool.logInfo(TAG, "Canceling the presented alert"); + + CancelInteraction cancelInteraction = new CancelInteraction(FunctionID.ALERT.getId(), cancelId); + cancelInteraction.setOnRPCResponseListener(new OnRPCResponseListener() { + @Override + public void onResponse(int correlationId, RPCResponse response) { + if (!response.getSuccess()) { + DebugTool.logInfo(TAG, "Error canceling the presented alert: " + response.getInfo()); + onFinished(); + return; + } + DebugTool.logInfo(TAG, "The presented alert was canceled successfully"); + onFinished(); + } + }); + internalInterface.get().sendRPC(cancelInteraction); + } else { + DebugTool.logInfo(TAG, "Cancelling an alert that has not yet started"); + this.cancelTask(); + } + } + + // Getters / Setters + + Alert alertRpc() { + Alert alert = new Alert(); + alert = assembleAlertText(alert); + alert.setDuration(alertView.getTimeout() * 1000); + + if (alertView.getIcon() != null && supportsAlertIcon() && !(fileManager.get().hasUploadedFile(alertView.getIcon()))) { + alert.setAlertIcon(alertView.getIcon().getImageRPC()); + } + + alert.setProgressIndicator(alertView.isShowWaitIndicator()); + alert.setCancelID(this.cancelId); + + if (alertView.getSoftButtons() != null) { + List<SoftButton> softButtons = new ArrayList<>(); + for (int i = 0; i < getSoftButtonCount(); i++) { + softButtons.add(alertView.getSoftButtons().get(i).getCurrentStateSoftButton()); + } + alert.setSoftButtons(softButtons); + } + + if (alertView.getAudio() != null) { + alert.setPlayTone(alertView.getAudio().isPlayTone()); + alert.setTtsChunks(getTTSChunksForAlert(alertView)); + } + return alert; + } + + /** + * Limits the number of SoftButtons that can be set in the AlertRPC to 4 + * + * @return The maximum number of soft buttons that can be sent to the module + */ + private int getSoftButtonCount() { + return alertView.getSoftButtons().size() <= 4 ? alertView.getSoftButtons().size() : SOFTBUTTON_COUNT; + } + + /** + * Checks if AudioFiles are supported by module and removes them form audioData list if they are not + * @param alertView + * @return List of ttsChunks + */ + private List<TTSChunk> getTTSChunksForAlert(AlertView alertView) { + AlertAudioData alertAudioData = alertView.getAudio(); + List<TTSChunk> ttsChunks = new ArrayList<>(); + for (TTSChunk chunk : alertAudioData.getAudioData()) { + if (chunk.getType() == SpeechCapabilities.FILE && !supportsAlertAudioFile()) { + continue; + } + ttsChunks.add(chunk); + } + return ttsChunks.size() > 0 ? ttsChunks : null; + } + + /** + * Checks if the connected module or current template supports soft button images. + * + * @return True if soft button images are currently supported; false if not. + */ + private boolean supportsSoftButtonImages() { + SoftButtonCapabilities softButtonCapabilities = currentWindowCapability.getSoftButtonCapabilities().get(0); + return softButtonCapabilities.getImageSupported().booleanValue(); + } + + /** + * Checks if the connected module supports audio files. Using an audio file in an alert will only work if connected to modules supporting RPC spec versions 5.0+. + * If the module does not return a speechCapabilities, assume that the module supports playing an audio file. + * + * @return True if the module supports playing audio files in an alert; false if not. + */ + private boolean supportsAlertAudioFile() { + return (internalInterface.get() != null && internalInterface.get().getSdlMsgVersion() != null && internalInterface.get().getSdlMsgVersion().getMajorVersion() >= 5 && speechCapabilities != null && speechCapabilities.contains(SpeechCapabilities.FILE)); + } + + /** + * Checks if the connected module or current template supports alert icons. + * + * @return True if alert icons are currently supported; false if not. + */ + private boolean supportsAlertIcon() { + return ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName(currentWindowCapability, ImageFieldName.alertIcon); + } + + // Text Helpers + + private Alert assembleAlertText(Alert alert) { + List<String> nonNullFields = findNonNullTextFields(); + if (nonNullFields.isEmpty()) { + return alert; + } + int numberOfLines = currentWindowCapability!= null ? ManagerUtility.WindowCapabilityUtility.getMaxNumberOfAlertFieldLines(currentWindowCapability) : 3; + switch (numberOfLines) { + case 1: + alert = assembleOneLineAlertText(alert, nonNullFields); + break; + case 2: + alert = assembleTwoLineAlertText(alert, nonNullFields); + break; + case 3: + alert = assembleThreeLineAlertText(alert, nonNullFields); + break; + } + return alert; + } + + private List<String> findNonNullTextFields() { + List<String> list = new ArrayList<>(); + + if (alertView.getText() != null && alertView.getText().length() > 0) { + list.add(alertView.getText()); + } + + if (alertView.getSecondaryText() != null && alertView.getSecondaryText().length() > 0) { + list.add(alertView.getSecondaryText()); + } + + if (alertView.getTertiaryText() != null && alertView.getTertiaryText().length() > 0) { + list.add(alertView.getTertiaryText()); + } + + return list; + } + + private Alert assembleOneLineAlertText(Alert alert, List<String> alertFields) { + StringBuilder alertString1 = new StringBuilder(); + for (int i = 0; i < alertFields.size(); i++) { + if (i > 0) { + alertString1.append(" - ").append(alertFields.get(i)); + } else { + alertString1.append(alertFields.get(i)); + } + } + alert.setAlertText1(alertString1.toString()); + return alert; + } + + private Alert assembleTwoLineAlertText(Alert alert, List<String> alertFields) { + if (alertFields.size() <= 2) { + alert.setAlertText1(alertFields.size() > 0 ? alertFields.get(0) : null); + alert.setAlertText2(alertFields.size() > 1 ? alertFields.get(1) : null); + } else { + alert.setAlertText1(alertFields.size() > 0 ? alertFields.get(0) : null); + alert.setAlertText2(alertFields.get(1) + " - " + alertFields.get(2)); + } + return alert; + } + + private Alert assembleThreeLineAlertText(Alert alert, List<String> alertFields) { + alert.setAlertText1(alertFields.size() > 0 ? alertFields.get(0) : null); + alert.setAlertText2(alertFields.size() > 1 ? alertFields.get(1) : null); + alert.setAlertText3(alertFields.size() > 2 ? alertFields.get(2) : null); + return alert; + } + + private void finishOperation(boolean success, Integer tryAgainTime) { + DebugTool.logInfo(TAG, "Finishing present alert operation"); + if (listener != null) { + listener.onComplete(success, tryAgainTime); + } + // If alertView has SoftButtons, we need to clear out their references in BaseAlertManager + if (alertView.getSoftButtons() != null) { + alertSoftButtonClearListener.onButtonClear(alertView.getSoftButtons()); + } + onFinished(); + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java index e401f8824..76301bc07 100644 --- a/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java +++ b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java @@ -50,7 +50,7 @@ import java.util.List; * * @see SoftButtonState */ -public class SoftButtonObject { +public class SoftButtonObject implements Cloneable{ private static final String TAG = "SoftButtonObject"; static final int SOFT_BUTTON_ID_NOT_SET_VALUE = -1; static final int SOFT_BUTTON_ID_MIN_VALUE = 0; @@ -295,12 +295,14 @@ public class SoftButtonObject { } /** + * DO NOT USE! let the managers assign ID's. In next major version change this will be restricted to the library * Sets the id of the SoftButtonObject <br> * <strong>Note: If the developer did not set buttonId, the manager will automatically assign an id before the SoftButtons are sent to the head unit. * Please note that the manager may reuse ids from previous batch of SoftButtons that were already sent to the head unit</strong> * * @param buttonId an int value that represents the id of the SoftButtonObject */ + @Deprecated public void setButtonId(int buttonId) { if (buttonId < SOFT_BUTTON_ID_MIN_VALUE) { DebugTool.logError(TAG, "buttonId has to be equal or more than " + SOFT_BUTTON_ID_MIN_VALUE); @@ -375,4 +377,22 @@ public class SoftButtonObject { // return comparison return hashCode() == o.hashCode(); } + + /** + * Creates a deep copy of the object + * + * @return deep copy of the object, null if an exception occurred + */ + @Override + public SoftButtonObject clone() { + try { + SoftButtonObject softButtonObject = (SoftButtonObject) super.clone(); + return softButtonObject; + } catch (CloneNotSupportedException e) { + if (DebugTool.isDebugEnabled()) { + throw new RuntimeException("Clone not supported by super class"); + } + } + return null; + } } 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 f36ad2190..44303c4bd 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 @@ -102,7 +102,8 @@ abstract class BaseChoiceSetManager extends BaseSubManager { int nextChoiceId; int nextCancelId; final int choiceCellIdMin = 1; - final int choiceCellCancelIdMin = 1; + private final int choiceCellCancelIdMin = 101; + private final int choiceCellCancelIdMax = 200; boolean isVROptional; BaseChoiceSetManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) { @@ -365,11 +366,11 @@ abstract class BaseChoiceSetManager extends BaseSubManager { if (keyboardListener == null) { // Non-searchable choice set DebugTool.logInfo(TAG, "Creating non-searchable choice set"); - presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, null, null, privateChoiceListener, nextCancelId++); + presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, null, null, privateChoiceListener, getNextCancelId()); } else { // Searchable choice set DebugTool.logInfo(TAG, "Creating searchable choice set"); - presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, keyboardConfiguration, keyboardListener, privateChoiceListener, nextCancelId++); + presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, keyboardConfiguration, keyboardListener, privateChoiceListener, getNextCancelId()); } transactionQueue.add(presentOp, false); @@ -412,7 +413,7 @@ abstract class BaseChoiceSetManager extends BaseSubManager { // Present a keyboard with the choice set that we used to test VR's optional state DebugTool.logInfo(TAG, "Presenting Keyboard - Choice Set Manager"); - Integer keyboardCancelID = nextCancelId++; + Integer keyboardCancelID = getNextCancelId(); PresentKeyboardOperation keyboardOp = new PresentKeyboardOperation(internalInterface, keyboardConfiguration, initialText, customKeyboardConfig, listener, keyboardCancelID); currentlyPresentedKeyboardOperation = keyboardOp; transactionQueue.add(keyboardOp, false); @@ -730,4 +731,18 @@ abstract class BaseChoiceSetManager extends BaseSubManager { defaultProperties.setKeypressMode(KeypressMode.RESEND_CURRENT_ENTRY); return defaultProperties; } + + /** + * Checks and increments the cancelID to keep in in a defined range + * + * @return an integer for cancelId to be sent to operations. + */ + private int getNextCancelId() { + if (nextCancelId >= choiceCellCancelIdMax) { + nextCancelId = choiceCellCancelIdMin; + } else { + nextCancelId++; + } + return nextCancelId; + } } diff --git a/javaSE/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java b/javaSE/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java index d4eef253b..8b15abaf4 100644 --- a/javaSE/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java +++ b/javaSE/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java @@ -32,11 +32,13 @@ package com.smartdevicelink.java; +import com.smartdevicelink.managers.AlertCompletionListener; import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.SdlManager; import com.smartdevicelink.managers.SdlManagerListener; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate; +import com.smartdevicelink.managers.screen.AlertView; import com.smartdevicelink.managers.screen.OnButtonListener; import com.smartdevicelink.managers.screen.choiceset.ChoiceCell; import com.smartdevicelink.managers.screen.choiceset.ChoiceSet; @@ -373,10 +375,16 @@ public class SdlService { } private void showAlert(String text) { - Alert alert = new Alert(); - alert.setAlertText1(text); - alert.setDuration(5000); - sdlManager.sendRPC(alert); + AlertView.Builder builder = new AlertView.Builder(); + builder.setText(text); + builder.setTimeout(5); + AlertView alertView = builder.build(); + sdlManager.getScreenManager().presentAlert(alertView, new AlertCompletionListener() { + @Override + public void onComplete(boolean success, Integer tryAgainTime) { + DebugTool.logInfo(TAG, "Alert presented: "+ success); + } + }); } public interface SdlServiceCallback { diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java index 09a495355..879ff0f27 100644 --- a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java +++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java @@ -34,11 +34,27 @@ package com.smartdevicelink.managers; import androidx.annotation.NonNull; +import com.livio.taskmaster.Taskmaster; import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager; import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.managers.screen.ScreenManager; +import com.smartdevicelink.protocol.ISdlServiceListener; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.protocol.enums.SessionType; +import com.smartdevicelink.proxy.RPCMessage; +import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; +import com.smartdevicelink.proxy.rpc.SdlMsgVersion; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCRequestListener; +import com.smartdevicelink.streaming.video.VideoStreamingParameters; import com.smartdevicelink.transport.enums.TransportType; import com.smartdevicelink.util.DebugTool; +import com.smartdevicelink.util.Version; + +import java.util.List; /** * <strong>SDLManager</strong> <br> @@ -178,4 +194,132 @@ public class SdlManager extends BaseSdlManager { super(appId, appName, listener); } } + private ISdl _internalInterface = new ISdl() { + @Override + public void start() { + lifecycleManager.getInternalInterface(SdlManager.this).start(); + } + + @Override + public void stop() { + lifecycleManager.getInternalInterface(SdlManager.this).start(); + } + + @Override + public boolean isConnected() { + return lifecycleManager.getInternalInterface(SdlManager.this).isConnected(); + } + + @Override + public void addServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { + lifecycleManager.getInternalInterface(SdlManager.this).addServiceListener(serviceType, sdlServiceListener); + } + + @Override + public void removeServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { + lifecycleManager.getInternalInterface(SdlManager.this).removeServiceListener(serviceType, sdlServiceListener); + } + + @Override + public void startVideoService(VideoStreamingParameters parameters, boolean encrypted) { + lifecycleManager.getInternalInterface(SdlManager.this).startVideoService(parameters,encrypted); + } + + @Override + public void startAudioService(boolean encrypted) { + lifecycleManager.getInternalInterface(SdlManager.this).startAudioService(encrypted); + } + + @Override + public void sendRPC(RPCMessage message) { + lifecycleManager.getInternalInterface(SdlManager.this).sendRPC(message); + } + + @Override + public void sendRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).sendRPCs(rpcs, listener); + } + + @Override + public void sendSequentialRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).sendSequentialRPCs(rpcs, listener); + } + + @Override + public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).addOnRPCNotificationListener(notificationId, listener); + } + + @Override + public boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + return lifecycleManager.getInternalInterface(SdlManager.this).removeOnRPCNotificationListener(notificationId, listener); + } + + @Override + public void addOnRPCRequestListener(FunctionID functionID, OnRPCRequestListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).addOnRPCRequestListener(functionID, listener); + } + + @Override + public boolean removeOnRPCRequestListener(FunctionID functionID, OnRPCRequestListener listener) { + return lifecycleManager.getInternalInterface(SdlManager.this).removeOnRPCRequestListener(functionID, listener); + } + + @Override + public void addOnRPCListener(FunctionID responseId, OnRPCListener listener) { + lifecycleManager.getInternalInterface(SdlManager.this).addOnRPCListener(responseId, listener); + } + + @Override + public boolean removeOnRPCListener(FunctionID responseId, OnRPCListener listener) { + return lifecycleManager.getInternalInterface(SdlManager.this).removeOnRPCListener(responseId, listener); + } + + @Override + public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() { + return lifecycleManager.getInternalInterface(SdlManager.this).getRegisterAppInterfaceResponse(); + } + + @Override + public boolean isTransportForServiceAvailable(SessionType serviceType) { + return lifecycleManager.getInternalInterface(SdlManager.this).isTransportForServiceAvailable(serviceType); + } + + @NonNull + @Override + public SdlMsgVersion getSdlMsgVersion() { + return lifecycleManager.getInternalInterface(SdlManager.this).getSdlMsgVersion(); + } + + @NonNull + @Override + public Version getProtocolVersion() { + return lifecycleManager.getInternalInterface(SdlManager.this).getProtocolVersion(); + } + + @Override + public long getMtu(SessionType serviceType) { + return lifecycleManager.getInternalInterface(SdlManager.this).getMtu(serviceType); + } + + @Override + public void startRPCEncryption() { + lifecycleManager.getInternalInterface(SdlManager.this).startRPCEncryption(); + } + + @Override + public Taskmaster getTaskmaster() { + return lifecycleManager.getInternalInterface(SdlManager.this).getTaskmaster(); + } + + @Override + public SystemCapabilityManager getSystemCapabilityManager() { + return lifecycleManager.getInternalInterface(SdlManager.this).getSystemCapabilityManager(); + } + + @Override + public PermissionManager getPermissionManager() { + return permissionManager; + } + }; } diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/AlertManager.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/AlertManager.java new file mode 100644 index 000000000..addcee13d --- /dev/null +++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/screen/AlertManager.java @@ -0,0 +1,18 @@ +package com.smartdevicelink.managers.screen; +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; + +import com.smartdevicelink.managers.ISdl; +import com.smartdevicelink.managers.file.FileManager; +/** + * <strong>AlertManager</strong> <br> + * <p> + * Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br> + */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +public class AlertManager extends BaseAlertManager { + + public AlertManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) { + super(internalInterface, fileManager); + } +} |