Skip to content
This repository has been archived by the owner on Aug 20, 2018. It is now read-only.

Implement Indicator and IndicatorManager classes #579

Merged
merged 21 commits into from Dec 10, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b518e51
WIP implementation of the Indicator and IndicatorManager Nokia-specif…
Nov 12, 2014
d647e27
Add desktop-notification permission to the manifest
Nov 12, 2014
2ae1ab6
Register pipe to send notifications using the Notification API
Nov 12, 2014
8f23351
Send notification when Indicator::setActive is called with active=tru…
Nov 12, 2014
57ea34f
Show the MIDlet name in the notification text
Nov 12, 2014
b5fcfd9
Merge branch 'master' of https://github.com/andreasgal/j2me.js into i…
Dec 5, 2014
56afe70
WIP implementation of nokia.active-standby localmsg server (Common an…
Dec 5, 2014
3cee452
Merge branch 'master' of https://github.com/andreasgal/j2me.js into i…
Dec 5, 2014
31a5b71
Temporarily undo changes to Indicator and IndicatorManager
Dec 5, 2014
acf40a8
Support DataEncoder::put(String,byte[],int)
Dec 5, 2014
b113208
Send 'Activated' message right after 'Register'
Dec 5, 2014
4eaad70
Handle 'Update' method
Dec 6, 2014
4d78528
WIP update
Dec 6, 2014
589f91e
Merge branch 'master' of https://github.com/andreasgal/j2me.js into i…
Dec 7, 2014
ffbc3f1
Show notification when the Indicator is active, close the notificatio…
Dec 8, 2014
25869a4
Resize icon to 32x32 pixels, needed for the Notifications API to work
Dec 8, 2014
82d19ef
Add test for nokia.active-standby, Indicator and IndicatorManager
Dec 8, 2014
503bf8d
Update number of passing tests
Dec 8, 2014
f959560
Add comments explaining how we handle notifications in Indicator and …
Dec 10, 2014
3005e6a
Merge branch 'master' of https://github.com/andreasgal/j2me.js into i…
Dec 10, 2014
1ac1f0b
Add logging if the parameters to Indicator and IndicatorManager are u…
Dec 10, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 54 additions & 1 deletion index.js
Expand Up @@ -106,7 +106,7 @@ var DumbPipe = {
try {
document.getElementById("mozbrowser").contentWindow.postMessage(envelope, "*");
} catch (e) {
console.log("Error " + e + " while sending message: " + JSON.stringify(message));
console.log("Error " + e + " while sending message: " + JSON.stringify(envelope));
}
},

Expand Down Expand Up @@ -421,3 +421,56 @@ DumbPipe.registerOpener("camera", function(message, sender) {
}
};
});

var notification = null;
DumbPipe.registerOpener("notification", function(message, sender) {
if (notification) {
notification.close();
notification = null;
}

var img = new Image();
img.src = URL.createObjectURL(new Blob([ new Uint8Array(message.icon) ], { type : message.mime_type }));
img.onload = function() {
var width = Math.min(32, img.naturalWidth);
var height = Math.min(32, img.naturalHeight);

var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);

message.options.icon = canvas.toDataURL();

function permissionGranted() {
notification = new Notification(message.title, message.options);
notification.onshow = function() {
sender({ type: "opened" });
};
}

if (Notification.permission === "granted") {
permissionGranted();
} else if (Notification.permission !== 'denied') {
Notification.requestPermission(function(permission) {
if (permission === "granted") {
permissionGranted();
}
});
}
}

return function(message) {
switch(message.type) {
case "close":
if (notification) {
notification.close();
notification = null;
}

sender({ type: "close" });
break;
}
}
});
4 changes: 1 addition & 3 deletions java/custom/com/nokia/mid/s40/codec/DataEncoder.java
Expand Up @@ -23,7 +23,5 @@ public DataEncoder(String name) throws IOException {

public native void put(int tag, String name, double value) throws IOException;

public void put(String aString, byte[] aArray, int aInt) throws IOException {
throw new RuntimeException("DataEncoder::put(String,byte[],int) not implemented");
}
public native void put(String name, byte[] data, int length) throws IOException;
}
30 changes: 25 additions & 5 deletions java/custom/com/nokia/mid/ui/lcdui/Indicator.java
Expand Up @@ -2,16 +2,36 @@

import javax.microedition.lcdui.Image;

/*
* This class is undocumented.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the int supposed to be? Should we log the fact that we just drop it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, it's always been 0 in my testing. Unfortunately there's no documentation available for this class (and for IndicatorManager).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK in that case let's just go with this until something breaks :)

* We're implementing indicators using the Notifications API.
* When setActive is called with true, we enable showing notifications.
* When setActive is called with false, we dismiss notifications.
* This class is associated with the nokia.active-standby localmsg
* server, that gives us the text associated with the Indicator.
* We show a notification when the nokia.active-standby receives an
* "Update" event and the Indicator is active.
*
* We're storing an icon in the image property, but we're not really
* using it (we're using the icon sent to the nokia.active-standby
* server).
*
*/

