package edu.berkeley.cs.amplab.carat.android; import java.net.InetAddress; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; import edu.berkeley.cs.amplab.carat.android.fragments.BugsOrHogsFragment; import edu.berkeley.cs.amplab.carat.android.fragments.SuggestionsFragment; import edu.berkeley.cs.amplab.carat.android.model_classes.MyDeviceData; import edu.berkeley.cs.amplab.carat.android.protocol.CommunicationManager; import edu.berkeley.cs.amplab.carat.android.protocol.SampleSender; import edu.berkeley.cs.amplab.carat.android.sampling.Sampler; import edu.berkeley.cs.amplab.carat.android.sampling.SamplingLibrary; import edu.berkeley.cs.amplab.carat.android.storage.CaratDataStorage; import edu.berkeley.cs.amplab.carat.thrift.Reports; /** * Application class for Carat Android App. Place App-global static constants * and methods here. * * @author Eemil Lagerspetz * */ public class CaratApplication extends Application { private static CaratApplication mInstance; // Used for logging private static final String TAG = "CaratApp"; public static Context mAppContext = null; public static SharedPreferences mPrefs = null; // Used to map importances to human readable strings for sending samples to // the server, and showing them in the process list. private static final SparseArray<String> importanceToString = new SparseArray<String>(); { importanceToString.put(RunningAppProcessInfo.IMPORTANCE_EMPTY, "Not running"); importanceToString.put(RunningAppProcessInfo.IMPORTANCE_BACKGROUND, "Background process"); importanceToString.put(RunningAppProcessInfo.IMPORTANCE_SERVICE, "Service"); importanceToString.put(RunningAppProcessInfo.IMPORTANCE_VISIBLE, "Visible task"); importanceToString.put(RunningAppProcessInfo.IMPORTANCE_FOREGROUND, "Foreground app"); importanceToString.put(Constants.IMPORTANCE_PERCEPTIBLE, "Perceptible task"); importanceToString.put(Constants.IMPORTANCE_SUGGESTION, "Suggestion"); mInstance = this; } public static Context getContext() { return mInstance; } // NOTE: This needs to be initialized before CommunicationManager. public static CaratDataStorage storage = null; // NOTE: The CommunicationManager requires a working instance of // CaratDataStorage. public CommunicationManager commManager = null; // Activity pointers so that all activity UIs can be updated with a callback // to CaratApplication static MainActivity main = null; private static BugsOrHogsFragment bugsActivity = null; private static BugsOrHogsFragment hogsActivity = null; private static SuggestionsFragment actionList = null; // The Sampler samples the battery level when it changes. private static Sampler sampler = null; public static MyDeviceData myDeviceData = new MyDeviceData(); // used to check if Internet is available private static ConnectivityManager mConnectivityManager = null; // Application overrides: @Override public void onLowMemory() { super.onLowMemory(); } @Override public void onTerminate() { super.onTerminate(); } /** * 1. Create CaratDataStorage and read reports from disk. Does not seem too * slow. * * 2. Take a sample in a new thread so that the GUI has fresh data. * * 3. Create CommunicationManager for communicating with the Carat server. * * 4. Communicate with the server to fetch new reports if current ones are * outdated, and to send old stored and the new just-recorded sample. See * MainActivity for this task. */ @Override public void onCreate() { mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); // use a private preference file (created and used only by CaratApp). // don't use PreferenceManager.getDefaultSharedPreferences (it might cause problem in different OS versions). new Thread() { public void run() { mPrefs = CaratApplication.this.getSharedPreferences(Constants.PREFERENCE_FILE_NAME, Context.MODE_PRIVATE); } }.start(); storage = new CaratDataStorage(this); new Thread() { private IntentFilter intentFilter; public void run() { /* * Schedule recurring sampling event: (currently not used) */ /* * SharedPreferences p = PreferenceManager * .getDefaultSharedPreferences(CaratApplication.this); * boolean firstRun = p.getBoolean(PREFERENCE_SAMPLE_FIRST_RUN, true); */ // do this always for now for debugging purposes: // if (firstRun) { // What to start when the event fires (this is unused at the // moment) /* * Intent intent = new Intent(getApplicationContext(), Sampler.class); * intent.setAction(ACTION_CARAT_SAMPLE); * // In reality, you would want to have a static variable for the * // request code instead of 192837 * PendingIntent sender = * PendingIntent.getBroadcast( CaratApplication.this, 192837, * intent, PendingIntent.FLAG_UPDATE_CURRENT); * // Cancel if this has been set up. * // Do not use timer at all any more. * sender.cancel(); */ // Let sampling happen on battery change intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); /* * intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); * intentFilter.addDataScheme("package"); // add addDataScheme */ sampler = Sampler.getInstance(); // Unregister, since Carat may have been started multiple times // since reboot try { unregisterReceiver(sampler); } catch (IllegalArgumentException e) { } registerReceiver(sampler, intentFilter); // register for screen_on and screen-off as well // for the debugging purpose, let's comment out these actions // TODO: re-enable // intentFilter.addAction(Intent.ACTION_SCREEN_ON); // registerReceiver(sampler, intentFilter); // intentFilter.addAction(Intent.ACTION_SCREEN_OFF); // registerReceiver(sampler, intentFilter); } }.start(); new Thread() { public void run() { commManager = new CommunicationManager(CaratApplication.this); } }.start(); super.onCreate(); } // Utility methods /** * Converts <code>importance</code> to a human readable string. * * @param importance * the importance from Android process info. * @return a human readable String describing the importance. */ public static String importanceString(int importance) { String s = importanceToString.get(importance); if (s == null || s.length() == 0) { Log.e("Importance not found:", "" + importance); s = "Unknown"; } return s; } public static MainActivity getMainActivity() { return main; } public static String translatedPriority(String importanceString) { if (main != null) { if (importanceString == null) return main.getString(R.string.priorityDefault); if (importanceString.equals("Not running")) { return main.getString(R.string.prioritynotrunning); } else if (importanceString.equals("Background process")) { return main.getString(R.string.prioritybackground); } else if (importanceString.equals("Service")) { return main.getString(R.string.priorityservice); } else if (importanceString.equals("Visible task")) { return main.getString(R.string.priorityvisible); } else if (importanceString.equals("Foreground app")) { return main.getString(R.string.priorityforeground); } else if (importanceString.equals("Perceptible task")) { return main.getString(R.string.priorityperceptible); } else if (importanceString.equals("Suggestion")) { return main.getString(R.string.prioritysuggestion); } else return main.getString(R.string.priorityDefault); } else return importanceString; } /** * Return a Drawable that contains an app icon for the named app. If not * found, return the Drawable for the Carat icon. * * @param appName * the application name * @return the Drawable for the application's icon */ public static Drawable iconForApp(Context context, String appName) { try { return context.getPackageManager().getApplicationIcon(appName); } catch (NameNotFoundException e) { return context.getResources().getDrawable(R.drawable.ic_launcher); } } /** * Return a human readable application label for the named app. If not * found, return appName. * * @param appName * the application name * @return the human readable application label */ public static String labelForApp(Context context, String appName) { if (appName == null) return "Unknown"; try { ApplicationInfo i = context.getPackageManager().getApplicationInfo(appName, 0); if (i != null) return context.getPackageManager().getApplicationLabel(i).toString(); else return appName; } catch (NameNotFoundException e) { return appName; } } public static int getJscore() { final Reports reports = storage.getReports(); int jscore = 0; if (reports != null) { jscore = ((int) (reports.getJScore() * 100)); } return jscore; } public static void refreshBugs() { if (bugsActivity != null) { main.runOnUiThread(new Runnable() { public void run() { bugsActivity.refresh(); } }); } } public static void refreshHogs() { if (hogsActivity != null) { main.runOnUiThread(new Runnable() { public void run() { hogsActivity.refresh(); } }); } } /** * Return titles from the drawer items array. * @return titles from the drawer items array. */ public static String[] getTitles() { Resources res = getContext().getResources(); return res.getStringArray(R.array.drawer_items); } public static void setActionInProgress() { if (main != null) { main.runOnUiThread(new Runnable() { public void run() { // Updating done main.setTitleUpdating(getTitles()[2]); main.setProgress(0); main.setProgressBarVisibility(true); main.setProgressBarIndeterminateVisibility(true); } }); } } public static void refreshActions() { if (actionList != null) { main.runOnUiThread(new Runnable() { public void run() { actionList.refresh(); } }); } } public static void setActionProgress(final int progress, final String what, final boolean fail) { if (main != null) { main.runOnUiThread(new Runnable() { public void run() { if (fail) main.setTitleUpdatingFailed(what); else main.setTitleUpdating(what); main.setProgress(progress * 100); } }); } } public static void setActionFinished() { if (main != null) { main.runOnUiThread(new Runnable() { public void run() { // Updating done main.setTitleNormal(); main.setProgress(100); main.setProgressBarVisibility(false); main.setProgressBarIndeterminateVisibility(false); } }); } } public static void setMain(MainActivity mainActivity) { main = mainActivity; } public static void setBugs(BugsOrHogsFragment bugsOrHogsFragment) { bugsActivity = bugsOrHogsFragment; } public static void setHogs(BugsOrHogsFragment bugsOrHogsFragment) { hogsActivity = bugsOrHogsFragment; } public static void setActionList(SuggestionsFragment suggestionsFragment) { actionList = suggestionsFragment; } public static String getRegisteredUuid() { return Constants.REGISTERED_UUID; } public void refreshUi() { boolean connecting = false; Context co = getApplicationContext(); // TODO: using a shared preferences object might cause problem in different OS versions. replace with a private one. see MainActivity.AsyncTask.doInBackground(). final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(co); final boolean useWifiOnly = p.getBoolean(Constants.WIFI_ONLY_PREFERENCE_KEY, false); String networkStatus = SamplingLibrary.getNetworkStatus(getApplicationContext()); String networkType = SamplingLibrary.getNetworkType(co); boolean connected = (!useWifiOnly && networkStatus == SamplingLibrary.NETWORKSTATUS_CONNECTED) || networkType.equals("WIFI"); if (connected && commManager != null) { // Show we are updating... CaratApplication.setActionInProgress(); try { commManager.refreshAllReports(); // Log.d(TAG, "Reports refreshed."); } catch (Throwable th) { // Any sort of malformed response, too short string, // etc... Log.w(TAG, "Failed to refresh reports: " + th + Constants.MSG_TRY_AGAIN); th.printStackTrace(); } connecting = false; } else if (networkStatus.equals(SamplingLibrary.NETWORKSTATUS_CONNECTING)) { Log.w(TAG, "Network status: " + networkStatus + ", trying again in 10s."); connecting = true; } // do this regardless setReportData(); CaratApplication.setActionProgress(90, getString(R.string.finishing), false); if (!connecting) CaratApplication.setActionFinished(); if (connecting) { // wait for WiFi to come up try { Thread.sleep(Constants.COMMS_WIFI_WAIT); } catch (InterruptedException e1) { // ignore } connecting = false; // Show we are updating... CaratApplication.setActionInProgress(); try { commManager.refreshAllReports(); // Log.d(TAG, "Reports refreshed."); } catch (Throwable th) { // Any sort of malformed response, too short string, // etc... Log.w(TAG, "Failed to refresh reports: " + th + Constants.MSG_TRY_AGAIN); th.printStackTrace(); } connecting = false; // do this regardless setReportData(); setActionProgress(90, getString(R.string.finishing), false); } CaratApplication.setActionFinished(); SampleSender.sendSamples(CaratApplication.this); CaratApplication.setActionFinished(); } public static boolean isInternetAvailable2() { try { InetAddress ipAddr = InetAddress.getByName("google.com"); //You can replace it with your name if (ipAddr.equals("")) { return false; } else { return true; } } catch (Exception e) { return false; } } /** * Checks whether WiFi or mobile data is enabled * @return true of false */ public static boolean isInternetAvailable() { boolean haveConnectedWifi = false; boolean haveConnectedMobile = false; NetworkInfo[] netInfo = mConnectivityManager.getAllNetworkInfo(); for (NetworkInfo ni : netInfo) { if (ni.getTypeName().equalsIgnoreCase("WIFI")) if (ni.isConnected()) haveConnectedWifi = true; if (ni.getTypeName().equalsIgnoreCase("MOBILE")) if (ni.isConnected()) haveConnectedMobile = true; } return haveConnectedWifi || haveConnectedMobile; } public static void setReportData() { final Reports r = storage.getReports(); Log.d(TAG, "Got reports."); long freshness = CaratApplication.storage.getFreshness(); long l = System.currentTimeMillis() - freshness; final long h = l / 3600000; final long min = (l - h * 3600000) / 60000; double bl = 0; double error = 0; if (r != null) { Log.d(TAG, "r (reports) not null."); // Try exact battery life if (r.jScoreWith != null) { // Log.d(TAG, "jscoreWith not null."); double exp = r.jScoreWith.expectedValue; if (exp > 0.0) { bl = 100 / exp; error = 100 / (exp + r.jScoreWith.error); } else if (r.getModel() != null) { exp = r.getModel().expectedValue; Log.d(TAG, "Model expected value: " + exp); if (exp > 0.0) { bl = 100 / exp; error = 100 / (exp + r.getModel().error); } } // If not possible, try model battery life } } // Only take the error part error = bl - error; int blh = (int) (bl / 3600); bl -= blh * 3600; int blmin = (int) (bl / 60); int errorH = 0; int errorMin = 0; if (error > 7200) { errorH = (int) (error / 3600); error -= errorH * 3600; } errorMin = (int) (error / 60); final String blS = blh + "h " + blmin + "m \u00B1 " + (errorH > 0 ? errorH + "h " : "") + errorMin + " m"; /* * we removed direct manipulation of MyDevice fragment, * and moved the data pertaining to this fragment to a class field, called myDeviceData. * In the onResume() method of MyDeviceFragment, we fetch this data and show (see setViewData()) * The reason for this movement is that we migrated from tabs to fragments. * We cannot change a fragment's view while it's not in the foreground * (fragments get replaced by a fragment transaction: * the parent activity which hosts a frame-layout * (a placeholder for fragment's layout), replaces the frame-layout with * the new fragment's layout) */ SharedPreferences p = PreferenceManager .getDefaultSharedPreferences(getMainActivity()); String caratId = p.getString(Constants.REGISTERED_UUID, "0"); myDeviceData.setAllFields(freshness, h, min, caratId, blS); } }