Bug 749551 - Alarm API (AlarmsManager and AlarmService). r=vivien

This commit is contained in:
Gene Lian 2012-06-22 15:39:07 +08:00
parent 6cd0768686
commit a993ee244f
4 changed files with 389 additions and 4 deletions

View File

@ -14,6 +14,7 @@ Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/ContactService.jsm');
Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
Cu.import('resource://gre/modules/Webapps.jsm');
Cu.import('resource://gre/modules/AlarmService.jsm');
XPCOMUtils.defineLazyServiceGetter(Services, 'env',
'@mozilla.org/process/environment;1',

299
dom/alarm/AlarmService.jsm Normal file
View File

@ -0,0 +1,299 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* static functions */
const DEBUG = false;
function debug(aStr) {
if (DEBUG)
dump("AlarmService: " + aStr + "\n");
}
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AlarmDB.jsm");
let EXPORTED_SYMBOLS = ["AlarmService"];
XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
});
let myGlobal = this;
let AlarmService = {
init: function init() {
debug("init()");
this._currentTimezoneOffset = (new Date()).getTimezoneOffset();
let alarmHalService = this._alarmHalService = Cc["@mozilla.org/alarmHalService;1"].getService(Ci.nsIAlarmHalService);
alarmHalService.setAlarmFiredCb(this._onAlarmFired.bind(this));
alarmHalService.setTimezoneChangedCb(this._onTimezoneChanged.bind(this));
// add the messages to be listened
const messages = ["AlarmsManager:GetAll", "AlarmsManager:Add", "AlarmsManager:Remove"];
messages.forEach(function addMessage(msgName) {
ppmm.addMessageListener(msgName, this);
}.bind(this));
// set the indexeddb database
let idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
idbManager.initWindowless(myGlobal);
this._db = new AlarmDB(myGlobal);
this._db.init(myGlobal);
// variable to save alarms waiting to be set
this._alarmQueue = [];
this._restoreAlarmsFromDb();
},
// getter/setter to access the current alarm set in system
_alarm: null,
get _currentAlarm() {
return this._alarm;
},
set _currentAlarm(aAlarm) {
this._alarm = aAlarm;
if (!aAlarm)
return;
if (!this._alarmHalService.setAlarm(this._getAlarmTime(aAlarm) / 1000, 0))
throw Components.results.NS_ERROR_FAILURE;
},
receiveMessage: function receiveMessage(aMessage) {
debug("receiveMessage(): " + aMessage.name);
let json = aMessage.json;
switch (aMessage.name) {
case "AlarmsManager:GetAll":
this._db.getAll(
function getAllSuccessCb(aAlarms) {
debug("Callback after getting alarms from database: " + JSON.stringify(aAlarms));
ppmm.sendAsyncMessage(
"AlarmsManager:GetAll:Return:OK",
{ requestID: json.requestID, alarms: aAlarms }
);
}.bind(this),
function getAllErrorCb(aErrorMsg) {
ppmm.sendAsyncMessage(
"AlarmsManager:GetAll:Return:KO",
{ requestID: json.requestID, errorMsg: aErrorMsg }
);
}.bind(this)
);
break;
case "AlarmsManager:Add":
// prepare a record for the new alarm to be added
let newAlarm = {
date: json.date,
ignoreTimezone: json.ignoreTimezone,
timezoneOffset: this._currentTimezoneOffset,
data: json.data
};
this._db.add(
newAlarm,
function addSuccessCb(aNewId) {
debug("Callback after adding alarm in database.");
newAlarm['id'] = aNewId;
let newAlarmTime = this._getAlarmTime(newAlarm);
if (newAlarmTime <= Date.now()) {
debug("Adding a alarm that has past time. Don't set it in system.");
this._debugCurrentAlarm();
return;
}
// if there is no alarm being set in system, set the new alarm
if (this._currentAlarm == null) {
this._currentAlarm = newAlarm;
this._debugCurrentAlarm();
return;
}
// if the new alarm is earlier than the current alarm
// swap them and push the previous alarm back to queue
let alarmQueue = this._alarmQueue;
let currentAlarmTime = this._getAlarmTime(this._currentAlarm);
if (newAlarmTime < currentAlarmTime) {
alarmQueue.unshift(this._currentAlarm);
this._currentAlarm = newAlarm;
this._debugCurrentAlarm();
return;
}
//push the new alarm in the queue
alarmQueue.push(newAlarm);
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
this._debugCurrentAlarm();
ppmm.sendAsyncMessage(
"AlarmsManager:Add:Return:OK",
{ requestID: json.requestID, id: aNewId }
);
}.bind(this),
function addErrorCb(aErrorMsg) {
ppmm.sendAsyncMessage(
"AlarmsManager:Add:Return:KO",
{ requestID: json.requestID, errorMsg: aErrorMsg }
);
}.bind(this)
);
break;
case "AlarmsManager:Remove":
this._db.remove(
json.id,
function removeSuccessCb() {
debug("Callback after removing alarm from database.");
// if there is no alarm being set
if (!this._currentAlarm) {
this._debugCurrentAlarm();
return;
}
// check if the alarm to be removed is in the queue
let alarmQueue = this._alarmQueue;
if (this._currentAlarm.id != json.id) {
for (let i = 0; i < alarmQueue.length; i++) {
if (alarmQueue[i].id == json.id) {
alarmQueue.splice(i, 1);
break;
}
}
this._debugCurrentAlarm();
return;
}
// the alarm to be removed is the current alarm
// reset the next alarm from queue if any
if (alarmQueue.length) {
this._currentAlarm = alarmQueue.shift();
this._debugCurrentAlarm();
return;
}
// no alarm waiting to be set in the queue
this._currentAlarm = null;
this._debugCurrentAlarm();
}.bind(this),
function removeErrorCb(aErrorMsg) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
);
break;
default:
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
break;
}
},
_onAlarmFired: function _onAlarmFired() {
debug("_onAlarmFired()");
if (this._currentAlarm) {
debug("Fire system intent: " + JSON.stringify(this._currentAlarm));
// TODO Fire a system message, see bug 755245
this._currentAlarm = null;
}
// reset the next alarm from the queue
let nowTime = Date.now();
let alarmQueue = this._alarmQueue;
while (alarmQueue.length > 0) {
let nextAlarm = alarmQueue.shift();
let nextAlarmTime = this._getAlarmTime(nextAlarm);
// if the next alarm has been expired, directly
// fire system intent for it instead of setting it
if (nextAlarmTime <= nowTime) {
debug("Fire system intent: " + JSON.stringify(nextAlarm));
// TODO Fire a system message, see bug 755245
} else {
this._currentAlarm = nextAlarm;
break;
}
}
this._debugCurrentAlarm();
},
_onTimezoneChanged: function _onTimezoneChanged(aTimezoneOffset) {
debug("_onTimezoneChanged()");
this._currentTimezoneOffset = aTimezoneOffset;
this._restoreAlarmsFromDb();
},
_restoreAlarmsFromDb: function _restoreAlarmsFromDb() {
debug("_restoreAlarmsFromDb()");
this._db.getAll(
function getAllSuccessCb(aAlarms) {
debug("Callback after getting alarms from database: " + JSON.stringify(aAlarms));
// clear any alarms set or queued in the cache
let alarmQueue = this._alarmQueue;
alarmQueue.length = 0;
this._currentAlarm = null;
// only add the alarm that is valid
let nowTime = Date.now();
aAlarms.forEach(function addAlarm(aAlarm) {
if (this._getAlarmTime(aAlarm) > nowTime)
alarmQueue.push(aAlarm);
}.bind(this));
// set the next alarm from queue
if (alarmQueue.length) {
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
this._currentAlarm = alarmQueue.shift();
}
this._debugCurrentAlarm();
}.bind(this),
function getAllErrorCb(aErrorMsg) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
);
},
_getAlarmTime: function _getAlarmTime(aAlarm) {
let alarmTime = (new Date(aAlarm.date)).getTime();
// For an alarm specified with "ignoreTimezone",
// it must be fired respect to the user's timezone.
// Supposing an alarm was set at 7:00pm at Tokyo,
// it must be gone off at 7:00pm respect to Paris'
// local time when the user is located at Paris.
// We can adjust the alarm UTC time by calculating
// the difference of the orginal timezone and the
// current timezone.
if (aAlarm.ignoreTimezone)
alarmTime += (this._currentTimezoneOffset - aAlarm.timezoneOffset) * 60000;
return alarmTime;
},
_sortAlarmByTimeStamps: function _sortAlarmByTimeStamps(aAlarm1, aAlarm2) {
return this._getAlarmTime(aAlarm1) - this._getAlarmTime(aAlarm2);
},
_debugCurrentAlarm: function _debugCurrentAlarm() {
debug("Current alarm: " + JSON.stringify(this._currentAlarm));
debug("Alarm queue: " + JSON.stringify(this._alarmQueue));
},
}
AlarmService.init();

