package com.myqsc.mobile2.Timetable.AppWidget;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.widget.RemoteViews;
import com.myqsc.mobile2.R;
import com.myqsc.mobile2.Timetable.Information.Task;
import com.myqsc.mobile2.Timetable.Information.TimetableManager;
import com.myqsc.mobile2.Utility.TimeUtils;
import java.util.Calendar;
import java.util.Iterator;
import java.util.SortedSet;
public abstract class LegacyProvider extends AppWidgetProvider {
// Actions
private static final String ACTION_DATE_INDEX_CHANGED = "com.myqsc.mobile2.Timetable.AppWidget.DATE_INDEX_CHANGED";
private static final String ACTION_UPDATE_TODAY = "com.myqsc.mobile2.Timetable.AppWidget.UPDATE_TODAY";
private static final String ACTION_UPDATE_DATE = "com.myqsc.mobile2.Timetable.AppWidget.UPDATE_DATE";
// Intent extras
private static final String EXTRA_DATE_INDEX = "DATE_INDEX";
// Constants for displaying information
private static final int DATE_COUNT = 7;
private static final int DATE_INDEX_TODAY = 3;
// Delay time for update today
private static final int UPDATE_ALARM_DELAY_MILLISECOND = 1000;
// AppWidget display parameters, supplied by subclasses.
protected abstract int getDisplayedDateCount();
protected abstract int getDisplayedTaskCount();
// Date index for each AppWidget stored in DateIndexManager.
private DateIndexManager getDateIndexManager(Context context) {
return DateIndexManager.getInstance(context, this.getClass(), DATE_INDEX_TODAY);
}
private Calendar getDate(int dateIndex) {
Calendar date = TimeUtils.getDate();
if (dateIndex != DATE_INDEX_TODAY) {
date.add(Calendar.DAY_OF_YEAR, dateIndex - DATE_INDEX_TODAY);
}
return date;
}
// Build and update AppWidget views.
// Get weekday strings for display in AppWidget.
private String getWeekdayString(Context context, Calendar date) {
switch (date.get(Calendar.DAY_OF_WEEK)) {
case Calendar.MONDAY:
return context.getResources().getString(R.string.timetable_appwidget_date_monday);
case Calendar.TUESDAY:
return context.getResources().getString(R.string.timetable_appwidget_date_tuesday);
case Calendar.WEDNESDAY:
return context.getResources().getString(R.string.timetable_appwidget_date_wednesday);
case Calendar.THURSDAY:
return context.getResources().getString(R.string.timetable_appwidget_date_thursday);
case Calendar.FRIDAY:
return context.getResources().getString(R.string.timetable_appwidget_date_friday);
case Calendar.SATURDAY:
return context.getResources().getString(R.string.timetable_appwidget_date_saturday);
case Calendar.SUNDAY:
return context.getResources().getString(R.string.timetable_appwidget_date_sunday);
default:
return null;
}
}
// NOTICE: Always use this.getClass() when building intents in case there are subclasses.
private PendingIntent makeDateChangedPendingIntent(Context context, int appWidgetId, int dateIndex) {
Intent dateChangedIntent = new Intent(context, this.getClass());
dateChangedIntent.setAction(ACTION_DATE_INDEX_CHANGED);
dateChangedIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
dateChangedIntent.putExtra(EXTRA_DATE_INDEX, dateIndex);
// Using requestCode to differentiate pendingIntents.
return PendingIntent.getBroadcast(context, appWidgetId << 16 + dateIndex, dateChangedIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private void setTaskViewColor(RemoteViews taskViews, Context context, int color) {
taskViews.setTextColor(R.id.appwidget_timetable_task_name, context.getResources().getColor(color));
taskViews.setTextColor(R.id.appwidget_timetable_task_detail, context.getResources().getColor(color));
taskViews.setTextColor(R.id.appwidget_timetable_task_time_start, context.getResources().getColor(color));
taskViews.setTextColor(R.id.appwidget_timetable_task_time_end, context.getResources().getColor(color));
}
// NOTE: Color passed to setTaskViewColor() will be resolved there.
@SuppressLint("ResourceAsColor")
private void buildUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
// Get the display specification.
int displayedDateCount = getDisplayedDateCount();
int displayedTaskCount = getDisplayedTaskCount();
// Build AppWidgetView.
RemoteViews appWidgetViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy);
// Remove sub views in case the host recycles the view, while this also removes the loading TextView.
appWidgetViews.removeAllViews(R.id.appwidget_timetable_date_list);
appWidgetViews.removeAllViews(R.id.appwidget_timetable_task_list);
// Add subviews.
RemoteViews subViews;
// Cache the date indexes.
int dateIndex = getDateIndexManager(context).get(appWidgetId);
int dateShownIndexStart = dateIndex - displayedDateCount / 2;
// Set text color and PendingIntent for date navigation view.
// Canceling PendingIntent in case the view is reused causes exception in host.
// Filtering in onDateIndexChanged().
if (dateIndex != 0) {
appWidgetViews.setTextColor(R.id.appwidget_timetable_date_backward, context.getResources().getColor(R.color.white));
appWidgetViews.setOnClickPendingIntent(R.id.appwidget_timetable_date_backward, makeDateChangedPendingIntent(context, appWidgetId, dateIndex - 1));
} else {
appWidgetViews.setTextColor(R.id.appwidget_timetable_date_backward, context.getResources().getColor(R.color.white_fading));
//makeDateChangedPendingIntent(context, appWidgetId, 0).cancel();
}
if (dateIndex != DATE_COUNT - 1) {
appWidgetViews.setTextColor(R.id.appwidget_timetable_date_forward, context.getResources().getColor(R.color.white));
appWidgetViews.setOnClickPendingIntent(R.id.appwidget_timetable_date_forward, makeDateChangedPendingIntent(context, appWidgetId, dateIndex + 1));
} else {
appWidgetViews.setTextColor(R.id.appwidget_timetable_date_forward, context.getResources().getColor(R.color.white_fading));
//makeDateChangedPendingIntent(context, appWidgetId, DATE_COUNT - 1).cancel();
}
// Add dates to view.
for (int i = 0; i != displayedDateCount; ++i) {
subViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy_date);
// Determine whether the date view should be empty or not.
if (dateShownIndexStart + i >= 0 && dateShownIndexStart + i <= DATE_COUNT - 1) {
// Add content to the date view.
if (dateShownIndexStart + i == DATE_INDEX_TODAY) {
subViews.setTextViewText(R.id.appwidget_timetable_date, context.getString(R.string.timetable_appwidget_date_today));
} else {
subViews.setTextViewText(R.id.appwidget_timetable_date, getWeekdayString(context, getDate(dateShownIndexStart + i)));
}
if (i == displayedDateCount / 2) {
subViews.setTextColor(R.id.appwidget_timetable_date, context.getResources().getColor(R.color.white));
} else {
subViews.setTextColor(R.id.appwidget_timetable_date, context.getResources().getColor(R.color.white_fading));
subViews.setOnClickPendingIntent(R.id.appwidget_timetable_date, makeDateChangedPendingIntent(context, appWidgetId, dateShownIndexStart + i));
}
}
appWidgetViews.addView(R.id.appwidget_timetable_date_list, subViews);
}
// Add tasks to view.
SortedSet<Task> timetable = TimetableManager.getInstance(context).getTimetable(getDate(dateIndex));
if (timetable.size() == 0) {
// Add no-task view.
subViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy_notask);
appWidgetViews.addView(R.id.appwidget_timetable_task_list, subViews);
} else {
// Add the first divider.
subViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy_task_list_divider);
subViews.setImageViewResource(R.id.appwidget_timetable_task_list_divider, R.drawable.appwidget_timetable_task_list_divider_normal);
appWidgetViews.addView(R.id.appwidget_timetable_task_list, subViews);
// Add task views.
Iterator<Task> iter = timetable.iterator();
Calendar now = TimeUtils.getNow();
// Auto-scroll the list to show more tasks if today.
if (dateIndex == DATE_INDEX_TODAY) {
Iterator<Task> iter2 = timetable.iterator();
int start = 0;
while (timetable.size() - start > displayedTaskCount && iter2.next().getEndTime().before(now)) {
iter.next();
++start;
}
}
for (int i = 0; i != displayedTaskCount; ++i) {
if (iter.hasNext()) {
Task task = iter.next();
// Add task view.
subViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy_task);
// Add content for task view.
subViews.setTextViewText(R.id.appwidget_timetable_task_name, task.getName());
subViews.setTextViewText(R.id.appwidget_timetable_task_detail, task.getDetail());
subViews.setTextViewText(R.id.appwidget_timetable_task_time_start, TimeUtils.getHourMinuteString(task.getStartTime()));
subViews.setTextViewText(R.id.appwidget_timetable_task_time_end, TimeUtils.getHourMinuteString(task.getEndTime()));
// Set color for today's task view.
// Android Studio prompts incorrectly at present; We just pass color id to our private function and resolve it inside.
if (dateIndex == DATE_INDEX_TODAY) {
if (task.getEndTime().before(now)) {
// Finished
setTaskViewColor(subViews, context, R.color.grey_medium);
} else if (task.getStartTime().after(now)) {
// Scheduled
setTaskViewColor(subViews, context, R.color.grey_dark);
} else {
// Active
setTaskViewColor(subViews, context, R.color.blue_light);
}
} else {
// Default
setTaskViewColor(subViews, context, R.color.grey_dark);
}
// Add to appWidgetViews.
appWidgetViews.addView(R.id.appwidget_timetable_task_list, subViews);
// Add divider.
subViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy_task_list_divider);
subViews.setImageViewResource(R.id.appwidget_timetable_task_list_divider, R.drawable.appwidget_timetable_task_list_divider_normal);
appWidgetViews.addView(R.id.appwidget_timetable_task_list, subViews);
} else {
// Add empty views for spacing in LinearLayout.
// Add default (empty) task view.
subViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy_task);
appWidgetViews.addView(R.id.appwidget_timetable_task_list, subViews);
// Add transparent divider.
subViews = new RemoteViews(context.getPackageName(), R.layout.timetable_appwidget_legacy_task_list_divider);
subViews.setImageViewResource(R.id.appwidget_timetable_task_list_divider, R.drawable.appwidget_timetable_task_list_divider_transparent);
appWidgetViews.addView(R.id.appwidget_timetable_task_list, subViews);
}
}
}
// Update AppWidget.
appWidgetManager.updateAppWidget(appWidgetId, appWidgetViews);
}
// Manage alarms.
// Returns null if no update is needed.
private Calendar getUpdateTime(Context context) {
// Cache now, set the initial value of updateTime to null.
Calendar now = TimeUtils.getNow(), updateTime = null;
// Cache the timetable.
SortedSet<Task> timetable = TimetableManager.getInstance(context).getTimetable(TimeUtils.getDate());
// Traverse the timetable if not empty.
if (!timetable.isEmpty()) {
Iterator<Task> iter = timetable.iterator();
Task task;
Calendar time;
while (iter.hasNext()) {
task = iter.next();
time = task.getStartTime();
if (time.after(now) && (updateTime == null || time.before(updateTime))) {
updateTime = time;
}
time = task.getEndTime();
if (time.after(now) && (updateTime == null || time.before(updateTime))) {
updateTime = time;
}
}
}
return updateTime;
}
// Set or cancel the update today alarm.
// NOTICE: Should be called whenever the DateIndex of any AppWidget is changed or the timetable of today is changed.
private void updateUpdateTodayAlarm(Context context) {
// Build the intent and some others.
Intent updateTodayIntent = new Intent(context, this.getClass());
updateTodayIntent.setAction(ACTION_UPDATE_TODAY);
PendingIntent updateTodayPendingIntent;
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// Check if the alarm should be set.
int[] idsShowingToday = getDateIndexManager(context).getIdsShowingToday();
if (idsShowingToday.length != 0) {
Calendar updateTime = getUpdateTime(context);
if (updateTime != null) {
// Set the alarm and return.
updateTodayIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idsShowingToday);
updateTodayPendingIntent = PendingIntent.getBroadcast(context, 0, updateTodayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC, updateTime.getTimeInMillis() + UPDATE_ALARM_DELAY_MILLISECOND, updateTodayPendingIntent);
} else {
alarmManager.set(AlarmManager.RTC, updateTime.getTimeInMillis() + UPDATE_ALARM_DELAY_MILLISECOND, updateTodayPendingIntent);
}
return;
}
}
// Cancel the alarm
updateTodayPendingIntent = PendingIntent.getBroadcast(context, 0, updateTodayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(updateTodayPendingIntent);
}
// Set the update date alarm.
private void setUpdateDateAlarm(Context context) {
Intent updateDateIntent = new Intent(context, this.getClass());
updateDateIntent.setAction(ACTION_UPDATE_DATE);
PendingIntent updateDatePendingIntent = PendingIntent.getBroadcast(context, 0, updateDateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Calendar updateTime = TimeUtils.getDate();
updateTime.add(Calendar.DAY_OF_YEAR, 1);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC, updateTime.getTimeInMillis() + UPDATE_ALARM_DELAY_MILLISECOND, updateDatePendingIntent);
} else {
alarmManager.set(AlarmManager.RTC, updateTime.getTimeInMillis() + UPDATE_ALARM_DELAY_MILLISECOND, updateDatePendingIntent);
}
}
// Cancel the update date alarm.
private void cancelUpdateDateAlarm(Context context) {
Intent updateDateIntent = new Intent(context, this.getClass());
updateDateIntent.setAction(ACTION_UPDATE_DATE);
PendingIntent updateDatePendingIntent = PendingIntent.getBroadcast(context, 0, updateDateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(updateDatePendingIntent);
}
// Handle actions.
private void onDateIndexChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, int dateIndex) {
// Handle date index change.
DateIndexManager dateIndexManager = getDateIndexManager(context);
// Filter invalid intents cause by the reuse of view.
if (dateIndex == dateIndexManager.get(appWidgetId)) {
return;
}
// Change date index.
getDateIndexManager(context).set(appWidgetId, dateIndex);
// Update view.
buildUpdate(context, appWidgetManager, appWidgetId);
// Update update today alarm because the date index has changed.
updateUpdateTodayAlarm(context);
// Ensure that update date alarm is set.
setUpdateDateAlarm(context);
}
private void onUpdateToday(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// Update every AppWidget given.
for (int appWidgetId : appWidgetIds) {
buildUpdate(context, appWidgetManager, appWidgetId);
}
// Update update today alarm.
updateUpdateTodayAlarm(context);
// Ensure that update date alarm is set.
setUpdateDateAlarm(context);
}
private void onUpdateDate(Context context, AppWidgetManager appWidgetManager) {
// Update every AppWidget.
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, this.getClass()));
for (int appWidgetId : appWidgetIds) {
buildUpdate(context, appWidgetManager, appWidgetId);
}
// Set update date alarm for the next update.
setUpdateDateAlarm(context);
// Update update today alarm because the timetable for today has changed.
updateUpdateTodayAlarm(context);
}
@Override
public void onDisabled(Context context) {
// Clear date index storage.
getDateIndexManager(context).clear();
// Cancel update date alarm.
cancelUpdateDateAlarm(context);
super.onDisabled(context);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// Update every AppWidget given.
for (int appWidgetId : appWidgetIds) {
buildUpdate(context, appWidgetManager, appWidgetId);
}
// Update update today alarm because instances with new date indexes has been added.
updateUpdateTodayAlarm(context);
// Ensure that update date alarm is set.
setUpdateDateAlarm(context);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
DateIndexManager dateIndexManager = getDateIndexManager(context);
// Remove date indexes for deleted AppWidgets
for (int appWidgetId : appWidgetIds) {
dateIndexManager.remove(appWidgetId);
}
// Update update today alarm because instances with date indexes has been removed.
updateUpdateTodayAlarm(context);
super.onDeleted(context, appWidgetIds);
}
// TODO: Handle TimetableManager update broadcast.
// TODO: Handle system date changed.
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_DATE_INDEX_CHANGED.equals(action)) {
onDateIndexChanged(context, AppWidgetManager.getInstance(context), intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID), intent.getIntExtra(EXTRA_DATE_INDEX, 0));
} else if (ACTION_UPDATE_TODAY.equals(action)) {
onUpdateToday(context, AppWidgetManager.getInstance(context), intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS));
} else if (ACTION_UPDATE_DATE.equals(action)) {
onUpdateDate(context, AppWidgetManager.getInstance(context));
} else {
super.onReceive(context, intent);
}
}
}