package de.tum.in.tumcampusapp.activities;
import android.Manifest;
import android.app.AlertDialog;
import android.content.ContentUris;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.format.DateUtils;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import com.alamkanak.weekview.DateTimeInterpreter;
import com.alamkanak.weekview.MonthLoader;
import com.alamkanak.weekview.WeekView;
import com.alamkanak.weekview.WeekViewEvent;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import de.tum.in.tumcampusapp.R;
import de.tum.in.tumcampusapp.activities.generic.ActivityForAccessingTumOnline;
import de.tum.in.tumcampusapp.auxiliary.Const;
import de.tum.in.tumcampusapp.auxiliary.Utils;
import de.tum.in.tumcampusapp.auxiliary.calendar.IntegratedCalendarEvent;
import de.tum.in.tumcampusapp.managers.CalendarManager;
import de.tum.in.tumcampusapp.managers.SyncManager;
import de.tum.in.tumcampusapp.models.tumo.CalendarRowSet;
import de.tum.in.tumcampusapp.tumonline.TUMOnlineConst;
/**
* Activity showing the user's calendar. Calendar items (events) are fetched from TUMOnline and displayed as blocks on a timeline.
*/
public class CalendarActivity extends ActivityForAccessingTumOnline<CalendarRowSet> implements OnClickListener, MonthLoader.MonthChangeListener, WeekView.EventClickListener {
/**
* The space between the first and the last date
*/
public static final int MONTH_AFTER = 3;
public static final int MONTH_BEFORE = 0;
public static final String EVENT_TIME = "event_time";
private static final int REQUEST_SYNC = 0;
private static final int REQUEST_DELETE = 1;
private static final String[] PERMISSIONS_CALENDAR = {Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR};
private static final int TIME_TO_SYNC_CALENDAR = 604800; // 1 week
private CalendarManager calendarManager;
/**
* Used as a flag, if there are results fetched from internet
*/
private boolean isFetched;
private boolean mWeekMode;
private Calendar mShowDate;
private MenuItem menuItemSwitchView;
private WeekView mWeekView;
public CalendarActivity() {
super(TUMOnlineConst.CALENDER, R.layout.activity_calendar);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get a reference for the week view in the layout.
mWeekView = (WeekView) findViewById(R.id.weekView);
// The week view has infinite scrolling horizontally. We have to provide the events of a
// month every time the month changes on the week view.
mWeekView.setMonthChangeListener(this);
mWeekView.setOnEventClickListener(this);
// Get time to show e.g. a lectures starting time or 0 for now
Intent i = getIntent();
mShowDate = GregorianCalendar.getInstance();
if (i != null && i.hasExtra(EVENT_TIME)) {
long time = i.getLongExtra(EVENT_TIME, 0);
mShowDate.setTime(new Date(time));
} else {
mShowDate.setTime(new Date());
}
//Get setting from sharedprefs and refresh the view with everything
this.mWeekMode = Utils.getInternalSettingBool(this, Const.CALENDAR_WEEK_MODE, false);
this.refreshWeekView();
calendarManager = new CalendarManager(this);
// Set the time space between now and after this date and before this
// Dates before the current date
requestHandler.setParameter("pMonateVor", String.valueOf(MONTH_BEFORE));
// Dates after the current date
requestHandler.setParameter("pMonateNach", String.valueOf(MONTH_AFTER));
if (new SyncManager(this).needSync(Const.SYNC_CALENDAR_IMPORT, TIME_TO_SYNC_CALENDAR)) {
requestFetch();
} else {
isFetched = true;
}
}
@Override
public void onFetch(final CalendarRowSet rawResponse) {
// parsing and saving xml response
new AsyncTask<Void, Void, Void>() {
@Override
protected void onPreExecute() {
isFetched = true;
}
@Override
protected Void doInBackground(Void... params) {
calendarManager.importCalendar(rawResponse);
return null;
}
@Override
protected void onPostExecute(Void result) {
showLoadingEnded();
// update the action bar to display the enabled menu options
CalendarActivity.this.invalidateOptionsMenu();
startService(new Intent(CalendarActivity.this, CalendarManager.QueryLocationsService.class));
}
}.execute();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu_sync_calendar, menu);
menuItemSwitchView = menu.findItem(R.id.action_switch_view_mode);
//Refresh the icon according to us having day or weekview
this.refreshWeekView();
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem menuItemExportGoogle = menu.findItem(R.id.action_export_calendar);
MenuItem menuItemDeleteCalendar = menu.findItem(R.id.action_delete_calendar);
// the Calendar export is not supported for API < 14
menuItemExportGoogle.setEnabled(isFetched);
menuItemDeleteCalendar.setEnabled(isFetched);
boolean bed = Utils.getInternalSettingBool(this, Const.SYNC_CALENDAR, false);
menuItemExportGoogle.setVisible(!bed);
menuItemDeleteCalendar.setVisible(bed);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int i = item.getItemId();
if (i == R.id.action_switch_view_mode) {
mWeekMode = !mWeekMode;
Utils.setInternalSetting(this, Const.CALENDAR_WEEK_MODE, mWeekMode);
this.refreshWeekView();
return true;
} else if (i == R.id.action_export_calendar) {
exportCalendarToGoogle();
// Enable automatic calendar synchronisation
Utils.setInternalSetting(this, Const.SYNC_CALENDAR, true);
supportInvalidateOptionsMenu();
return true;
} else if (i == R.id.action_delete_calendar) {
deleteCalendarFromGoogle();
return true;
} else {
isFetched = false;
return super.onOptionsItemSelected(item);
}
}
/**
* Load up the week view with correct settings
*/
private void refreshWeekView() {
setupDateTimeInterpreter(mWeekMode);
int icon;
if (mWeekMode) {
icon = R.drawable.ic_action_day_view;
mWeekView.setNumberOfVisibleDays(7);
// Lets change some dimensions to best fit the view.
mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics()));
mWeekView.setXScrollingSpeed(1);
} else {
icon = R.drawable.ic_action_week_view;
mWeekView.setNumberOfVisibleDays(1);
// Lets change some dimensions to best fit the view.
mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
mWeekView.setXScrollingSpeed(0.4f);
}
//Go to current date or the one givin in the intent
mWeekView.goToDate((Calendar) this.mShowDate.clone()); //Pass a deep copy, as this method changes the hour to 0
mWeekView.goToHour(this.mShowDate.get(Calendar.HOUR_OF_DAY));
//When called from constructor this member is not yet initialized
if (menuItemSwitchView != null) {
menuItemSwitchView.setIcon(icon);
}
}
/**
* Asynchronous task for exporting the calendar to a local Google calendar
*/
private void exportCalendarToGoogle() {
//Check Calendar permission for Android 6.0
if (!isPermissionGranted(REQUEST_SYNC)) {
return;
}
AsyncTask<Void, Void, Boolean> backgroundTask;
backgroundTask = new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
CalendarManager.syncCalendar(CalendarActivity.this);
return true;
}
@Override
protected void onPostExecute(Boolean result) {
// Informs the user about the ongoing action
if (!CalendarActivity.this.isFinishing()) {
AlertDialog.Builder builder = new AlertDialog.Builder(CalendarActivity.this);
builder.setMessage(CalendarActivity.this.getString(R.string.dialog_show_calendar))
.setPositiveButton(CalendarActivity.this.getString(R.string.yes), CalendarActivity.this)
.setNegativeButton(CalendarActivity.this.getString(R.string.no), CalendarActivity.this).show();
showLoadingEnded();
}
}
@Override
protected void onPreExecute() {
showLoadingStart();
}
};
backgroundTask.execute();
}
/**
* Check Calendar permission for Android 6.0
*
* @param id the request id
* @return If the calendar permission was granted
*/
private boolean isPermissionGranted(int id) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
return true;
} else {
// Provide an additional rationale to the user if the permission was not granted
// and the user would benefit from additional context for the use of the permission.
// For example, if the request has been denied previously.
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CALENDAR) ||
ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_CALENDAR)) {
// Display an AlertDialog with an explanation and a button to trigger the request.
new AlertDialog.Builder(this)
.setMessage(getString(R.string.permission_calendar_explanation))
.setPositiveButton(R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
ActivityCompat
.requestPermissions(CalendarActivity.this, PERMISSIONS_CALENDAR, id);
}
}).show();
} else {
ActivityCompat.requestPermissions(this, PERMISSIONS_CALENDAR, id);
}
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//Check if we got all Calendar permissions
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return;
}
}
//Rerun the interrupted action
if (requestCode == REQUEST_SYNC) {
exportCalendarToGoogle();
} else if (requestCode == REQUEST_DELETE) {
deleteCalendarFromGoogle();
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
displayCalendarOnGoogleCalendar();
}
}
/**
* Starts the Google calendar Activity to display the exported calendar.
*/
private void displayCalendarOnGoogleCalendar() {
// displaying Calendar
Calendar beginTime = Calendar.getInstance();
long startMillis = beginTime.getTimeInMillis();
Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
builder.appendPath("time");
ContentUris.appendId(builder, startMillis);
Intent intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
startActivity(intent);
}
/**
* Async task for deleting the calendar from local Google calendar
*/
private void deleteCalendarFromGoogle() {
//Check Calendar permission for Android 6.0
if (!isPermissionGranted(REQUEST_DELETE)) {
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.dialog_delete_calendar)).setPositiveButton(getString(R.string.yes), new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
int deleted = CalendarManager.deleteLocalCalendar(CalendarActivity.this);
Utils.setInternalSetting(CalendarActivity.this, Const.SYNC_CALENDAR, false);
CalendarActivity.this.invalidateOptionsMenu();
if (deleted > 0) {
Utils.showToast(CalendarActivity.this, R.string.calendar_deleted_toast);
} else {
Utils.showToast(CalendarActivity.this, R.string.calendar_not_existing_toast);
}
}
}).setNegativeButton(getString(R.string.no), null).show();
}
@Override
public List<WeekViewEvent> onMonthChange(int newYear, int newMonth) {
// Populate the week view with the events of the month to display
List<WeekViewEvent> events = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
//Note the (-1), since the calendar starts with month 0, but we get months starting with 1
calendar.set(newYear, newMonth - 1, 1);
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
//Probably refactor this to a good SQL query
for (int curDay = 1; curDay <= daysInMonth; curDay++) {
calendar.set(Calendar.DAY_OF_MONTH, curDay);
Cursor cEvents = calendarManager.getFromDbForDate(new Date(calendar.getTimeInMillis()));
while (cEvents.moveToNext()) {
events.add(new IntegratedCalendarEvent(cEvents));
}
}
return events;
}
/**
* Set up a date time interpreter which will show short date values when in week view and long
* date values otherwise.
*
* @param shortDate True if the date values should be short.
*/
private void setupDateTimeInterpreter(final boolean shortDate) {
mWeekView.setDateTimeInterpreter(new DateTimeInterpreter() {
@Override
public String interpretDate(Calendar date) {
final String weekDayFormat;
if (shortDate) { //Only one character
weekDayFormat = "EEEEE";
} else {
weekDayFormat = "EEEE";
}
SimpleDateFormat weekdayNameFormat = new SimpleDateFormat(weekDayFormat, Locale.getDefault());
String weekday = weekdayNameFormat.format(date.getTime());
String dateString = DateUtils.formatDateTime(getApplicationContext(),
date.getTimeInMillis(), DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_NO_YEAR);
return weekday.toUpperCase(Locale.getDefault()) + ' ' + dateString;
}
@Override
public String interpretTime(int hour) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, 0);
DateFormat hourFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
return hourFormat.format(cal.getTime());
}
});
}
@Override
public void onEventClick(WeekViewEvent weekViewEvent, RectF rectF) {
IntegratedCalendarEvent event = (IntegratedCalendarEvent) weekViewEvent;
Intent i = new Intent(this, RoomFinderDetailsActivity.class);
i.putExtra(RoomFinderDetailsActivity.EXTRA_LOCATION, event.getLocation());
this.startActivity(i);
}
}