View File

@ -4,6 +4,14 @@
"use strict";
/* static functions */
const DEBUG = false;
function debug(aStr) {
if (DEBUG)
dump("AlarmsManager: " + aStr + "\n");
}
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -17,6 +25,7 @@ const nsIClassInfo = Ci.nsIClassInfo;
function AlarmsManager()
{
debug("Constructor");
}
AlarmsManager.prototype = {
@ -34,19 +43,90 @@ AlarmsManager.prototype = {
flags: nsIClassInfo.DOM_OBJECT }),
add: function add(aDate, aRespectTimezone, aData) {
return null;
debug("add()");
let isIgnoreTimezone = true;
switch (aRespectTimezone) {
case "honorTimezone":
isIgnoreTimezone = false;
break;
case "ignoreTimezone":
isIgnoreTimezone = true;
break;
default:
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
break;
}
let request = this.createRequest();
this._cpmm.sendAsyncMessage(
"AlarmsManager:Add",
{ requestID: this.getRequestId(request), date: aDate, ignoreTimezone: isIgnoreTimezone, data: aData }
);
return request;
},
remove: function remove(aId) {
return;
debug("remove()");
return this._cpmm.sendSyncMessage(
"AlarmsManager:Remove",
{ id: aId }
);
},
getAll: function getAll() {
return null;
debug("getAll()");
let request = this.createRequest();
this._cpmm.sendAsyncMessage(
"AlarmsManager:GetAll",
{ requestID: this.getRequestId(request) }
);
return request;
},
receiveMessage: function receiveMessage(aMessage) {
debug("receiveMessage(): " + aMessage.name);
let json = aMessage.json;
let request = this.getRequest(json.requestID);
if (!request) {
debug("No request stored! " + json.requestID);
return;
}
switch (aMessage.name) {
case "AlarmsManager:Add:Return:OK":
Services.DOMRequest.fireSuccess(request, json.id);
break;
case "AlarmsManager:GetAll:Return:OK":
Services.DOMRequest.fireSuccess(request, json.alarms);
break;
case "AlarmsManager:Add:Return:KO":
Services.DOMRequest.fireError(request, json.errorMsg);
break;
case "AlarmsManager:GetAll:Return:KO":
Services.DOMRequest.fireError(request, json.errorMsg);
break;
default:
debug("Wrong message: " + aMessage.name);
break;
}
this.removeRequest(json.requestID);
},
// nsIDOMGlobalPropertyInitializer implementation
init: function init(aWindow) {
debug("init()");
// Set navigator.mozAlarms to null.
if (!Services.prefs.getBoolPref("dom.mozAlarms.enabled"))
return null;
@ -63,12 +143,16 @@ AlarmsManager.prototype = {
if (!this.hasPrivileges)
return null;
this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
// Add the valid messages to be listened.
this.initHelper(aWindow, []);
this.initHelper(aWindow, ["AlarmsManager:Add:Return:OK", "AlarmsManager:Add:Return:KO",
"AlarmsManager:GetAll:Return:OK", "AlarmsManager:GetAll:Return:KO"]);
},
// Called from DOMRequestIpcHelper.
uninit: function uninit() {
debug("uninit()");
},
}

View File

@ -27,6 +27,7 @@ EXTRA_COMPONENTS = \
EXTRA_JS_MODULES = \
AlarmDB.jsm \
AlarmService.jsm \
$(NULL)
XPIDLSRCS = \