Java tutorial
// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt package com.google.appinventor.components.runtime; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import android.content.Context; import android.os.Handler; import android.os.Message; import android.provider.CallLog; import android.text.format.DateFormat; import android.util.Log; import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.DesignerProperty; import com.google.appinventor.components.annotations.SimpleEvent; import com.google.appinventor.components.annotations.SimpleFunction; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.annotations.UsesLibraries; import com.google.appinventor.components.annotations.UsesPermissions; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import edu.mit.media.funf.FunfManager; import edu.mit.media.funf.json.IJsonObject; import edu.mit.media.funf.probe.Probe.DataListener; import edu.mit.media.funf.probe.builtin.CallLogProbe; import edu.mit.media.funf.probe.builtin.ProbeKeys; import edu.mit.media.funf.time.TimeUnit; @DesignerComponent(version = YaVersion.CALLLOGHISTORY_COMPONENT_VERSION, description = "Return information of recent calls. A wrapper around Android.Calllog.Calls " + "(see http://developer.android.com/reference/android/provider/CallLog.Calls.html). " + "Could specifiy \"afterDate\" parameter to only read calllog information after that date. " + "Some of the returning fields (name, number, numberType, numberLabel) can be returned as hashed " + "for privacy reasons", category = ComponentCategory.SENSORS, nonVisible = true, iconName = "images/calllogProbe.png") @SimpleObject @UsesPermissions(permissionNames = "android.permission.READ_CONTACTS") @UsesLibraries(libraries = "funf.jar") public class CallLogHistory extends ProbeBase { private final String TAG = "CallLogProbe"; private CallLogProbe probe; private final String CALLLOG_PROBE = "edu.mit.media.funf.probe.builtin.CallLogProbe"; //fields in the returned json private String name; //name (one way hashed) associated with the phone number private String number; //The phone number (one way hashed) as the user entered it. private String numberLabel; //The cached number label, for a custom number type, associated with the phone number, if it exists. private String numberType; //The cached number type (Home, Work, etc) associated with the phone number, if it exists. private long date; //The date the call occurred, in milliseconds since the epoch private int duration; //The duration of the call in seconds private String type; //The type of the call (incoming(1), outgoing(2) or missed(3)). //parameters for this probe private long afterDate = 0; // Only read calllogs that are after this date (in seconds since epoch). Without this, the probe will return all calllogs //default settings for schedule private final int SCHEDULE_INTERVAL = 86400; //read calllogs every 86400 seconds (every 24 hours) private final int SCHEDULE_DURATION = 15; //scan for 15 seconds everytime SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //util JsonParser jsonParser = new JsonParser(); Context context; private DataListener listener = new DataListener() { @Override public void onDataCompleted(IJsonObject completeProbeUri, JsonElement arg1) { // do nothing, CallLogProbe does not generate onDataCompleted event } @Override public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) { Log.i(TAG, "receive data of calllog"); /* * TODO: bud in the json format of the returning name/number * returned json format * { "_id":975,"date":1338751506200, * "duration":2, * "name": "{\"ONE_WAY_HASH\":\"80d2125f300a1ad8baafd5ef295a7f544bf25409\"}", * "number":"{\"ONE_WAY_HASH\":\"1662c65b0f53de6ce19c1f3138b9b5edc3d27630\"}", * "numberlabel":"{\"ONE_WAY_HASH\":\"\"}", * "numbertype": "{\"ONE_WAY_HASH\":\"da4b9237bacccdf19c0760cab7aec4a8359010b0\"}", * "timestamp":1338751506.2, * "type":1} */ // debug Log.i(TAG, "DATA: " + data.toString()); //save data to DB is enabledSaveToDB is true if (enabledSaveToDB) { saveToDB(completeProbeUri, data); } Message msg = myHandler.obtainMessage(); msg.obj = data; myHandler.sendMessage(msg); } }; final Handler myHandler = new Handler() { @Override public void handleMessage(Message msg) { //When sensitive is set to true. Funf will generate all hashed values for all the fields, // including those that are empty (e.g. numberLabel); // But if we set to true, then we have to take care of those empty values ourselves. IJsonObject data = (IJsonObject) msg.obj; Log.i(TAG, "Update component's varibles....."); if (privacySafe) { String hashedName = data.get(ProbeKeys.CallLogKeys.NAME).getAsString(); String hashedNumber = data.get(ProbeKeys.CallLogKeys.NUMBER).getAsString(); String hashedNnumberType = data.get(ProbeKeys.CallLogKeys.NUMBER_TYPE).getAsString(); String haseddNumberLabel = data.get(ProbeKeys.CallLogKeys.NUMBER_LABEL).getAsString(); JsonObject nameJson = jsonParser.parse(hashedName).getAsJsonObject(); JsonObject numberJson = jsonParser.parse(hashedNumber).getAsJsonObject(); JsonObject numberTypeJson = jsonParser.parse(hashedNnumberType).getAsJsonObject(); JsonObject numberLabelJson = jsonParser.parse(haseddNumberLabel).getAsJsonObject(); name = nameJson.get("ONE_WAY_HASH").getAsString(); number = numberJson.get("ONE_WAY_HASH").getAsString(); numberType = numberTypeJson.get("ONE_WAY_HASH").getAsString(); numberLabel = numberLabelJson.get("ONE_WAY_HASH").getAsString(); } else { //clear text will be returned name = data.get(ProbeKeys.CallLogKeys.NAME) == null ? "" : data.get(ProbeKeys.CallLogKeys.NAME).getAsString(); number = data.get(ProbeKeys.CallLogKeys.NUMBER) == null ? "" : data.get(ProbeKeys.CallLogKeys.NUMBER).getAsString(); numberType = data.get(ProbeKeys.CallLogKeys.NUMBER_TYPE) == null ? "" : data.get(ProbeKeys.CallLogKeys.NUMBER_TYPE).getAsString(); numberLabel = data.get(ProbeKeys.CallLogKeys.NUMBER_LABEL) == null ? "" : data.get(ProbeKeys.CallLogKeys.NUMBER_LABEL).getAsString(); } date = data.get(ProbeKeys.CallLogKeys.DATE).getAsLong(); duration = data.get(ProbeKeys.CallLogKeys.DURATION).getAsInt(); // get the string representation of call type type = getTypeName(data.get(ProbeKeys.CallLogKeys.TYPE).getAsInt()); CalllogsInfoReceived(date, duration, name, number, numberType, type); } }; private String getTypeName(int typeConst) { String type = ""; switch (typeConst) { case CallLog.Calls.INCOMING_TYPE: type = "INCOMING"; break; case CallLog.Calls.OUTGOING_TYPE: type = "OUTGOING"; break; case CallLog.Calls.MISSED_TYPE: type = "MISSED"; } return type; } public CallLogHistory(ComponentContainer container) { super(container); // TODO Auto-generated constructor stub form.registerForOnDestroy(this); context = container.$context(); mainUIThreadActivity = container.$context(); Log.i(TAG, "Before create probe"); gson = new GsonBuilder().registerTypeAdapterFactory(FunfManager.getProbeFactory(mainUIThreadActivity)) .create(); JsonObject config = new JsonObject(); probe = gson.fromJson(config, CallLogProbe.class); interval = SCHEDULE_INTERVAL; duration = SCHEDULE_DURATION; } /** * Specify the date after which the callogs occurred * The formate should be "YYYY-MM-DD HH:mm:ss" (see http://docs.oracle.com/javase /1.5.0/docs/api/java/text/SimpleDateFormat.html) * for example "2012-06-01 20:00:00" will calllogs after June 1, 8pm * Empty value will return all calllogs */ @SimpleFunction(description = "Specify the date after which the callogs occurred. The formate should be \"YYYY-MM-DD HH:mm:ss\"") public void AfterDate(String datePoint) { Date date = null; if ("".equals(datePoint)) { ; //do nothing } else { try { date = sdf.parse(datePoint); } catch (ParseException e) { // TODO Auto-generated catch block form.dispatchErrorOccurredEvent(CallLogHistory.this, "AfterDate", ErrorMessages.ERROR_DATE_FORMAT, e.getMessage()); } long timeInMillisSinceEpoch = date.getTime(); this.afterDate = TimeUnit.MILLISECONDS.toSeconds(timeInMillisSinceEpoch); Log.i(TAG, "afterDate: " + DateFormat.getDateFormat(context).format(afterDate * 1000)); } } // /** // * Set the range of calllog history want to retrieve, starting from n days ago until now // * @param nDaysAgo // * Set to retrieve only callogs from N days ago // * TODO: remove from this component, because it's confusing when set this value in a reoccurring event for probing. // */ // // @SimpleFunction(description = "Set to retrieve only callogs from N days ago with the specified N") // public void SetCallLogsRange(int nDaysAgo) { // long totalSeconds = nDaysAgo * 86400; // long currentSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); // // this.afterDate = currentSeconds - totalSeconds; // //debug // Log.i(TAG, "afterDate: " + DateFormat.getDateFormat(context).format(afterDate * 1000)); // // } @SimpleFunction(description = "Enable calllog history probe to run once") @Override public void Enabled(boolean enabled) { // TODO Auto-generated method stub JsonObject newConfig = null; if (this.enabled != enabled) this.enabled = enabled; newConfig = new JsonObject(); if (enabled) { if (afterDate != 0) { // recreate json config newConfig.addProperty("afterDate", this.afterDate); } newConfig.addProperty("hideSensitiveData", privacySafe); probe = gson.fromJson(newConfig, CallLogProbe.class); probe.registerListener(listener); Log.i(TAG, "run-once config:" + newConfig); } else { probe.unregisterListener(listener); } } @Override public void unregisterDataRequest() { Log.i(TAG, "Unregistering calllog data requests."); //mBoundFunfManager.stopForeground(true); mBoundFunfManager.unrequestAllData2(listener); } @Override public void registerDataRequest(int interval, int duration) { // TODO Auto-generated method stub Log.i(TAG, "Registering calllogs requests."); JsonElement dataRequest = null; dataRequest = getDataRequest(interval, duration, CALLLOG_PROBE); if (afterDate != 0) ((JsonObject) dataRequest).addProperty("afterDate", afterDate); ((JsonObject) dataRequest).addProperty("hideSensitiveData", privacySafe); Log.i(TAG, "CallLog request: " + dataRequest.toString()); mBoundFunfManager.requestData(listener, dataRequest); } /** * Indicates that the calllog info has been received. */ @SimpleEvent public void CalllogsInfoReceived(final long date, final long duration, final String name, final String number, final String numberType, final String type) { if (enabled || enabledSchedule) { mainUIThreadActivity.runOnUiThread(new Runnable() { public void run() { Log.i(TAG, "CalllogsInfoReceived() is called"); EventDispatcher.dispatchEvent(CallLogHistory.this, "CalllogsInfoReceived", date, duration, name, number, numberType, type); } }); } } /** * Returns hashedName associated with the phone number */ // @SimpleProperty(description = "Name associated with the phone number. " + // "Return in hashed value if HideSensitiveData is set to True") // public String Name(){ // Log.i(TAG, "returning hashedName: " + name); // return name; // } /** * The phone number (hashed) as the user entered it */ // @SimpleProperty(description = "The phone number as the user entered it. Return in hashed " + // "value if HideSensitiveData is set to True") // public String Number(){ // Log.i(TAG, "returning hashedNumber: " + number); // return number; // } /** * The duration of the call in seconds */ // @SimpleProperty(description = "The duration of the call in seconds") // public int Duration(){ // Log.i(TAG, "returning duration " + duration); // return duration; // } /** * The date the call occured, in milliseconds since the epoch */ // @SimpleProperty(description = "The date the call occured, in milliseconds since the epoch") // public long Date(){ // Log.i(TAG, "returning date " + date); // return date; // } /** * The cached number label, for a custom number type, * associated with the phone number, if it exists. */ // @SimpleProperty(description = "The number label, for a custom number type, " + // "associated with the phone number, if it exists. Return in hashed value if HideSensitiveData" + // "is set to True") // public String NumberLabel(){ // Log.i(TAG, "returning numberLabel: " + numberLabel); // return numberLabel; // } /** * The cached number type (Home, Work, etc) associated * with the phone number, if it exists. */ // @SimpleProperty(description = "The number type (Home, Work, etc) associated with the phone number. " + // "Return in hashed value if HideSensitiveData is set to True") // public String NumberType(){ // Log.i(TAG, "returning numberLabel: " + numberType); // return numberType; // } /** * The type of the call (incoming = "1", outgoing = "2" or missed = "3"). */ // @SimpleProperty(description = "The type of the call " + // "(incoming = \"1\", outgoing = \"2\" or missed = \"3\")") // public String CallType(){ // Log.i(TAG, "returning callType: " + type); // return type; // } /* * Returns the default interval between each scan for this probe */ @SimpleProperty(description = "The default interval (in seconds) between each scan for this probe") public float DefaultInterval() { return SCHEDULE_INTERVAL; } }