/**
*
*/
package com.asksven.android.common.kernelutils;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.asksven.andoid.common.contrib.Shell;
import com.asksven.andoid.common.contrib.Util;
import com.asksven.android.common.CommonLogSettings;
import com.asksven.android.common.RootShell;
import com.asksven.android.common.privateapiproxies.NativeKernelWakelock;
import com.asksven.android.common.privateapiproxies.NetworkUsage;
import com.asksven.android.common.privateapiproxies.StatElement;
import com.asksven.android.common.shellutils.Exec;
import com.asksven.android.common.shellutils.ExecResult;
import com.asksven.android.common.utils.StringUtils;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.util.Log;
/**
* @author sven
*
* Parsing of /proc/wakelocks
*
* Fields:
* total_time: accumulates the total amount of time that the corresponding suspend blocker has been held.
* active_since: tracks how long a suspend blocker has been held since it was last acquired, or (presumably) zero if it is not currently held.
* count: the number of times that the suspend blocker has been acquired. This is useful in combination with total_time, as it allows you to calculate the average hold time for the suspend blocker.
* expire_count: the number of times that the suspend blocker has timed out. This indicates that some application has an input device open, but is not reading from it, which is a bug, as noted earlier.
* max_time: the longest hold time for the suspend blocker. This allows finding cases where suspend blockers are held for too long, but are eventually released. (In contrast, active_since is more useful in the held-forever case.)
* sleep_time: the total time that the suspend blocker was held while the display was powered off.
* wake_count: the number of times that the suspend blocker was the first to be acquired in the resume path.
*
*/
public class Wakelocks
{
private final static String TAG ="Wakelocks";
private static String FILE_PATH = "/proc/wakelocks";
public static ArrayList<StatElement> parseProcWakelocks(Context context)
{
if (CommonLogSettings.DEBUG)
{
Log.i(TAG, "Parsing " + FILE_PATH);
}
String delimiter = String.valueOf('\t');
ArrayList<StatElement> myRet = new ArrayList<StatElement>();
// format
// [name, count, expire_count, wake_count, active_since, total_time, sleep_time, max_time, last_change]
ArrayList<String[]> rows = parseDelimitedFile(FILE_PATH, delimiter);
long msSinceBoot = SystemClock.elapsedRealtime();
// list the running processes
ActivityManager actvityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> procInfos = actvityManager.getRunningAppProcesses();
PackageManager pack=context.getPackageManager();
// start with 1
for (int i=1; i < rows.size(); i++ )
{
try
{
// times in file are microseconds
String[] data = (String[]) rows.get(i);
String name = data[0];
int count = Integer.valueOf(data[1]);
int expire_count = Integer.valueOf(data[2]);
int wake_count = Integer.valueOf(data[3]);
long active_since = Long.valueOf(data[4]);
long total_time = Long.valueOf(data[5]) / 1000000;
long sleep_time = Long.valueOf(data[6]) / 1000000;
long max_time = Long.valueOf(data[7]) / 1000000;
long last_change = Long.valueOf(data[8]);
// post-processing of eventX-YYYY processes
String details = "";
// name = "event3-30240";
// we start with a " here as that is the way the data comes from /proc
if (name.startsWith("\"event"))
{
String process = name.replaceAll("\"", "");
if (CommonLogSettings.DEBUG)
{
Log.d(TAG, "Pattern 'event' found in " + process);
}
int proc = 0;
String[] parts = process.split("-");
if (parts.length == 2)
{
try
{
proc = Integer.valueOf(parts[1]);
if (CommonLogSettings.DEBUG)
{
Log.d(TAG, "Resolving proc name for 'event' " + proc);
}
}
catch (Exception e)
{
Log.e(TAG, "Cound not split process name " + process);
}
}
if (proc != 0)
{
// search for the process in the task list
for (int psCount = 0; psCount < procInfos.size(); psCount++)
{
int id = procInfos.get(psCount).pid;
if ( id == proc)
{
String processName = procInfos.get(psCount).processName;
details= processName;
String appName = "";
String[] pkgList = procInfos.get(count).pkgList;
for (int j=0; j < pkgList.length; j++)
{
if (details.length() > 0)
{
details += ", ";
}
details += pkgList[j];
}
if (CommonLogSettings.DEBUG)
{
Log.d(TAG, "Pattern 'event' resolved to " + details);
}
}
}
}
}
NativeKernelWakelock wl = new NativeKernelWakelock(
name, details, count, expire_count, wake_count,
active_since, total_time, sleep_time, max_time,
last_change, msSinceBoot);
myRet.add(wl);
}
catch (Exception e)
{
// go on
}
}
return myRet;
}
protected static ArrayList<String[]> parseDelimitedFile(String filePath, String delimiter)
{
ArrayList<String[]> rows = new ArrayList<String[]>();
try
{
FileReader fr = new FileReader(filePath);
BufferedReader br = new BufferedReader(fr);
String currentRecord;
while ((currentRecord = br.readLine()) != null)
rows.add(currentRecord.split(delimiter));
br.close();
}
catch (Exception e)
{
Log.i(TAG, "An error occured while parsing " + filePath + ": " + e.getMessage() + ". Retrying with root");
// retry with root
List<String> res = Shell.SU.run("cat " + filePath);
for (int i=0; i < res.size(); i++)
{
rows.add(res.get(i).split(delimiter));
}
if (res.isEmpty())
{
Log.i(TAG, "Wakelocks could not be read from " + filePath + ", even with root");
}
}
return rows;
}
public static boolean fileExists()
{
boolean exists = false;
FileReader fr = null;
try
{
fr = new FileReader(FILE_PATH);
exists = true;
}
catch (Exception e)
{
exists = false;
}
finally
{
if (exists)
{
try
{
fr.close();
}
catch (IOException e)
{
// do nothing
}
}
}
return exists;
}
public static boolean isDiscreteKwlPatch()
{
boolean ret = false;
String filePath = "/sys/module/wakelock/parameters/default_stats";
try
{
FileReader fr = new FileReader(filePath);
BufferedReader br = new BufferedReader(fr);
// read first line
String currentRecord = br.readLine();
if (!currentRecord.equals("0"))
{
ret = true;
}
br.close();
}
catch (Exception e)
{
}
return ret;
}
public static boolean hasWakelocks(Context context)
{
boolean myRet = false;
String filePath = "/sys/power/wake_lock";
ArrayList<String> res = Util.run("su", "cat " + filePath);
// Format: 0 /sys/power/wake_lock
final ArrayList<String> values = new ArrayList<String>();
if (res.size() != 0)
{
String line = res.get(0);
try
{
myRet = line.contains("PowerManagerService");
if (myRet)
{
Log.i(TAG, "Detected userland wakelock in line " + line);
}
else
{
Log.i(TAG, "No userland wakelock detected in line " + line);
}
}
catch (Exception e)
{
// something went wrong
Log.e(TAG, "Exeception processsing " + filePath + ": " + e.getMessage());
myRet = false;
}
}
return myRet;
}
}