mobile/android/base/GeckoBatteryManager.java

changeset 0
6474c204b198
     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 +}

mercurial