/**
*
*/
package com.asksven.android.common.kernelutils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.os.Build;
import android.util.Log;
import com.asksven.andoid.common.contrib.Shell;
import com.asksven.andoid.common.contrib.Util;
import com.asksven.android.common.privateapiproxies.Alarm;
import com.asksven.android.common.privateapiproxies.StatElement;
import com.asksven.android.common.shellutils.Exec;
import com.asksven.android.common.shellutils.ExecResult;
/**
* Parses the content of 'dumpsys alarm'
* processes the result of 'dumpsys alarm' as explained in KB article
* https://github.com/asksven/BetterBatteryStats-Knowledge-Base/wiki/AlarmManager
* @author sven
*/
public class AlarmsDumpsys
{
static final String TAG = "AlarmsDumpsys";
static final String PERMISSION_DENIED = "su rights required to access alarms are not available / were not granted";
public static ArrayList<StatElement> getAlarms()
{
String release = Build.VERSION.RELEASE;
int sdk = Build.VERSION.SDK_INT;
Log.i(TAG, "getAlarms: SDK=" + sdk + ", RELEASE=" + release);
if (sdk < 17) // Build.VERSION_CODES.JELLY_BEAN_MR1)
{
return getAlarmsPriorTo_4_2_2();
}
else if (sdk == 17) // Build.VERSION_CODES.JELLY_BEAN_MR1)
{
if (release.equals("4.2.2"))
{
return getAlarmsFrom_4_2_2();
}
else
{
return getAlarmsPriorTo_4_2_2();
}
}
else
{
return getAlarmsFrom_4_2_2();
}
}
/**
* Returns a list of alarm value objects
* @return
* @throws Exception
*/
private static ArrayList<StatElement> getAlarmsPriorTo_4_2_2()
{
ArrayList<StatElement> myAlarms = null;
long nTotalCount = 0;
// ExecResult res = Exec.execPrint(new String[]{"/system/bin/su", "-c", "/system/bin/dumpsys alarm"});
// ExecResult res = Exec.execPrint(new String[]{"su", "-c", "dumpsys alarm"});
List<String> res = Shell.SU.run("dumpsys alarm");
// if (res.getSuccess())
if ((res != null) && (res.size() != 0))
{
// String strRes = res.getResultLine();
if (true) //strRes.contains("Permission Denial"))
{
Pattern begin = Pattern.compile("Alarm Stats");
boolean bParsing = false;
// ArrayList<String> myRes = res.getResult(); // getTestData();
// we are looking for multiline entries in the format
// ' <package name>
// ' <time> ms running, <number> wakeups
// ' <number> alarms: act=<intent name> flg=<flag> (repeating 1..n times)
Pattern packagePattern = Pattern.compile("\\s\\s([a-z][a-zA-Z0-9\\.]+)");
Pattern timePattern = Pattern.compile("\\s\\s(\\d+)ms running, (\\d+) wakeups");
Pattern numberPattern = Pattern.compile("\\s\\s(\\d+) alarms: (flg=[a-z0-9]+\\s){0,1}(act|cmp)=([A-Za-z0-9\\-\\_\\.\\{\\}\\/\\{\\}\\$]+)");
myAlarms = new ArrayList<StatElement>();
Alarm myAlarm = null;
// process the file
for (int i=0; i < res.size(); i++)
{
// skip till start mark found
if (bParsing)
{
// parse the alarms by block
String line = res.get(i);
Matcher mPackage = packagePattern.matcher(line);
Matcher mTime = timePattern.matcher(line);
Matcher mNumber = numberPattern.matcher(line);
// first line
if ( mPackage.find() )
{
try
{
// if there was a previous Alarm populated store it
if (myAlarm != null)
{
myAlarms.add(myAlarm);
}
// we are interested in the first token
String strPackageName = mPackage.group(1);
myAlarm = new Alarm(strPackageName);
}
catch (Exception e)
{
Log.e(TAG, "Error: parsing error in package line (" + line + ")");
}
}
// second line
if ( mTime.find() )
{
try
{
// we are interested in the second token
String strWakeups = mTime.group(2);
long nWakeups = Long.parseLong(strWakeups);
if (myAlarm == null)
{
Log.e(TAG, "Error: time line found but without alarm object (" + line + ")");
}
else
{
myAlarm.setWakeups(nWakeups);
nTotalCount += nWakeups;
}
}
catch (Exception e)
{
Log.e(TAG, "Error: parsing error in time line (" + line + ")");
}
}
// third line (and following till next package
if ( mNumber.find() )
{
try
{
// we are interested in the first and second token
String strNumber = mNumber.group(1);
String strIntent = mNumber.group(4);
long nNumber = Long.parseLong(strNumber);
if (myAlarm == null)
{
Log.e(TAG, "Error: number line found but without alarm object (" + line + ")");
}
else
{
myAlarm.addItem(nNumber, strIntent);
}
}
catch (Exception e)
{
Log.e(TAG, "Error: parsing error in number line (" + line + ")");
}
}
}
else
{
// look for beginning
Matcher line = begin.matcher(res.get(i));
if (line.find())
{
bParsing = true;
}
}
}
// the last populated alarms has not been added to the list yet
myAlarms.add(myAlarm);
}
else
{
myAlarms = new ArrayList<StatElement>();
Alarm myAlarm = new Alarm(PERMISSION_DENIED);
myAlarm.setWakeups(1);
myAlarms.add(myAlarm);
}
}
else
{
myAlarms = new ArrayList<StatElement>();
Alarm myAlarm = new Alarm(PERMISSION_DENIED);
myAlarm.setWakeups(1);
myAlarms.add(myAlarm);
}
for (int i=0; i < myAlarms.size(); i++)
{
((Alarm)myAlarms.get(i)).setTotalCount(nTotalCount);
}
return myAlarms;
}
private static ArrayList<StatElement> getAlarmsFrom_4_2_2()
{
ArrayList<StatElement> myAlarms = null;
long nTotalCount = 0;
List<String> res = Shell.SU.run("dumpsys alarm");
if ((res != null) && (res.size() != 0))
{
Pattern begin = Pattern.compile("Alarm Stats");
boolean bParsing = false;
// we are looking for multiline entries in the format
// ' <package name> +<time>ms running, <number> wakeups
// ' +<time>ms <number> wakes <number> alarms: act=<intern> (repeating 1..n times)
Pattern packagePattern = Pattern.compile("\\s\\s([a-z][a-zA-Z0-9\\.]+)\\s\\+(.*), (\\d+) wakeups:");
Pattern numberPattern = Pattern.compile("\\s\\s\\s\\s\\+([0-9a-z]+)ms (\\d+) wakes (\\d+) alarms: (act|cmp)=([A-Za-z0-9\\-\\_\\.\\$\\{\\}]+)");
myAlarms = new ArrayList<StatElement>();
Alarm myAlarm = null;
// process the file
for (int i=0; i < res.size(); i++)
{
// skip till start mark found
if (bParsing)
{
// parse the alarms by block
String line = res.get(i);
Matcher mPackage = packagePattern.matcher(line);
Matcher mNumber = numberPattern.matcher(line);
// first line
if ( mPackage.find() )
{
try
{
// if there was a previous Alarm populated store it
if (myAlarm != null)
{
myAlarms.add(myAlarm);
}
// we are interested in the first token
String strPackageName = mPackage.group(1);
myAlarm = new Alarm(strPackageName);
String strWakeups = mPackage.group(3);
long nWakeups = Long.parseLong(strWakeups);
myAlarm.setWakeups(nWakeups);
nTotalCount += nWakeups;
}
catch (Exception e)
{
Log.e(TAG, "Error: parsing error in package line (" + line + ")");
}
}
// second line (and following till next package)
if ( mNumber.find() )
{
try
{
// we are interested in the first and second token
String strNumber = mNumber.group(2);
String strIntent = mNumber.group(5);
long nNumber = Long.parseLong(strNumber);
if (myAlarm == null)
{
Log.e(TAG, "Error: number line found but without alarm object (" + line + ")");
}
else
{
myAlarm.addItem(nNumber, strIntent);
}
}
catch (Exception e)
{
Log.e(TAG, "Error: parsing error in number line (" + line + ")");
}
}
}
else
{
// look for beginning
Matcher line = begin.matcher(res.get(i));
if (line.find())
{
bParsing = true;
}
}
}
// the last populated alarms has not been added to the list yet
myAlarms.add(myAlarm);
}
else
{
myAlarms = new ArrayList<StatElement>();
Alarm myAlarm = new Alarm(PERMISSION_DENIED);
myAlarm.setWakeups(1);
myAlarms.add(myAlarm);
}
for (int i=0; i < myAlarms.size(); i++)
{
Alarm myAlarm = (Alarm)myAlarms.get(i);
if (myAlarm != null)
{
myAlarm.setTotalCount(nTotalCount);
}
}
return myAlarms;
}
static ArrayList<String> getTestData()
{
ArrayList<String> myRet = new ArrayList<String>()
{{
add("Alarm Stats:");
add(" com.google.android.gsf");
add(" 8417ms running, 204 wakeups");
add(" 17 alarms: act=com.google.android.intent.action.GTALK_RECONNECT flg=0x4");
add(" 187 alarms: flg=0x4");
// com.anod.calendar
// 311ms running, 0 wakeups
// 4 alarms: act=android.appwidget.action.APPWIDGET_UPDATE dat=com.anod.calendar://widget/id/45 flg=0x4
// com.yahoo.mobile.client.android.mail
// 1248ms running, 96 wakeups
// 2 alarms: act=com.yahoo.android.push.Timer_VitalizeSessionybres89mail flg=0x4 cmp=com.yahoo.mobile.client.android.mail/com.yahoo.mobile.client.share.push.HTTPKeepAliveService
// 2 alarms: act=com.yahoo.android.push.Connection_Watchdogybres89mail flg=0x4 cmp=com.yahoo.mobile.client.android.mail/com.yahoo.mobile.client.share.push.HTTPKeepAliveService
// 92 alarms: act=com.yahoo.android.push.Connection_Recoveryybres89mail flg=0x4 cmp=com.yahoo.mobile.client.android.mail/com.yahoo.mobile.client.share.push.HTTPKeepAliveService
// com.android.vending
// 6669ms running, 0 wakeups
// 12 alarms: act=com.android.vending.FORCE_UPDATE_CHECK flg=0x4
// com.moctav.weather
// 3783ms running, 0 wakeups
// 94 alarms: act=Autoupdate flg=0x4 cmp=com.moctav.weather/.WeatherUpdateService
// android
// 517606ms running, 137 wakeups
// 4 alarms: act=android.intent.action.DATE_CHANGED flg=0x30000004
// 70 alarms: act=com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD flg=0x4
// 5599 alarms: act=android.intent.action.TIME_TICK flg=0x40000004
// 557 alarms: act=com.android.server.ThrottleManager.action.POLL flg=0x4
// 54 alarms: act=android.app.backup.intent.RUN flg=0x40000004
// 13 alarms: act=android.content.syncmanager.SYNC_ALARM flg=0x4
// com.whatsapp
// 36757ms running, 11 wakeups
// 4 alarms: act=ALARM_REPORT_SYNCS flg=0x4
// 4 alarms: act=ALARM_MESSAGES_DB_BACKUP flg=0x4
// 4 alarms: act=ALARM_ROTATE_LOGS flg=0x4
// 2 alarms: act=ALARM_AVAILABLE_TIMEOUT flg=0x4
// 114 alarms: act=ALARM_ACTION flg=0x4
// 1 alarms: act=com.whatsapp.MessageService.RECONNECT flg=0x4 cmp=com.whatsapp/.messaging.MessageService
// com.google.android.apps.maps
// 2238ms running, 93 wakeups
// 93 alarms: flg=0x4 cmp=com.google.android.apps.maps/com.google.googlenav.prefetch.android.PrefetcherService
// com.android.providers.calendar
// 344ms running, 4 wakeups
// 4 alarms: act=com.android.providers.calendar.SCHEDULE_ALARM flg=0x4
// com.android.deskclock
// 1219ms running, 4 wakeups
// 4 alarms: act=com.android.deskclock.ALARM_ALERT flg=0x4
add(" com.carl.trafficcounter");
add(" 446486ms running, 5584 wakeups");
add(" 5584 alarms: act=com.carl.trafficcounter.UPDATE_RUN flg=0x4");
}};
return myRet;
}
}