/* (C) 2012 Pragmatic Software This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/ */ package com.googlecode.networklog; import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ApplicationInfo; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.widget.ImageView; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.List; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; public class ApplicationsTracker { public static ArrayList<AppEntry> installedApps; public static HashMap<String, AppEntry> uidMap; public static HashMap<String, AppEntry> packageMap; public static HashMap<String, Drawable> iconMap; public static ProgressDialog dialog; public static int appCount; public static Object dialogLock = new Object(); public static Object installedAppsLock = new Object(); public static PackageManager pm = null; public static Drawable loading_icon = null; public static class AppEntry { String name; String nameLowerCase; String packageName; int uid; String uidString; public String toString() { return "(" + uidString + ") " + name; } } public static class AppCache { public HashMap<String, String> labelCache; private Context context; private boolean isDirty = false; private AppCache() {} public AppCache(Context context) { this.context = context; loadCache(); } public String getLabel(PackageManager pm, ApplicationInfo app) { String label = labelCache.get(app.packageName); if(label == null) { label = StringPool.get(pm.getApplicationLabel(app).toString()); labelCache.put(app.packageName, label); isDirty = true; } return label; } public void loadCache() { try { File file = new File(context.getDir("data", Context.MODE_PRIVATE), "applabels.cache"); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file)); labelCache = (HashMap<String, String>) inputStream.readObject(); isDirty = false; inputStream.close(); } catch (Exception e) { Log.w("NetworkLog", "Exception loading app cache", e); labelCache = new HashMap<String, String>(); } } public void saveCache() { if(isDirty == false) { return; } try { File file = new File(context.getDir("data", Context.MODE_PRIVATE), "applabels.cache"); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file)); outputStream.writeObject(labelCache); outputStream.flush(); outputStream.close(); } catch (Exception e) { Log.w("NetworkLog", "Exception saving app cache" , e); } } } public static void restoreData(RetainInstanceData data) { synchronized(installedAppsLock) { installedApps = data.applicationsTrackerInstalledApps; } uidMap = data.applicationsTrackerUidMap; packageMap = data.applicationsTrackerPackageMap; } public static Drawable loadIcon(final Context context, final ImageView view, final String packageName) { if(loading_icon == null) { loading_icon = context.getResources().getDrawable(R.drawable.loading_icon); } AppEntry item = packageMap.get(packageName); if(item == null) { Log.w("NetworkLog", "Failed to find icon item for " + packageName); return loading_icon; } Drawable cached_icon = iconMap.get(packageName); if(cached_icon != null) { return cached_icon; } if(MyLog.enabled && MyLog.level >= 3) { MyLog.d(3, "Loading icon for " + item); } new Thread(new Runnable() { public void run() { try { if(pm == null) { pm = context.getPackageManager(); } final Drawable drawable = pm.getApplicationIcon(packageName); iconMap.put(packageName, drawable); /* Ensure that view hasn't been recycled for another package */ String tag = (String)view.getTag(); if(tag != null && tag.equals(packageName)) { /* Ugh, we have to do it this way instead of using setDrawableByLayerId() since 2.x doesn't support it very well */ NetworkLog.handler.post(new Runnable() { public void run() { TransitionDrawable td = new TransitionDrawable(new Drawable[] { loading_icon, drawable }); td.setCrossFadeEnabled(true); view.setImageDrawable(td); td.startTransition(750); } }); } } catch(Exception e) { // ignored } } }, "LoadIcon:" + packageName).start(); return loading_icon; } public static class PackageIntentReceiver extends BroadcastReceiver { final Context context; public PackageIntentReceiver(Context context) { this.context = context; // Register for events related to package installation. IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); context.registerReceiver(this, filter); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); context.registerReceiver(this, sdFilter); } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d("NetworkLog", "Received package broadcast: " + action); if(Intent.ACTION_PACKAGE_ADDED.equals(action)) { Uri uri = intent.getData(); String packageName = uri != null ? uri.getSchemeSpecificPart() : null; Bundle b = intent.getExtras(); int uid = b.getInt(Intent.EXTRA_UID); boolean replacing = b.getBoolean(Intent.EXTRA_REPLACING); Log.d("NetworkLog", "Package added: " + packageName + " " + uid + "; replacing: " + replacing); addApp(context, packageName); } if(Intent.ACTION_PACKAGE_REMOVED.equals(action)) { Uri uri = intent.getData(); String packageName = uri != null ? uri.getSchemeSpecificPart() : null; Bundle b = intent.getExtras(); int uid = b.getInt(Intent.EXTRA_UID); boolean replacing = b.getBoolean(Intent.EXTRA_REPLACING); Log.d("NetworkLog", "Package removed: " + packageName + " " + uid + "; replacing: " + replacing); if(replacing == false) { removeApp(packageName); } } if(Intent.ACTION_PACKAGE_CHANGED.equals(action)) { Uri uri = intent.getData(); String packageName = uri != null ? uri.getSchemeSpecificPart() : null; Bundle b = intent.getExtras(); int uid = b.getInt(Intent.EXTRA_UID); Log.d("NetworkLog", "Package changed: " + packageName + " " + uid); } } } public static PackageIntentReceiver packageIntentReceiver = null; public static void startWatchingPackages(Context context) { stopWatchingPackages(); Log.d("NetworkLog", "Now listening for package broadcasts"); packageIntentReceiver = new PackageIntentReceiver(context); } public static void stopWatchingPackages() { if(packageIntentReceiver != null) { try { Log.d("NetworkLog", "Stopped listening for package broadcasts"); packageIntentReceiver.context.unregisterReceiver(packageIntentReceiver); packageIntentReceiver = null; } catch (Exception e) { Log.d("NetworkLog", "Caught exception while unregistering package receiver: ", e); } } } public static void removeApp(String packageName) { AppEntry app; for(Iterator<AppEntry> iterator = installedApps.iterator(); iterator.hasNext();) { app = iterator.next(); if(app.packageName.equals(packageName)) { iterator.remove(); break; } } for(Iterator<AppEntry> iterator = uidMap.values().iterator(); iterator.hasNext();) { app = iterator.next(); if(app.packageName.equals(packageName)) { iterator.remove(); break; } } packageMap.remove(packageName); iconMap.remove(packageName); if(NetworkLog.logFragment != null) { NetworkLog.logFragment.removeApp(packageName); } if(NetworkLog.appFragment != null) { NetworkLog.appFragment.removeApp(packageName); } } public static void addApp(Context context, String packageName) { boolean newApp = false; if(pm == null) { pm = context.getPackageManager(); } try { ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0); AppEntry app = packageMap.get(packageName); if(app == null) { app = new AppEntry(); newApp = true; } app.name = StringPool.get(pm.getApplicationLabel(appInfo).toString()); app.nameLowerCase = StringPool.get(StringPool.getLowerCase(app.name)); app.uid = appInfo.uid; app.uidString = StringPool.get(String.valueOf(app.uid)); app.packageName = StringPool.get(appInfo.packageName); if(newApp) { installedApps.add(app); packageMap.put(app.packageName, app); uidMap.put(app.uidString, app); } if(newApp && NetworkLog.appFragment != null) { NetworkLog.appFragment.addApp(app); } } catch (PackageManager.NameNotFoundException nfe) { Log.e("NetworkLog", "AppTracker addApp NameNotFoundException caught:", nfe); } } public static void getInstalledApps(final Context context, final Handler handler) { MyLog.d("[LoadApps] Loading installed apps"); startWatchingPackages(context); synchronized(installedAppsLock) { if(NetworkLog.data == null) { installedApps = new ArrayList<AppEntry>(); uidMap = new HashMap<String, AppEntry>(); packageMap = new HashMap<String, AppEntry>(); iconMap = new HashMap<String, Drawable>(); } else { restoreData(NetworkLog.data); installedApps.clear(); uidMap.clear(); packageMap.clear(); iconMap.clear(); } if(pm == null) { pm = context.getPackageManager(); } List<ApplicationInfo> apps = pm.getInstalledApplications(0); appCount = apps.size(); if(handler != null) { handler.post(new Runnable() { public void run() { MyLog.d("[LoadApps] Showing progress dialog; size: " + appCount); synchronized(dialogLock) { dialog = new ProgressDialog(context); dialog.setIndeterminate(false); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setMax(appCount); dialog.setCancelable(false); dialog.setTitle(""); dialog.setMessage(context.getResources().getString(R.string.loading_apps)); dialog.show(); } } }); } int count = 0; AppCache appCache = new AppCache(context); for(final ApplicationInfo app : apps) { MyLog.d("Processing app " + app); if(NetworkLog.initRunner != null && NetworkLog.initRunner.running == false) { MyLog.d("[LoadApps] Initialization aborted"); return; } final int progress = ++count; if(handler != null) { handler.post(new Runnable() { public void run() { synchronized(dialogLock) { if(dialog != null) { dialog.setProgress(progress); } } } }); } int uid = app.uid; String uidString = Integer.toString(uid); AppEntry entryHash = uidMap.get(uidString); AppEntry entry = new AppEntry(); entry.name = appCache.getLabel(pm, app); entry.nameLowerCase = StringPool.get(StringPool.getLowerCase(entry.name)); entry.uid = uid; entry.uidString = StringPool.get(String.valueOf(uid)); entry.packageName = StringPool.get(app.packageName); installedApps.add(entry); packageMap.put(entry.packageName, entry); if(entryHash != null) { entryHash.name.concat("; " + entry.name); } else { uidMap.put(uidString, entry); } } appCache.saveCache(); AppEntry entry = new AppEntry(); entry.name = StringPool.get("Kernel"); entry.nameLowerCase = StringPool.getLowerCase("Kernel"); entry.packageName = StringPool.get(entry.nameLowerCase); entry.uid = -1; entry.uidString = StringPool.get("-1"); iconMap.put(entry.packageName, context.getResources().getDrawable(R.drawable.linux_icon)); installedApps.add(entry); uidMap.put("-1", entry); packageMap.put(entry.packageName, entry); String[] systemUids = { "root", "system", "radio", "bluetooth", "nobody", "misc", "graphics", "input", "audio", "camera", "log", "compass", "mount", "wifi", "dhcp", "adb", "install", "media", "nfc", "shell", "cache", "diag", "vpn", "keystore", "usb", "gps", "inet", "net_raw", "net_admin", "net_bt_admin", "net_bt", /* motorola */ "mot_accy", "mot_pwric", "mot_usb", "mot_drm", "mot_tcmd", "mot_sec_rtc", "mot_tombstone", "mot_tpapi", "mot_secclkd" }; for (String name : systemUids) { int uid = android.os.Process.getUidForName(name); if(uid == -1) { continue; } String uidString = StringPool.get(String.valueOf(uid)); AppEntry entryHash = uidMap.get(uidString); if(entryHash == null) { entry = new AppEntry(); entry.name = StringPool.get(name); entry.nameLowerCase = StringPool.getLowerCase(name); entry.packageName = StringPool.get(entry.nameLowerCase); entry.uid = uid; entry.uidString = StringPool.get(uidString); iconMap.put(entry.packageName, context.getResources().getDrawable(R.drawable.android_icon)); installedApps.add(entry); uidMap.put(uidString, entry); packageMap.put(entry.packageName, entry); } } if(handler != null) { handler.post(new Runnable() { public void run() { MyLog.d("[LoadApps] Dismissing dialog"); synchronized(dialogLock) { if(dialog != null) { dialog.dismiss(); dialog = null; } } } }); } MyLog.d("[LoadApps] Done getting installed apps"); } } }