content: 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);
  	}		
  }
  