/* 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.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import android.app.ActivityManager; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.Process; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; public class SystemInfo { private static final String TAG = "SystemInfo"; private static SystemInfo instance = new SystemInfo(); public static SystemInfo getInstance() { return instance; } /* Uids as listed in android_filesystem_config.h */ public static final int AID_ALL = -1; /* A special constant we will * use to indicate a request * for global information. */ public static final int AID_ROOT = 0; /* traditional unix root user */ public static final int AID_SYSTEM = 1000; /* system server */ public static final int AID_RADIO = 1001; /* telephony subsystem, RIL */ public static final int AID_BLUETOOTH = 1002; /* bluetooth subsystem */ public static final int AID_GRAPHICS = 1003; /* graphics devices */ public static final int AID_INPUT = 1004; /* input devices */ public static final int AID_AUDIO = 1005; /* audio devices */ public static final int AID_CAMERA = 1006; /* camera devices */ public static final int AID_LOG = 1007; /* log devices */ public static final int AID_COMPASS = 1008; /* compass device */ public static final int AID_MOUNT = 1009; /* mountd socket */ public static final int AID_WIFI = 1010; /* wifi subsystem */ public static final int AID_ADB = 1011; /* android debug bridge (adbd) */ public static final int AID_INSTALL = 1012; /* group for installing packages */ public static final int AID_MEDIA = 1013; /* mediaserver process */ public static final int AID_DHCP = 1014; /* dhcp client */ public static final int AID_SHELL = 2000; /* adb and debug shell user */ public static final int AID_CACHE = 2001; /* cache access */ public static final int AID_DIAG = 2002; /* access to diagnostic resources */ /* The 3000 series are intended for use as supplemental group id's only. * They indicate special Android capabilities that the kernel is aware of. */ public static final int AID_NET_BT_ADMIN= 3001; /* bluetooth: create any socket */ public static final int AID_NET_BT = 3002; /* bluetooth: create sco, rfcomm or l2cap sockets */ public static final int AID_INET = 3003; /* can create AF_INET and AF_INET6 sockets */ public static final int AID_NET_RAW = 3004; /* can create raw INET sockets */ public static final int AID_MISC = 9998; /* access to misc storage */ public static final int AID_NOBODY = 9999; public static final int AID_APP =10000; /* first app user */ /* These are stolen from Process.java which hides these constants. */ public static final int PROC_SPACE_TERM = (int)' '; public static final int PROC_TAB_TERM = (int)'\t'; public static final int PROC_LINE_TERM = (int)'\n'; public static final int PROC_COMBINE = 0x100; public static final int PROC_OUT_LONG = 0x2000; private static final int[] READ_LONG_FORMAT = new int[] { PROC_SPACE_TERM|PROC_OUT_LONG }; private static final int[] PROCESS_STATS_FORMAT = new int[] { PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM, PROC_SPACE_TERM|PROC_OUT_LONG, // 13: utime PROC_SPACE_TERM|PROC_OUT_LONG // 14: stime }; private static final int[] PROCESS_TOTAL_STATS_FORMAT = new int[] { PROC_SPACE_TERM, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_SPACE_TERM|PROC_OUT_LONG, }; private static final int[] PROC_MEMINFO_FORMAT = new int[] { PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM, PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM, PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM, PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM, }; public static final int INDEX_USER_TIME = 0; public static final int INDEX_SYS_TIME = 1; public static final int INDEX_TOTAL_TIME = 2; public static final int INDEX_MEM_TOTAL = 0; public static final int INDEX_MEM_FREE = 1; public static final int INDEX_MEM_BUFFERS = 2; public static final int INDEX_MEM_CACHED = 3; /* We are going to take advantage of the hidden API within Process.java that * makes use of JNI so that we can perform the top task efficiently. */ private Field fieldUid; private Method methodGetUidForPid; private Method methodGetPids; private Method methodReadProcFile; private Method methodGetProperty; private long[] readBuf; @SuppressWarnings("unchecked") private SystemInfo() { try { fieldUid = ActivityManager.RunningAppProcessInfo.class.getField("uid"); } catch(NoSuchFieldException e) { /* API level 3 doesn't have this field unfortunately. */ } try { methodGetUidForPid = Process.class.getMethod("getUidForPid", int.class); } catch(NoSuchMethodException e) { Log.w(TAG, "Could not access getUidForPid method"); } try { methodGetPids = Process.class.getMethod("getPids", String.class, int[].class); } catch(NoSuchMethodException e) { Log.w(TAG, "Could not access getPids method"); } try { methodReadProcFile = Process.class.getMethod("readProcFile", String.class, int[].class, String[].class, long[].class, float[].class); } catch(NoSuchMethodException e) { Log.w(TAG, "Could not access readProcFile method"); } try { Class classSystemProperties = Class.forName("android.os.SystemProperties"); methodGetProperty = classSystemProperties.getMethod("get", String.class); } catch(NoSuchMethodException e) { Log.w(TAG, "Could not access SystemProperties.get"); } catch(ClassNotFoundException e) { Log.w(TAG, "Could not find class android.os.SystemProperties"); } readBuf = new long[1]; } public int getUidForPid(int pid) { if(methodGetUidForPid != null) try { return (Integer)methodGetUidForPid.invoke(null, pid); } catch(InvocationTargetException e) { Log.w(TAG, "Call to getUidForPid failed"); } catch(IllegalAccessException e) { Log.w(TAG, "Call to getUidForPid failed"); } else try { BufferedReader rdr = new BufferedReader(new InputStreamReader( new FileInputStream("/proc/" + pid + "/status")), 256); for(String line = rdr.readLine(); line != null; line = rdr.readLine()) { if(line.startsWith("Uid:")) { String tokens[] = line.substring(4).split("[ \t]+"); String realUidToken = tokens[tokens[0].length() == 0 ? 1 : 0]; try { return Integer.parseInt(realUidToken); } catch(NumberFormatException e) { return -1; } } } } catch(IOException e) { Log.w(TAG, "Failed to manually read in process uid"); } return -1; } public int getUidForProcessInfo( ActivityManager.RunningAppProcessInfo app) { /* Try to access the uid field first if it is avaialble. Otherwise just * convert the pid to a uid. */ if(fieldUid != null) try { return (Integer)fieldUid.get(app); } catch(IllegalAccessException e) { } return getUidForPid(app.pid); } /* lastPids can be null. It is just used to avoid memory reallocation if * at all possible. Returns null on failure. If lastPids can hold the new * pid list the extra entries will be filled with -1 at the end. */ public int[] getPids(int[] lastPids) { if(methodGetPids == null) return manualGetInts("/proc", lastPids); try { return (int[])methodGetPids.invoke(null, "/proc", lastPids); } catch(IllegalAccessException e) { Log.w(TAG, "Failed to get process cpu usage"); } catch(InvocationTargetException e) { Log.w(TAG, "Exception thrown while getting cpu usage"); } return null; } /* Gets a property on Android accessible through getprop. */ public String getProperty(String property) { if(methodGetProperty == null) return null; try { return (String)methodGetProperty.invoke(null, property); } catch(IllegalAccessException e) { Log.w(TAG, "Failed to get property"); } catch(InvocationTargetException e) { Log.w(TAG, "Exception thrown while getting property"); } return null; } /* lastUids can be null. It is just used to avoid memory reallocation if * at all possible. Returns null on failure. If lastUids can hold the new * uid list the extra entries will be filled with -1 at the end. */ public int[] getUids(int[] lastUids) { if(methodGetPids == null) return manualGetInts("/proc/uid_stat", lastUids); try { return (int[])methodGetPids.invoke(null, "/proc/uid_stat", lastUids); } catch(IllegalAccessException e) { Log.w(TAG, "Failed to get process cpu usage"); } catch(InvocationTargetException e) { Log.w(TAG, "Exception thrown while getting cpu usage"); } return null; } private int[] manualGetInts(String dir, int[] lastInts) { File[] files = new File(dir).listFiles(); int sz = files == null ? 0 : files.length; if(lastInts == null || lastInts.length < sz) { lastInts = new int[sz]; } else if(2 * sz < lastInts.length) { lastInts = new int[sz]; } int pos = 0; for(int i = 0; i < sz; i++) { try { int v = Integer.parseInt(files[i].getName()); lastInts[pos++] = v; } catch(NumberFormatException e) { } } while(pos < lastInts.length) lastInts[pos++] = -1; return lastInts; } /* times should contain two elements. times[INDEX_USER_TIME] will be filled * with the user time for this pid and times[INDEX_SYS_TIME] will be filled * with the sys time for this pid. Returns true on sucess. */ public boolean getPidUsrSysTime(int pid, long[] times) { if(methodReadProcFile == null) return false; try { return (Boolean)methodReadProcFile.invoke( null, "/proc/" + pid + "/stat", PROCESS_STATS_FORMAT, null, times, null); } catch(IllegalAccessException e) { Log.w(TAG, "Failed to get pid cpu usage"); } catch(InvocationTargetException e) { Log.w(TAG, "Exception thrown while getting pid cpu usage"); } return false; } /* times should contain seven elements. times[INDEX_USER_TIME] will be filled * with the total user time, times[INDEX_SYS_TIME] will be filled * with the total sys time, and times[INDEX_TOTAL_TIME] will have the total * time (including idle cycles). Returns true on success. */ public boolean getUsrSysTotalTime(long[] times) { if(methodReadProcFile == null) return false; try { if((Boolean)methodReadProcFile.invoke( null, "/proc/stat", PROCESS_TOTAL_STATS_FORMAT, null, times, null)) { long usr = times[0] + times[1]; long sys = times[2] + times[5] + times[6]; long total = usr + sys + times[3] + times[4]; times[INDEX_USER_TIME] = usr; times[INDEX_SYS_TIME] = sys; times[INDEX_TOTAL_TIME] = total; return true; } } catch(IllegalAccessException e) { Log.w(TAG, "Failed to get total cpu usage"); } catch(InvocationTargetException e) { Log.w(TAG, "Exception thrown while getting total cpu usage"); } return false; } /* mem should contain 4 elements. mem[INDEX_MEM_TOTAL] will contain total * memory available in kb, mem[INDEX_MEM_FREE] will give the amount of free * memory in kb, mem[INDEX_MEM_BUFFERS] will give the size of kernel buffers * in kb, and mem[INDEX_MEM_CACHED] will give the size of kernel caches in kb. * Returns true on success. */ public boolean getMemInfo(long[] mem) { if(methodReadProcFile == null) return false; try { if((Boolean)methodReadProcFile.invoke( null, "/proc/meminfo", PROC_MEMINFO_FORMAT, null, mem, null)) { return true; } } catch(IllegalAccessException e) { Log.w(TAG, "Failed to get mem info"); } catch(InvocationTargetException e) { Log.w(TAG, "Exception thrown while getting mem info"); } return false; } /* Returns -1 on failure. */ public long readLongFromFile(String file) { if(methodReadProcFile == null) return -1; try { if((Boolean)methodReadProcFile.invoke( null, file, READ_LONG_FORMAT, null, readBuf, null)) { return readBuf[0]; } } catch(IllegalAccessException e) { Log.w(TAG, "Failed to get pid cpu usage"); } catch(InvocationTargetException e) { Log.w(TAG, "Exception thrown while getting pid cpu usage"); } return -1L; } SparseArray<UidCacheEntry> uidCache = new SparseArray<UidCacheEntry>(); public synchronized String getAppId(int uid, PackageManager pm) { UidCacheEntry cacheEntry = uidCache.get(uid); if(cacheEntry == null) { cacheEntry = new UidCacheEntry(); uidCache.put(uid, cacheEntry); } cacheEntry.clearIfExpired(); if(cacheEntry.getAppId() != null) { return cacheEntry.getAppId(); } String result = getAppIdNoCache(uid, pm); cacheEntry.setAppId(result); return result; } private String getAppIdNoCache(int uid, PackageManager pm) { if(uid < SystemInfo.AID_APP) { Log.e(TAG, "Only pass application uids to getAppId"); return null; } int versionCode = -1; String[] packages = pm.getPackagesForUid(uid); if(packages != null) for(String packageName : packages) { try { PackageInfo info = pm.getPackageInfo(packageName, 0); versionCode = info.versionCode; } catch(PackageManager.NameNotFoundException e) { } } String name = pm.getNameForUid(uid); name = name == null ? "none" : name; return pm.getNameForUid(uid) + "@" + versionCode; } public synchronized String getUidName(int uid, PackageManager pm) { UidCacheEntry cacheEntry = uidCache.get(uid); if(cacheEntry == null) { cacheEntry = new UidCacheEntry(); uidCache.put(uid, cacheEntry); } cacheEntry.clearIfExpired(); if(cacheEntry.getName() != null) { return cacheEntry.getName(); } String result = getUidNameNoCache(uid, pm); cacheEntry.setName(result); return result; } private String getUidNameNoCache(int uid, PackageManager pm) { switch(uid) { case AID_ROOT: return "Kernel"; case AID_SYSTEM: return "System"; case AID_RADIO: return "Radio Subsystem"; case AID_BLUETOOTH: return "Bluetooth Subsystem"; case AID_GRAPHICS: return "Graphics Devices"; case AID_INPUT: return "Input Devices"; case AID_AUDIO: return "Audio Devices"; case AID_CAMERA: return "Camera Devices"; case AID_LOG: return "Log Devices"; case AID_COMPASS: return "Compass Device (e.g. akmd)"; case AID_MOUNT: return "Mount"; case AID_WIFI: return "Wifi Subsystem"; case AID_ADB: return "Android Debug Bridge"; case AID_INSTALL: return "Install"; case AID_MEDIA: return "Media Server"; case AID_DHCP: return "DHCP Client"; case AID_SHELL: return "Debug Shell"; case AID_CACHE: return "Cache Access"; case AID_DIAG: return "Diagnostics"; } if(uid < AID_APP) { return "sys_" + uid; } String[] packages = pm.getPackagesForUid(uid); if(packages != null) for(String packageName : packages) { try { PackageInfo info = pm.getPackageInfo(packageName, 0); CharSequence label = info.applicationInfo.loadLabel(pm); if(label != null) { return label.toString(); } } catch(PackageManager.NameNotFoundException e) { } } String uidName = pm.getNameForUid(uid); if(uidName != null) { return uidName; } return "app_" + uid; } public synchronized Drawable getUidIcon(int uid, PackageManager pm) { UidCacheEntry cacheEntry = uidCache.get(uid); if(cacheEntry == null) { cacheEntry = new UidCacheEntry(); uidCache.put(uid, cacheEntry); } cacheEntry.clearIfExpired(); if(cacheEntry.getIcon() != null) { return cacheEntry.getIcon(); } Drawable result = getUidIconNoCache(uid, pm); cacheEntry.setIcon(result); return result; } public Drawable getUidIconNoCache(int uid, PackageManager pm) { String[] packages = pm.getPackagesForUid(uid); if(packages != null) for (int i = 0; i < packages.length; i++) { try { ApplicationInfo ai = pm.getApplicationInfo(packages[i], 0); if(ai.icon != 0) { return ai.loadIcon(pm); } } catch(PackageManager.NameNotFoundException e) { } } return pm.getDefaultActivityIcon(); } public synchronized void voidUidCache(int uid) { uidCache.remove(uid); } private static class UidCacheEntry { private static long EXPIRATION_TIME = 1000 * 60 * 10; // 10 minutes private String appId; private String name; private Drawable icon; private long updateTime; public UidCacheEntry() { updateTime = -1; } public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getName() { return name; } public void setName(String name) { this.name = name; if(updateTime == -1) { updateTime = SystemClock.elapsedRealtime(); } } public Drawable getIcon() { return icon; } public void setIcon(Drawable icon) { this.icon = icon; if(updateTime == -1) { updateTime = SystemClock.elapsedRealtime(); } } public void clearIfExpired() { if(updateTime != -1 && updateTime + EXPIRATION_TIME < SystemClock.elapsedRealtime()) { updateTime = -1; name = null; icon = null; } } } }