tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeckoBatteryManager.java (7398B)


      1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 package org.mozilla.gecko;
      7 
      8 import android.content.BroadcastReceiver;
      9 import android.content.Context;
     10 import android.content.Intent;
     11 import android.content.IntentFilter;
     12 import android.os.BatteryManager;
     13 import android.os.Build;
     14 import android.os.SystemClock;
     15 import android.util.Log;
     16 import org.mozilla.gecko.annotation.WrapForJNI;
     17 
     18 public class GeckoBatteryManager extends BroadcastReceiver {
     19  private static final String LOGTAG = "GeckoBatteryManager";
     20 
     21  // Those constants should be keep in sync with the ones in:
     22  // dom/battery/Constants.h
     23  private static final double kDefaultLevel = 1.0;
     24  private static final boolean kDefaultCharging = true;
     25  private static final double kDefaultRemainingTime = 0.0;
     26  private static final double kUnknownRemainingTime = -1.0;
     27 
     28  private static long sLastLevelChange;
     29  private static boolean sNotificationsEnabled;
     30  private static double sLevel = kDefaultLevel;
     31  private static boolean sCharging = kDefaultCharging;
     32  private static double sRemainingTime = kDefaultRemainingTime;
     33 
     34  private static final GeckoBatteryManager sInstance = new GeckoBatteryManager();
     35 
     36  private final IntentFilter mFilter;
     37  private Context mApplicationContext;
     38  private boolean mIsEnabled;
     39 
     40  public static GeckoBatteryManager getInstance() {
     41    return sInstance;
     42  }
     43 
     44  private GeckoBatteryManager() {
     45    mFilter = new IntentFilter();
     46    mFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
     47  }
     48 
     49  public synchronized void start(final Context context) {
     50    if (mIsEnabled) {
     51      Log.w(LOGTAG, "Already started!");
     52      return;
     53    }
     54 
     55    mApplicationContext = context.getApplicationContext();
     56    // registerReceiver will return null if registering fails.
     57    final Intent intent = mApplicationContext.registerReceiver(this, mFilter);
     58    if (intent == null) {
     59      Log.e(LOGTAG, "Registering receiver failed");
     60      return;
     61    }
     62 
     63    mIsEnabled = true;
     64    final double current = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
     65    final double max = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
     66    if (current == -1 || max == -1) {
     67      Log.e(LOGTAG, "Failed to get battery level!");
     68      sLevel = kDefaultLevel;
     69    } else {
     70      sLevel = current / max;
     71    }
     72  }
     73 
     74  public synchronized void stop() {
     75    if (!mIsEnabled) {
     76      Log.w(LOGTAG, "Already stopped!");
     77      return;
     78    }
     79 
     80    mApplicationContext.unregisterReceiver(this);
     81    mApplicationContext = null;
     82    mIsEnabled = false;
     83  }
     84 
     85  @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
     86  private static native void onBatteryChange(double level, boolean charging, double remainingTime);
     87 
     88  @Override
     89  public void onReceive(final Context context, final Intent intent) {
     90    if (!intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
     91      Log.e(LOGTAG, "Got an unexpected intent!");
     92      return;
     93    }
     94 
     95    final boolean previousCharging = isCharging();
     96    final double previousLevel = getLevel();
     97 
     98    // NOTE: it might not be common (in 2012) but technically, Android can run
     99    // on a device that has no battery so we want to make sure it's not the case
    100    // before bothering checking for battery state.
    101    // However, the Galaxy Nexus phone advertises itself as battery-less which
    102    // force us to special-case the logic.
    103    // See the Google bug: https://code.google.com/p/android/issues/detail?id=22035
    104    if (intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false)
    105        || Build.MODEL.equals("Galaxy Nexus")) {
    106      final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    107      if (plugged == -1) {
    108        sCharging = kDefaultCharging;
    109        Log.e(LOGTAG, "Failed to get the plugged status!");
    110      } else {
    111        // Likely, if plugged > 0, it's likely plugged and charging but the doc
    112        // isn't clear about that.
    113        sCharging = plugged != 0;
    114      }
    115 
    116      if (sCharging != previousCharging) {
    117        sRemainingTime = kUnknownRemainingTime;
    118        // The new remaining time is going to take some time to show up but
    119        // it's the best way to show a not too wrong value.
    120        sLastLevelChange = 0;
    121      }
    122 
    123      // We need two doubles because sLevel is a double.
    124      final double current = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    125      final double max = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    126      if (current == -1 || max == -1) {
    127        Log.e(LOGTAG, "Failed to get battery level!");
    128        sLevel = kDefaultLevel;
    129      } else {
    130        sLevel = current / max;
    131      }
    132 
    133      if (sLevel == 1.0 && sCharging) {
    134        sRemainingTime = kDefaultRemainingTime;
    135      } else if (sLevel != previousLevel) {
    136        // Estimate remaining time.
    137        if (sLastLevelChange != 0) {
    138          // Use elapsedRealtime() because we want to track time across device sleeps.
    139          final long currentTime = SystemClock.elapsedRealtime();
    140          final long dt = (currentTime - sLastLevelChange) / 1000;
    141          final double dLevel = sLevel - previousLevel;
    142 
    143          if (sCharging) {
    144            if (dLevel < 0) {
    145              sRemainingTime = kUnknownRemainingTime;
    146            } else {
    147              sRemainingTime = Math.round(dt / dLevel * (1.0 - sLevel));
    148            }
    149          } else {
    150            if (dLevel > 0) {
    151              Log.w(LOGTAG, "When discharging, level should decrease!");
    152              sRemainingTime = kUnknownRemainingTime;
    153            } else {
    154              sRemainingTime = Math.round(dt / -dLevel * sLevel);
    155            }
    156          }
    157 
    158          sLastLevelChange = currentTime;
    159        } else {
    160          // That's the first time we got an update, we can't do anything.
    161          sLastLevelChange = SystemClock.elapsedRealtime();
    162        }
    163      }
    164    } else {
    165      sLevel = kDefaultLevel;
    166      sCharging = kDefaultCharging;
    167      sRemainingTime = kDefaultRemainingTime;
    168    }
    169 
    170    /*
    171     * We want to inform listeners if the following conditions are fulfilled:
    172     *  - we have at least one observer;
    173     *  - the charging state or the level has changed.
    174     *
    175     * Note: no need to check for a remaining time change given that it's only
    176     * updated if there is a level change or a charging change.
    177     *
    178     * The idea is to prevent doing all the way to the DOM code in the child
    179     * process to finally not send an event.
    180     */
    181    if (sNotificationsEnabled
    182        && (previousCharging != isCharging() || previousLevel != getLevel())) {
    183      onBatteryChange(getLevel(), isCharging(), getRemainingTime());
    184    }
    185  }
    186 
    187  public static boolean isCharging() {
    188    return sCharging;
    189  }
    190 
    191  public static double getLevel() {
    192    return sLevel;
    193  }
    194 
    195  public static double getRemainingTime() {
    196    return sRemainingTime;
    197  }
    198 
    199  public static void enableNotifications() {
    200    sNotificationsEnabled = true;
    201  }
    202 
    203  public static void disableNotifications() {
    204    sNotificationsEnabled = false;
    205  }
    206 
    207  public static double[] getCurrentInformation(final Context context) {
    208    if (!getInstance().mIsEnabled) {
    209      getInstance().start(context);
    210    }
    211    return new double[] {getLevel(), isCharging() ? 1.0 : 0.0, getRemainingTime()};
    212  }
    213 }