content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.android.internal.os;
  
  import android.os.BatteryStats;
  import android.telephony.SignalStrength;
  import android.util.Log;
  
  public class MobileRadioPowerCalculator extends PowerCalculator {
      private static final String TAG = "MobileRadioPowerController";
      private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
      private final double mPowerRadioOn;
      private final double[] mPowerBins = new double[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
      private final double mPowerScan;
      private BatteryStats mStats;
      private long mTotalAppMobileActiveMs = 0;
  
      /**
       * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
       */
      private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) {
          final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
          final double MOBILE_POWER = mPowerRadioOn / 3600;
  
          final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
                  statsType);
          final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
                  statsType);
          final long mobileData = mobileRx + mobileTx;
  
          final long radioDataUptimeMs =
                  mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
          final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
                  ? (mobileData / (double)radioDataUptimeMs)
                  : (((double)MOBILE_BPS) / 8 / 2048);
          return (MOBILE_POWER / mobilePps) / (60*60);
      }
  
      public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
          double temp =
                  profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
          if (temp != -1) {
              mPowerRadioOn = temp;
          } else {
              double sum = 0;
              sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
              for (int i = 0; i < mPowerBins.length; i++) {
                  sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
              }
              mPowerRadioOn = sum / (mPowerBins.length + 1);
          }
  
          temp = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1);
          if (temp != -1 ) {
              for (int i = 0; i < mPowerBins.length; i++) {
                  mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
              }
          } else {
              double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE);
              mPowerBins[0] = idle * 25 / 180;
              for (int i = 1; i < mPowerBins.length; i++) {
                  mPowerBins[i] = Math.max(1, idle / 256);
              }
          }
  
          mPowerScan = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0);
          mStats = stats;
      }
  
      @Override
      public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                               long rawUptimeUs, int statsType) {
          // Add cost of mobile traffic.
          app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
                  statsType);
          app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
                  statsType);
          app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
          app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
          app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
                  statsType);
          app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
                  statsType);
  
          if (app.mobileActive > 0) {
              // We are tracking when the radio is up, so can use the active time to
              // determine power use.
              mTotalAppMobileActiveMs += app.mobileActive;
              app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
          } else {
              // We are not tracking when the radio is up, so must approximate power use
              // based on the number of packets.
              app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
                      * getMobilePowerPerPacket(rawRealtimeUs, statsType);
          }
          if (DEBUG && app.mobileRadioPowerMah != 0) {
              Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
                      + (app.mobileRxPackets + app.mobileTxPackets)
                      + " active time " + app.mobileActive
                      + " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah));
          }
      }
  
      @Override
      public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
                                     long rawUptimeUs, int statsType) {
          double power = 0;
          long signalTimeMs = 0;
          long noCoverageTimeMs = 0;
          for (int i = 0; i < mPowerBins.length; i++) {
              long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
                      / 1000;
              final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
              if (DEBUG && p != 0) {
                  Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
                          + BatteryStatsHelper.makemAh(p));
              }
              power += p;
              signalTimeMs += strengthTimeMs;
              if (i == 0) {
                  noCoverageTimeMs = strengthTimeMs;
              }
          }
  
          final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
                  / 1000;
          final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
          if (DEBUG && p != 0) {
              Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
                      + " power=" + BatteryStatsHelper.makemAh(p));
          }
          power += p;
          long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
          long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
          if (remainingActiveTimeMs > 0) {
              power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
          }
  
          if (power != 0) {
              if (signalTimeMs != 0) {
                  app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
              }
              app.mobileActive = remainingActiveTimeMs;
              app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
              app.mobileRadioPowerMah = power;
          }
      }
  
      @Override
      public void reset() {
          mTotalAppMobileActiveMs = 0;
      }
  
      public void reset(BatteryStats stats) {
          reset();
          mStats = stats;
      }
  }
  