Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
665 lines
20 KiB
JavaScript
Executable file
665 lines
20 KiB
JavaScript
Executable file
/* global SettingsHelper */
|
|
/* global asyncStorage */
|
|
/* global Requester */
|
|
/* global Commands */
|
|
/* global Config */
|
|
/* global DUMP */
|
|
/* global IAC_API_WAKEUP_REASON_ENABLED_CHANGED */
|
|
/* global IAC_API_WAKEUP_REASON_LOGIN */
|
|
/* global IAC_API_WAKEUP_REASON_LOGOUT */
|
|
/* global IAC_API_WAKEUP_REASON_STALE_REGISTRATION */
|
|
/* global IAC_API_WAKEUP_REASON_TRY_DISABLE */
|
|
/* global IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED */
|
|
|
|
'use strict';
|
|
|
|
var FindMyDevice = {
|
|
_state: null,
|
|
|
|
_registering: false,
|
|
|
|
_refreshingClientID: false,
|
|
|
|
_reply: {},
|
|
|
|
_registered: false,
|
|
|
|
_registeredHelper: null,
|
|
|
|
_enabled: false,
|
|
|
|
_enabledHelper: null,
|
|
|
|
_loggedIn: false,
|
|
|
|
_currentClientID: '',
|
|
|
|
_currentClientIDHelper: null,
|
|
|
|
_canDisable: false,
|
|
|
|
_canDisableHelper: null,
|
|
|
|
_retryCount: -1,
|
|
|
|
_retryCountHelper: null,
|
|
|
|
_disableAttempt: false,
|
|
|
|
// There are two situations in which we want to make sure
|
|
// FMD is alive:
|
|
// - when it is running client logic that accesses the network;
|
|
// - when it is executing a command with a duration.
|
|
// Command wakelocks are managed by individual commands.
|
|
// For client logic wakelocks, we use the fact that when a network
|
|
// request succeeds, we'll either update 'findmydevice.registered',
|
|
// or 'findmydevice.can-disable', or process a command and set
|
|
// a 'ping' timer, and when it fails, we'll set a timer to retry.
|
|
// Since each network transaction finishes with either a change
|
|
// in one of these settings or with a timer, we release the locks
|
|
// in the observers for the settings and in _scheduleAlarm.
|
|
_highPriorityWakeLocks: {
|
|
clientLogic: [],
|
|
command: []
|
|
},
|
|
|
|
_fxaReady: false,
|
|
|
|
init: function fmd_init() {
|
|
var self = this;
|
|
|
|
navigator.mozId.watch({
|
|
wantIssuer: 'firefox-accounts',
|
|
audience: Config.api_url,
|
|
onready: self._onReady.bind(self),
|
|
onlogin: self._onLogin.bind(self),
|
|
onlogout: self._onLogout.bind(self),
|
|
onerror: self._onFxAError.bind(self)
|
|
});
|
|
},
|
|
|
|
_observeSettings: function fmd_observe_settings() {
|
|
var settings = navigator.mozSettings;
|
|
|
|
settings.addObserver('findmydevice.registered',
|
|
this._onRegisteredChanged.bind(this));
|
|
settings.addObserver('findmydevice.enabled',
|
|
this._onEnabledChanged.bind(this));
|
|
settings.addObserver('findmydevice.current-clientid',
|
|
this._onClientIDChanged.bind(this));
|
|
|
|
// We only allow disabling Find My Device if the same person
|
|
// who first enabled it is logged in to FxA, and in that case
|
|
// 'findmydevice.can-disable' is true.
|
|
// However, note that FMD doesn't store the user's email address,
|
|
// but rather a client ID that is derived from the FxA assertion
|
|
// and stored in this._state.clientid.
|
|
settings.addObserver('findmydevice.can-disable',
|
|
this._onCanDisableChanged.bind(this));
|
|
},
|
|
|
|
_onReady: function fmd_fxa_onready() {
|
|
var self = this;
|
|
this._loadState(function() {
|
|
self._initSettings(self._initMessageHandlers.bind(self));
|
|
});
|
|
|
|
this._fxaReady = true;
|
|
this._observeSettings();
|
|
},
|
|
|
|
_loadState: function fmd_load_state(callback) {
|
|
var self = this;
|
|
asyncStorage.getItem('findmydevice-state', function(state) {
|
|
self._state = state;
|
|
if (state) {
|
|
Requester.setHawkCredentials(state.deviceid, state.secret);
|
|
}
|
|
|
|
callback && callback();
|
|
});
|
|
},
|
|
|
|
_initSettings: function fmd_init_settings(callback) {
|
|
// for each setting we're interested in, create a SettingsHelper,
|
|
// cache its initial value in a property (e.g., _enabled), and
|
|
// store the SettingsHelper as another property (e.g., _enabledHelper).
|
|
var settingsToProperties = {
|
|
// setting name : property name for cached value
|
|
'findmydevice.enabled': '_enabled',
|
|
'findmydevice.registered': '_registered',
|
|
'findmydevice.current-clientid': '_currentClientID',
|
|
'findmydevice.can-disable': '_canDisable',
|
|
'findmydevice.retry-count': '_retryCount'
|
|
};
|
|
var settings = Object.keys(settingsToProperties);
|
|
|
|
var loaded = 0;
|
|
var totalSettings = Object.keys(settingsToProperties).length;
|
|
settings.forEach(function(s) {
|
|
var prop = settingsToProperties[s];
|
|
var helper = this[prop + 'Helper'] = SettingsHelper(s);
|
|
helper.get((function(value) {
|
|
this[prop] = value;
|
|
if (++loaded === totalSettings) {
|
|
callback && callback();
|
|
}
|
|
}).bind(this));
|
|
}, this);
|
|
},
|
|
|
|
_initMessageHandlers: function fmd_init_message_handlers() {
|
|
navigator.mozSetMessageHandler('push', (function(message) {
|
|
DUMP('findmydevice got push notification!');
|
|
this._contactServer();
|
|
}).bind(this));
|
|
|
|
navigator.mozSetMessageHandler('push-register', (function(message) {
|
|
DUMP('findmydevice lost push endpoint, re-registering');
|
|
this.beginHighPriority('clientLogic');
|
|
this._registeredHelper.set(false);
|
|
}).bind(this));
|
|
|
|
navigator.mozSetMessageHandler('alarm', (function(alarm) {
|
|
DUMP('findmydevice alarm!');
|
|
this._contactServer();
|
|
this._refreshClientIDIfRegistered(false);
|
|
}).bind(this));
|
|
|
|
navigator.mozSetMessageHandler('connection', (function(request) {
|
|
var port = request.port;
|
|
port.onmessage = (function(event) {
|
|
if (request.keyword === 'findmydevice-wakeup') {
|
|
DUMP('got wake up request');
|
|
|
|
var reason = event.data;
|
|
if (reason === IAC_API_WAKEUP_REASON_ENABLED_CHANGED) {
|
|
DUMP('enabled state changed, trying to reach the server');
|
|
// Ensure the retry counter is reset to 0 on enable
|
|
this._retryCountHelper.set(0);
|
|
this._contactServer();
|
|
} else if (reason === IAC_API_WAKEUP_REASON_STALE_REGISTRATION) {
|
|
DUMP('stale registration, re-registering');
|
|
this.beginHighPriority('clientLogic');
|
|
this._registeredHelper.set(false);
|
|
} else if (reason === IAC_API_WAKEUP_REASON_LOGIN) {
|
|
DUMP('new login, invalidating client id');
|
|
this.beginHighPriority('clientLogic');
|
|
this._loggedIn = true;
|
|
this._currentClientIDHelper.set('');
|
|
} else if (reason === IAC_API_WAKEUP_REASON_LOGOUT) {
|
|
DUMP('logout, invalidating client id');
|
|
this.beginHighPriority('clientLogic');
|
|
this._loggedIn = false;
|
|
this._currentClientIDHelper.set('');
|
|
} else if (reason === IAC_API_WAKEUP_REASON_TRY_DISABLE) {
|
|
DUMP('refreshing client id and attempting to disable');
|
|
this._disableAttempt = true;
|
|
this._refreshClientIDIfRegistered(true);
|
|
} else if (reason === IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED) {
|
|
DUMP('unlocked');
|
|
this._onLockscreenClosed();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (request.keyword === 'findmydevice-test') {
|
|
DUMP('got request for test command!');
|
|
this._processCommands(event.data, true);
|
|
}
|
|
}).bind(this);
|
|
}).bind(this));
|
|
},
|
|
|
|
_contactServer: function fmd_contact_server() {
|
|
this.beginHighPriority('clientLogic');
|
|
if (this._registered && this._enabled) {
|
|
this._replyAndFetchCommands();
|
|
} else if (this._registered && !this._enabled) {
|
|
this._reportDisabled();
|
|
} else if (!this._registered && this._enabled) {
|
|
this._register();
|
|
} else {
|
|
// XXX(ggp) this should never happen, but let's play
|
|
// it safe and release lock we acquired
|
|
DUMP('can\'t contact the server while not registered and not enabled!!');
|
|
this.endHighPriority('clientLogic');
|
|
}
|
|
},
|
|
|
|
_reportDisabled: function fmd_report_disabled() {
|
|
DUMP('reporting disabled');
|
|
Requester.post(
|
|
this._getCommandEndpoint(),
|
|
{enabled: false},
|
|
this._handleServerResponse.bind(this),
|
|
this._handleServerError.bind(this));
|
|
},
|
|
|
|
_register: function fmd_register() {
|
|
DUMP('findmydevice attempting registration.');
|
|
DUMP('registering: ' + this._registering);
|
|
|
|
if (this._registering) {
|
|
this._registering = false;
|
|
this.endHighPriority('clientLogic');
|
|
return;
|
|
}
|
|
|
|
this._registering = true;
|
|
|
|
if (this._state === null) {
|
|
// We are not registered yet, so we need to give the server a FxA
|
|
// assertion. Request a fresh one here and continue the registration
|
|
// process from the login callback.
|
|
|
|
if (!this._loggedIn) {
|
|
// Can't request() while not logged in, as that would bring up the
|
|
// FxA dialog. Attempting a first registration while logged out is
|
|
// theoretically possible (if, for example, a first registration
|
|
// failed, and this is an alarm-driven re-attempt, and the user logged
|
|
// out while we slept before this attempt), but it's a very unlikely
|
|
// corner case. For now, just give up if this happens, but we still
|
|
// need to notify the user somehow (bug 1013423).
|
|
this._registering = false;
|
|
this.endHighPriority('clientLogic');
|
|
return;
|
|
}
|
|
|
|
navigator.mozId.request();
|
|
} else {
|
|
// We don't need to send assertions on re-registrations, just rely on
|
|
// our client ID and HAWK signature to authenticate us. This works even
|
|
// if we're logged out of FxA, so use null for the assertion.
|
|
this._continueRegistration(null);
|
|
}
|
|
},
|
|
|
|
_onLogin: function fmd_on_login(assertion) {
|
|
DUMP('logged in to FxA');
|
|
|
|
// XXX(ggp) When initializing, we'll process FxA's automatic invocation
|
|
// of onlogin/onlogout before any IAC message, so use these calls to
|
|
// initialize _loggedIn. However, when FMD is already running, there's
|
|
// no guaranteee that onlogin/onlogout will fire before the IAC handler,
|
|
// so we have dedicated IAC messages (IAC_API_WAKEUP_REASON_LOGIN and
|
|
// IAC_API_WAKEUP_REASON_LOGOUT) to cover login state changes, and we
|
|
// use the handlers for these messages to update _loggedIn.
|
|
this._loggedIn = true;
|
|
|
|
if (this._refreshingClientID) {
|
|
DUMP('resuming client id refresh');
|
|
this._fetchClientID(assertion);
|
|
}
|
|
|
|
if (!this._enabled || !this._registering) {
|
|
return;
|
|
}
|
|
|
|
// We are in the middle of a registration, so continue here
|
|
this._continueRegistration(assertion);
|
|
},
|
|
|
|
_continueRegistration: function fmd_continue_registration(assertion) {
|
|
var self = this;
|
|
|
|
if (assertion == null && this._state == null) {
|
|
// this shouldn't happen
|
|
throw new Error('Trying to register with no assertion and no state!');
|
|
}
|
|
|
|
var pushRequest = navigator.push.register();
|
|
pushRequest.onsuccess = function fmd_push_handler() {
|
|
DUMP('findmydevice received push endpoint!');
|
|
|
|
var endpoint = pushRequest.result;
|
|
if (self._enabled) {
|
|
self._requestRegistration(assertion, endpoint);
|
|
}
|
|
|
|
self._registering = false;
|
|
};
|
|
|
|
pushRequest.onerror = function fmd_push_error_handler() {
|
|
DUMP('findmydevice push request failed!');
|
|
|
|
self._registering = false;
|
|
self._countRegistrationRetry();
|
|
};
|
|
},
|
|
|
|
_requestRegistration: function fmd_request_registration(assertion, endpoint) {
|
|
var obj = {
|
|
pushurl: endpoint,
|
|
accepts: Commands.getEnabledCommands()
|
|
};
|
|
|
|
if (assertion != null) {
|
|
obj.assert = assertion;
|
|
}
|
|
|
|
if (this._state !== null) {
|
|
obj.deviceid = this._state.deviceid;
|
|
}
|
|
|
|
var self = this;
|
|
Requester.post('/register/', obj, function(response) {
|
|
DUMP('findmydevice successfully registered: ', response);
|
|
|
|
asyncStorage.setItem('findmydevice-state', response, function() {
|
|
self._registeredHelper.set(true);
|
|
});
|
|
}, this._handleServerError.bind(this));
|
|
},
|
|
|
|
_onLogout: function fmd_fxa_onlogout() {
|
|
DUMP('logged out of FxA');
|
|
this._loggedIn = false;
|
|
},
|
|
|
|
_onFxAError: function fmd_on_error(error) {
|
|
DUMP('FxA error: ' + error);
|
|
|
|
if (!this._fxaReady) {
|
|
// FIXME(ggp) workaround for bug 1040935, if FxA errors out
|
|
// while we are initializing (usually due to an unverified
|
|
// account), just give up for now.
|
|
window.close();
|
|
}
|
|
|
|
if (this._refreshingClientID) {
|
|
this._cancelClientIDRefresh();
|
|
}
|
|
|
|
this._scheduleAlarm('retry');
|
|
},
|
|
|
|
_cancelClientIDRefresh: function fmd_cancel_clientid_refresh() {
|
|
this._disableAttempt = false;
|
|
this._refreshingClientID = false;
|
|
|
|
// If we've been woken up by the Settings app because of a disable
|
|
// attempt, it expects us to change these two settings at the end
|
|
// of the operation, so set them to their current values if we're
|
|
// canceling to make sure the state in the Settings is updated
|
|
// properly.
|
|
this._enabledHelper.set(this._enabled);
|
|
this._currentClientIDHelper.set(this._currentClientID);
|
|
|
|
// XXX(ggp) no need to unlock the 'currentClient' lock here, let
|
|
// _onClientIDChanged do it for us
|
|
},
|
|
|
|
_scheduleAlarm: function fmd_schedule_alarm(mode) {
|
|
var nextAlarm = new Date();
|
|
|
|
if (mode === 'ping') {
|
|
// this is just a regular ping to the server to make
|
|
// sure we're still registered, so use a long interval
|
|
nextAlarm.setHours(nextAlarm.getHours() + 6);
|
|
} else if (mode === 'retry') {
|
|
// something went wrong when registering or talking to the
|
|
// server, we should check back shortly
|
|
var interval = 1 + Math.floor(5 * Math.random());
|
|
nextAlarm.setMinutes(nextAlarm.getMinutes() + interval);
|
|
} else {
|
|
DUMP('invalid alarm mode!');
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
var request = navigator.mozAlarms.getAll();
|
|
request.onsuccess = function fmd_alarms_get_all() {
|
|
this.result.forEach(function(alarm) {
|
|
navigator.mozAlarms.remove(alarm.id);
|
|
});
|
|
|
|
var data = {type: 'findmydevice-alarm'};
|
|
var request = navigator.mozAlarms.add(nextAlarm, 'honorTimezone', data);
|
|
request.onsuccess = function() {
|
|
self.endHighPriority('clientLogic');
|
|
};
|
|
};
|
|
},
|
|
|
|
_replyAndFetchCommands: function fmd_reply_and_fetch() {
|
|
this._reply.has_passcode = Commands.deviceHasPasscode();
|
|
Requester.post(
|
|
this._getCommandEndpoint(),
|
|
this._reply,
|
|
this._handleServerResponse.bind(this),
|
|
this._handleServerError.bind(this));
|
|
|
|
this._reply = {};
|
|
},
|
|
|
|
_onRegisteredChanged: function fmd_registered_changed(event) {
|
|
this._registered = event.settingValue;
|
|
DUMP('registered: ' + this._registered);
|
|
|
|
if (!this._registered) {
|
|
this._contactServer();
|
|
this.endHighPriority('clientLogic');
|
|
} else {
|
|
this._loadState((function() {
|
|
this._contactServer();
|
|
this._currentClientIDHelper.set('');
|
|
// XXX(ggp) we're still holding a 'clientLogic' lock, but let
|
|
// _onClientIDChanged or _onCanDisableChanged release it (depending
|
|
// on whether we're logged in or not).
|
|
}).bind(this));
|
|
}
|
|
},
|
|
|
|
_onEnabledChanged: function fmd_enabled_changed(event) {
|
|
// No need to contact the server here if we've been enabled,
|
|
// since that means we'll get a wake up request from the System
|
|
// app
|
|
this._enabled = event.settingValue;
|
|
DUMP('enabled: ' + this._enabled);
|
|
},
|
|
|
|
_onClientIDChanged: function fmd_client_id_changed(event) {
|
|
this._currentClientID = event.settingValue;
|
|
DUMP('current id set to: ', this._currentClientID);
|
|
|
|
if (this._loggedIn && this._currentClientID === '') {
|
|
this._refreshClientIDIfRegistered(false);
|
|
this.endHighPriority('clientLogic');
|
|
} else if (this._registered) {
|
|
this._canDisableHelper.set(
|
|
this._loggedIn &&
|
|
this._currentClientID === this._state.clientid);
|
|
} else {
|
|
this.endHighPriority('clientLogic');
|
|
}
|
|
},
|
|
|
|
_onCanDisableChanged: function fmd_can_disable_changed(event) {
|
|
if (event.settingValue === true && this._disableAttempt) {
|
|
this._enabledHelper.set(false);
|
|
}
|
|
|
|
this._disableAttempt = false;
|
|
this.endHighPriority('clientLogic');
|
|
},
|
|
|
|
_refreshClientIDIfRegistered: function fmd_refresh_client_id(forceReauth) {
|
|
DUMP('refreshing client id if registered and logged in: ',
|
|
{registered: this._registered, loggedIn: this._loggedIn});
|
|
|
|
if (!this._registered || !this._loggedIn) {
|
|
return;
|
|
}
|
|
|
|
DUMP('requesting assertion to refresh client id, forceReauth: ' +
|
|
forceReauth);
|
|
this.beginHighPriority('clientLogic');
|
|
|
|
this._refreshingClientID = true;
|
|
var mozIdRequestOptions = {};
|
|
if (forceReauth) {
|
|
mozIdRequestOptions.refreshAuthentication = 0;
|
|
mozIdRequestOptions.oncancel = this._cancelClientIDRefresh.bind(this);
|
|
}
|
|
|
|
navigator.mozId.request(mozIdRequestOptions);
|
|
},
|
|
|
|
_onLockscreenClosed: function fmd_on_lockscreen_closed() {
|
|
if (Commands.deviceHasPasscode()) {
|
|
DUMP('cancelling ring and track');
|
|
Commands.invokeCommand('ring', [0]);
|
|
Commands.invokeCommand('track', [0]);
|
|
}
|
|
},
|
|
|
|
_fetchClientID: function fmd_fetch_client_id(assertion) {
|
|
Requester.post('/validate/', {assert: assertion},
|
|
this._onClientIDResponse.bind(this),
|
|
this._onClientIDServerError.bind(this));
|
|
this._refreshingClientID = false;
|
|
},
|
|
|
|
_onClientIDResponse: function fmd_on_client_id(response) {
|
|
DUMP('got clientid reponse: ', response);
|
|
|
|
if (response.valid) {
|
|
this._currentClientIDHelper.set(response.uid);
|
|
return;
|
|
}
|
|
|
|
DUMP('failed to verify assertion for client id!');
|
|
this._scheduleAlarm('retry');
|
|
},
|
|
|
|
_onClientIDServerError: function fmd_on_client_id_error(err) {
|
|
DUMP('failed to fetch client id with status: ' + err.status);
|
|
this._scheduleAlarm('retry');
|
|
this._disableAttempt = false;
|
|
},
|
|
|
|
_handleServerResponse: function(response, testing) {
|
|
if (response && (this._enabled || testing)) {
|
|
this._processCommands(response);
|
|
}
|
|
|
|
this._scheduleAlarm('ping');
|
|
},
|
|
|
|
_processCommands: function fmd_process_commands(cmdobj) {
|
|
function noop() {} // callback for testing
|
|
|
|
for (var cmd in cmdobj) {
|
|
// map server (short) commands to methods in the
|
|
// commands object, and parse the arguments.
|
|
var argsObj = cmdobj[cmd], command, args;
|
|
switch (cmd) {
|
|
case 'e':
|
|
command = 'erase';
|
|
args = [];
|
|
break;
|
|
case 'k':
|
|
command = 'killswitch';
|
|
args = [];
|
|
break;
|
|
case 'l':
|
|
command = 'lock';
|
|
args = [argsObj.m, argsObj.c];
|
|
break;
|
|
case 'r':
|
|
command = 'ring';
|
|
args = [parseInt(argsObj.d, 10)];
|
|
break;
|
|
case 't':
|
|
command = 'track';
|
|
args = [parseInt(argsObj.d, 10)];
|
|
break;
|
|
default:
|
|
this._replyCallback(cmd, false, 'command not available');
|
|
return;
|
|
}
|
|
|
|
DUMP('command ' + cmd + ', args ' + JSON.stringify(args));
|
|
|
|
// add the callback as the last argument
|
|
if (cmdobj.testing !== true) {
|
|
args.push(this._replyCallback.bind(this, cmd));
|
|
} else {
|
|
args.push(noop);
|
|
}
|
|
|
|
Commands.invokeCommand(command, args);
|
|
}
|
|
},
|
|
|
|
_countRegistrationRetry: function fmd_count_registration_retry (){
|
|
this._scheduleAlarm('retry');
|
|
|
|
if (!navigator.onLine) {
|
|
return;
|
|
}
|
|
|
|
if (!this._registered) {
|
|
this._retryCountHelper.get(count => {
|
|
this._retryCountHelper.set((count || 0) + 1);
|
|
});
|
|
}
|
|
},
|
|
|
|
_handleServerError: function fmd_handle_server_error(err) {
|
|
DUMP('findmydevice request failed with status: ' + err.status);
|
|
if (err.status === 401 && this._registered) {
|
|
this._registeredHelper.set(false);
|
|
} else {
|
|
this._countRegistrationRetry();
|
|
}
|
|
},
|
|
|
|
_replyCallback: function fmd_reply(cmd, ok, retval) {
|
|
var value = {ok: ok};
|
|
|
|
if (cmd === 't' && ok === true && retval !== undefined) {
|
|
value.la = retval.coords.latitude;
|
|
value.lo = retval.coords.longitude;
|
|
value.acc = retval.coords.accuracy;
|
|
value.ti = retval.timestamp;
|
|
} else if (ok === false) {
|
|
value.error = retval;
|
|
}
|
|
|
|
this._reply[cmd] = value;
|
|
this._contactServer();
|
|
},
|
|
|
|
_getCommandEndpoint: function fmd_get_command_endpoint() {
|
|
return '/cmd/' + this._state.deviceid;
|
|
},
|
|
|
|
beginHighPriority: function(reason) {
|
|
DUMP('begin high priority section, reason: ', reason);
|
|
if (Object.keys(this._highPriorityWakeLocks).indexOf(reason) === -1) {
|
|
DUMP('unknown reason for high priority section?!');
|
|
return;
|
|
}
|
|
|
|
DUMP('acquiring one wakelock, wakelocks are: ',
|
|
this._highPriorityWakeLocks);
|
|
this._highPriorityWakeLocks[reason].push(
|
|
navigator.requestWakeLock('high-priority'));
|
|
},
|
|
|
|
endHighPriority: function(reason) {
|
|
DUMP('end high priority section, reason: ', reason);
|
|
if (!this._highPriorityWakeLocks[reason]) {
|
|
return;
|
|
}
|
|
|
|
DUMP('releasing one wakelock, wakelocks are: ',
|
|
this._highPriorityWakeLocks);
|
|
this._highPriorityWakeLocks[reason].pop().unlock();
|
|
}
|
|
};
|
|
|
|
FindMyDevice.init();
|