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.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 //    private static PendingIntent getNewEventPendingIntent(Context context) {
232 //        Intent newEventIntent = new Intent(Intent.ACTION_EDIT);
233 //        newEventIntent.setClass(context, EditEventActivity.class);
234 //        Builder builder = CalendarContract.CONTENT_URI.buildUpon();
235 //        builder.appendPath("events");
236 //        newEventIntent.setData(builder.build());
237 //        return PendingIntent.getActivity(context, 0, newEventIntent,
238 //                PendingIntent.FLAG_UPDATE_CURRENT);
239 //    }
240 }
241