/* Copyright (C) 2011 The University of Michigan This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Please send inquiries to powertutor@umich.edu */ package edu.umich.PowerTutor.components; import edu.umich.PowerTutor.phone.PhoneConstants; import edu.umich.PowerTutor.service.IterationData; import edu.umich.PowerTutor.service.PowerData; import edu.umich.PowerTutor.service.PowerEstimator; import edu.umich.PowerTutor.util.Recycler; import edu.umich.PowerTutor.util.SystemInfo; import android.content.Context; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import android.telephony.TelephonyManager; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.RandomAccessFile; public class Threeg extends PowerComponent { public static class ThreegData extends PowerData { private static Recycler<ThreegData> recycler = new Recycler<ThreegData>(); public static ThreegData obtain() { ThreegData result = recycler.obtain(); if(result != null) return result; return new ThreegData(); } @Override public void recycle() { recycler.recycle(this); } public boolean threegOn; public long packets; public long uplinkBytes; public long downlinkBytes; public int powerState; public String oper; private ThreegData() { } public void init() { threegOn = false; } public void init(long packets, long uplinkBytes, long downlinkBytes, int powerState, String oper) { threegOn = true; this.packets = packets; this.uplinkBytes = uplinkBytes; this.downlinkBytes = downlinkBytes; this.powerState = powerState; this.oper = oper; } public void writeLogDataInfo(OutputStreamWriter out) throws IOException { StringBuilder res = new StringBuilder(); res.append("3G-on ").append(threegOn).append("\n"); if(threegOn) { res.append("3G-uplinkBytes ").append(uplinkBytes) .append("\n3G-downlinkBytes ").append(downlinkBytes) .append("\n3G-packets ").append(packets) .append("\n3G-state ").append(Threeg.POWER_STATE_NAMES[powerState]) .append("\n3G-oper ").append(oper) .append("\n"); } out.write(res.toString()); } } public static final int POWER_STATE_IDLE = 0; public static final int POWER_STATE_FACH = 1; public static final int POWER_STATE_DCH = 2; public static final String[] POWER_STATE_NAMES = {"IDLE", "FACH", "DCH"}; private static final String TAG = "Threeg"; private PhoneConstants phoneConstants; private TelephonyManager telephonyManager; private SystemInfo sysInfo; private String oper; private int dchFachDelay; private int fachIdleDelay; private int uplinkQueueSize; private int downlinkQueueSize; private int[] lastUids; private ThreegStateKeeper threegState; private SparseArray<ThreegStateKeeper> uidStates; private String transPacketsFile; private String readPacketsFile; private String readBytesFile; private String transBytesFile; private File uidStatsFolder; public Threeg(Context context, PhoneConstants phoneConstants) { this.phoneConstants = phoneConstants; telephonyManager = (TelephonyManager)context.getSystemService( Context.TELEPHONY_SERVICE); String interfaceName = phoneConstants.threegInterface(); threegState = new ThreegStateKeeper(); uidStates = new SparseArray<ThreegStateKeeper>(); transPacketsFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/tx_packets"; readPacketsFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/rx_packets"; readBytesFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/rx_bytes"; transBytesFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/tx_bytes"; uidStatsFolder = new File("/proc/uid_stat"); sysInfo = SystemInfo.getInstance(); } @Override public IterationData calculateIteration(long iteration) { IterationData result = IterationData.obtain(); int netType = telephonyManager.getNetworkType(); if((netType != TelephonyManager.NETWORK_TYPE_UMTS && netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) { // TODO: Actually get models for the different network types. netType = TelephonyManager.NETWORK_TYPE_UMTS; } if(telephonyManager.getDataState() != TelephonyManager.DATA_CONNECTED || (netType != TelephonyManager.NETWORK_TYPE_UMTS && netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) { /* We need to allow the real iterface state keeper to reset it's state * so that the next update it knows it's coming back from an off state. * We also need to clear all the uid information. */ oper = null; threegState.interfaceOff(); uidStates.clear(); ThreegData data = ThreegData.obtain(); data.init(); result.setPowerData(data); return result; } if(oper == null) { oper = telephonyManager.getNetworkOperatorName(); dchFachDelay = phoneConstants.threegDchFachDelay(oper); fachIdleDelay = phoneConstants.threegFachIdleDelay(oper); uplinkQueueSize = phoneConstants.threegUplinkQueue(oper); downlinkQueueSize = phoneConstants.threegDownlinkQueue(oper); } long transmitPackets = readLongFromFile(transPacketsFile); long receivePackets = readLongFromFile(readPacketsFile); long transmitBytes = readLongFromFile(transBytesFile); long receiveBytes = readLongFromFile(readBytesFile); if(transmitBytes == -1 || receiveBytes == -1) { /* Couldn't read interface data files. */ Log.w(TAG, "Failed to read packet and byte counts from wifi interface"); return result; } if(threegState.isInitialized()) { threegState.updateState(transmitPackets, receivePackets, transmitBytes, receiveBytes, dchFachDelay, fachIdleDelay, uplinkQueueSize, downlinkQueueSize); ThreegData data = ThreegData.obtain(); data.init(threegState.getPackets(), threegState.getUplinkBytes(), threegState.getDownlinkBytes(), threegState.getPowerState(), oper); result.setPowerData(data); } else { threegState.updateState(transmitPackets, receivePackets, transmitBytes, receiveBytes, dchFachDelay, fachIdleDelay, uplinkQueueSize, downlinkQueueSize); } lastUids = sysInfo.getUids(lastUids); if(lastUids != null) for(int uid : lastUids) { if(uid == -1) { continue; } try { ThreegStateKeeper uidState = uidStates.get(uid); if(uidState == null) { uidState = new ThreegStateKeeper(); uidStates.put(uid, uidState); } if(!uidState.isStale()) { /* We use a huerstic here so that we don't poll for uids that haven't * had much activity recently. */ continue; } /* These read operations are the expensive part of polling. */ receiveBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_rcv"); transmitBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_snd"); if(receiveBytes == -1 || transmitBytes == -1) { Log.w(TAG, "Failed to read uid read/write byte counts"); } else if(uidState.isInitialized()) { uidState.updateState(-1, -1, transmitBytes, receiveBytes, dchFachDelay, fachIdleDelay, uplinkQueueSize, downlinkQueueSize); if(uidState.getUplinkBytes() + uidState.getDownlinkBytes() != 0 || uidState.getPowerState() != POWER_STATE_IDLE) { ThreegData uidData = ThreegData.obtain(); uidData.init(uidState.getPackets(), uidState.getUplinkBytes(), uidState.getDownlinkBytes(), uidState.getPowerState(), oper); result.addUidPowerData(uid, uidData); } } else { uidState.updateState(-1, -1, transmitBytes, receiveBytes, dchFachDelay, fachIdleDelay, uplinkQueueSize, downlinkQueueSize); } } catch(NumberFormatException e) { Log.w(TAG, "Non-uid files in /proc/uid_stat"); } } return result; } private static class ThreegStateKeeper { private long lastTransmitPackets; private long lastReceivePackets; private long lastTransmitBytes; private long lastReceiveBytes; private long lastTime; private long deltaPackets; private long deltaUplinkBytes; private long deltaDownlinkBytes; private int powerState; private int stateTime; private long inactiveTime; public ThreegStateKeeper() { lastTransmitBytes = lastReceiveBytes = lastTime = -1; deltaUplinkBytes = deltaDownlinkBytes = -1; powerState = POWER_STATE_IDLE; stateTime = 0; inactiveTime = 0; } public void interfaceOff() { lastTime = SystemClock.elapsedRealtime(); powerState = POWER_STATE_IDLE; } public boolean isInitialized() { return lastTime != -1; } public void updateState(long transmitPackets, long receivePackets, long transmitBytes, long receiveBytes, int dchFachDelay, int fachIdleDelay, int uplinkQueueSize, int downlinkQueueSize) { long curTime = SystemClock.elapsedRealtime(); if(lastTime != -1 && curTime > lastTime) { double deltaTime = curTime - lastTime; deltaPackets = transmitPackets + receivePackets - lastTransmitPackets - lastReceivePackets; deltaUplinkBytes = transmitBytes - lastTransmitBytes; deltaDownlinkBytes = receiveBytes - lastReceiveBytes; boolean inactive = deltaUplinkBytes == 0 && deltaDownlinkBytes == 0; inactiveTime = inactive ? inactiveTime + curTime - lastTime : 0; // TODO: make this always work. int timeMult = 1; if(1000 % PowerEstimator.ITERATION_INTERVAL != 0) { Log.w(TAG, "Cannot handle iteration intervals that are a factor of 1 second"); } else { timeMult = 1000 / PowerEstimator.ITERATION_INTERVAL; } switch(powerState) { case POWER_STATE_IDLE: if(!inactive) { powerState = POWER_STATE_FACH; } break; case POWER_STATE_FACH: if(inactive) { stateTime++; if(stateTime >= fachIdleDelay * timeMult) { stateTime = 0; powerState = POWER_STATE_IDLE; } } else { stateTime = 0; if(deltaUplinkBytes > 0 || deltaDownlinkBytes > 0) { powerState = POWER_STATE_DCH; } } break; default: // case POWER_STATE_DCH: if(inactive) { stateTime++; if(stateTime >= dchFachDelay * timeMult) { stateTime = 0; powerState = POWER_STATE_FACH; } } else { stateTime = 0; } } } lastTime = curTime; lastTransmitPackets = transmitPackets; lastReceivePackets = receivePackets; lastTransmitBytes = transmitBytes; lastReceiveBytes = receiveBytes; } public int getPowerState() { return powerState; } public long getPackets() { return deltaPackets; } public long getUplinkBytes() { return deltaUplinkBytes; } public long getDownlinkBytes() { return deltaDownlinkBytes; } /* The idea here is that we don't want to have to read uid information * every single iteration for each uid as it just takes too long. So here * we are designing a hueristic that helps us avoid polling for too many * uids. */ public boolean isStale() { if(powerState != POWER_STATE_IDLE) return true; long curTime = SystemClock.elapsedRealtime(); return curTime - lastTime > (long)Math.min(10000, inactiveTime); } } private final static byte[] buf = new byte[16]; private long readLongFromFile(String filePath) { return sysInfo.readLongFromFile(filePath); } @Override public boolean hasUidInformation() { return uidStatsFolder.exists(); } @Override public String getComponentName() { return "3G"; } }