diff --git a/PepperDashEssentials/UI/JoinConstants/UIBoolJoin.cs b/PepperDashEssentials/UI/JoinConstants/UIBoolJoin.cs index 81be9002..dd454d97 100644 --- a/PepperDashEssentials/UI/JoinConstants/UIBoolJoin.cs +++ b/PepperDashEssentials/UI/JoinConstants/UIBoolJoin.cs @@ -948,5 +948,24 @@ namespace PepperDash.Essentials /// 15214 /// public const uint PinDialogDot4 = 15214; + + // Password Prompt Dialog ************************** + + /// + /// 15301 + /// + public const uint PasswordPromptDialogVisible = 15301; + /// + /// 15302 + /// + public const uint PasswordPromptTextPress = 15302; + /// + /// 15306 + /// + public const uint PasswordPromptCancelPress = 15306; + /// + /// 15307 + /// + public const uint PasswordPromptErrorVisible = 15307; } } \ No newline at end of file diff --git a/PepperDashEssentials/UI/JoinConstants/UIStringlJoin.cs b/PepperDashEssentials/UI/JoinConstants/UIStringlJoin.cs index 9b2b8a95..67a5c6e2 100644 --- a/PepperDashEssentials/UI/JoinConstants/UIStringlJoin.cs +++ b/PepperDashEssentials/UI/JoinConstants/UIStringlJoin.cs @@ -118,6 +118,14 @@ namespace PepperDash.Essentials //----- through 3120 + /// + /// 3201 + /// + public const uint PasswordPromptMessageText = 3201; + /// + /// 3202 + /// + public const uint PasswordPromptPasswordText = 3202; /// /// 3812 diff --git a/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs b/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs index 612f7eeb..6dba53f5 100644 --- a/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs +++ b/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs @@ -83,6 +83,9 @@ namespace PepperDash.Essentials.UIDrivers.VC StringBuilder SearchStringBuilder = new StringBuilder(); BoolFeedback SearchStringBackspaceVisibleFeedback; + StringFeedback PasswordStringFeedback; + StringBuilder PasswordStringBuilder = new StringBuilder(); + ModalDialog IncomingCallModal; eKeypadMode KeypadMode; @@ -180,8 +183,22 @@ namespace PepperDash.Essentials.UIDrivers.VC }); SearchStringFeedback.LinkInputSig(triList.StringInput[UIStringJoin.CodecDirectorySearchEntryText]); - SetupDirectoryList(); + PasswordStringFeedback = new StringFeedback(() => + { + if (PasswordStringBuilder.Length > 0) + { + Parent.Keyboard.EnableGoButton(); + return PasswordStringBuilder.ToString(); + } + else + { + Parent.Keyboard.DisableGoButton(); + return ""; + } + }); + PasswordStringFeedback.LinkInputSig(triList.StringInput[UIStringJoin.PasswordPromptPasswordText]); + SetupDirectoryList(); SearchStringBackspaceVisibleFeedback = new BoolFeedback(() => SearchStringBuilder.Length > 0); SearchStringBackspaceVisibleFeedback.LinkInputSig(triList.BooleanInput[UIBoolJoin.VCDirectoryBackspaceVisible]); @@ -199,6 +216,12 @@ namespace PepperDash.Essentials.UIDrivers.VC triList.SetSigHeldAction(UIBoolJoin.VCDirectoryBackspacePress, 500, StartSearchBackspaceRepeat, StopSearchBackspaceRepeat, SearchKeypadBackspacePress); + + if (Codec is IPasswordPrompt) + { + SetupPasswordPrompt(); + } + } catch (Exception e) { @@ -299,6 +322,7 @@ namespace PepperDash.Essentials.UIDrivers.VC { case eCodecCallStatus.Connected: // fire at SRL item + HidePasswordPrompt(); KeypadMode = eKeypadMode.DTMF; DialStringBuilder.Remove(0, DialStringBuilder.Length); DialStringFeedback.FireUpdate(); @@ -1328,7 +1352,21 @@ namespace PepperDash.Essentials.UIDrivers.VC /// void RevealKeyboard() { - if (VCControlsInterlock.CurrentJoin == UIBoolJoin.VCKeypadWithFavoritesVisible && KeypadMode == eKeypadMode.Dial) + if (_passwordPromptDialogVisible) + { + Debug.Console(2, "Attaching Keyboard to PasswordPromptDialog"); + DetachDialKeyboard(); + DetachSearchKeyboard(); + var kb = Parent.Keyboard; + kb.KeyPress -= Keyboard_PasswordKeyPress; + kb.KeyPress += Keyboard_PasswordKeyPress; + kb.HideAction = this.DetachPasswordKeyboard; + kb.GoButtonText = "Submit"; + kb.GoButtonVisible = true; + PasswordStringCheckEnables(); + kb.Show(); + } + else if (VCControlsInterlock.CurrentJoin == UIBoolJoin.VCKeypadWithFavoritesVisible && KeypadMode == eKeypadMode.Dial) { var kb = Parent.Keyboard; kb.KeyPress -= Keyboard_DialKeyPress; @@ -1350,6 +1388,7 @@ namespace PepperDash.Essentials.UIDrivers.VC SearchStringKeypadCheckEnables(); kb.Show(); } + } /// @@ -1405,6 +1444,32 @@ namespace PepperDash.Essentials.UIDrivers.VC } } + /// + /// Event handler for keyboard dialing + /// + void Keyboard_PasswordKeyPress(object sender, PepperDash.Essentials.Core.Touchpanels.Keyboards.KeyboardControllerPressEventArgs e) + { + if (_passwordPromptDialogVisible) + { + if (e.Text != null) + PasswordStringBuilder.Append(e.Text); + else + { + if (e.SpecialKey == KeyboardSpecialKey.Backspace) + PasswordKeypadBackspacePress(); + else if (e.SpecialKey == KeyboardSpecialKey.Clear) + PasswordKeypadClear(); + else if (e.SpecialKey == KeyboardSpecialKey.GoButton) + { + (Codec as IPasswordPrompt).SubmitPassword(PasswordStringBuilder.ToString()); + HidePasswordPrompt(); + } + } + PasswordStringFeedback.FireUpdate(); + PasswordStringCheckEnables(); + } + } + /// /// Call /// @@ -1418,6 +1483,11 @@ namespace PepperDash.Essentials.UIDrivers.VC Parent.Keyboard.KeyPress -= Keyboard_SearchKeyPress; } + void DetachPasswordKeyboard() + { + Parent.Keyboard.KeyPress -= Keyboard_PasswordKeyPress; + } + /// /// Shows the camera controls subpage /// @@ -1671,6 +1741,40 @@ namespace PepperDash.Essentials.UIDrivers.VC Parent.Keyboard.DisableGoButton(); } + /// + /// Clears the Password keypad + /// + void PasswordKeypadClear() + { + PasswordStringBuilder.Remove(0, SearchStringBuilder.Length); + PasswordStringFeedback.FireUpdate(); + PasswordStringCheckEnables(); + + } + + /// + /// + /// + void PasswordKeypadBackspacePress() + { + PasswordStringBuilder.Remove(PasswordStringBuilder.Length - 1, 1); + + PasswordStringFeedback.FireUpdate(); + PasswordStringCheckEnables(); + } + + /// + /// Checks the enabled states of various elements around the keypad + /// + void PasswordStringCheckEnables() + { + var textIsEntered = PasswordStringBuilder.Length > 0; + if (textIsEntered) + Parent.Keyboard.EnableGoButton(); + else + Parent.Keyboard.DisableGoButton(); + } + /// /// Returns the text value for the keypad dial entry field @@ -1716,5 +1820,61 @@ namespace PepperDash.Essentials.UIDrivers.VC Dial = 0, DTMF } + + void SetupPasswordPrompt() + { + var passwordPromptCodec = Codec as IPasswordPrompt; + + passwordPromptCodec.PasswordRequired += new EventHandler(passwordPromptCodec_PasswordRequired); + + TriList.SetSigFalseAction(UIBoolJoin.PasswordPromptCancelPress, HidePasswordPrompt); + TriList.SetSigFalseAction(UIBoolJoin.PasswordPromptTextPress, RevealKeyboard); + } + + void passwordPromptCodec_PasswordRequired(object sender, PasswordPromptEventArgs e) + { + if (e.LoginAttemptCancelled) + { + HidePasswordPrompt(); + return; + } + + if (!string.IsNullOrEmpty(e.Message)) + { + TriList.SetString(UIStringJoin.PasswordPromptMessageText, e.Message); + } + + if (e.LoginAttemptFailed) + { + // TODO: Show a message modal to indicate the login attempt failed + return; + } + + TriList.SetBool(UIBoolJoin.PasswordPromptErrorVisible, e.LastAttemptWasIncorrect); + + ShowPasswordPrompt(); + } + + private bool _passwordPromptDialogVisible; + + void ShowPasswordPrompt() + { + // Clear out any previous data + PasswordKeypadClear(); + + _passwordPromptDialogVisible = true; + TriList.SetBool(UIBoolJoin.PasswordPromptDialogVisible, _passwordPromptDialogVisible); + RevealKeyboard(); + } + + void HidePasswordPrompt() + { + if (_passwordPromptDialogVisible) + { + _passwordPromptDialogVisible = false; + Parent.Keyboard.Hide(); + TriList.SetBool(UIBoolJoin.PasswordPromptDialogVisible, _passwordPromptDialogVisible); + } + } } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IPasswordPrompt.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IPasswordPrompt.cs new file mode 100644 index 00000000..6ecdd775 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IPasswordPrompt.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Core +{ + /// + /// Describes the functionality required to prompt a user to enter a password + /// + public interface IPasswordPrompt + { + /// + /// Notifies when a password is required or is entered incorrectly + /// + event EventHandler PasswordRequired; + + /// + /// Submits the password + /// + /// + void SubmitPassword(string password); + } + + public class PasswordPromptEventArgs : EventArgs + { + /// + /// Indicates if the last submitted password was incorrect + /// + public bool LastAttemptWasIncorrect { get; private set; } + + /// + /// Indicates that the login attempt has failed + /// + public bool LoginAttemptFailed { get; private set; } + + /// + /// Indicates that the process was cancelled and the prompt should be dismissed + /// + public bool LoginAttemptCancelled { get; private set; } + + /// + /// A message to be displayed to the user + /// + public string Message { get; private set; } + + public PasswordPromptEventArgs(bool lastAttemptIncorrect, bool loginFailed, bool loginCancelled, string message) + { + LastAttemptWasIncorrect = lastAttemptIncorrect; + LoginAttemptFailed = loginFailed; + LoginAttemptCancelled = loginCancelled; + Message = message; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 36992629..85c66a13 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -203,6 +203,7 @@ + diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs index 29a46466..b2ccb29c 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs @@ -160,7 +160,19 @@ namespace PepperDash.Essentials.Devices.Common.Codec /// public interface IInvitableContact { + bool IsInvitableContact { get; } + } + public class InvitableDirectoryContact : DirectoryContact, IInvitableContact + { + [JsonProperty("isInvitableContact")] + public bool IsInvitableContact + { + get + { + return this is IInvitableContact; + } + } } /// @@ -209,8 +221,6 @@ namespace PepperDash.Essentials.Devices.Common.Codec [JsonProperty("title")] public string Title { get; set; } - - [JsonProperty("contactMethods")] public List ContactMethods { get; set; } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj index 21821d22..75d01b0f 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj @@ -123,6 +123,7 @@ + diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs index d31fbda1..4db4084c 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs @@ -736,6 +736,10 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } + /// + /// Appends the delimiter and send the command to the codec + /// + /// public void SendText(string command) { if (CommDebuggingIsOn) diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasStartMeeting.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasStartMeeting.cs new file mode 100644 index 00000000..a01ec099 --- /dev/null +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasStartMeeting.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces +{ + /// + /// Describes the ability to start an ad-hoc meeting + /// + public interface IHasStartMeeting + { + /// + /// The default meeting duration in minutes + /// + uint DefaultMeetingDurationMin { get; } + + /// + /// Start an ad-hoc meeting for the specified duration + /// + /// + void StartMeeting(uint duration); + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs index c116a4a8..28073eb8 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs @@ -236,14 +236,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } - /// - /// Used to be able to inplement IInvitableContact on DirectoryContact - /// - public class ZoomDirectoryContact : DirectoryContact, IInvitableContact - { - - } - public class Phonebook { [JsonProperty("Contacts")] @@ -302,7 +294,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom foreach (Contact c in zoomContacts) { - var contact = new ZoomDirectoryContact { Name = c.ScreenName, ContactId = c.Jid }; + var contact = new InvitableDirectoryContact { Name = c.ScreenName, ContactId = c.Jid }; contact.ContactMethods.Add(new ContactMethod() { Number = c.Jid, Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video, ContactMethodId = c.Jid }); @@ -927,6 +919,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom PhoneCallStatus_InCall, PhoneCallStatus_Init, } + + public class MeetingNeedsPassword + { + [JsonProperty("needsPassword")] + public bool NeedsPassword { get; set; } + + [JsonProperty("wrongAndRetry")] + public bool WrongAndRetry { get; set; } + } } /// diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs index 701ce6f2..83597081 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs @@ -25,10 +25,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom IRouting, IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraMute, IHasCameraAutoMode, IHasFarEndContentStatus, IHasSelfviewPosition, IHasPhoneDialing, IHasZoomRoomLayouts, IHasParticipantPinUnpin, - IHasParticipantAudioMute, IHasSelfviewSize + IHasParticipantAudioMute, IHasSelfviewSize, IPasswordPrompt, IHasStartMeeting { private const long MeetingRefreshTimer = 60000; - private const uint DefaultMeetingDurationMin = 30; + public uint DefaultMeetingDurationMin { get; private set; } + private const string Delimiter = "\x0D\x0A"; private readonly GenericQueue _receiveQueue; @@ -45,12 +46,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom private StringBuilder _jsonMessage; private int _previousVolumeLevel; private CameraBase _selectedCamera; + private string _lastDialedMeetingNumber; private readonly ZoomRoomPropertiesConfig _props; public ZoomRoom(DeviceConfig config, IBasicCommunication comm) : base(config) { + DefaultMeetingDurationMin = 30; + _props = JsonConvert.DeserializeObject(config.Properties.ToString()); _receiveQueue = new GenericQueue(Key + "-rxQueue", Thread.eThreadPriority.MediumPriority, 512); @@ -730,7 +734,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom ConsoleAccessLevelEnum.AccessOperator); if (!_props.DisablePhonebookAutoDownload) { - CrestronConsole.AddNewConsoleCommand(s => SendText("zCommand Phonebook List Offset: 0 Limit: 512"), + CrestronConsole.AddNewConsoleCommand(s => SendText("zCommand Phonebook List Offset: 0 Limit: 10000"), "GetZoomRoomContacts", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator); } @@ -851,7 +855,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom if (!_props.DisablePhonebookAutoDownload) { - _syncState.AddQueryToQueue("zCommand Phonebook List Offset: 0 Limit: 512"); + _syncState.AddQueryToQueue("zCommand Phonebook List Offset: 0 Limit: 10000"); } _syncState.AddQueryToQueue("zCommand Bookings List"); @@ -1349,7 +1353,20 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } case "meetingneedspassword": { - // TODO: notify user to enter a password + var meetingNeedsPassword = + responseObj.ToObject(); + + if (meetingNeedsPassword.NeedsPassword) + { + var prompt = "Password required to join this meeting. Please enter the meeting password."; + + OnPasswordRequired(meetingNeedsPassword.WrongAndRetry, false, false, prompt); + } + else + { + OnPasswordRequired(false, false, true, ""); + } + break; } case "needwaitforhost": @@ -2041,9 +2058,21 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public override void Dial(string number) { + _lastDialedMeetingNumber = number; SendText(string.Format("zCommand Dial Join meetingNumber: {0}", number)); } + /// + /// Dials a meeting with a password + /// + /// + /// + public void Dial(string number, string password) + { + Debug.Console(2, this, "Dialing meeting number: {0} with password: {1}", number, password); + SendText(string.Format("zCommand Dial Join meetingNumber: {0} password: {1}", number, password)); + } + /// /// Invites a contact to either a new meeting (if not already in a meeting) or the current meeting. /// Currently only invites a single user @@ -2051,7 +2080,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom /// public override void Dial(IInvitableContact contact) { - var ic = contact as zStatus.ZoomDirectoryContact; + var ic = contact as InvitableDirectoryContact; if (ic != null) { @@ -2069,6 +2098,21 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } + + /// + /// Starts a PMI Meeting for the specified duration (or default meeting duration if 0 is specified) + /// + /// duration of meeting + public void StartMeeting(uint duration) + { + uint dur = DefaultMeetingDurationMin; + + if (duration > 0) + dur = duration; + + SendText(string.Format("zCommand Dial StartPmi Duration: {0}", dur)); + } + public override void EndCall(CodecActiveCallItem call) { SendText("zCommand Call Disconnect"); @@ -2672,7 +2716,28 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } #endregion - } + + #region IPasswordPrompt Members + + public event EventHandler PasswordRequired; + + public void SubmitPassword(string password) + { + Debug.Console(2, this, "Password Submitted: {0}", password); + Dial(_lastDialedMeetingNumber, password); + } + + void OnPasswordRequired(bool lastAttemptIncorrect, bool loginFailed, bool loginCancelled, string message) + { + var handler = PasswordRequired; + if (handler != null) + { + handler(this, new PasswordPromptEventArgs(lastAttemptIncorrect, loginFailed, loginCancelled, message)); + } + } + + #endregion + } /// /// Zoom Room specific info object