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.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.ProbeKeys; import edu.mit.media.funf.probe.builtin.ProbeKeys.AndroidInternal.TextBasedSmsColumns; import edu.mit.media.funf.probe.builtin.SmsProbe; import edu.mit.media.funf.time.TimeUnit; @DesignerComponent(version = YaVersion.SMSLOGHISTORY_COMPONENT_VERSION, description = "Messages sent and received by this device using SMS. Sensitive data is hashed for user privacy." + "Could specifiy \"afterDate\" parameter to only read sms information after that date.", category = ComponentCategory.SENSORS, nonVisible = true, iconName = "images/smsProbe.png") @SimpleObject @UsesPermissions(permissionNames = "android.permission.READ_SMS") @UsesLibraries(libraries = "funf.jar") public class SmsHistory extends ProbeBase { //android.permission.READ_SMS private final String TAG = "SmsProbe"; private SmsProbe probe; private final String SMS_PROBE = "edu.mit.media.funf.probe.builtin.SmsProbe"; //fields in the returned json private String address; //phone number associated with message private String body; private long date; //The date that the message was sent or received private String type; // http://stackoverflow.com/questions/8447735/android-sms-type-constants private boolean read; //whether the sms message is read or not //default settings for schedule private final int SCHEDULE_INTERVAL = 43200; //read calllogs every 43200 seconds (every 12 hours) private final int SCHEDULE_DURATION = 15; //scan for 15 seconds everytime SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //parameters for this probe private long afterDate = 0; // Only read smsLog that are after this date (in seconds since epoch). Without this, the probe will return all calllogs 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 * {"address":"{\"ONE_WAY_HASH\":\"4ce667aa063d4ecf712bc97a7142c2cb0118d5c9\"}", * "body":"{\"ONE_WAY_HASH\":\"4a111bc11f23bd65316b257ea806d8ce0b51dbb2\"}", * "date":1335467311983,"locked":false,"person":"{\"ONE_WAY_HASH\":\"\"}", * "protocol":0, * "read":true, * "reply_path_present":false, * "status":-1, * "subject":"{\"ONE_WAY_HASH\":\"\"}", * "thread_id":25, * "timestamp":1335467311.983, * "type":2} */ // If privacySafe is set to False. Return clear text // {"address":"+9999999", // "body":"(1/2)You've got a new voicemail", // "date":1379705102582, // "locked":false, // "protocol":0, // "read":true, // "reply_path_present":false, // "service_center":"+12404492158", // "status":-1, // "thread_id":44, // "timestamp":1379705102.582, // "type":1} /* * Some notes for SMS:type (http://stackoverflow.com/questions/8447735/android-sms-type-constants) * MESSAGE_TYPE_ALL = 0; * MESSAGE_TYPE_INBOX = 1; * MESSAGE_TYPE_SENT = 2; * MESSAGE_TYPE_DRAFT = 3; * MESSAGE_TYPE_OUTBOX = 4; * MESSAGE_TYPE_FAILED = 5; // for failed outgoing messages * MESSAGE_TYPE_QUEUED = 6; // for messages to send later */ // 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) { IJsonObject data = (IJsonObject) msg.obj; Log.i(TAG, "Update component's varibles....."); //TODO: it's possible that not all fields will exist if (privacySafe) { String hashedAddress = data.get(ProbeKeys.SmsKeys.ADDRESS) == null ? "" : data.get(ProbeKeys.SmsKeys.ADDRESS).getAsString(); String hashedBody = data.get(ProbeKeys.SmsKeys.BODY) == null ? "" : data.get(ProbeKeys.SmsKeys.BODY).getAsString(); if (hashedAddress != "") { JsonObject addrJson = jsonParser.parse(hashedAddress).getAsJsonObject(); address = addrJson.get("ONE_WAY_HASH").getAsString(); } else { address = ""; } if (hashedBody != "") { JsonObject bodyjson = jsonParser.parse(hashedBody).getAsJsonObject(); body = bodyjson.get("ONE_WAY_HASH").getAsString(); } else { body = ""; } } else { address = (data.get(ProbeKeys.SmsKeys.ADDRESS) == null) ? "" : data.get(ProbeKeys.SmsKeys.ADDRESS).getAsString(); body = (data.get(ProbeKeys.SmsKeys.BODY) == null) ? "" : data.get(ProbeKeys.SmsKeys.BODY).getAsString(); } date = data.get(ProbeKeys.SmsKeys.DATE).getAsLong(); read = data.get(ProbeKeys.SmsKeys.READ).getAsBoolean(); // get the string representation of sms type type = getTypeName(data.get(ProbeKeys.SmsKeys.TYPE).getAsInt()); // //fields in the returned json // private String address; //phone number associated with message // private String body; // private long date; //The date that the message was sent or received // private String type; // http://stackoverflow.com/questions/8447735/android-sms-type-constants // private boolean read; //whether the sms message is read or not SmsInfoReceived(date, address, body, type, read); } }; private String getTypeName(int typeConst) { String type = ""; switch (typeConst) { case TextBasedSmsColumns.MESSAGE_TYPE_INBOX: type = "INBOX"; break; case TextBasedSmsColumns.MESSAGE_TYPE_DRAFT: type = "DRAFT"; break; case TextBasedSmsColumns.MESSAGE_TYPE_OUTBOX: type = "OUTBOX"; break; case TextBasedSmsColumns.MESSAGE_TYPE_FAILED: type = "FAILED"; break; case TextBasedSmsColumns.MESSAGE_TYPE_SENT: type = "SENT"; break; case TextBasedSmsColumns.MESSAGE_TYPE_QUEUED: type = "QUEUED"; break; } return type; } public SmsHistory(ComponentContainer container) { super(container); // init sms types hashmap 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, SmsProbe.class); interval = SCHEDULE_INTERVAL; duration = SCHEDULE_DURATION; } /** * Specify the date after which the sms message occurred * The format 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 read all SMS after June 1, 8pm * Empty value will return all SMS messages */ @SimpleFunction(description = "Specify the date after which the SMS messages occurred. " + "The formate should be \"YYYY-MM-DD HH:mm:ss\". Note everytime when the SMS messages are read, one needs to" + "reset AfterDate if likes to read those messages again." + "") 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(SmsHistory.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)); } } @SimpleFunction(description = "Enable sms history sensor 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, SmsProbe.class); probe.registerListener(listener); Log.i(TAG, "run-once config for SMS:" + newConfig); } else { probe.unregisterListener(listener); } } @Override public void unregisterDataRequest() { Log.i(TAG, "Unregistering sms 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 sms requests."); JsonElement dataRequest = null; dataRequest = getDataRequest(interval, duration, SMS_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 SmsInfoReceived(final long date, final String address, final String body, final String type, final boolean read) { if (enabled || enabledSchedule) { mainUIThreadActivity.runOnUiThread(new Runnable() { public void run() { Log.i(TAG, "SmsInfoReceived() is called"); EventDispatcher.dispatchEvent(SmsHistory.this, "SmsInfoReceived", date, address, body, type, read); } }); } } // /** // * The phone number associated with message. (hashed for privacy reason) // */ // @SimpleProperty(description = "The address (phone number) associated with message. If HideSensitiveData is set " + // "to True then hashed values will be returned for privacy reason.") // public String Address(){ // Log.i(TAG, "returning address of the message: " + address); // return address; // } // // // /** // * The body of the message. // */ // @SimpleProperty(description = "The body of the message. If HideSensitiveData is set " + // "to True then hashed values will be returned for privacy reason.") // public String Body(){ // Log.i(TAG, "returning address of the message: " + address); // return body; // } // // // /** // * The date the sms was sent or received, in milliseconds since the epoch // */ // @SimpleProperty(description = "The date the sms was sent or received, in milliseconds since the epoch") // public long Date(){ // Log.i(TAG, "returning date " + date); // return date; // } // // // /** // * The type of the message // */ // @SimpleProperty(description = "The type of the message " ) // public String MessageType(){ // Log.i(TAG, "returning message: " + type); // return type; // } // // /** // * Indicate whether the message is read or not // */ // @SimpleProperty(description = "Indicate whether the message is read or not " ) // public boolean Read(){ // Log.i(TAG, "returning message: " + read); // return read; // } /* * 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; } }