1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/GeckoBatteryManager.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,197 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import android.content.BroadcastReceiver; 1.12 +import android.content.Context; 1.13 +import android.content.Intent; 1.14 +import android.content.IntentFilter; 1.15 +import android.os.BatteryManager; 1.16 +import android.os.Build; 1.17 +import android.os.SystemClock; 1.18 +import android.util.Log; 1.19 + 1.20 +public class GeckoBatteryManager extends BroadcastReceiver { 1.21 + private static final String LOGTAG = "GeckoBatteryManager"; 1.22 + 1.23 + // Those constants should be keep in sync with the ones in: 1.24 + // dom/battery/Constants.h 1.25 + private final static double kDefaultLevel = 1.0; 1.26 + private final static boolean kDefaultCharging = true; 1.27 + private final static double kDefaultRemainingTime = 0.0; 1.28 + private final static double kUnknownRemainingTime = -1.0; 1.29 + 1.30 + private static long sLastLevelChange = 0; 1.31 + private static boolean sNotificationsEnabled = false; 1.32 + private static double sLevel = kDefaultLevel; 1.33 + private static boolean sCharging = kDefaultCharging; 1.34 + private static double sRemainingTime = kDefaultRemainingTime; 1.35 + 1.36 + private static GeckoBatteryManager sInstance = new GeckoBatteryManager(); 1.37 + 1.38 + private final IntentFilter mFilter; 1.39 + private Context mApplicationContext; 1.40 + private boolean mIsEnabled; 1.41 + 1.42 + public static GeckoBatteryManager getInstance() { 1.43 + return sInstance; 1.44 + } 1.45 + 1.46 + private GeckoBatteryManager() { 1.47 + mFilter = new IntentFilter(); 1.48 + mFilter.addAction(Intent.ACTION_BATTERY_CHANGED); 1.49 + } 1.50 + 1.51 + public synchronized void start(final Context context) { 1.52 + if (mIsEnabled) { 1.53 + Log.w(LOGTAG, "Already started!"); 1.54 + return; 1.55 + } 1.56 + 1.57 + mApplicationContext = context.getApplicationContext(); 1.58 + // registerReceiver will return null if registering fails. 1.59 + if (mApplicationContext.registerReceiver(this, mFilter) == null) { 1.60 + Log.e(LOGTAG, "Registering receiver failed"); 1.61 + } else { 1.62 + mIsEnabled = true; 1.63 + } 1.64 + } 1.65 + 1.66 + public synchronized void stop() { 1.67 + if (!mIsEnabled) { 1.68 + Log.w(LOGTAG, "Already stopped!"); 1.69 + return; 1.70 + } 1.71 + 1.72 + mApplicationContext.unregisterReceiver(this); 1.73 + mApplicationContext = null; 1.74 + mIsEnabled = false; 1.75 + } 1.76 + 1.77 + @Override 1.78 + public void onReceive(Context context, Intent intent) { 1.79 + if (!intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { 1.80 + Log.e(LOGTAG, "Got an unexpected intent!"); 1.81 + return; 1.82 + } 1.83 + 1.84 + boolean previousCharging = isCharging(); 1.85 + double previousLevel = getLevel(); 1.86 + 1.87 + // NOTE: it might not be common (in 2012) but technically, Android can run 1.88 + // on a device that has no battery so we want to make sure it's not the case 1.89 + // before bothering checking for battery state. 1.90 + // However, the Galaxy Nexus phone advertizes itself as battery-less which 1.91 + // force us to special-case the logic. 1.92 + // See the Google bug: https://code.google.com/p/android/issues/detail?id=22035 1.93 + if (intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false) || 1.94 + Build.MODEL.equals("Galaxy Nexus")) { 1.95 + int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); 1.96 + if (plugged == -1) { 1.97 + sCharging = kDefaultCharging; 1.98 + Log.e(LOGTAG, "Failed to get the plugged status!"); 1.99 + } else { 1.100 + // Likely, if plugged > 0, it's likely plugged and charging but the doc 1.101 + // isn't clear about that. 1.102 + sCharging = plugged != 0; 1.103 + } 1.104 + 1.105 + if (sCharging != previousCharging) { 1.106 + sRemainingTime = kUnknownRemainingTime; 1.107 + // The new remaining time is going to take some time to show up but 1.108 + // it's the best way to show a not too wrong value. 1.109 + sLastLevelChange = 0; 1.110 + } 1.111 + 1.112 + // We need two doubles because sLevel is a double. 1.113 + double current = (double)intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 1.114 + double max = (double)intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 1.115 + if (current == -1 || max == -1) { 1.116 + Log.e(LOGTAG, "Failed to get battery level!"); 1.117 + sLevel = kDefaultLevel; 1.118 + } else { 1.119 + sLevel = current / max; 1.120 + } 1.121 + 1.122 + if (sLevel == 1.0 && sCharging) { 1.123 + sRemainingTime = kDefaultRemainingTime; 1.124 + } else if (sLevel != previousLevel) { 1.125 + // Estimate remaining time. 1.126 + if (sLastLevelChange != 0) { 1.127 + // Use elapsedRealtime() because we want to track time across device sleeps. 1.128 + long currentTime = SystemClock.elapsedRealtime(); 1.129 + long dt = (currentTime - sLastLevelChange) / 1000; 1.130 + double dLevel = sLevel - previousLevel; 1.131 + 1.132 + if (sCharging) { 1.133 + if (dLevel < 0) { 1.134 + Log.w(LOGTAG, "When charging, level should increase!"); 1.135 + sRemainingTime = kUnknownRemainingTime; 1.136 + } else { 1.137 + sRemainingTime = Math.round(dt / dLevel * (1.0 - sLevel)); 1.138 + } 1.139 + } else { 1.140 + if (dLevel > 0) { 1.141 + Log.w(LOGTAG, "When discharging, level should decrease!"); 1.142 + sRemainingTime = kUnknownRemainingTime; 1.143 + } else { 1.144 + sRemainingTime = Math.round(dt / -dLevel * sLevel); 1.145 + } 1.146 + } 1.147 + 1.148 + sLastLevelChange = currentTime; 1.149 + } else { 1.150 + // That's the first time we got an update, we can't do anything. 1.151 + sLastLevelChange = SystemClock.elapsedRealtime(); 1.152 + } 1.153 + } 1.154 + } else { 1.155 + sLevel = kDefaultLevel; 1.156 + sCharging = kDefaultCharging; 1.157 + sRemainingTime = kDefaultRemainingTime; 1.158 + } 1.159 + 1.160 + /* 1.161 + * We want to inform listeners if the following conditions are fulfilled: 1.162 + * - we have at least one observer; 1.163 + * - the charging state or the level has changed. 1.164 + * 1.165 + * Note: no need to check for a remaining time change given that it's only 1.166 + * updated if there is a level change or a charging change. 1.167 + * 1.168 + * The idea is to prevent doing all the way to the DOM code in the child 1.169 + * process to finally not send an event. 1.170 + */ 1.171 + if (sNotificationsEnabled && 1.172 + (previousCharging != isCharging() || previousLevel != getLevel())) { 1.173 + GeckoAppShell.notifyBatteryChange(getLevel(), isCharging(), getRemainingTime()); 1.174 + } 1.175 + } 1.176 + 1.177 + public static boolean isCharging() { 1.178 + return sCharging; 1.179 + } 1.180 + 1.181 + public static double getLevel() { 1.182 + return sLevel; 1.183 + } 1.184 + 1.185 + public static double getRemainingTime() { 1.186 + return sRemainingTime; 1.187 + } 1.188 + 1.189 + public static void enableNotifications() { 1.190 + sNotificationsEnabled = true; 1.191 + } 1.192 + 1.193 + public static void disableNotifications() { 1.194 + sNotificationsEnabled = false; 1.195 + } 1.196 + 1.197 + public static double[] getCurrentInformation() { 1.198 + return new double[] { getLevel(), isCharging() ? 1.0 : 0.0, getRemainingTime() }; 1.199 + } 1.200 +}