content: package edu.berkeley.cs.amplab.carat.android.protocol;
  
  import java.io.BufferedReader;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.net.URL;
  import java.net.URLConnection;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.List;
  
  import org.apache.thrift.TException;
  import org.apache.thrift.protocol.TProtocol;
  import org.apache.thrift.transport.TTransport;
  
  import com.flurry.android.FlurryAgent;
  
  import android.content.SharedPreferences;
  import android.preference.PreferenceManager;
  import android.util.Log;
  import edu.berkeley.cs.amplab.carat.android.CaratApplication;
  import edu.berkeley.cs.amplab.carat.android.Constants;
  import edu.berkeley.cs.amplab.carat.android.R;
  import edu.berkeley.cs.amplab.carat.android.sampling.SamplingLibrary;
  import edu.berkeley.cs.amplab.carat.thrift.CaratService;
  import edu.berkeley.cs.amplab.carat.thrift.Feature;
  import edu.berkeley.cs.amplab.carat.thrift.HogBugReport;
  import edu.berkeley.cs.amplab.carat.thrift.ProcessInfo;
  import edu.berkeley.cs.amplab.carat.thrift.Registration;
  import edu.berkeley.cs.amplab.carat.thrift.Reports;
  import edu.berkeley.cs.amplab.carat.thrift.Sample;
  
  public class CommunicationManager {
  
  	private static final String TAG = "CommsManager";
  	private static final String DAEMONS_URL = "http://carat.cs.helsinki.fi/daemons.txt";
  	private static final String QUESTIONNAIRE_URL = "http://www.cs.helsinki.fi/u/lagerspe/caratapp/questionnaire-url.txt";
  
  	private CaratApplication a = null;
  
  	private boolean registered = false;
  	private boolean register = true;
  	private boolean newuuid = false;
  	private boolean timeBasedUuid = false;
  	private SharedPreferences p = null;
  
  	public CommunicationManager(CaratApplication a) {
  		this.a = a;
  		p = PreferenceManager.getDefaultSharedPreferences(this.a);
  
  		/*
  		 * Either: 1. Never registered -> register 2. registered, but no stored
  		 * uuid -> register 3. registered, with stored uuid, but uuid, model or
  		 * os are different -> register 4. registered, all fields equal to
  		 * stored -> do not register
  		 */
  		timeBasedUuid = p.getBoolean(Constants.PREFERENCE_TIME_BASED_UUID, false);
  		newuuid = p.getBoolean(Constants.PREFERENCE_NEW_UUID, false);
  		registered = !p.getBoolean(Constants.PREFERENCE_FIRST_RUN, true);
  		register = !registered;
  		String storedUuid = p.getString(CaratApplication.getRegisteredUuid(), null);
  		if (!register) {
  			if (storedUuid == null)
  				register = true;
  			else {
  				String storedOs = p.getString(Constants.REGISTERED_OS, null);
  				String storedModel = p.getString(Constants.REGISTERED_MODEL, null);
  
  				String uuid = storedUuid;
  
  				String os = SamplingLibrary.getOsVersion();
  				String model = SamplingLibrary.getModel();
  
  				if (storedUuid == null || os == null || model == null || storedModel == null || storedOs == null
  						|| uuid == null || !(storedOs.equals(os) && storedModel.equals(model))) {
  					// need to re-reg
  					register = true;
  				} else
  					register = false;
  			}
  		}
  	}
  
  	private void registerMe(CaratService.Client instance, String uuId, String os, String model) throws TException {
  		if (uuId == null || os == null || model == null) {
  			Log.e("registerMe", "Null uuId, os, or model given to registerMe!");
  			System.exit(1);
  			return;
  		}
  		Registration registration = new Registration(uuId);
  		registration.setPlatformId(model);
  		registration.setSystemVersion(os);
  		registration.setTimestamp(System.currentTimeMillis() / 1000.0);
  		registration.setKernelVersion(SamplingLibrary.getKernelVersion());
  		registration.setSystemDistribution(SamplingLibrary.getManufacturer() + ";" + SamplingLibrary.getBrand());
  		FlurryAgent.logEvent("Registering " + uuId + "," + model + "," + os);
  		instance.registerMe(registration);
  	}
  
  	public int uploadSamples(Collection<Sample> samples) {
  		CaratService.Client instance = null;
  		int succeeded = 0;
  		ArrayList<Sample> samplesLeft = new ArrayList<Sample>();
  		registerLocal();
  		try {
  			instance = ProtocolClient.open(a.getApplicationContext());
  			registerOnFirstRun(instance);
  
  			for (Sample s : samples) {
  				boolean success = false;
  				try {
  					success = instance.uploadSample(s);
  				} catch (Throwable th) {
  					Log.e(TAG, "Error uploading sample.", th);
  				}
  				if (success)
  					succeeded++;
  				else
  					samplesLeft.add(s);
  			}
  
  			safeClose(instance);
  		} catch (Throwable th) {
  			Log.e(TAG, "Error refreshing main reports.", th);
  			safeClose(instance);
  		}
  		// Do not try again. It can cause a massive sample attack on the server.
  		return succeeded;
  	}
  
  	private void registerLocal() {
  		if (register) {
  			String uuId = p.getString(CaratApplication.getRegisteredUuid(), null);
  			if (uuId == null) {
  				if (registered && (!newuuid && !timeBasedUuid)) {
  					uuId = SamplingLibrary.getAndroidId(a);
  				} else if (registered && !timeBasedUuid) {
  					// "new" uuid
  					uuId = SamplingLibrary.getUuid(a);
  				} else {
  					// Time-based ID scheme
  					uuId = SamplingLibrary.getTimeBasedUuid(a);
  					Log.d("CommunicationManager", "Generated a new time-based UUID: " + uuId);
  					// This needs to be saved now, so that if server
  					// communication
  					// fails we have a stable UUID.
  					p.edit().putString(CaratApplication.getRegisteredUuid(), uuId).commit();
  					p.edit().putBoolean(Constants.PREFERENCE_TIME_BASED_UUID, true).commit();
  					timeBasedUuid = true;
  				}
  			}
  		}
  	}
  
  	private void registerOnFirstRun(CaratService.Client instance) {
  		if (register) {
  			String uuId = p.getString(CaratApplication.getRegisteredUuid(), null);
  			// Only use new uuid if reg'd after this version for the first time.
  			if (registered && (!newuuid && !timeBasedUuid)) {
  				uuId = SamplingLibrary.getAndroidId(a);
  			} else if (registered && !timeBasedUuid) {
  				// "new" uuid
  				uuId = SamplingLibrary.getUuid(a);
  			} else {
  				// Time-based ID scheme
  				if (uuId == null)
  					uuId = SamplingLibrary.getTimeBasedUuid(a);
  				Log.d("CommunicationManager", "Generated a new time-based UUID: " + uuId);
  				// This needs to be saved now, so that if server communication
  				// fails we have a stable UUID.
  				p.edit().putString(CaratApplication.getRegisteredUuid(), uuId).commit();
  				p.edit().putBoolean(Constants.PREFERENCE_TIME_BASED_UUID, true).commit();
  				timeBasedUuid = true;
  			}
  			String os = SamplingLibrary.getOsVersion();
  			String model = SamplingLibrary.getModel();
  			Log.d("CommunicationManager", "First run, registering this device: " + uuId + ", " + os + ", " + model);
  			try {
  				registerMe(instance, uuId, os, model);
  				p.edit().putBoolean(Constants.PREFERENCE_FIRST_RUN, false).commit();
  				register = false;
  				registered = true;
  				p.edit().putString(CaratApplication.getRegisteredUuid(), uuId).commit();
  				p.edit().putString(Constants.REGISTERED_OS, os).commit();
  				p.edit().putString(Constants.REGISTERED_MODEL, model).commit();
  			} catch (TException e) {
  				Log.e("CommunicationManager", "Registration failed, will try again next time: " + e);
  				e.printStackTrace();
  			}
  		}
  	}
  
  	/**
  	 * Used by UiRefreshThread which needs to know about exceptions.
  	 * 
  	 * @throws TException
  	 */
  	public void refreshAllReports() {
  		registerLocal();
  		// Do not refresh if not connected
  		if (!SamplingLibrary.networkAvailable(a.getApplicationContext()))
  			return;
  		if (System.currentTimeMillis() - CaratApplication.storage.getFreshness() < Constants.FRESHNESS_TIMEOUT)
  			return;
  		// Establish connection
  		if (register) {
  			CaratService.Client instance = null;
  			try {
  				instance = ProtocolClient.open(a.getApplicationContext());
  				registerOnFirstRun(instance);
  				safeClose(instance);
  			} catch (Throwable th) {
  				Log.e(TAG, "Error refreshing main reports.", th);
  				safeClose(instance);
  			}
  		}
  
  		String uuId = p.getString(CaratApplication.getRegisteredUuid(), null);
  		String model = SamplingLibrary.getModel();
  		String OS = SamplingLibrary.getOsVersion();
  
  		// NOTE: Fake data for simulator
  		if (model.equals("sdk")) {
  			uuId = "97c542cd8e99d948"; // My S3
  			model = "GT-I9300";
  			OS = "4.0.4";
  		}
  
  		Log.d(TAG, "Getting reports for " + uuId + " model=" + model + " os=" + OS);
  		FlurryAgent.logEvent("Getting reports for " + uuId + "," + model + "," + OS);
  
  		int progress = 0;
  		
  		String[] titles = CaratApplication.getTitles();
  		if (titles != null){
  			String[] temp = Arrays.copyOfRange(titles, 2, 5);
  			titles = temp;
  		}
  
  		CaratApplication.setActionProgress(progress, titles[0], false);
  		boolean success = refreshMainReports(uuId, OS, model);
  		if (success) {
  			progress += 20;
  			CaratApplication.setActionProgress(progress, titles[1], false);
  			Log.d(TAG, "Successfully got main report");
  		} else {
  			CaratApplication.setActionProgress(progress, titles[0], true);
  			Log.d(TAG, "Failed getting main report");
  		}
  		success = refreshBugReports(uuId, model);
  		
  		if (success) {
  			progress += 20;
  			CaratApplication.setActionProgress(progress, titles[2], false);
  			Log.d(TAG, "Successfully got bug report");
  		} else {
  			CaratApplication.setActionProgress(progress, titles[1], true);
  			Log.d(TAG, "Failed getting bug report");
  		}
  		
  //		success = refreshSettingsReports(uuId, model);
  //		
  //		if (success) {
  //			progress += 20;
  //			CaratApplication.setActionProgress(progress, a.getString(R.string.tab_hogs), false);
  //			Log.d(TAG, "Successfully got settings report");
  //		} else {
  //			CaratApplication.setActionProgress(progress, a.getString(R.string.tab_settings), true);
  //			Log.d(TAG, "Failed getting settings report");
  //		}
  		
  		success = refreshHogReports(uuId, model);
  
  		boolean bl = true;
  		if (System.currentTimeMillis() - CaratApplication.storage.getBlacklistFreshness() < Constants.FRESHNESS_TIMEOUT_BLACKLIST)
  			bl = false;
  
  		if (success) {
  			progress += 40; // changed to 40
  			CaratApplication.setActionProgress(progress,
  					bl ? a.getString(R.string.blacklist) : a.getString(R.string.finishing), false);
  			Log.d(TAG, "Successfully got hog report");
  		} else {
  			CaratApplication.setActionProgress(progress, titles[2], true);
  			Log.d(TAG, "Failed getting hog report");
  		}
  		
  		// NOTE: Check for having a J-Score, and in case there is none, send the
  		// new message
  		Reports r = CaratApplication.storage.getReports();
  		if (r == null || r.jScoreWith == null || r.jScoreWith.expectedValue <= 0) {
  			success = getQuickHogsAndMaybeRegister(uuId, OS, model);
  			if (success)
  				Log.d(TAG, "Got quickHogs.");
  			else
  				Log.d(TAG, "Failed getting GuickHogs.");
  		}
  
  		if (bl) {
  			refreshBlacklist();
  			refreshQuestionnaireLink();
  		}
  
  		CaratApplication.storage.writeFreshness();
  		Log.d(TAG, "Wrote freshness");
  	}
  
  	private boolean refreshMainReports(String uuid, String os, String model) {
  		if (System.currentTimeMillis() - CaratApplication.storage.getFreshness() < Constants.FRESHNESS_TIMEOUT)
  			return false;
  		CaratService.Client instance = null;
  		try {
  			instance = ProtocolClient.open(a.getApplicationContext());
  			Reports r = instance.getReports(uuid, getFeatures("Model", model, "OS", os));
  			// Assume multiple invocations, do not close
  			// ProtocolClient.close();
  			if (r != null) {
  				Log.d("CommunicationManager.refreshMainReports()",
  						"got the main report (action list)" + ", model=" + r.getModel() 
  						+ ", jscore=" + r.getJScore() + ". Storing the report in the databse");
  				CaratApplication.storage.writeReports(r);
  			} else {
  				Log.d("CommunicationManager.refreshMainReports()", 
  						"the fetched MAIN report is null");
  			}
  			// Assume freshness written by caller.
  			// s.writeFreshness();
  			safeClose(instance);
  			return true;
  		} catch (Throwable th) {
  			Log.e(TAG, "Error refreshing main reports.", th);
  			safeClose(instance);
  		}
  		return false;
  	}
  
  	private boolean refreshBugReports(String uuid, String model) {
  		if (System.currentTimeMillis() - CaratApplication.storage.getFreshness() < Constants.FRESHNESS_TIMEOUT)
  			return false;
  		CaratService.Client instance = null;
  		try {
  			instance = ProtocolClient.open(a.getApplicationContext());
  			HogBugReport r = instance.getHogOrBugReport(uuid, getFeatures("ReportType", "Bug", "Model", model));
  			// Assume multiple invocations, do not close
  			// ProtocolClient.close();
  			if (r != null) {
  				CaratApplication.storage.writeBugReport(r);
  				Log.d("CommunicationManager.refreshBugReports()", 
  						"got the bug list: " + r.getHbList().toString());
  			} else {
  				Log.d("CommunicationManager.refreshBugReports()", 
  						"the fetched bug report is null");
  			}
  			safeClose(instance);
  			return true;
  		} catch (Throwable th) {
  			Log.e(TAG, "Error refreshing bug reports.", th);
  			safeClose(instance);
  		}
  		return false;
  	}
  
  	private boolean refreshHogReports(String uuid, String model) {
  		if (System.currentTimeMillis() - CaratApplication.storage.getFreshness() < Constants.FRESHNESS_TIMEOUT)
  			return false;
  		CaratService.Client instance = null;
  		try {
  			instance = ProtocolClient.open(a.getApplicationContext());
  			HogBugReport r = instance.getHogOrBugReport(uuid, getFeatures("ReportType", "Hog", "Model", model));
  
  			// Assume multiple invocations, do not close
  			// ProtocolClient.close();
  			if (r != null) {
  				CaratApplication.storage.writeHogReport(r);
  				Log.d("CommunicationManager.refreshHogReports()", 
  						"got the hog list: " + r.getHbList().toString());
  			} else {
  				Log.d("CommunicationManager.refreshHogReports()", 
  						"the fetched hog report is null");
  			}
  			// Assume freshness written by caller.
  			// s.writeFreshness();
  			safeClose(instance);
  			return true;
  		} catch (Throwable th) {
  			Log.e(TAG, "Error refreshing hog reports.", th);
  			safeClose(instance);
  		}
  		return false;
  	}
  
  //	private boolean refreshSettingsReports(String uuid, String model) {
  //		if (System.currentTimeMillis() - CaratApplication.storage.getFreshness() < Constants.FRESHNESS_TIMEOUT)
  //			return false;
  //		CaratService.Client instance = null;
  //		try {
  //			instance = ProtocolClient.open(a.getApplicationContext());
  //			HogBugReport r = instance.getHogOrBugReport(uuid, getFeatures("ReportType", "Settings", "Model", model));
  //
  //			if (r != null) {
  //				CaratApplication.storage.writeSettingsReport(r);
  //				Log.d("CommunicationManager.refreshSettingsReports()", 
  //						"got the settings list: " + r.getHbList().toString());
  //			} else {
  //				Log.d("CommunicationManager.refreshSettingsReports()", 
  //						"the fetched settings report is null");
  //			}
  //			// Assume freshness written by caller.
  //			// s.writeFreshness();
  //			safeClose(instance);
  //			return true;
  //		} catch (Throwable th) {
  //			Log.e(TAG, "Error refreshing settings reports.", th);
  //			safeClose(instance);
  //		}
  //		return false;
  //	}
  	
  	private void refreshBlacklist() {
  		// I/O, let's do it on the background.
  		new Thread() {
  			public void run() {
  				final List<String> blacklist = new ArrayList<String>();
  				final List<String> globlist = new ArrayList<String>();
  				try {
  					URL u = new URL(DAEMONS_URL);
  					URLConnection c = u.openConnection();
  					InputStream is = c.getInputStream();
  					if (is != null) {
  						BufferedReader rd = new BufferedReader(new InputStreamReader(is));
  						String s = rd.readLine();
  						while (s != null) {
  							// Optimization for android: Only add names that
  							// have a dot
  							// Does not work, since for example "system" has no
  							// dots.
  							blacklist.add(s);
  							if (s.endsWith("*") || s.startsWith("*"))
  								globlist.add(s);
  							s = rd.readLine();
  						}
  						rd.close();
  						Log.v(TAG, "Downloaded blacklist: " + blacklist);
  						Log.v(TAG, "Downloaded globlist: " + globlist);
  						CaratApplication.storage.writeBlacklist(blacklist);
  						// List of *something or something* expressions:
  						if (globlist.size() > 0)
  							CaratApplication.storage.writeGloblist(globlist);
  
  					}
  				} catch (Throwable th) {
  					Log.e(TAG, "Could not retrieve blacklist!", th);
  				}
  				// So we don't try again too often.
  				CaratApplication.storage.writeBlacklistFreshness();
  			}
  		}.start();
  	}
  
  	private void refreshQuestionnaireLink() {
  		// I/O, let's do it on the background.
  		new Thread() {
  			public void run() {
  				String s = null;
  				try {
  					URL u = new URL(QUESTIONNAIRE_URL);
  					URLConnection c = u.openConnection();
  					InputStream is = c.getInputStream();
  					if (is != null) {
  						BufferedReader rd = new BufferedReader(new InputStreamReader(is));
  						s = rd.readLine();
  						rd.close();
  						if (s != null && s.length() > 7 && s.startsWith("http"))
  							CaratApplication.storage.writeQuestionnaireUrl(s);
  						else
  							CaratApplication.storage.writeQuestionnaireUrl(" ");
  					}
  				} catch (Throwable th) {
  					Log.e(TAG, "Could not retrieve blacklist!", th);
  				}
  			}
  		}.start();
  	}
  
  	private boolean getQuickHogsAndMaybeRegister(String uuid, String os, String model) {
  		if (System.currentTimeMillis() - CaratApplication.storage.getQuickHogsFreshness() < Constants.FRESHNESS_TIMEOUT_QUICKHOGS)
  			return false;
  		CaratService.Client instance = null;
  		try {
  			instance = ProtocolClient.open(a.getApplicationContext());
  			Registration registration = new Registration(uuid);
  			registration.setPlatformId(model);
  			registration.setSystemVersion(os);
  			registration.setTimestamp(System.currentTimeMillis() / 1000.0);
  			List<ProcessInfo> pi = SamplingLibrary.getRunningAppInfo(a.getApplicationContext());
  			List<String> processList = new ArrayList<String>();
  			for (ProcessInfo p : pi)
  				processList.add(p.pName);
  
  			HogBugReport r = instance.getQuickHogsAndMaybeRegister(registration, processList);
  			// Assume multiple invocations, do not close
  			// ProtocolClient.close();
  			if (r != null) {
  				CaratApplication.storage.writeHogReport(r);
  				CaratApplication.storage.writeQuickHogsFreshness();
  			}
  			// Assume freshness written by caller.
  			// s.writeFreshness();
  			safeClose(instance);
  			return true;
  		} catch (Throwable th) {
  			Log.e(TAG, "Error refreshing main reports.", th);
  			safeClose(instance);
  		}
  		return false;
  	}
  
  	public static void safeClose(CaratService.Client c) {
  		if (c == null)
  			return;
  		TProtocol i = c.getInputProtocol();
  		TProtocol o = c.getOutputProtocol();
  		if (i != null) {
  			TTransport it = i.getTransport();
  			if (it != null)
  				it.close();
  		}
  		if (o != null) {
  			TTransport it = o.getTransport();
  			if (it != null)
  				it.close();
  		}
  	}
  
  	private List<Feature> getFeatures(String key1, String val1, String key2, String val2) {
  		List<Feature> features = new ArrayList<Feature>();
  		if (key1 == null || val1 == null || key2 == null || val2 == null) {
  			Log.e("getFeatures", "Null key or value given to getFeatures!");
  			System.exit(1);
  			return features;
  		}
  		Feature feature = new Feature();
  		feature.setKey(key1);
  		feature.setValue(val1);
  		features.add(feature);
  
  		feature = new Feature();
  		feature.setKey(key2);
  		feature.setValue(val2);
  		features.add(feature);
  		return features;
  	}
  }
  