package org.mots.haxsync.services;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mots.haxsync.R;
import org.mots.haxsync.provider.Event;
import org.mots.haxsync.provider.EventAttendee;
import org.mots.haxsync.utilities.CalendarUtil;
import org.mots.haxsync.utilities.DeviceUtil;
import org.mots.haxsync.utilities.FacebookUtil;
import android.accounts.Account;
import android.accounts.OperationCanceledException;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.CalendarContract;
import android.provider.ContactsContract.RawContacts;
import android.text.format.Time;
import android.util.Log;
public class CalendarSyncAdapterService extends Service {
private static final String TAG = "CalendarSyncAdapterService";
private static SyncAdapterImpl sSyncAdapter = null;
private static ContentResolver mContentResolver = null;
public CalendarSyncAdapterService() {
super();
}
private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
private Context mContext;
public SyncAdapterImpl(Context context) {
super(context, true);
mContext = context;
}
@Override
public void onPerformSync(Account account, Bundle extras,
String authority, ContentProviderClient provider,
SyncResult syncResult) {
try {
CalendarSyncAdapterService.performSync(mContext, account,
extras, authority, provider, syncResult);
} catch (OperationCanceledException e) {
}
}
}
@Override
public IBinder onBind(Intent intent) {
IBinder ret = null;
ret = getSyncAdapter().getSyncAdapterBinder();
return ret;
}
private SyncAdapterImpl getSyncAdapter() {
if (sSyncAdapter == null)
sSyncAdapter = new SyncAdapterImpl(this);
return sSyncAdapter;
}
private static long getCalendarID(Account account, String name){
String[] projection = new String[] {
CalendarContract.Calendars._ID,
CalendarContract.Calendars.ACCOUNT_NAME,
};
String where = CalendarContract.Calendars.ACCOUNT_NAME + " = ? AND " + CalendarContract.Calendars.ACCOUNT_TYPE + " = '" + account.type
+ "' AND " + CalendarContract.Calendars.CALENDAR_DISPLAY_NAME + " = '" + name +"'";
Cursor calendarCursor = mContentResolver.query(CalendarContract.Calendars.CONTENT_URI, projection, where, new String[] {account.name}, null);
Log.i("CALENDARS FOUND:", String.valueOf(calendarCursor.getCount()));
if (calendarCursor.getCount() <= 0){
calendarCursor.close();
return -2;
} else{
calendarCursor.moveToFirst();
long id = calendarCursor.getLong(calendarCursor.getColumnIndex(CalendarContract.Calendars._ID));
calendarCursor.close();
return id;
}
}
private static long createCalendar(Account account, String name, int color){
ContentValues values = new ContentValues();
values.put(CalendarContract.Calendars.NAME, name);
values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, name);
values.put(CalendarContract.Calendars.CALENDAR_COLOR, color);
values.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_OWNER);
values.put(CalendarContract.Calendars.OWNER_ACCOUNT, account.name);
values.put(CalendarContract.Calendars.ACCOUNT_NAME, account.name);
values.put(CalendarContract.Calendars.ACCOUNT_TYPE, account.type);
values.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_READ);
values.put(CalendarContract.Calendars.SYNC_EVENTS, 1);
values.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, Time.getCurrentTimezone());
Uri calSyncUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
.appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
.build();
Uri calUri = mContentResolver.insert(calSyncUri, values);
long calId = ContentUris.parseId(calUri);
return calId;
}
private static long addBirthday(long calId, String name, long time){
String where = CalendarContract.Events.CALENDAR_ID + " = " + calId + " AND " + CalendarContract.Events.TITLE + " = \"" + name +"\"";
Cursor cursor = mContentResolver.query(CalendarContract.Events.CONTENT_URI, new String[] {CalendarContract.Events._ID}, where, null, null);
int count = cursor.getCount();
if (count == 0){
cursor.close();
ContentValues values = new ContentValues();
values.put(CalendarContract.Events.DTSTART, time);
values.put(CalendarContract.Events.TITLE, name);
values.put(CalendarContract.Events.ALL_DAY, 1);
values.put(CalendarContract.Events.RRULE, "FREQ=YEARLY");
values.put(CalendarContract.Events.CALENDAR_ID, calId);
values.put(CalendarContract.Events.DURATION, "P1D");
values.put(CalendarContract.Events.EVENT_TIMEZONE, "utc");
values.put(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_FREE);
return Long.valueOf(mContentResolver.insert(CalendarContract.Events.CONTENT_URI, values).getLastPathSegment());
} else {
cursor.moveToFirst();
long id = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events._ID));
cursor.close();
return id;
}
}
private static void addReminder(long eventID, long minutes){
//delete old reminder
String where = CalendarContract.Reminders.EVENT_ID + " = " + eventID;
mContentResolver.delete(CalendarContract.Reminders.CONTENT_URI, where, null);
ContentValues values = new ContentValues();
values.put(CalendarContract.Reminders.EVENT_ID, eventID);
values.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);
values.put(CalendarContract.Reminders.MINUTES, minutes);
mContentResolver.insert(CalendarContract.Reminders.CONTENT_URI, values);
}
private static long addEvent(Account acc, long calId, Event e){
String name = e.getName();
long start = e.getStartTime();
long end = e.getEndTime();
String location = e.getLocation();
String description = e.getDescription();
int rsvp = e.getRsvp();
long eid = e.getEventID();
Uri insertUri = CalendarContract.Events.CONTENT_URI.buildUpon()
.appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, acc.name)
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, acc.type)
.build();
if (eid != -2){
String where = CalendarContract.Events.CALENDAR_ID + " = " + calId + " AND " + CalendarContract.Events._SYNC_ID + " = " + eid;
Cursor cursor = mContentResolver.query(CalendarContract.Events.CONTENT_URI,
new String[] {CalendarContract.Events._ID, CalendarContract.Events.TITLE, CalendarContract.Events.DTSTART, CalendarContract.Events.DTEND,
CalendarContract.Events.SELF_ATTENDEE_STATUS, CalendarContract.Events.EVENT_LOCATION, CalendarContract.Events.DESCRIPTION}, where, null, null);
int count = cursor.getCount();
if (count == 0){
cursor.close();
ContentValues values = new ContentValues();
values.put(CalendarContract.Events.DTSTART, start);
values.put(CalendarContract.Events.DTEND, end);
values.put(CalendarContract.Events.TITLE, name);
values.put(CalendarContract.Events.HAS_ATTENDEE_DATA, true);
values.put(CalendarContract.Events.SELF_ATTENDEE_STATUS, rsvp);
values.put(CalendarContract.Events._SYNC_ID, eid);
if (location != null){
values.put(CalendarContract.Events.EVENT_LOCATION, location);
}
if (description != null)
values.put(CalendarContract.Events.DESCRIPTION, description);
if (rsvp != CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED){
values.put(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_FREE);
}else{
values.put(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY);
}
values.put(CalendarContract.Events.CALENDAR_ID, calId);
values.put(CalendarContract.Events.EVENT_TIMEZONE, Time.getCurrentTimezone());
return Long.valueOf(mContentResolver.insert(insertUri, values).getLastPathSegment());
} else {
cursor.moveToFirst();
long oldstart = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events.DTSTART));
long id = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events._ID));
long oldend = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events.DTEND));
String oldlocation = cursor.getString(cursor.getColumnIndex(CalendarContract.Events.EVENT_LOCATION));
String oldDescription = cursor.getString(cursor.getColumnIndex(CalendarContract.Events.DESCRIPTION));
int oldrsvp = cursor.getInt(cursor.getColumnIndex(CalendarContract.Events.SELF_ATTENDEE_STATUS));
String oldname = cursor.getString(cursor.getColumnIndex(CalendarContract.Events.TITLE));
cursor.close();
ContentValues values = new ContentValues();
if (oldstart != start)
values.put(CalendarContract.Events.DTSTART, start);
if (oldend != end)
values.put(CalendarContract.Events.DTEND, end);
if (! oldlocation.equals(location))
values.put(CalendarContract.Events.EVENT_LOCATION, location);
if (! oldDescription.equals(description))
values.put(CalendarContract.Events.DESCRIPTION, description);
if (! oldname.equals(name))
values.put(CalendarContract.Events.TITLE, name);
/*if (oldrsvp != rsvp)
values.put(CalendarContract.Events.SELF_ATTENDEE_STATUS, rsvp);*/
if (values.size() != 0)
mContentResolver.update(CalendarContract.Events.CONTENT_URI, values, CalendarContract.Events._ID + " = ?", new String[] {String.valueOf(id)});
return id;
}
}
return -1;
}
public static void removeCalendar(Context context, Account account, String name){
mContentResolver = context.getContentResolver();
long calID = getCalendarID(account, name);
if (calID == -2){
return;
}
Uri calcUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
.appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
.build();
mContentResolver.delete(calcUri, CalendarContract.Calendars._ID + " = " +calID, null);
}
public static void setCalendarColor(Context context, Account account, String name, int color){
mContentResolver = context.getContentResolver();
long calID = getCalendarID(account, name);
if (calID == -2){
return;
}
ContentValues values = new ContentValues();
values.put(CalendarContract.Calendars.CALENDAR_COLOR, color);
Uri calcUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
.appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
.build();
mContentResolver.update(calcUri, values, CalendarContract.Calendars._ID + " = " + calID, null);
}
private static Set<String> getFriends(Context context, Account account){
HashSet<String> friends = new HashSet<String>();
Uri rawContactUri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
.build();
Cursor c1 = mContentResolver.query(rawContactUri, new String[] { RawContacts.DISPLAY_NAME_PRIMARY }, null, null, null);
while (c1.moveToNext()) {
friends.add(c1.getString(0));
}
c1.close();
return friends;
}
public static void removeReminders(Context context, Account account, String calendarName){
mContentResolver = context.getContentResolver();
long calID = getCalendarID(account, calendarName);
if (calID == -2){
return;
}
for (long id : getEvents(calID)){
String where = CalendarContract.Reminders.EVENT_ID + " = " + id;
mContentResolver.delete(CalendarContract.Reminders.CONTENT_URI, where, null);
}
}
public static void updateReminders(Context context, Account account, String calendarName, long minutes){
mContentResolver = context.getContentResolver();
long calID = getCalendarID(account, calendarName);
if (calID == -2){
return;
}
for (long id : getEvents(calID)){
addReminder(id, minutes);
}
}
private static Set<Long> getEvents(long calendarID){
HashSet<Long> events = new HashSet<Long>();
Cursor c1 = mContentResolver.query(CalendarContract.Events.CONTENT_URI, new String[] {CalendarContract.Events._ID}, CalendarContract.Events.CALENDAR_ID + " = " +calendarID, null, null);
while (c1.moveToNext()) {
events.add(c1.getLong(0));
}
c1.close();
return events;
}
private static void performSync(Context context, Account account,
Bundle extras, String authority, ContentProviderClient provider,
SyncResult syncResult) throws OperationCanceledException {
mContentResolver = context.getContentResolver();
SharedPreferences prefs = context.getSharedPreferences(context.getPackageName() + "_preferences", MODE_MULTI_PROCESS);
boolean wifiOnly = prefs.getBoolean("wifi_only", false);
boolean chargingOnly = prefs.getBoolean("charging_only", false);
if (!((wifiOnly && !DeviceUtil.isWifi(context)) || (chargingOnly && ! DeviceUtil.isCharging(context)))){
FacebookUtil.refreshPermissions(context);
boolean eventSync = prefs.getBoolean("sync_events", true);
boolean birthdaySync = prefs.getBoolean("sync_birthdays", false);
boolean eventReminders = prefs.getBoolean("event_reminders", false);
boolean birthdayReminders = prefs.getBoolean("birthday_reminders", false);
long eventReminderTime = prefs.getLong("event_reminder_minutes", 30);
long birthdayReminderTime = prefs.getLong("birthday_reminder_minutes", 1440);
String statuses = prefs.getString("event_status", "attending|unsure");
Log.i("event sync", String.valueOf(eventSync));
if (FacebookUtil.authorize(context, account)){
long birthdayCalendarID = getCalendarID(account, context.getString(R.string.birthday_cal));
if (birthdaySync){
if (birthdayCalendarID == -2){
int birthdayColor = prefs.getInt("birthday_color", 0xff1212);
birthdayCalendarID = createCalendar(account, context.getString(R.string.birthday_cal), birthdayColor);
}
boolean phoneOnly = prefs.getBoolean("phone_only_cal", false);
HashMap<String, Long> birthdays = FacebookUtil.getBirthdays();
Set<String> friends = getFriends(context, account);
Log.i("friends", friends.toString());
if (birthdays != null){
for (String name : birthdays.keySet()){
if (!phoneOnly || friends.contains(name)){
long eventID = addBirthday(birthdayCalendarID, String.format(context.getString(R.string.birthday), name), birthdays.get(name));
if (birthdayReminders)
addReminder(eventID, birthdayReminderTime);
}
}
}
} else if (birthdayCalendarID != -2){
removeCalendar(context, account, context.getString(R.string.birthday_cal));
}
if (eventSync){
long eventCalendarID = getCalendarID(account, context.getString(R.string.event_cal));
if (eventCalendarID == -2){
int color = prefs.getInt("event_color", 0xff2525);
eventCalendarID = createCalendar(account, context.getString(R.string.event_cal), color);
}
List<Event> events = FacebookUtil.getEvents(statuses);
for (Event e : events){
long eventID = addEvent(account, eventCalendarID, e);
CalendarUtil.removeAttendees(context, eventID);
List<EventAttendee> attendees = FacebookUtil.getEventAttendees(e.getEventID());
for (EventAttendee a : attendees){
CalendarUtil.addAttendee(context, eventID, a);
}
if (eventReminders && (eventID != -1))
addReminder(eventID, eventReminderTime);
}
}
}
}else {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("missed_calendar_sync", true);
editor.commit();
}
}
}