1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.calendar.widget; 18 19 import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY; 20 import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; 21 import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; 22 23 import android.app.AlarmManager; 24 import android.app.PendingIntent; 25 import android.appwidget.AppWidgetManager; 26 import android.appwidget.AppWidgetProvider; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.net.Uri; 31 import android.provider.CalendarContract; 32 import android.text.format.DateUtils; 33 import android.text.format.Time; 34 import android.util.Log; 35 import android.widget.RemoteViews; 36 37 import com.android.calendar.AllInOneActivity; 38 import com.android.calendar.EventInfoActivity; 39 import com.android.calendar.R; 40 import com.android.calendar.Utils; 41 42 /** 43 * Simple widget to show next upcoming calendar event. 44 */ 45 public class CalendarAppWidgetProvider extends AppWidgetProvider { 46 static final String TAG = "CalendarAppWidgetProvider"; 47 static final boolean LOGD = false; 48 49 // TODO Move these to Calendar.java 50 static final String EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS"; 51 52 /** 53 * {@inheritDoc} 54 */ 55 @Override onReceive(Context context, Intent intent)56 public void onReceive(Context context, Intent intent) { 57 // Handle calendar-specific updates ourselves because they might be 58 // coming in without extras, which AppWidgetProvider then blocks. 59 final String action = intent.getAction(); 60 if (LOGD) 61 Log.d(TAG, "AppWidgetProvider got the intent: " + intent.toString()); 62 if (Utils.getWidgetUpdateAction(context).equals(action)) { 63 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 64 performUpdate(context, appWidgetManager, 65 appWidgetManager.getAppWidgetIds(getComponentName(context)), 66 null /* no eventIds */); 67 } else if (action != null && (action.equals(Intent.ACTION_PROVIDER_CHANGED) 68 || action.equals(Intent.ACTION_TIME_CHANGED) 69 || action.equals(Intent.ACTION_TIMEZONE_CHANGED) 70 || action.equals(Intent.ACTION_DATE_CHANGED) 71 || action.equals(Utils.getWidgetScheduledUpdateAction(context)))) { 72 Intent service = new Intent(context, CalendarAppWidgetService.class); 73 context.startService(service); 74 } else { 75 super.onReceive(context, intent); 76 } 77 } 78 79 /** 80 * {@inheritDoc} 81 */ 82 @Override onDisabled(Context context)83 public void onDisabled(Context context) { 84 // Unsubscribe from all AlarmManager updates 85 AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 86 PendingIntent pendingUpdate = getUpdateIntent(context); 87 am.cancel(pendingUpdate); 88 } 89 90 /** 91 * {@inheritDoc} 92 */ 93 @Override onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)94 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 95 performUpdate(context, appWidgetManager, appWidgetIds, null /* no eventIds */); 96 } 97 98 99 /** 100 * Build {@link ComponentName} describing this specific 101 * {@link AppWidgetProvider} 102 */ getComponentName(Context context)103 static ComponentName getComponentName(Context context) { 104 return new ComponentName(context, CalendarAppWidgetProvider.class); 105 } 106 107 /** 108 * Process and push out an update for the given appWidgetIds. This call 109 * actually fires an intent to start {@link CalendarAppWidgetService} as a 110 * background service which handles the actual update, to prevent ANR'ing 111 * during database queries. 112 * 113 * @param context Context to use when starting {@link CalendarAppWidgetService}. 114 * @param appWidgetIds List of specific appWidgetIds to update, or null for 115 * all. 116 * @param changedEventIds Specific events known to be changed. If present, 117 * we use it to decide if an update is necessary. 118 */ performUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, long[] changedEventIds)119 private void performUpdate(Context context, 120 AppWidgetManager appWidgetManager, int[] appWidgetIds, 121 long[] changedEventIds) { 122 // Launch over to service so it can perform update 123 for (int appWidgetId : appWidgetIds) { 124 if (LOGD) Log.d(TAG, "Building widget update..."); 125 Intent updateIntent = new Intent(context, CalendarAppWidgetService.class); 126 updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 127 if (changedEventIds != null) { 128 updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds); 129 } 130 updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME))); 131 132 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget); 133 // Calendar header 134 Time time = new Time(Utils.getTimeZone(context, null)); 135 time.setToNow(); 136 long millis = time.toMillis(true); 137 final String dayOfWeek = DateUtils.getDayOfWeekString(time.weekDay + 1, 138 DateUtils.LENGTH_MEDIUM); 139 final String date = Utils.formatDateRange(context, millis, millis, 140 DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE 141 | DateUtils.FORMAT_NO_YEAR); 142 views.setTextViewText(R.id.day_of_week, dayOfWeek); 143 views.setTextViewText(R.id.date, date); 144 // Attach to list of events 145 views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent); 146 appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list); 147 148 149 // Launch calendar app when the user taps on the header 150 final Intent launchCalendarIntent = new Intent(Intent.ACTION_VIEW); 151 launchCalendarIntent.setClass(context, AllInOneActivity.class); 152 launchCalendarIntent 153 .setData(Uri.parse("content://com.android.calendar/time/" + millis)); 154 final PendingIntent launchCalendarPendingIntent = PendingIntent.getActivity( 155 context, 0 /* no requestCode */, launchCalendarIntent, 0 /* no flags */); 156 views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent); 157 158 // Each list item will call setOnClickExtra() to let the list know 159 // which item 160 // is selected by a user. 161 final PendingIntent updateEventIntent = getLaunchPendingIntentTemplate(context); 162 views.setPendingIntentTemplate(R.id.events_list, updateEventIntent); 163 164 appWidgetManager.updateAppWidget(appWidgetId, views); 165 } 166 } 167 168 /** 169 * Build the {@link PendingIntent} used to trigger an update of all calendar 170 * widgets. Uses {@link Utils#getWidgetScheduledUpdateAction(Context)} to 171 * directly target all widgets instead of using 172 * {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}. 173 * 174 * @param context Context to use when building broadcast. 175 */ getUpdateIntent(Context context)176 static PendingIntent getUpdateIntent(Context context) { 177 Intent intent = new Intent(Utils.getWidgetScheduledUpdateAction(context)); 178 intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE); 179 return PendingIntent.getBroadcast(context, 0 /* no requestCode */, intent, 180 0 /* no flags */); 181 } 182 183 /** 184 * Build a {@link PendingIntent} to launch the Calendar app. This should be used 185 * in combination with {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)}. 186 */ getLaunchPendingIntentTemplate(Context context)187 static PendingIntent getLaunchPendingIntentTemplate(Context context) { 188 Intent launchIntent = new Intent(); 189 launchIntent.setAction(Intent.ACTION_VIEW); 190 launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | 191 Intent.FLAG_ACTIVITY_TASK_ON_HOME); 192 launchIntent.setClass(context, AllInOneActivity.class); 193 return PendingIntent.getActivity(context, 0 /* no requestCode */, launchIntent, 194 PendingIntent.FLAG_UPDATE_CURRENT); 195 } 196 197 /** 198 * Build an {@link Intent} available as FillInIntent to launch the Calendar app. 199 * This should be used in combination with 200 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 201 * If the go to time is 0, then calendar will be launched without a starting time. 202 * 203 * @param goToTime time that calendar should take the user to, or 0 to 204 * indicate no specific start time. 205 */ getLaunchFillInIntent(Context context, long id, long start, long end, boolean allDay)206 static Intent getLaunchFillInIntent(Context context, long id, long start, long end, 207 boolean allDay) { 208 final Intent fillInIntent = new Intent(); 209 String dataString = "content://com.android.calendar/events"; 210 if (id != 0) { 211 fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true); 212 fillInIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | 213 Intent.FLAG_ACTIVITY_TASK_ON_HOME); 214 215 dataString += "/" + id; 216 // If we have an event id - start the event info activity 217 fillInIntent.setClass(context, EventInfoActivity.class); 218 } else { 219 // If we do not have an event id - start AllInOne 220 fillInIntent.setClass(context, AllInOneActivity.class); 221 } 222 Uri data = Uri.parse(dataString); 223 fillInIntent.setData(data); 224 fillInIntent.putExtra(EXTRA_EVENT_BEGIN_TIME, start); 225 fillInIntent.putExtra(EXTRA_EVENT_END_TIME, end); 226 fillInIntent.putExtra(EXTRA_EVENT_ALL_DAY, allDay); 227 228 return fillInIntent; 229 } 230 } 231