Running out of battery is not fun: maybe one of the worst things that can happen to a mobile phone user! Big screens, fast processors and wireless Internet connection are important, but they are expensive when it comes to energy consumption. That's why mobile apps should be built with battery consumption in mind, performance and memory usage optimization being paramount. This article looks at one way to manage energy consumption: The Battery Status API. Using this we can manage a web application's battery usage, perhaps turning off power hungry features or providing a simplified experience when the battery starts to run down.
Situations in which battery information is useful
In the context of Open Web Apps, knowing the battery status can be useful in a number of situations:
- Utility apps that collect statistics on battery usage or simply inform the user if the device is charged enough to play a game, watch a movie, or browse the Web
- High-quality apps that optimize battery consuption: for example an email client may check the server for new email less frequently if the device is low on battery
- A word processor could save changes automatically before the battery runs out in order to prevent data loss
The Battery Status API
Open Web Apps can retrieve battery status information thanks to the Battery Status API, a W3C Recommendation supported by Firefox since version 16, and by Firefox OS as a Regular API. The Battery Status API is one of the Web APIs, a suite of device compatibility and access APIs that allow Web apps and content to access device hardware, as well as access to data stored on the device. For more information have a look at the Web APIs page on MDN.
Below we will look at using the Battery Status API in an Instant Messagging App for Firefox OS.
Demo: Low Energy Messenger
Low Energy Messenger is an instant messaging Web App that pays very high attention to battery status. Here is a screenshot taken from the Firefox OS Simulator:
Low Energy Messenger has the following features:
- A battery status bar, containing battery status information
- A chat section, containing all the messages received or sent
- An action bar, containing a text field, a button to send a message, a button to take a photo and a button to install the app on Firefox OS
The app doesn’t allow users to take photos when the battery level is low or the device is not charging. It has been made using HTML + CSS + Javascript, with no libs and using static data, so no web services on Internet, but real integration with the Battery Status API and a realistic look & feel. It is published here and you can find the code on Github.
Let’s dive into the code!
HTML & CSS
The Battery Status API is a Javascript API, so showing the HTML and the CSS might be considered out of scope here, but I think it is crucial to understand at least the elements composing the page on which JavaScript operates.
The main elements are:
- The battery status bar
- The chat
- The action bar
The battery status bar
The battery status bar contains:
- A visual representation of the battery level
- The battery level expressed as a percentage (0-100%)
- Text indicating if the battery is charging or discharging, and the time remaining until the battery is completely charged or discharged
Here is the HTML code:
<div id="battery-status-bar"> <div class="battery"> <div class="power"> <div class="level"></div> </div> </div> <div class="percentage"></div> <div class="time"></div> </div>
Each id
is used by Javascript to manipulate the DOM at run-time. The battery status bar is not visible initially: it will be shown by JavaScript if the browser supports the Battery Status API.
#battery-status-bar { display: none; margin-top: 0.5em; }
The visual representation of the battery level is made entirely in CSS, and the level class is changed by JavaScript at run-time:
.battery .level { position: absolute; right: 80%; /* this property will be changed via javascript */ background: blue; ... }
You can find the CSS code in the shapes.css file.
The chat section
The chat section is composed of a set of article elements containing a list of messages.
Here is the HTML code:
<section id="chat"> <article> <div class="avatar"><img alt="franciov" src="img/franciov.png" /></div> <div class="message"> <div> <h2>Franciov</h2> <p>Hello, ... </p> </div> </div> </article> </section>
The first article is visible when the page loads in order to say welcome for demo purposes. The articles display messages on the left columns by default. When the article has class you
, the articles display messages on the right column instead. This is handled in CSS using the float
property.
section#chat article > div { float: left; } section#chat article.you > div { float: right; }
Note: The you
avatar is made entirely in CSS: you can find the code in the shapes.css file.
A div
with id="bottom"
is placed just after the chat section: this element is used by JavaScript to scroll down the page when a message is added to the chat.
<div id="bottom"></div>
The action bar
The action bar contains:
- A text field
- A button to send a message
- A button to take a photo
- A button to install the app on Firefox OS
Here is the HTML code:
<nav> <div> <div class="send-box"> <input type="text" id="text-message" value="Hello! :)" /> <button id="send-message">Send</button> </div> <div id="take-photo"> <div class="camera"> <div class="lens"></div> </div> </div> </div> <div> <button id="install">Install</button> </div> </nav>
Each id
is used by JavaScript to manipulate the DOM at run-time. The action bar has fixed
position, so it can be always visible on the bottom of the page.
nav { position: fixed; }
Note: The take-photo button is made entirely in CSS: you can find the code in the shapes.css file.
The install button is not visible initially: it will be shown by JavaScript if the browser is Firefox and the app is not already installed on Firefox OS.
nav button#install { display: none; }
JavaScript
Low Energy Messenger implements the following JavaScript files:
<!-- UTILS --> <script src="scripts/utils/string-utils.js"></script> <script src="scripts/utils/energy-manager.js"></script> <script src="scripts/utils/proximity-manager.js"></script> <!-- CONTROLLERS --> <script src="scripts/controllers/battery-status-bar.js"></script> <script src="scripts/controllers/action-bar.js"></script> <script src="scripts/controllers/chat.js"></script> <!-- APP --> <script src="scripts/app.js"></script> <script src="scripts/install.js"></script> <script src="scripts/index.js"></script>
This demo is built using Object Oriented Programming. For an introduction to Object Oriented Javascript have a look at this MDN page.
Each JavaScript file represents a component that executes specific tasks in the application. The EnergyManager
is the component that retrieves battery status information, so the most interesting code for the purposes of this tutorial is inside EnergyManager.js. However, in order to understand how the app works, we’ll briefly have a look at all the JavaScript files. Feel free to skip to The Energy Manager section if you are mainly interested in the Battery Status API.
App init
The App.init()
function is created when window
is loaded. It creates, initializes and starts the objects needed by the Low Energy Messenger. First of all, controllers and utility objects are initialized, so that they can be used by the app.
BatteryStatusBar.init(); ActionBar.init(); Chat.init();
Then the app checks whether the Battery Status API is supported:
if (EnergyManager.isBatteryStatusAPISupported()) {
If so, it initialize the EnergyManager
EnergyManager.init(function() {
proviging a callback function called when the battery is retrieved. This callback function adds an handler to manage battery status change events
EnergyManager.handleChangeEvents(BatteryStatusBar.update.bind(BatteryStatusBar));
updates the battery status bar for the first time
BatteryStatusBar.update();
and notifies the user that the Battery Status API is supported
Chat.receiveMessage('Congratulations! ...');
If it is not supported, it instead notifies the user that the Battery Status API is not supported
Chat.receiveMessage('I\'m sorry: ...');
Install
The install.js file contains the code to install the Low Energy Messenger on Firefox OS. There’s no need to cover this topic here: if you don’t know how to build a Web App for Firefox OS, please have a look at our Quickstart app tutorial.
The ActionBar
The ActionBar
manages the actions that can be performed on the chat:
- Sending a message
- Sending a photo
Visually, this is like so:
This is the constructor:
/* ActionBar */ init: function() { /* Initialize DOM Objects */ this.takePhotoButton = document.querySelector('#take-photo'); this.sendMessageButton = document.querySelector('#send-message'); this.textMessageField = document.querySelector('#text-message'); /* Call takePhoto() when the takePhoto button is clicked */ this.takePhotoButton.onclick = this.takePhoto; /* Call send() When the send button is clicked */ this.sendMessageButton.onclick = this.send; }
The send()
method simply sends the text contained in the input field to the chat:
Chat.sendMessage(this.textMessageField.value);
The takePhoto()
method on the other hand contains much more interesting code: it has to check the battery status before performing the action.
if (EnergyManager.getBatteryPercentage() > 30 // not low || EnergyManager.isBatteryCharging() // charging || EnergyManager.isBatteryFullyCharged() // fully charged ) { // send the photo … }
So the user is allowed to take a photo only if the battery level is greater than 30% or the battery is charging or fully charged. The EnergyManager
object, with all the methods called above, will be described in full detail further down this page, along with the Battery Status API.
The Chat
The Chat
manages the DOM object with id="chat"
:
/* Chat */ init: function() { /* Initialize DOM Objects */ var chat = document.querySelector('#chat'); }
It also provides the methods sendMessage()
and receiveMessage()
, which simply add HTML article elements to the chat section in order to show messages passed as parameters:
sendMessage: function(message) { … }, receiveMessage: function(message) { … }, scrollDown: function() { window.location = "#bottom"; }
The scrollDown()
method simply scrolls the chat to the bottom in order to make sure that the last sent or received message is always visible by the user.
The BatteryStatusBar
The BatteryStatusBar
manages the battery indicators in the battery status bar, which are:
this.batteryStatusBar = document.querySelector('#battery-status-bar'); this.batteryLevelDomObj = document.querySelector('#battery-status-bar .level'); this.batteryPercentageDomObj = document.querySelector('#battery-status-bar .percentage'); this.batteryTimeDomObj = document.querySelector('#battery-status-bar .time');
The
method performs a number of interesting tasks. First of all it updates the battery level information:BatteryStatusBar.
update()
/* Level */ var percentage = EnergyManager.getBatteryPercentage(); this.batteryLevelDomObj.style.right = (100 - percentage) + '%'; this.batteryPercentageDomObj.innerHTML = percentage + '%'; /* Color */ if (percentage < 20) { this.batteryLevelDomObj.setAttribute('class', 'low level'); } else if (percentage < 60) { this.batteryLevelDomObj.setAttribute('class', 'medium level'); } else { this.batteryLevelDomObj.setAttribute('class', 'high level'); }
So the visual representation of the battery changes depending on the charge level. For example:
Then the battery charging status and time are updated.
/* Charging status and time */ var chargingStatusAndTime = ''; if (EnergyManager.isBatteryFullyCharged()) { // battery fully charged chargingStatusAndTime = 'fully charged'; } else if (EnergyManager.isBatteryCharging()) { // battery charging var batteryChargingTime = EnergyManager.getBatteryChargingTime(); chargingStatusAndTime = 'Charging: '; if (batteryChargingTime) { chargingStatusAndTime += batteryChargingTime; chargingStatusAndTime += ' until full'; } else { // charging time unknown chargingStatusAndTime += 'calculating time until full ...'; } } else { // battery discharging var batteryDischarginTime = EnergyManager.getBatteryDischargingTime(); chargingStatusAndTime = 'Discharging: '; if (batteryDischarginTime) { chargingStatusAndTime += batteryDischarginTime; chargingStatusAndTime += ' remaining'; } else { // discharging time unknown chargingStatusAndTime += 'calculating time remaining ...'; } } this.batteryTimeDomObj.innerHTML = chargingStatusAndTime;
Finally update()
makes sure the battery status bar is visible.
/* Show the battery status bar */ this.batteryStatusBar.style.display = 'block';
The EnergyManager
Now that we know how the Web App works, it's time to reveal EnergyManager.js and find out how the Battery Status API is used.
The EnergyManager constructor initializes the battery
object, of type EnergyManager
, provided by the navigator.getBattery
method or by the deprecated navigator.battery
property.
/* EnergyManager */ init: function(callback) { var _self = this; /* Initialize the battery object */ if (navigator.getBattery) { navigator.getBattery().then(function(battery) { _self.battery = battery; callback(); }); } else if (navigator.battery || navigator.mozBattery) { // deprecated battery object _self.battery = navigator.battery || navigator.mozBattery; callback(); } }
The navigator.getBattery
method returns a battery promise, which is resolved in a BatteryManager
object providing events you can handle to monitor the battery status. The deprecated navigator.battery
attribute returns the BatteryManager
object directly; the implementation above checks for vendor prefixes as well. Once the battery object has retrieved, it is saved as attribute of EnergyManager
, so that can be referred by the this
keyword.
The EnergyManager
provides a number of methods based on the battery
object initialized in the constructor. Let’s have a look at each of them.
First, isBatteryStatusAPISupported()
checks whether the Battery Status API is supported by the browser and returns a boolean accordingly. The code is pretty easy to understand.
isBatteryStatusAPISupported: function() { if (navigator.getBattery || navigator.battery || navigator.mozBattery) { return true; } return false; }
log()
writes logs into the console. This is useful to understand how the Battery Status API actually works.
log: function(event) { if (event) { console.warn(event); } console.log('battery.level: ' + this.battery.level); console.log('battery.charging: ' + this.battery.charging); console.log('battery.chargingTime: ' + this.battery.chargingTime); console.log('battery.dischargingTime: ' + this.battery.dischargingTime); }
Here is how the logs appear on the console:
getBatteryPercentage()
gets the battery level from the battery
object — expressed as a number between 0 and 1, calculates the percentage, then returns a number between 0 and 100.
getBatteryPercentage: function() { var percentage = Math.round(this.battery.level * 100); return percentage; },
isBatteryFullyCharged()
checks whether the battery is fully charged and returns a boolean
accordingly:
isBatteryFullyCharged: function() { if (this.battery.level === 1) { return true; } return false; },
isBatteryCharging()
checks whether the battery is charging and returns a boolean
accordingly:
isBatteryCharging: function() { // the battery cannot be charging because is completely charged if (this.battery.level === 1) { return false; } return this.battery.charging; },
getBatteryChargingTime()
gets the battery charging time in seconds and converts it to a human readable time, using the StringUtils
provided by the app:
getBatteryChargingTime: function() { if (this.battery.chargingTime === Infinity) { return undefined; } var time = StringUtils.getHumanReadableTime(this.battery.chargingTime); return time; },
Similarly, getBatteryDischargingTime()
gets the battery discharging time in seconds and converts it to a human readable time, using the StringUtils
provided by the app:
getBatteryDischargingTime: function() { if (this.battery.dischargingTime === Infinity) { return undefined; } var time = StringUtils.getHumanReadableTime(this.battery.dischargingTime); return time; },
The handleChangeEvents()
method is called when the Web App starts. It registers the handler passed as a parameter to every *-change
event fired by the battery
object:
handleChangeEvents: function(handler) { /* Update the battery status bar on battery level change */ this.battery.onlevelchange = function(e) { handler(e); }; /* Update the battery status bar on battery charging change */ this.battery.onchargingchange = function(e) { handler(e); }; /* Update the battery status bar on battery charging time change */ this.battery.onchargingtimechange = function(e) { handler(e); }; /* Update the battery status bar on battery discharging time change */ this.battery.ondischargingtimechange = function(e) { handler(e); }; }
As you may have noticed, the EnergyManager
uses all the attributes and events specified by the Battery Status API to retrieve battery status information.