/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set shiftwidth=4 tabstop=4 autoindent cindent expandtab: */ 'use strict'; var LocalMsgConnection = function() { this.waitingForConnection = null; this.serverWaiting = null; this.clientWaiting = null; this.serverMessages = []; this.clientMessages = []; } LocalMsgConnection.prototype.notifyConnection = function() { if (this.waitingForConnection) { this.waitingForConnection(); } } LocalMsgConnection.prototype.waitConnection = function() { return new Promise((function(resolve, reject) { this.waitingForConnection = function() { this.waitingForConnection = null; resolve(); } }).bind(this)); } LocalMsgConnection.prototype.copyMessage = function(messageQueue, data) { var msg = messageQueue.shift(); for (var i = 0; i < msg.length; i++) { data[i] = msg.data[i + msg.offset]; } return msg.length; } LocalMsgConnection.prototype.sendMessageToClient = function(message) { this.clientMessages.push(message); if (this.clientWaiting) { this.clientWaiting(); } } LocalMsgConnection.prototype.clientReceiveMessage = function(data) { return new Promise((function(resolve, reject) { if (this.clientMessages.length == 0) { this.clientWaiting = function() { this.clientWaiting = null; resolve(this.copyMessage(this.clientMessages, data)); } return; } resolve(this.copyMessage(this.clientMessages, data)); }).bind(this)); } LocalMsgConnection.prototype.sendMessageToServer = function(message) { this.serverMessages.push(message); if (this.serverWaiting) { this.serverWaiting(); } } LocalMsgConnection.prototype.serverReceiveMessage = function(data) { return new Promise((function(resolve, reject) { if (this.serverMessages.length == 0) { this.serverWaiting = function() { this.serverWaiting = null; resolve(this.copyMessage(this.serverMessages, data)); } return; } resolve(this.copyMessage(this.serverMessages, data)); }).bind(this)); } var NokiaMessagingLocalMsgConnection = function() { LocalMsgConnection.call(this); } NokiaMessagingLocalMsgConnection.prototype = Object.create(LocalMsgConnection.prototype); NokiaMessagingLocalMsgConnection.prototype.receiveSMS = function(sms) { var encoder = new DataEncoder(); encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "MessageNotify"); encoder.put(DataType.USHORT, "trans_id", Date.now() % 255); // The meaning of this field is unknown encoder.put(DataType.STRING, "type", "SMS"); // The name of this field is unknown encoder.put(DataType.ULONG, "message_id", sms.id); encoder.putEnd(DataType.STRUCT, "event"); var data = new TextEncoder().encode(encoder.getData()); this.sendMessageToClient({ data: data, length: data.length, offset: 0, }); } NokiaMessagingLocalMsgConnection.prototype.sendMessageToServer = function(message) { var encoder = new DataEncoder(); var decoder = new DataDecoder(message.data, message.offset, message.length); decoder.getStart(DataType.STRUCT); var name = decoder.getValue(DataType.METHOD); switch (name) { case "Common": encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "Common"); encoder.putStart(DataType.STRUCT, "message"); encoder.put(DataType.METHOD, "name", "ProtocolVersion"); encoder.put(DataType.STRING, "version", "2.[0-10]"); encoder.putEnd(DataType.STRUCT, "message"); encoder.putEnd(DataType.STRUCT, "event"); break; case "SubscribeMessages": encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "SubscribeMessages"); encoder.put(DataType.USHORT, "trans_id", decoder.getValue(DataType.USHORT)); // The meaning of this field is unknown encoder.put(DataType.STRING, "result", "OK"); // The name of this field is unknown encoder.putEnd(DataType.STRUCT, "event"); break; case "GetMessageEntity": var trans_id = decoder.getValue(DataType.USHORT); var sms_id = decoder.getValue(DataType.ULONG); var sms; for (var i = 0; i < MIDP.nokiaSMSMessages.length; i++) { if (MIDP.nokiaSMSMessages[i].id == sms_id) { sms = MIDP.nokiaSMSMessages[i]; break; } } encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "GetMessageEntity"); encoder.put(DataType.USHORT, "trans_id", trans_id); // The meaning of this field is unknown encoder.put(DataType.STRING, "result", "OK"); // The name of this field is unknown encoder.put(DataType.ULONG, "message_id", sms_id); encoder.putStart(DataType.LIST, "list_name_unknown"); // The name of this field is unknown encoder.put(DataType.WSTRING, "body_text", sms.text); encoder.put(DataType.STRING, "address", sms.addr); encoder.putEnd(DataType.LIST); encoder.putEnd(DataType.STRUCT, "event"); break; case "DeleteMessages": decoder.getValue(DataType.USHORT); decoder.getStart(DataType.ARRAY); var sms_id = decoder.getValue(DataType.ULONG); for (var i = 0; i < MIDP.nokiaSMSMessages.length; i++) { if (MIDP.nokiaSMSMessages[i].id == sms_id) { MIDP.nokiaSMSMessages.splice(i, 1); break; } } break; default: console.error("(nokia.messaging) event " + name + " not implemented " + util.decodeUtf8(new Uint8Array(message.data.buffer, message.offset, message.length))); return; } var data = new TextEncoder().encode(encoder.getData()); this.sendMessageToClient({ data: data, length: data.length, offset: 0, }); } var NokiaContactsLocalMsgConnection = function() { LocalMsgConnection.call(this); } var NokiaPhoneStatusLocalMsgConnection = function() { LocalMsgConnection.call(this); }; NokiaPhoneStatusLocalMsgConnection.prototype = Object.create(LocalMsgConnection.prototype); NokiaPhoneStatusLocalMsgConnection.prototype.sendMessageToServer = function(message) { var decoder = new DataDecoder(message.data, message.offset, message.length); decoder.getStart(DataType.STRUCT); var name = decoder.getValue(DataType.METHOD); var encoder = new DataEncoder(); switch (name) { case "Common": encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "Common"); encoder.putStart(DataType.STRUCT, "message"); encoder.put(DataType.METHOD, "name", "ProtocolVersion"); encoder.put(DataType.STRING, "version", "2.[0-10]"); encoder.putEnd(DataType.STRUCT, "message"); encoder.putEnd(DataType.STRUCT, "event"); break; case "Query": encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "Query"); encoder.put(DataType.STRING, "status", "OK"); encoder.putStart(DataType.LIST, "subscriptions"); // subscriptions decoder.getStart(DataType.LIST); while (decoder.getTag() == DataType.STRING) { switch (decoder.getName()) { case "network_status": encoder.putStart(DataType.STRUCT, "network_status"); encoder.put(DataType.STRING, "", ""); // unknow name encoder.put(DataType.BOOLEAN, "", 1); // unknow name encoder.putEnd(DataType.STRUCT, "network_status"); break; case "wifi_status": encoder.putStart(DataType.STRUCT, "wifi_status"); encoder.put(DataType.BOOLEAN, "", 1); // unknow name, but it should indicate if the wifi is connected, and let's assume it's always connected. encoder.putEnd(DataType.STRUCT, "wifi_status"); break; case "battery": encoder.putStart(DataType.STRUCT, "battery"); encoder.put(DataType.BYTE, "", 1); // unknow name encoder.put(DataType.BOOLEAN, "", 1); // unknow name encoder.putEnd(DataType.STRUCT, "battery"); break; default: console.error("(nokia.phone-status) Query " + decoder.getName() + " not implemented " + util.decodeUtf8(new Uint8Array(message.data.buffer, message.offset, message.length))); break; } decoder.getValue(DataType.STRING); } encoder.putEnd(DataType.LIST, "subscriptions"); encoder.putEnd(DataType.STRUCT, "event"); break; default: console.error("(nokia.phone-status) event " + name + " not implemented " + util.decodeUtf8(new Uint8Array(message.data.buffer, message.offset, message.length))); return; } var data = new TextEncoder().encode(encoder.getData()); this.sendMessageToClient({ data: data, length: data.length, offset: 0, }); }; NokiaContactsLocalMsgConnection.prototype = Object.create(LocalMsgConnection.prototype); NokiaContactsLocalMsgConnection.prototype.sendContact = function(trans_id, contact) { var encoder = new DataEncoder(); encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "Notify"); encoder.put(DataType.ULONG, "trans_id", trans_id); // The meaning of this field is unknown encoder.put(DataType.BYTE, "type", 1); // The name of this field is unknown (the value may be 1, 2, 3 according to the event (I'd guess CREATE, DELETE, UPDATE)) encoder.putStart(DataType.LIST, "Contact"); encoder.put(DataType.WSTRING, "ContactID", contact.id.toString()); encoder.put(DataType.WSTRING, "DisplayName", contact.name[0]); encoder.putStart(DataType.ARRAY, "Numbers"); contact.tel.forEach(function(tel) { encoder.putStart(DataType.LIST, "NumbersList"); // The name of this field is unknown // encoder.put(DataType.ULONG, "Kind", ???); // The meaning of this field is unknown encoder.put(DataType.WSTRING, "Number", tel.value); encoder.putEnd(DataType.LIST, "NumbersList"); }); encoder.putEnd(DataType.ARRAY, "Numbers"); encoder.putEnd(DataType.LIST, "Contact"); encoder.putEnd(DataType.STRUCT, "event"); var data = new TextEncoder().encode(encoder.getData()); this.sendMessageToClient({ data: data, length: data.length, offset: 0, }); }; NokiaContactsLocalMsgConnection.prototype.sendMessageToServer = function(message) { var decoder = new DataDecoder(message.data, message.offset, message.length); decoder.getStart(DataType.STRUCT); var name = decoder.getValue(DataType.METHOD); switch (name) { case "Common": var encoder = new DataEncoder(); encoder.putStart(DataType.STRUCT, "event"); encoder.put(DataType.METHOD, "name", "Common"); encoder.putStart(DataType.STRUCT, "message"); encoder.put(DataType.METHOD, "name", "ProtocolVersion"); encoder.put(DataType.STRING, "version", "2.[0-10]"); encoder.putEnd(DataType.STRUCT, "message"); encoder.putEnd(DataType.STRUCT, "event"); var data = new TextEncoder().encode(encoder.getData()); this.sendMessageToClient({ data: data, length: data.length, offset: 0, }); break; case "NotifySubscribe": contacts.forEach(this.sendContact.bind(this, decoder.getValue(DataType.ULONG))); break; default: console.error("(nokia.contacts) event " + name + " not implemented " + util.decodeUtf8(new Uint8Array(message.data.buffer, message.offset, message.length))); return; } } MIDP.LocalMsgConnections = {}; // Add some fake servers because some MIDlets assume they exist. // MIDlets are usually happy even if the servers don't reply, but we should // remember to implement them in case they will be needed. MIDP.FakeLocalMsgServers = [ "nokia.active-standby", "nokia.profile", "nokia.connectivity-settings", "nokia.file-ui" ]; MIDP.FakeLocalMsgServers.forEach(function(server) { MIDP.LocalMsgConnections[server] = new LocalMsgConnection(); }); MIDP.LocalMsgConnections["nokia.contacts"] = new NokiaContactsLocalMsgConnection(); MIDP.LocalMsgConnections["nokia.messaging"] = new NokiaMessagingLocalMsgConnection(); MIDP.LocalMsgConnections["nokia.phone-status"] = new NokiaPhoneStatusLocalMsgConnection(); Native.create("org/mozilla/io/LocalMsgConnection.init.(Ljava/lang/String;)V", function(jName) { var name = util.fromJavaString(jName); this.server = (name[2] == ":"); this.protocolName = name.slice((name[2] == ':') ? 3 : 2); return new Promise((function(resolve, reject) { if (this.server) { MIDP.LocalMsgConnections[this.protocolName] = new LocalMsgConnection(); MIDP.ConnectionRegistry.pushNotify("localmsg:" + this.protocolName); } else { // Actually, there should always be a server, but we need this check // for apps that use the Nokia built-in servers (because we haven't // implemented them yet). if (!MIDP.LocalMsgConnections[this.protocolName]) { console.warn("localmsg server (" + this.protocolName + ") unimplemented"); // Return without resolving the promise, we want the thread that is connecting // to this unimplemented server to stop indefinitely. return; } if (MIDP.FakeLocalMsgServers.indexOf(this.protocolName) != -1) { console.warn("connect to an unimplemented localmsg server (" + this.protocolName + ")"); } MIDP.LocalMsgConnections[this.protocolName].notifyConnection(); } resolve(); }).bind(this)); }); Native.create("org/mozilla/io/LocalMsgConnection.waitConnection.()V", function() { return MIDP.LocalMsgConnections[this.protocolName].waitConnection(); }); Native.create("org/mozilla/io/LocalMsgConnection.sendData.([BII)V", function(data, offset, length) { var message = { data: data, offset: offset, length: length, }; if (this.server) { MIDP.LocalMsgConnections[this.protocolName].sendMessageToClient(message); } else { if (MIDP.FakeLocalMsgServers.indexOf(this.protocolName) != -1) { console.warn("sendData (" + util.decodeUtf8(new Uint8Array(data.buffer, offset, length)) + ") to an unimplemented localmsg server (" + this.protocolName + ")"); } MIDP.LocalMsgConnections[this.protocolName].sendMessageToServer(message); } }); Native.create("org/mozilla/io/LocalMsgConnection.receiveData.([B)I", function(data) { if (this.server) { return MIDP.LocalMsgConnections[this.protocolName].serverReceiveMessage(data); } if (MIDP.FakeLocalMsgServers.indexOf(this.protocolName) != -1) { console.warn("receiveData from an unimplemented localmsg server (" + this.protocolName + ")"); } return MIDP.LocalMsgConnections[this.protocolName].clientReceiveMessage(data); }); Native.create("org/mozilla/io/LocalMsgConnection.closeConnection.()V", function() { if (this.server) { delete MIDP.LocalMsgConnections[this.protocolName]; } });