public class Indicator {
Image image;

public Indicator(int aInt, Image aImage) {
System.out.println("Indicator(IL...Image;) not implemented (" + aInt + ", " + aImage + ")");
if (aInt != 0) {
System.out.println("Indicator(IL...Image;) unexpected value (" + aInt + ", " + aImage + ")");
}
setIcon(aImage);
}

public void setActive(boolean active) {
System.out.println("Indicator.setActive(Z)V not implemented (" + active + ")");
}
public native void setActive(boolean active);

public void setIcon(Image image) {
System.out.println("Indicator.setIcon(IL...Image;)V not implemented.");
this.image = image;
}
}
15 changes: 14 additions & 1 deletion java/custom/com/nokia/mid/ui/lcdui/IndicatorManager.java
@@ -1,5 +1,16 @@
package com.nokia.mid.ui.lcdui;

/*
* This class is undocumented.
*
* We think this class is needed when multiple background MIDlets
* need to append an Indicator to the list of the system indicators.
* We don't really need to do anything in appendIndicator, since
* we're using the Notifications API to implement indicators. When
* a notification is created, it is automatically shown to the user.
*
*/

public class IndicatorManager {
private static IndicatorManager instance = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't seem to be implementing this; why are we removing the notification?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need to implement this because it doesn't map to how the Notifications API work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we don't expect this function to ever be called? Logging won't hurt us in that case and it would alert us if our expectation is broken

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function (and the Indicator constructor) are called at startup.
So I think what the MIDlet does is:
Indicator indicator = new Indicator(0, image);
IndicatorManager indicatorManager = IndicatorManager.getIndicatorManager();
indicatorManager.appendIndicator(indicator, false);

I guess the IndicatorManager is needed when you have multiple MIDlets running simultaneously in the background, so each one needs an indicator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. It seems like in the future we might run into problems if an app creates an Indicator but doesn't intend to append it (or will append it conditionally at some later time). Let's cross that bridge if we ever get to it; we can land this as is and deal with issues in the future if they ever come up.


Expand All @@ -12,7 +23,9 @@ public static IndicatorManager getIndicatorManager() {
}

public int appendIndicator(Indicator indicator, boolean paramBoolean) {
System.out.println("IndicatorManager.appendIndicator(L...Indicator;Z)I not implemented");
if (paramBoolean) {
System.out.println("IndicatorManager.appendIndicator(L...Indicator;Z)I unexpected value");
}
return 0;
}
}
3 changes: 3 additions & 0 deletions manifest.webapp
Expand Up @@ -29,6 +29,9 @@
},
"video-capture": {
"description": "Required to take pictures"
},
"desktop-notification": {
"description": "Required to display notifications"
}
},
"type": "privileged"
Expand Down
13 changes: 13 additions & 0 deletions midp/codec.js
Expand Up @@ -54,6 +54,13 @@ DataEncoder.prototype.put = function(tag, name, value) {
});
}

DataEncoder.prototype.putNoTag = function(name, value) {
this.data.push({
name: name,
value: value,
});
}

DataEncoder.prototype.getData = function() {
return JSON.stringify(this.data);
}
Expand Down Expand Up @@ -148,6 +155,12 @@ Native.create("com/nokia/mid/s40/codec/DataEncoder.put.(ILjava/lang/String;Z)V",
this.encoder.put(tag, util.fromJavaString(name), value);
});

Native.create("com/nokia/mid/s40/codec/DataEncoder.put.(Ljava/lang/String;[BI)V", function(name, data, length) {
var array = Array.prototype.slice.call(data.subarray(0, length));
array.constructor = Array;
this.encoder.putNoTag(util.fromJavaString(name), array);
});

Native.create("com/nokia/mid/s40/codec/DataEncoder.putEnd.(ILjava/lang/String;)V", function(tag, name) {
this.encoder.putEnd(tag, util.fromJavaString(name));
});
Expand Down
139 changes: 136 additions & 3 deletions midp/localmsg.js
Expand Up @@ -756,13 +756,145 @@ NokiaImageProcessingLocalMsgConnection.prototype.sendMessageToServer = function(
}
};

var NokiaActiveStandbyLocalMsgConnection = function() {
LocalMsgConnection.call(this);
}

NokiaActiveStandbyLocalMsgConnection.indicatorActive = false;
NokiaActiveStandbyLocalMsgConnection.pipeSender = null;

NokiaActiveStandbyLocalMsgConnection.prototype = Object.create(LocalMsgConnection.prototype);

NokiaActiveStandbyLocalMsgConnection.prototype.recipient = function(message) {
switch (message.type) {
case "close":
DumbPipe.close(NokiaActiveStandbyLocalMsgConnection.pipeSender);
NokiaActiveStandbyLocalMsgConnection.pipeSender = null;
break;
}
}

