<?xml version="1.0" encoding="UTF-8"?>
<templates version="1.26.11">
<template prio="30" name="@string/speed_dial_title" label="@param-@string/uri" icon="@drawable/invite">
<keyConfiguration>
<lua>
<code><![CDATA[local gUserpath = "/identities/identity["..pIdentity.."]/"
local gSubscriptionIsTrying = false
local gSubscriptionIsWorking = false
local gUser = "" -- username of associated identity
local gDomain = "" -- domain (registrar) of associated identity
local gPickupCode = nil -- pickup-code of associated identity
local gDoPickupWithReplaces = false -- whether or not to use a replaces header (instead of pickup-code) to pick a call
local gRemoteUri = nil -- sipUri-object of remote
local gRemoteUser = nil -- user-part of remote uri
local gLabel = nil -- label set for the key (when != gRemoteUser -> show gRemoteUser in 2nd(info)-line)
local gOngoingSubscription = nil
local gWithSubscription = pSubscriptionEnabled=="true"
local gWithPickup = pDoPickup=="true"
local gWithMissed = pBlinkOnMissed=="true"
local gWithAutoAnswer = pRequestAutoAnswer=="true"
local gHaveMissedCall = false
-- remember what we're signaling to the user, this will influence what is expected when key is pressed
-- one of:
-- - errors-state: error, trying
-- - subscription-event: idle, pickable, busy
-- - local call: calling, active, holding, ringing
-- - missed call: missed
-- - nothing going on: idle
local gLastReportedState = nil
local gPrevReportedState = nil
local gLocalCallId = nil -- wenn we have local call with gRemoteUser -> store its call-id here (e.g. TC@4)
local gLocalCallState = nil -- wenn we have local call with gRemoteUser -> store its state here (calling, active, holding or ringing)
local gFctName = "Blf" -- name for this key in logs (uri and identity will be added during initialization)
-- keep infos of all monitored calls on our remote, only used when gWithSubscription==true
-- contains these entries:
-- - "entity" : attribute from dialog-info-xml
-- - "version" : attribute from dialog-info-xml
-- - "metaState" : one of free/pickable/busy -> combined meta-state accumulated from all dialogs (monitored calls)
-- - "activeDialogs" : array of all dialog-ids of the all dialogs responsible for signaled meta-state.
-- - "dialogs" : map by dialog-id of all dialogs (monitored calls)
local gDialogInfo = nil
local function isValid() -- reports if key has all it's params set to valid values.
if pIdentity == "" or pIdentity == nil then
pIdentity = "1" -- old wui's allowed to set no identity, let's fix this for the user
end
if pRemote == "" or pRemote == nil then
return false
end
return true
end
local function getKeySettingsPath()
if key and key.INDEX and key.MODULE then
return "keys/"..key.MODULE.."/key[@keyNumber=\""..key.INDEX.."\"]"
end
return nil
end
-- Create value for replaces-header for first pickable dialog
local function getFirstReplacesInfo()
if #gDialogInfo.activeDialogs == 0 or not gDialogInfo.metaState == "pickable" then
debug.log(gFctName..": cannot find pickable call for replaces", "e")
return nil
end
local dlgXml = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]
local callId = dlgXml["call-id"]
if not callId or string.len(callId) == 0 then
debug.log(gFctName..": cannot find call-id for replaces-header", "e")
return nil
end
return sipTools.mkReplaces{callId=callId, to=dlgXml["remote-tag"], from=dlgXml["local-tag"]}
end
-- analyze existing dialogs and find a text that describes who our monitored remote is talking to
local function getBlfRemoteText()
if #gDialogInfo.activeDialogs == 0 then
return nil
elseif #gDialogInfo.activeDialogs > 1 and not gDoPickupWithReplaces then
-- wouldn't know which one would be picked
return "@string/multiple_calls"
end
-- find something to name the pick-party:
local remote = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]:find("remote")
if not remote then
debug.log(gFctName..": missing <remote> within <dialog-info>, cannot name remote party", "w")
return ""
end
local dispName = remote:find("identity", "display")
if dispName and string.len(dispName.display) > 0 then return dispName.display end
local remote_usr = remote:find("identity")
if not remote_usr then remote_usr = remote:find("target") end
if not remote_usr or not remote_usr[1] then
debug.log(gFctName..": missing remote-uri within <dialog-info>, cannot name remote party", "w")
return ""
end
if string.find(string.lower(remote_usr[1]), "anonymous") then
return "@string/call_anonyme"
end
local remote_uri = sipTools.parseUri{sipUri=remote_usr[1]}
if not remote_uri or not remote_uri:getUiNumber() then
return remote_usr[1]
end
return remote_uri:getUiNumber()
end
local function updateKey()
local desiredInfo = nil
local desiredIcon = nil
local prevReportedState = gLastReportedState
-- default to showing tel-nr in 2nd line (when it's not already shown in 1st line)
if gLabel and gLabel ~= gRemoteUser and gLabel ~= "" then
desiredInfo = gRemoteUser
end
if not isValid() then
gLastReportedState = "error"
key:setLed("orange", false)
desiredInfo = "@string/setup_failed"
desiredIcon = "@drawable/block"
else
gLastReportedState = "idle"
if gLocalCallState == "calling" then
gLastReportedState = "calling"
key:setLed("red", true)
desiredInfo = "@string/callstate_calling"
elseif gLocalCallState == "ringing" then
gLastReportedState = "ringing"
key:setLed("red", true)
desiredInfo = "@string/callstate_ringing"
elseif gLocalCallState == "holding" then
gLastReportedState = "holding"
key:setLed("red", true)
desiredInfo = "@string/callstate_holding"
elseif gLocalCallState == "active" then
gLastReportedState = "active"
key:setLed("red", false)
desiredInfo = "@string/callstate_active"
elseif gWithSubscription then
if gSubscriptionIsWorking then
if gDialogInfo.metaState == "busy" then
gLastReportedState = "busy"
key:setLed("red", false)
desiredInfo = getBlfRemoteText()
elseif gDialogInfo.metaState == "pickable" then
gLastReportedState = "pickable"
key:setLed("red", true)
desiredIcon = "@drawable/pick_up"
desiredInfo = getBlfRemoteText()
else
gLastReportedState = "idle"
key:setLed("off")
end
elseif gSubscriptionIsTrying then
gLastReportedState = "trying"
key:setLed("orange", true)
desiredInfo = "@string/subscription_trying"
else
gLastReportedState = "error"
key:setLed("orange", false)
desiredInfo = "@string/subscription_failed"
end
else
key:setLed("off")
end
if gLastReportedState == "idle" and gHaveMissedCall then
gLastReportedState = "missed"
key:setLed("green", true)
desiredInfo = "@string/missed_call"
end
end
if not (prevReportedState == gLastReportedState) then
gPrevReportedState = prevReportedState
end
key:setInfo(desiredInfo)
key:setIcon(desiredIcon)
end
function onDbResult(entries)
gHaveMissedCall = false
for i, queryParam in ipairs(entries) do
local entryUser = sipTools.parseUri{sipUri=queryParam.number}:getUser()
if entryUser == gRemoteUser then
debug.log(gFctName..": found missed call from "..tostring(queryParam.number), "d")
gHaveMissedCall = true
break
end
end
updateKey()
end
function onCallEvent(event, call1, call2)
if event == nil or call1 == nil then
debug.log(gFctName..": invalid parameters at callListChanged("..tostring(event).." ,"..tostring(call1)..")", "e")
return
end
local callId = call1:getId()
local state = call1:getState()
local number, name = call1:getRemote()
debug.log("received "..tostring(event).." for call to "..tostring(number)..", state="..tostring(state)..", callId="..tostring(callId), "v")
local remoteUri = sipTools.parseUri{sipUri=number}
local remoteUser = remoteUri:getUser()
if remoteUser ~= gRemoteUser then
if callId == gLocalCallId then
debug.log("call-partner of local "..callId.." changed, call no longer belongs to "..gFctName, "d")
gLocalCallId = nil
gLocalCallState = nil
updateKey()
end
return
end
if state == "calling" or state == "ringing" or state == "active" or state == "holding" then
if gLocalCallId ~= callId then
debug.log(gFctName.." found new local call "..callId.." in state "..state, "d")
elseif gLocalCallState ~= state then
debug.log(gFctName.." "..callId.." changed state to "..state, "d")
end
gLocalCallId = callId
gLocalCallState = state
else
debug.log(gFctName.." "..callId.." ended", "d")
gLocalCallId = nil
gLocalCallState = nil
end
updateKey()
end
local function createDialogInfoStruct(dlgVers, dlgEntity)
local result = {}
result.entity = dlgEntity
result.version = dlgVers
result.metaState = "free"
result.activeDialogs = {}
result.dialogs = {}
return result
end
local function handleNotify(subscr, data, headers)
gSubscriptionIsWorking = true
gSubscriptionIsTrying = false
local allGood, xmlOrError = pcall(xml.eval, data)
if not allGood then
debug.log(gFctName.." received Notify was not a valid XML: "..tostring(xmlOrError), "e")
debug.log(gFctName.." ignoring un-parsable notify: "..data, "e")
return
end
local dlgInfo = xmlOrError
if dlgInfo ~= nil then
debug.log(gFctName.." received Notify", "d")
if dlgInfo:tag() ~= "dialog-info" then
debug.log(gFctName..": did not receive a dialog-info, ignoring "..tostring(dlgInfo:tag()) , "e")
return
end
local dlgVers = dlgInfo.version
local dlgEntity = dlgInfo.entity
local dlgState = dlgInfo.state
if not dlgEntity or not dlgVers or not dlgState then
debug.log(gFctName..": received dialog-info is missing essencial attributes, ignoring dialog-info with "
..tostring(dlgVers).." / "..tostring(dlgEntity).." / "..tostring(dlgState) , "e")
return
end
if not gDialogInfo then
-- assuming this is the first dialog-info we've received -> initialize gDialogInfo
gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
else
if gDialogInfo.entity ~= dlgEntity then -- strange -> begin again
debug.log(gFctName..": entity in dialog-info changed, re-initializing gDialogInfo", "w")
gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
elseif gDialogInfo.version >= dlgVers then
debug.log(gFctName..": received outdated dialog-info -> ignoring version "..tostring(dlgVers), "i")
end
end
if dlgState == "full" then
gDialogInfo.dialogs = {}
end
for _,dlg in ipairs(dlgInfo) do
local dlgId = dlg.id
if dlgId then
gDialogInfo.dialogs[dlgId] = dlg
end
end
-- evaluate metaState:
local busyDlgs = {}
local pickableDlgs = {}
for dlgId, dlg in pairs(gDialogInfo.dialogs) do
local stateXml = dlg:find("state")
local state = nil
if stateXml and #stateXml >= 1 then state = stateXml[1] end
if state == "confirmed" then
busyDlgs[#busyDlgs+1] = dlgId
elseif state == "terminated" then
-- ignore
else
-- assuming proceeding, early oder trying
local direction = dlg.direction
if direction == "initiator" then
busyDlgs[#busyDlgs+1] = dlgId
else
pickableDlgs[#pickableDlgs+1] = dlgId
end
end
end
if #pickableDlgs > 0 then
gDialogInfo.activeDialogs = pickableDlgs
gDialogInfo.metaState = "pickable"
elseif #busyDlgs > 0 then
gDialogInfo.activeDialogs = busyDlgs
gDialogInfo.metaState = "busy"
else
gDialogInfo.activeDialogs = {}
gDialogInfo.metaState = "free"
end
debug.log(gFctName..": state from Notify: "..gDialogInfo.metaState, "d")
else
debug.log(gFctName..": received empty notify from "..subscr:getUri() , "e")
end
updateKey()
end
local function unsubscribe()
gSubscriptionIsWorking = false
gSubscriptionIsTrying = false
gDialogInfo = nil
if (gOngoingSubscription) then
gOngoingSubscription:unsubscribe()
gOngoingSubscription = nil
end
end
local function subscriptionTerminated(subscrObj, reason)
gSubscriptionIsWorking = false
gSubscriptionIsTrying = false
gDialogInfo = nil
if reason == "Account not found" or (reason == "Not Found" and gRemoteUser:sub(1,1) == "+") then
local cfgPath = getKeySettingsPath()
if cfgPath then
debug.log("Pbx reported surveiled account doesn't exist for "..gFctName.."-key -> disabling subscription", "e")
gWithSubscription = false
cfgPath = cfgPath.."/parameters/parameter[@name='@string/subscription_enabled']/@value"
config.set(cfgPath, "false")
end
else
debug.log(gFctName..": subscription failed with "..tostring(reason), "e")
end
updateKey()
end
local function onSubscrIsTrying()
gSubscriptionIsWorking = false
gSubscriptionIsTrying = true
updateKey()
end
local function subscribe()
unsubscribe()
if gWithSubscription then
gOngoingSubscription = sip.subscribe{uri=pRemote, onNotify=handleNotify, line=pIdentity,
onTerminated=subscriptionTerminated, onTrying=onSubscrIsTrying}
end
end
local function identityChanged(path)
gPickupCode = config.get(gUserpath .. "pickupCode")
gDoPickupWithReplaces = not gPickupCode or string.len(gPickupCode) == 0
local new_user = config.get(gUserpath .. "username")
local new_domain = sip.identities.getDomain(pIdentity);
if new_user ~= gUser or new_domain ~= domain then
unsubscribe()
gUser = new_user
gDomain = new_domain
subscribe()
updateKey()
end
end
local function setupRegChange()
config.register(gUserpath, identityChanged)
identityChanged()
end
function onKeyUp()
local headers = {}
local doPickup = false
debug.log(gFctName.."-key pressed", "d")
if not isValid() then
debug.log("missing parameter(s), "..gFctName.."-key won't work ever", "e")
return
elseif gLastReportedState == "error" then
debug.log(gFctName..": subscription had failed before, attempting restart", "d")
subscribe()
updateKey()
return
elseif gLastReportedState == "trying" then
debug.log(gFctName..": subscription is being tried: stopping and restarting", "d")
unsubscribe()
subscribe()
updateKey()
return
elseif gLastReportedState == "calling" then
debug.log(gFctName..": outgoing call thats not connected yet -> ignore key-press", "d")
return
elseif gLastReportedState == "active" then
debug.log(gFctName..": connected call -> hold", "d")
sip.calls.hold(gLocalCallId)
return
elseif gLastReportedState == "holding" then
debug.log(gFctName..": held call -> retrieve", "d")
sip.calls.resume(gLocalCallId)
return
elseif gLastReportedState == "ringing" then
debug.log(gFctName..": ringing call -> accept", "d")
sip.calls.accept(gLocalCallId)
return
elseif gWithPickup and (gLastReportedState == "pickable" or (gLastReportedState == "busy" and gPrevReportedState == "pickable")) then
debug.log(gFctName..": picking the call", "d")
local replHdr, replVal = getFirstReplacesInfo()
if replHdr then -- always add replace, even with pickup-code .. cant hurt
headers[replHdr] = replVal
end
if not gDoPickupWithReplaces then
debug.log(gFctName..": picking with pickup-code '"..gPickupCode.."'", "d")
doPickup = true
else
if replHdr then
debug.log(gFctName..": picking with Replaces-Header: "..replVal, "d")
else
debug.log(gFctName..": cannot pick, replaces-info unavailable", "e")
return
end
end
else
debug.log(gFctName..": simply calling the remote (state: "..gLastReportedState..", autoAnswer: "..tostring(gWithAutoAnswer)..")", "d")
if gWithAutoAnswer then
headers['Alert-Info'] = '<http://127.0.0.1>;info=alert-autoanswer'
end
end
sip.invite{uri=pRemote, line=pIdentity, hidden=false, headers=headers, pickup=doPickup}
end
function initialize()
key:setInfo("initializing")
if isValid() then
setupRegChange()
else
debug.log("missing parameters, "..gFctName.."-key won't work", "e")
updateKey()
return
end
gRemoteUri = sipTools.parseUri{sipUri=pRemote, fromUserInput=true, idIdx=pIdentity}
gRemoteUser = gRemoteUri:getUser()
if gWithMissed then
calllog.monitorMissed{onNewMissed=onDbResult, line=pIdentity, user=gRemoteUser}
end
sip.calls.listen{callback=onCallEvent, line=pIdentity, user=gRemoteUser}
gFctName = "BLF["..gRemoteUri:getUiNumber().." on id "..pIdentity.."]"
local cfgPath = getKeySettingsPath()
if cfgPath then
gLabel = config.get(cfgPath.."/@label")
end
updateKey()
end
initialize()]]></code>
<params>
<param name="pRemote"/>
<param name="pIdentity"/>
<param name="pSubscriptionEnabled"><value>true</value></param>
<param name="pDoPickup"><value>true</value></param>
<param name="pRequestAutoAnswer"><value>false</value></param>
<param name="pBlinkOnMissed"><value>true</value></param>
</params>
</lua>
</keyConfiguration>
<parameters>
<parameter name="@string/uri" type="sip_uri">
<path>//param[@name="pRemote"]/value</path>
</parameter>
<parameter optional="true" name="@string/identity" type="identity">
<path>//param[@name="pIdentity"]/value</path>
</parameter>
<parameter optional="true" name="@string/subscription_enabled" type="boolean">
<path>//param[@name="pSubscriptionEnabled"]/value</path>
</parameter>
<parameter optional="true" name="@string/do_pickup" type="boolean">
<path>//param[@name="pDoPickup"]/value</path>
</parameter>
<parameter optional="true" name="@string/auto_answer" type="boolean">
<path>//param[@name="pRequestAutoAnswer"]/value</path>
</parameter>
<parameter optional="true" name="@string/with_calllog" type="boolean">
<path>//param[@name="pBlinkOnMissed"]/value</path>
</parameter>
</parameters>
</template>
<template prio="20" name="@string/line_title" label="@param-@string/identity" icon="@drawable/line">
<keyConfiguration>
<line>
<id></id>
</line>
</keyConfiguration>
<parameters>
<parameter name="@string/identity" type="identity">
<path>//line/id</path>
</parameter>
</parameters>
</template>
<template prio="10" name="@string/park_position" label="@param-@string/uri" icon="@drawable/park_orbit">
<keyConfiguration>
<lua>
<code><![CDATA[local gUserpath = "/identities/identity["..pIdentity.."]/"
local gSubscriptionIsTrying = false
local gSubscriptionIsWorking = false
local gUser = "" -- username of associated identity
local gDomain = "" -- domain (registrar) of associated identity
local gPickupCode = nil -- pickup-code of associated identity
local gDoPickupWithReplaces = false -- whether or not to use a replaces header (instead of pickup-code) to pick a call
local gRemoteUri = nil -- sipUri-object of remote
local gRemoteUser = nil -- user-part of remote uri
local gOngoingSubscription = nil
-- remember what we're signaling to the user, this will influence what is expected when key is pressed
-- one of:
-- - errors-state: error, trying
-- - subscription-event: idle, pickable
-- - nothing going on: idle
local gLastReportedState = nil
local gLocalCall = nil -- when there is an active call using same identity as this orbit -> store it here in case user decides to park it
local gFctName = "ParkOrbit" -- name for this key in logs (uri and identity will be added during initialization)
-- keep infos of all monitored calls on our remote
-- contains these entries:
-- - "entity" : attribute from dialog-info-xml
-- - "version" : attribute from dialog-info-xml
-- - "metaState" : one of free/pickable -> combined meta-state accumulated from all dialogs (monitored calls)
-- - "activeDialogs" : array of all dialog-ids of the all dialogs responsible for signaled meta-state.
-- - "dialogs" : map by dialog-id of all dialogs (monitored calls)
local gDialogInfo = nil
local function isValid() -- reports if key has all it's params set to valid values. Set to nil to keep this default
if pIdentity == "" or pIdentity == nil then
return false
end
if pRemote == "" or pRemote == nil then
return false
end
return true
end
-- Create value for replaces-header for first pickable dialog
local function getFirstReplacesInfo()
if #gDialogInfo.activeDialogs == 0 or not gDialogInfo.metaState == "pickable" then
debug.log(gFctName..": cannot find pickable call for replaces", "e")
return nil
end
local dlgXml = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]
local callId = dlgXml["call-id"]
if not callId or string.len(callId) == 0 then
debug.log(gFctName..": cannot find call-id for replaces-header", "e")
return nil
end
return sipTools.mkReplaces{callId=callId, to=dlgXml["remote-tag"], from=dlgXml["local-tag"]}
end
-- analyze existing dialogs and find a text that describes who our monitored remote is talking to
local function getBlfRemoteText()
if #gDialogInfo.activeDialogs == 0 then
return nil
elseif #gDialogInfo.activeDialogs > 1 and not gDoPickupWithReplaces then
-- wouldn't know which one would be picked
return "@string/multiple_calls"
end
-- find something to name the pick-party:
local remote = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]:find("remote")
if not remote then
debug.log(gFctName..": missing <remote> within <dialog-info>, cannot name remote party", "w")
return "@string/call_unknown"
end
local dispName = remote:find("identity", "display")
if dispName and string.len(dispName.display) > 0 then return dispName.display end
local remote_usr = remote:find("identity")
if not remote_usr then remote_usr = remote:find("target") end
if not remote_usr or not remote_usr[1] then
debug.log(gFctName..": missing remote-uri within <dialog-info>, cannot name remote party", "w")
return "@string/call_unknown"
end
if string.find(string.lower(remote_usr[1]), "anonymous") then
return "@string/call_anonyme"
end
local remote_uri = sipTools.parseUri{sipUri=remote_usr[1]}
if not remote_uri or not remote_uri:getUiNumber() then
return remote_usr[1]
end
return remote_uri:getUiNumber()
end
local function updateKey()
local desiredTitle = ""
local desiredInfo = gRemoteUser
local desiredIcon = nil
if not isValid() then
gLastReportedState = "error"
key:setLed("orange", false)
desiredInfo = "@string/setup_failed"
desiredIcon = "@drawable/block"
else
if gSubscriptionIsWorking then
if gDialogInfo.metaState == "pickable" then
gLastReportedState = "pickable"
key:setLed("red", false)
desiredInfo = getBlfRemoteText()
else
gLastReportedState = "idle"
key:setLed("off")
end
elseif gSubscriptionIsTrying then
gLastReportedState = "trying"
key:setLed("orange", true)
desiredInfo = "@string/subscription_trying"
else
gLastReportedState = "error"
key:setLed("orange", false)
desiredInfo = "@string/subscription_failed"
end
end
key:setIcon(desiredIcon)
key:setTitle(desiredTitle)
key:setInfo(desiredInfo)
end
function getLocalCall(state)
local allCalls = sip.calls.get_all()
for _,aCall in ipairs(allCalls) do
if aCall:getState() == state and tostring(aCall:getIdentityIndex()) == pIdentity then
return aCall
end
end
return nil
end
function onCallEvent(event, call1, call2)
-- save the currently active call so we have it in case user wants to park it
local theCall = getLocalCall("active")
if not theCall then -- no active call .. maybe there is an incoming ringing one
theCall = getLocalCall("ringing")
end
gLocalCall = theCall
end
local function createDialogInfoStruct(dlgVers, dlgEntity)
local result = {}
result.entity = dlgEntity
result.version = dlgVers
result.metaState = "free"
result.activeDialogs = {}
result.dialogs = {}
return result
end
local function handleNotify(subscr, data, headers)
-- parse remote party and state (trying, confirmed or other)
gSubscriptionIsWorking = true
gSubscriptionIsTrying = false
local allGood, xmlOrError = pcall(xml.eval, data)
if not allGood then
debug.log(gFctName.." received Notify was not a valid XML: "..tostring(xmlOrError), "e")
debug.log(gFctName.." ignoring un-parsable notify: "..data, "e")
return
end
local dlgInfo = xmlOrError
if dlgInfo ~= nil then
debug.log(gFctName.." received Notify", "d")
if dlgInfo:tag() ~= "dialog-info" then
debug.log(gFctName..": did not receive a dialog-info, ignoring "..tostring(dlgInfo:tag()) , "e")
return
end
local dlgVers = dlgInfo.version
local dlgEntity = dlgInfo.entity
local dlgState = dlgInfo.state
if not dlgEntity or not dlgVers or not dlgState then
debug.log(gFctName..": received dialog-info is missing essencial attributes, ignoring dialog-info with "
..tostring(dlgVers).." / "..tostring(dlgEntity).." / "..tostring(dlgState) , "e")
return
end
if not gDialogInfo then
-- assuming this is the first dialog-info we've received -> initialize gDialogInfo
gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
else
if gDialogInfo.entity ~= dlgEntity then -- strange -> begin again
debug.log(gFctName..": entity in dialog-info changed, re-initializing gDialogInfo", "w")
gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
elseif gDialogInfo.version >= dlgVers then
debug.log(gFctName..": received outdated dialog-info -> ignoring version "..tostring(dlgVers), "i")
end
end
if dlgState == "full" then
gDialogInfo.dialogs = {}
end
for _,dlg in ipairs(dlgInfo) do
local dlgId = dlg.id
if dlgId then
gDialogInfo.dialogs[dlgId] = dlg
end
end
-- evaluate metaState:
local busyDlgs = {}
local pickableDlgs = {}
for dlgId, dlg in pairs(gDialogInfo.dialogs) do
local stateXml = dlg:find("state")
local state = nil
if stateXml and #stateXml >= 1 then state = stateXml[1] end
if state ~= "terminated" then
-- detailed state doesn't matter in park-orbits, it'll likely only contain confirmed dialogs and
-- whatever the state, each call should be pickable
pickableDlgs[#pickableDlgs+1] = dlgId
end
end
if #pickableDlgs > 0 then
gDialogInfo.activeDialogs = pickableDlgs
gDialogInfo.metaState = "pickable"
else
gDialogInfo.activeDialogs = {}
gDialogInfo.metaState = "free"
end
debug.log(gFctName..": state from Notify: "..gDialogInfo.metaState, "d")
else
debug.log(gFctName..": received empty notify from "..subscr:getUri() , "e")
end
updateKey()
end
local function unsubscribe()
gSubscriptionIsWorking = false
gSubscriptionIsTrying = false
gDialogInfo = nil
if (gOngoingSubscription) then
gOngoingSubscription:unsubscribe()
gOngoingSubscription = nil
end
end
local function subscriptionTerminated()
gSubscriptionIsWorking = false
gSubscriptionIsTrying = false
gDialogInfo = nil
updateKey()
end
local function onSubscrIsTrying()
gSubscriptionIsWorking = false
gSubscriptionIsTrying = true
updateKey()
end
local function subscribe()
unsubscribe()
gOngoingSubscription = sip.subscribe{uri=pRemote, onNotify=handleNotify, line=pIdentity,
onTerminated=subscriptionTerminated, onTrying=onSubscrIsTrying}
end
local function identityChanged(path)
gPickupCode = config.get(gUserpath .. "pickupCode")
gDoPickupWithReplaces = not gPickupCode or string.len(gPickupCode) == 0
local new_user = config.get(gUserpath .. "username")
local new_domain = sip.identities.getDomain(pIdentity);
if new_user ~= gUser or new_domain ~= domain then
unsubscribe()
gUser = new_user
gDomain = new_domain
subscribe()
updateKey()
end
end
local function setupRegChange()
config.register(gUserpath, identityChanged)
identityChanged()
end
function onKeyUp()
debug.log(gFctName.."-key pressed", "d")
if not isValid() then
debug.log("missing parameter(s), "..gFctName.."-key won't work ever", "e")
return
elseif gLastReportedState == "error" then
debug.log(gFctName..": subscription had failed before, attempting restart", "d")
subscribe()
updateKey()
return
elseif gLastReportedState == "trying" then
debug.log(gFctName..": subscription is being tried: stopping and restarting", "d")
unsubscribe()
subscribe()
updateKey()
return
elseif gLastReportedState == "pickable" then
debug.log(gFctName..": picking the call", "d")
sip.invite{uri=pRemote, line=pIdentity, callLogNr=2}
elseif gLocalCall then
debug.log(gFctName..": parking call to "..gLocalCall:getRemote().." on orbit "..gRemoteUser, "d")
gLocalCall:transfer(gRemoteUser)
else
debug.log(gFctName..": no call found that could be parked", "d")
end
end
function initialize()
if isValid() then
setupRegChange()
else
debug.log("missing parameters, "..gFctName.."-key won't work", "e")
updateKey()
return
end
gRemoteUri = sipTools.parseUri{sipUri=pRemote, fromUserInput=true, idIdx=pIdentity}
gRemoteUser = gRemoteUri:getUser()
sip.calls.listen(onCallEvent)
gFctName = "ParkOrbit["..gRemoteUri:getUiNumber().." on id "..pIdentity.."]"
updateKey()
end
initialize()]]></code>
<params>
<param name="pRemote"/>
<param name="pIdentity"/>
</params>
</lua>
</keyConfiguration>
<parameters>
<parameter name="@string/uri" type="sip_uri">
<path>//param[@name="pRemote"]/value</path>
</parameter>
<parameter optional="true" name="@string/identity" type="identity">
<path>//param[@name="pIdentity"]/value</path>
</parameter>
</parameters>
</template>
<template prio="10" name="@string/do_not_disturb_title" icon="@drawable/dnd">
<keyConfiguration>
<function>
<invocations>
<invocation>
<setting path="/telephony/doNotDisturb/active">
<values>
<value>true</value>
<led ledColor="red"/>
</values>
<values>
<value>false</value>
<led ledColor="off"/>
</values>
</setting>
</invocation>
</invocations>
</function>
</keyConfiguration>
</template>
<template prio="10" name="@string/call_forwarding_unconditional_title" icon="@drawable/call_forwarding">
<keyConfiguration>
<lua>
<code>pathPrefix = "/telephony/callForwarding/unconditional/"
require "callforward"</code>
</lua>
</keyConfiguration>
</template>
<template prio="10" name="@string/call_forwarding_busy_title" icon="@drawable/call_forwarding">
<keyConfiguration>
<lua>
<code>pathPrefix = "/telephony/callForwarding/busy/"
require "callforward"</code>
</lua>
</keyConfiguration>
</template>
<template prio="10" name="@string/call_forwarding_no_response_title" icon="@drawable/call_forwarding">
<keyConfiguration>
<lua>
<code>pathPrefix = "/telephony/callForwarding/noResponse/"
require "callforward"</code>
</lua>
</keyConfiguration>
</template>
<template prio="10" name="@string/http_request_title" label="@param-@string/web_url" icon="@drawable/http_request">
<keyConfiguration>
<function>
<invocations>
<invocation>
<http>
<request httpMethod="get">
<uri></uri>
</request>
<httpReactions>
<responseCode ledColor="off">
<responseCode>200</responseCode>
</responseCode>
</httpReactions>
</http>
</invocation>
</invocations>
</function>
</keyConfiguration>
<parameters>
<parameter name="@string/web_url" type="web_uri">
<path>//uri</path>
</parameter>
</parameters>
</template>
<template prio="10" name="@string/browser" label="@param-@string/web_url" icon="@drawable/http_request">
<keyConfiguration>
<function>
<invocations>
<invocation>
<intent>
<action>android.intent.action.VIEW</action>
<data></data>
<component>org.mozilla.klar/org.mozilla.focus.activity.IntentReceiverActivity</component>
</intent>
</invocation>
</invocations>
</function>
</keyConfiguration>
<parameters>
<parameter name="@string/web_url" type="web_uri">
<path>//data</path>
</parameter>
</parameters>
</template>
<template prio="10" name="@string/bluetooth" icon="@drawable/bluetooth">
<keyConfiguration>
<lua>
<code><![CDATA[
local adapter_state = 0
local function onStateChanged(state, name)
adapter_state = state
local color = "off"
local blink = false
if (state == -2) then
color = "orange"
blink = false
elseif (state == -1) then
color = "orange"
blink = false
elseif (state == 0) then
-- color off
elseif (state == 2) then
-- on
color = "green"
blink = false
else
-- turning on/off...
color = "green"
blink = true
end
key:setLed(color, blink)
end
function onKeyUp()
-- enable when state is STATE OFF otherwise disable
bluetooth.enable( adapter_state == 0 )
end
bluetooth.registerStateListener(onStateChanged) --]]></code>
</lua>
</keyConfiguration>
</template>
<template prio="10" name="@string/call_waiting_title" icon="@drawable/call_waiting">
<keyConfiguration>
<function>
<invocations>
<invocation>
<setting path="/telephony/callWaiting/active">
<values>
<value>true</value>
<led ledColor="green"/>
</values>
<values>
<value>false</value>
<led ledColor="off"/>
</values>
</setting>
</invocation>
</invocations>
</function>
</keyConfiguration>
</template>
<template prio="10" name="@string/clir_title" icon="@drawable/clir">
<keyConfiguration>
<function>
<invocations>
<invocation>
<setting path="/telephony/clir/active">
<values>
<value>true</value>
<led ledColor="green"/>
</values>
<values>
<value>false</value>
<led ledColor="off"/>
</values>
</setting>
</invocation>
</invocations>
</function>
</keyConfiguration>
</template>
<template prio="10" name="@string/vpn" icon="@drawable/setting">
<keyConfiguration>
<lua>
<code><![CDATA[local settingPath = "network/vpn/active"
local active = config.get( settingPath )
local vpnGoodViaNotification = false
local vpnGoodViaNetwork = false
local vpnHaveVpnNotis = false
local vpnNotis = {}
local function updateLed()
if active == "true" then
if vpnGoodViaNotification or vpnGoodViaNetwork then
key:setLed( "green" )
elseif vpnHaveVpnNotis then
key:setLed( "orange", true )
else
key:setLed( "orange" )
end
else
key:setLed( "off" )
end
end
local function configListener( path )
active = config.get( settingPath )
updateLed()
end
function onKeyUp()
if active == "false" then
debug.log("turning VPN on")
config.set( settingPath, "true" )
else
debug.log("turning VPN off")
config.set( settingPath, "false" )
end
end
local function onSuccessNotiConnected()
vpnGoodViaNotification = true;
updateLed()
end
local function onSuccessNotiConnectedStopped()
vpnGoodViaNotification = false;
updateLed()
end
local function onNotiConnected(note)
vpnNotis[note:getId()] = true
vpnHaveVpnNotis = true
updateLed()
end
local function onNotiConnectedStopped(note)
vpnNotis[note:getId()] = false
-- one note was stopped but maybe another vpn-note is still active..
local stillHaveVpn = false
for _,isActive in pairs(vpnNotis) do
if isActive then
stillHaveVpn = true
break
end
end
vpnHaveVpnNotis = stillHaveVpn
updateLed()
end
local function onVpnEstablished()
vpnGoodViaNetwork = true;
updateLed()
end
local function onVpnLost()
vpnGoodViaNetwork = false;
updateLed()
end
system.notifications.monitor{onCreated=onSuccessNotiConnected, onDeleted=onSuccessNotiConnectedStopped, pkg=system.notifications.constants.vpnPkg, id=system.notifications.constants.vpnIdSuccess}
system.notifications.monitor{onCreated=onNotiConnected, onDeleted=onNotiConnectedStopped, pkg=system.notifications.constants.vpnPkg}
system.network.monitor{onAvailable=onVpnEstablished, onLost=onVpnLost, transports="vpn"}
config.register( settingPath, configListener )
updateLed()--]]></code>
</lua>
</keyConfiguration>
</template>
<template prio="10" name="@string/dtmf_relay_title" label="@param-@string/dtmf_sequence" icon="@drawable/relay">
<keyConfiguration>
<lua>
<code>
function onKeyUp()
sip.calls.dtmf{dtmf = dtmf_sequence}
end
</code>
<params>
<param name="dtmf_sequence"/>
</params>
</lua>
</keyConfiguration>
<parameters>
<parameter name="@string/dtmf_sequence" type="phone">
<path>//param[@name="dtmf_sequence"]/value</path>
</parameter>
</parameters>
</template>
<template name="@string/silent_alert" prio="10" icon="@drawable/bluelight">
<keyConfiguration>
<lua>
<code>
local silentAlertNotifyId = 24242
local function onClickedNotify()
http.get{url=url, verifyHostname=false, acceptUnsignedCerts=true}
local notification = system.notifications.get{id=silentAlertNotifyId}
if notification then
notification:delete()
end
end
function onKeyUp()
if url ~= nil and url ~= "" and delay ~= nil then
if delay ~= "" and tonumber(delay) > 0 then
system.notifications.add{tag="SilentAlertTag", id=silentAlertNotifyId, title="@string/silent_alert",
message="@string/silent_alert_msg", autoClickDelay=tonumber(delay),
alert=true, alertSound=false, alertOkText="@string/silent_alert_ok", alertCancelText="",
onClicked=onClickedNotify}
else
http.get{url=url, verifyHostname=false, acceptUnsignedCerts=true}
end
end
end
</code>
<params>
<param name="url"/>
<param name="delay">
<value>10</value>
</param>
</params>
</lua>
</keyConfiguration>
<parameters>
<parameter name="@string/silent_alert_url" type="web_uri">
<path>//param[@name="url"]/value</path>
</parameter>
<parameter name="@string/silent_alert_delay" type="number" min="0" max="60">
<path>//param[@name="delay"]/value</path>
</parameter>
</parameters>
</template>
<template name="@string/sip_message" prio="10" icon="@drawable/message_outline" label="@param-@string/param_message">
<keyConfiguration>
<lua>
<code>
local silentAlertNotifyId = 24711
local function onClickedNotify()
sip.sendMessage{line=identity, message=msg, target=url}
local notification = system.notifications.get{id=silentAlertNotifyId}
if notification then
notification:delete()
end
end
function onKeyUp()
if url ~= nil and url ~= "" and delay ~= nil then
if delay ~= "" and tonumber(delay) > 0 then
system.notifications.add{tag="SipMsgTag", id=silentAlertNotifyId, title="@string/sip_message",
message="@string/silent_alert_msg", autoClickDelay=tonumber(delay),
alert=true, alertSound=false, alertOkText="@string/silent_alert_ok", alertCancelText="",
icon="@drawable/notification_icon_voice_bubble", onClicked=onClickedNotify}
else
sip.sendMessage{line=identity, message=msg, target=url}
end
end
end
</code>
<params>
<param name="url"/>
<param name="msg"/>
<param name="identity"/>
<param name="delay">
<value>0</value>
</param>
</params>
</lua>
</keyConfiguration>
<parameters>
<parameter name="@string/uri" type="sip_uri">
<path>//param[@name="url"]/value</path>
</parameter>
<parameter name="@string/identity" type="identity">
<path>//param[@name="identity"]/value</path>
</parameter>
<parameter name="@string/param_message" type="text">
<path>//param[@name="msg"]/value</path>
</parameter>
<parameter optional="true" name="@string/silent_alert_delay" type="number" min="0" max="60">
<path>//param[@name="delay"]/value</path>
</parameter>
</parameters>
</template>
</templates>