/*
* Copyright (C) 2011-2014 asksven
*
* 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.asksven.betterbatterystats.data;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import com.asksven.android.common.RootShell;
import com.asksven.android.common.privateapiproxies.NativeKernelWakelock;
import com.asksven.android.common.kernelutils.State;
import com.asksven.android.common.kernelutils.Wakelocks;
import com.asksven.android.common.nameutils.UidNameResolver;
import com.asksven.android.common.privateapiproxies.Alarm;
import com.asksven.android.common.privateapiproxies.Misc;
import com.asksven.android.common.privateapiproxies.NetworkUsage;
import com.asksven.android.common.privateapiproxies.SensorUsage;
import com.asksven.android.common.privateapiproxies.StatElement;
import com.asksven.android.common.privateapiproxies.Wakelock;
import com.asksven.android.common.privateapiproxies.Process;
import com.asksven.android.common.utils.DataStorage;
import com.asksven.android.common.utils.DateUtils;
import com.asksven.android.common.utils.SysUtils;
import com.asksven.betterbatterystats.R;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* A reading represents the data that was collected on a device between two references.
* This class is designed to be dumped as text or as JSON representation
* @author sven
*
*/
public class Reading implements Serializable
{
static final String TAG = "Reading";
String bbsVersion;
String creationDate;
String statType;
String duration;
long totalTime;
String buildVersionRelease;
String buildBrand;
String buildDevice;
String buildManufacturer;
String buildModel;
String osVersion;
String buildBootloader;
String buildHardware;
String buildFingerprint;
String buildId;
String buildTags;
String buildUser;
String buildProduct;
String buildRadio;
boolean rootPermissions;
boolean batteryStatsPermGranted;
boolean xposedBatteryStatsEnabled;
String seLinuxPolicy;
int batteryLevelLost;
int batteryVoltageLost;
String batteryLevelLostText;
String batteryVoltageLostText;
String note;
ArrayList<StatElement> otherStats;
ArrayList<StatElement> kernelWakelockStats;
ArrayList<StatElement> partialWakelockStats;
ArrayList<StatElement> alarmStats;
ArrayList<StatElement> networkStats;
ArrayList<StatElement> cpuStateStats;
ArrayList<StatElement> processStats;
ArrayList<StatElement> sensorUsage;
@SuppressLint({ "InlinedApi", "NewApi" })
public Reading(Context context, Reference refFrom, Reference refTo)
{
otherStats = new ArrayList<StatElement>();
kernelWakelockStats = new ArrayList<StatElement>();
partialWakelockStats = new ArrayList<StatElement>();
alarmStats = new ArrayList<StatElement>();
networkStats = new ArrayList<StatElement>();
cpuStateStats = new ArrayList<StatElement>();
processStats = new ArrayList<StatElement>();
sensorUsage = new ArrayList<StatElement>();
if ((refFrom == null) || (refTo == null))
{
Log.e(TAG, "Error: a Reading was instanciated with a null refFrom or refTo");
return;
}
try
{
PackageInfo pinfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
bbsVersion = pinfo.versionName;
}
catch (Exception e)
{
// do nothing
bbsVersion = "unknown";
}
creationDate = DateUtils.now();
statType = refFrom.getLabel() + " to "+ refTo.getLabel();
totalTime = StatsProvider.getInstance(context).getSince(refFrom, refTo);
duration = DateUtils.formatDuration(totalTime);
buildVersionRelease = Build.VERSION.RELEASE;
buildBrand = Build.BRAND;
buildDevice = Build.DEVICE;
buildManufacturer = Build.MANUFACTURER;
buildModel = Build.MODEL;
osVersion = System.getProperty("os.version");
if (Build.VERSION.SDK_INT >= 8)
{
buildBootloader = Build.BOOTLOADER;
buildHardware = Build.HARDWARE;
}
buildFingerprint = Build.FINGERPRINT;
buildId = Build.ID;
buildTags = Build.TAGS;
buildUser = Build.USER;
buildProduct = Build.PRODUCT;
buildRadio = "";
// from API14
if (Build.VERSION.SDK_INT >= 14)
{
buildRadio = Build.getRadioVersion();
}
else if (Build.VERSION.SDK_INT >= 8)
{
buildRadio = Build.RADIO;
}
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
rootPermissions = RootShell.getInstance().hasRootPermissions(); //Shell.SU.available();
batteryStatsPermGranted = SysUtils.hasBatteryStatsPermission(context);
xposedBatteryStatsEnabled = sharedPrefs.getBoolean("ignore_system_app", false);
seLinuxPolicy = SysUtils.getSELinuxPolicy();
batteryLevelLost = StatsProvider.getInstance(context).getBatteryLevelStat(refFrom, refTo);
batteryVoltageLost = StatsProvider.getInstance(context).getBatteryVoltageStat(refFrom, refTo);
batteryLevelLostText = StatsProvider.getInstance(context).getBatteryLevelFromTo(refFrom, refTo, false);
batteryVoltageLostText = StatsProvider.getInstance(context).getBatteryVoltageFromTo(refFrom, refTo);
// populate the stats
boolean bFilterStats = sharedPrefs.getBoolean("filter_data", true);
int iPctType = 0;
int iSort = 0;
try
{
ArrayList<StatElement> tempStats = StatsProvider.getInstance(context).getOtherUsageStatList(bFilterStats, refFrom, false, false, refTo);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof Misc)
{
otherStats.add((Misc) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type Misc");
}
}
tempStats = StatsProvider.getInstance(context).getWakelockStatList(bFilterStats, refFrom, iPctType, iSort, refTo);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof Wakelock)
{
partialWakelockStats.add((Wakelock) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type Wakelock");
}
}
tempStats = StatsProvider.getInstance(context).getKernelWakelockStatList(bFilterStats, refFrom, iPctType, iSort, refTo);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof NativeKernelWakelock)
{
kernelWakelockStats.add((NativeKernelWakelock) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type NativeKernelWakelock");
}
}
tempStats = StatsProvider.getInstance(context).getProcessStatList(bFilterStats, refFrom, iSort, refTo);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof Process)
{
processStats.add((Process) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type Process");
}
}
tempStats = StatsProvider.getInstance(context).getAlarmsStatList(bFilterStats, refFrom, refTo);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof Alarm)
{
alarmStats.add((Alarm) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type Alarm");
}
}
tempStats = StatsProvider.getInstance(context).getNetworkUsageStatList(bFilterStats, refFrom, refTo);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof NetworkUsage)
{
networkStats.add((NetworkUsage) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type NetworkUsage");
}
}
tempStats = StatsProvider.getInstance(context).getCpuStateList(refFrom, refTo, bFilterStats);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof State)
{
cpuStateStats.add((State) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type State");
}
}
tempStats = StatsProvider.getInstance(context).getSensorStatList(false, refFrom, refTo);
for (int i = 0; i < tempStats.size(); i++)
{
// make sure to load all data (even the lazy loaded one)
tempStats.get(i).getFqn(UidNameResolver.getInstance(context));
if (tempStats.get(i) instanceof SensorUsage)
{
sensorUsage.add((SensorUsage) tempStats.get(i));
}
else
{
Log.e(TAG, tempStats.get(i).toString() + " is not of type SensorUsage");
}
}
}
catch (Exception e)
{
Log.e(TAG, "An exception occured: " + e.getMessage());
}
}
private String toJson()
{
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
String json = gson.toJson(this);
return json;
}
public String toStringText(Context context, String note)
{
StringWriter out = new StringWriter();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// write header
out.write("===================\n");
out.write("General Information\n");
out.write("===================\n");
out.write("BetterBatteryStats version: " + bbsVersion + "\n");
out.write("Creation Date: " + creationDate + "\n");
out.write("Statistic Type: " + statType + "\n");
out.write("Since " + duration + "\n");
out.write("Note: " + note + "\n");
out.write("VERSION.RELEASE: " + buildVersionRelease + "\n");
out.write("BRAND: " + buildBrand + "\n");
out.write("DEVICE: " + buildDevice + "\n");
out.write("MANUFACTURER: " + buildManufacturer + "\n");
out.write("MODEL: " + buildModel + "\n");
out.write("OS.VERSION: " + osVersion + "\n");
if (Build.VERSION.SDK_INT >= 8)
{
out.write("BOOTLOADER: " + buildBootloader + "\n");
out.write("HARDWARE: " + buildHardware + "\n");
}
out.write("FINGERPRINT: " + buildFingerprint + "\n");
out.write("ID: " + buildId + "\n");
out.write("TAGS: " + buildTags + "\n");
out.write("USER: " + buildUser + "\n");
out.write("PRODUCT: " + buildProduct + "\n");
out.write("RADIO: " + buildRadio + "\n");
out.write("Root perms: " + rootPermissions + "\n");
out.write("SELinux Policy: " + seLinuxPolicy + "\n");
out.write("BATTERY_STATS permission granted: " + batteryStatsPermGranted + "\n");
out.write("XPosed BATTERY_STATS module enabled: " + xposedBatteryStatsEnabled + "\n");
out.write("============\n");
out.write("Battery Info\n");
out.write("============\n");
out.write("Level lost [%]: " + batteryLevelLostText + "\n");
out.write("Voltage lost [mV]: " + batteryVoltageLostText + "\n");
// write timing info
out.write("===========\n");
out.write("Other Usage\n");
out.write("===========\n");
dumpList(context, otherStats, out);
// write wakelock info
out.write("======================================================\n");
out.write("Wakelocks (requires root / system app on Android 4.4+)\n");
out.write("======================================================\n");
dumpList(context, partialWakelockStats, out);
String addendumKwl = "";
if (Wakelocks.isDiscreteKwlPatch())
{
addendumKwl = "!!! Discrete !!!";
}
if (!Wakelocks.fileExists())
{
addendumKwl = " !!! wakeup_sources !!!";
}
boolean alarmsUseAPI = sharedPrefs.getBoolean("force_alarms_api", false);
boolean kwlsUseAPI = sharedPrefs.getBoolean("force_kwl_api", false);
String addendumAlarms = "";
if (alarmsUseAPI)
{
addendumAlarms = "(uses API)";
}
if (kwlsUseAPI)
{
addendumKwl += "(uses API)";
}
// write kernel wakelock info
out.write("================\n");
out.write("Kernel Wakelocks " + addendumKwl + "\n");
out.write("================\n");
dumpList(context, kernelWakelockStats, out);
// write process info
out.write("======================================================\n");
out.write("Processes (requires root / system app on Android 4.4+)\n");
out.write("======================================================\n");
dumpList(context, processStats, out);
// write alarms info
out.write("======================\n");
out.write("Alarms (requires BATTERY_STATS permissions)" + addendumAlarms + "\n");
out.write("======================\n");
dumpList(context, alarmStats, out);
// write alarms info
out.write("======================\n");
out.write("Network (requires root)\n");
out.write("======================\n");
dumpList(context, networkStats, out);
// write cpu states info
out.write("==========\n");
out.write("CPU States\n");
out.write("==========\n");
dumpList(context, cpuStateStats, out);
// write sensor info
out.write("==========\n");
out.write("Sensors\n");
out.write("==========\n");
dumpList(context, sensorUsage, out);
out.write("========\n");
out.write("Services\n");
out.write("========\n");
out.write("Active since: The time when the service was first made active, either by someone starting or binding to it.\n");
out.write("Last activity: The time when there was last activity in the service (either explicit requests to start it or clients binding to it)\n");
out.write("See http://developer.android.com/reference/android/app/ActivityManager.RunningServiceInfo.html\n");
ActivityManager am = (ActivityManager) context
.getSystemService(context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> rs = am
.getRunningServices(50);
for (int i = 0; i < rs.size(); i++)
{
ActivityManager.RunningServiceInfo rsi = rs.get(i);
out.write(rsi.process + " ("
+ rsi.service.getClassName() + ")\n");
out.write(" Active since: "
+ DateUtils.formatDuration(rsi.activeSince)
+ "\n");
out.write(" Last activity: "
+ DateUtils
.formatDuration(rsi.lastActivityTime)
+ "\n");
out.write(" Crash count:" + rsi.crashCount + "\n");
}
// add chapter for reference info
out.write("==================\n");
out.write("Reference overview\n");
out.write("==================\n");
for (int i = 0; i < ReferenceStore.getReferenceNames(null, context).size(); i++)
{
String name = ReferenceStore.getReferenceNames(null, context).get(i);
Reference ref = ReferenceStore.getReferenceByName(name, context);
out.write (name + ": " + ref.whoAmI() + "\n");
}
return out.toString();
}
@SuppressLint("NewApi")
public Uri writeDumpfile(Context context, String note)
{
Uri fileUri = null;
SharedPreferences sharedPrefs = PreferenceManager
.getDefaultSharedPreferences(context);
if (!DataStorage.isExternalStorageWritable())
{
Log.e(TAG, "External storage can not be written");
Toast.makeText(context, context.getString(R.string.message_external_storage_write_error),
Toast.LENGTH_SHORT).show();
}
try
{
// open file for writing
File root;
boolean bSaveToPrivateStorage = sharedPrefs.getBoolean("files_to_private_storage", false);
if (bSaveToPrivateStorage)
{
try
{
root = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
}
catch (Exception e)
{
root = Environment.getExternalStorageDirectory();
}
}
else
{
root = new File(sharedPrefs.getString("storage_path",
Environment.getExternalStorageDirectory().getAbsolutePath()));
}
// check if file can be written
if (root.canWrite())
{
String strFilename = "BetterBatteryStats-"
+ DateUtils.now("yyyy-MM-dd_HHmmssSSS") + ".txt";
File dumpFile = new File(root, strFilename);
fileUri = Uri.fromFile(dumpFile);
FileWriter fw = new FileWriter(dumpFile);
BufferedWriter out = new BufferedWriter(fw);
out.write("/*\n");
out.write(this.toStringText(context, note));
out.write("------ human readable part end here\n");
out.write("*/\n");
out.write(this.toJson());
out.close();
// workaround: force mediascanner to run
DataStorage.forceMediaScanner(context, fileUri);
}
}
catch (Exception e)
{
Log.e(TAG, "Exception: " + e.getMessage());
}
return fileUri;
}
@SuppressLint("NewApi")
public String toStringJson(Context context)
{
return this.toJson();
}
/**
* Dump the elements on one list
*
* @param myList
* a list of StatElement
*/
private void dumpList(Context context, List<StatElement> myList, Writer out)
{
try
{
if (myList != null)
{
for (int i = 0; i < myList.size(); i++)
{
out.write(myList.get(i).getDumpData(UidNameResolver.getInstance(context), totalTime) + "\n");
}
}
}
catch (Exception e)
{
Log.e(TAG, "An error occured serializing list: " + e.getMessage());
}
}
}