NokiaActiveStandbyLocalMsgConnection.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", "1.[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 "Register":
var client_id = decoder.getValue(DataType.STRING);
var personalise_view_text = decoder.getValue(DataType.WSTRING);
decoder.getValue(DataType.BOOLEAN);

encoder.putStart(DataType.STRUCT, "event");
encoder.put(DataType.METHOD, "name", "Register");
encoder.put(DataType.WSTRING, "client_id", client_id);
encoder.put(DataType.STRING, "result", "OK"); // Name unknown
encoder.putEnd(DataType.STRUCT, "event");

var data = new TextEncoder().encode(encoder.getData());
this.sendMessageToClient({
data: data,
length: data.length,
offset: 0,
});

setZeroTimeout((function() {
var encoder = new DataEncoder();

encoder.putStart(DataType.STRUCT, "event");
encoder.put(DataType.METHOD, "name", "Activated");
encoder.put(DataType.WSTRING, "client_id", client_id);
encoder.putStart(DataType.LIST, "unknown_list");
// Unknown DataType.STRING elements
encoder.putEnd(DataType.LIST, "unknown_list");
encoder.put(DataType.BYTE, "unkown_byte", 1); // Name unknown
encoder.put(DataType.SHORT, "unknown_short_1", 0); // Name and value unknown
encoder.put(DataType.SHORT, "unknown_short_2", 0); // Name and value unknown
encoder.putEnd(DataType.STRUCT, "event");

var data = new TextEncoder().encode(encoder.getData());
this.sendMessageToClient({
data: data,
length: data.length,
offset: 0,
});
}).bind(this));
break;

case "Update":
var client_id = decoder.getValue(DataType.STRING);
var personalise_view_text = decoder.getValue(DataType.WSTRING);
var activate_scroll_events = decoder.getValue(DataType.BOOLEAN);
var content_icon = decoder.getNextValue();
var mime_type = decoder.getValue(DataType.STRING);
var context_text = decoder.getValue(DataType.WSTRING);

if (NokiaActiveStandbyLocalMsgConnection.indicatorActive) {
NokiaActiveStandbyLocalMsgConnection.pipeSender = DumbPipe.open("notification", {
title: personalise_view_text,
options: {
body: context_text,
},
icon: content_icon,
mime_type: mime_type,
}, this.recipient.bind(this));
}

encoder.putStart(DataType.STRUCT, "event");
encoder.put(DataType.METHOD, "name", "Update");
encoder.put(DataType.WSTRING, "client_id", client_id);
encoder.put(DataType.STRING, "result", "OK"); // Name unknown
encoder.putEnd(DataType.STRUCT, "event");

var data = new TextEncoder().encode(encoder.getData());
this.sendMessageToClient({
data: data,
length: data.length,
offset: 0,
});
break;

default:
console.error("(nokia.active-standby) event " + name + " not implemented " +
util.decodeUtf8(new Uint8Array(message.data.buffer, message.offset, message.length)));
return;
}
}

Native.create("com/nokia/mid/ui/lcdui/Indicator.setActive.(Z)V", function(active) {
NokiaActiveStandbyLocalMsgConnection.indicatorActive = active;

if (!active && NokiaActiveStandbyLocalMsgConnection.pipeSender) {
NokiaActiveStandbyLocalMsgConnection.pipeSender({ type: "close" });
}
});

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" ];
MIDP.FakeLocalMsgServers = [ "nokia.profile", "nokia.connectivity-settings" ];

MIDP.FakeLocalMsgServers.forEach(function(server) {
MIDP.LocalMsgConnections[server] = LocalMsgConnection;
Expand All @@ -774,6 +906,7 @@ MIDP.LocalMsgConnections["nokia.phone-status"] = NokiaPhoneStatusLocalMsgConnect
MIDP.LocalMsgConnections["nokia.file-ui"] = NokiaFileUILocalMsgConnection;
MIDP.LocalMsgConnections["nokia.image-processing"] = NokiaImageProcessingLocalMsgConnection;
MIDP.LocalMsgConnections["nokia.sa.service-registry"] = NokiaSASrvRegLocalMsgConnection;
MIDP.LocalMsgConnections["nokia.active-standby"] = NokiaActiveStandbyLocalMsgConnection;

Native.create("org/mozilla/io/LocalMsgConnection.init.(Ljava/lang/String;)V", function(jName) {
var name = util.fromJavaString(jName);
Expand All @@ -791,7 +924,7 @@ Native.create("org/mozilla/io/LocalMsgConnection.init.(Ljava/lang/String;)V", fu
// 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");
console.error("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;
Expand Down
2 changes: 1 addition & 1 deletion tests/automation.js
Expand Up @@ -58,7 +58,7 @@ var gfxTests = [
];

var expectedUnitTestResults = [
{ name: "pass", number: 71197 },
{ name: "pass", number: 71210 },
{ name: "fail", number: 0 },
{ name: "known fail", number: 180 },
{ name: "unknown pass", number: 0 }
Expand Down