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;
}
}