CalendarAppWidgetProvider.java revision c46c2dc5dbec57616d799b1d0290d7c827b48d0c
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
17package com.android.calendar.widget;
18
19import static android.provider.Calendar.EVENT_BEGIN_TIME;
20import static android.provider.Calendar.EVENT_END_TIME;
21
22import com.android.calendar.R;
23import com.android.calendar.Utils;
24
25import android.app.AlarmManager;
26import android.app.PendingIntent;
27import android.appwidget.AppWidgetManager;
28import android.appwidget.AppWidgetProvider;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.pm.PackageManager;
33import android.net.Uri;
34import android.net.Uri.Builder;
35import android.provider.Calendar;
36import android.text.format.DateUtils;
37import android.text.format.Time;
38import android.util.Log;
39import android.widget.RemoteViews;
40
41/**
42 * Simple widget to show next upcoming calendar event.
43 */
44public class CalendarAppWidgetProvider extends AppWidgetProvider {
45    static final String TAG = "CalendarAppWidgetProvider";
46    static final boolean LOGD = false;
47
48    static final String ACTION_CALENDAR_APPWIDGET_UPDATE =
49            "com.android.calendar.APPWIDGET_UPDATE";
50
51    // TODO Move these to Calendar.java
52    static final String EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS";
53
54    /**
55     * {@inheritDoc}
56     */
57    @Override
58    public void onReceive(Context context, Intent intent) {
59        // Handle calendar-specific updates ourselves because they might be
60        // coming in without extras, which AppWidgetProvider then blocks.
61        final String action = intent.getAction();
62        if (ACTION_CALENDAR_APPWIDGET_UPDATE.equals(action)) {
63            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
64            performUpdate(context, appWidgetManager,
65                    appWidgetManager.getAppWidgetIds(getComponentName(context)),
66                    null /* no eventIds */);
67        } else {
68            super.onReceive(context, intent);
69        }
70    }
71
72    /**
73     * {@inheritDoc}
74     */
75    @Override
76    public void onDisabled(Context context) {
77        // Unsubscribe from all AlarmManager updates
78        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
79        PendingIntent pendingUpdate = getUpdateIntent(context);
80        am.cancel(pendingUpdate);
81    }
82
83    /**
84     * {@inheritDoc}
85     */
86    @Override
87    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
88        performUpdate(context, appWidgetManager, appWidgetIds, null /* no eventIds */);
89    }
90
91
92    /**
93     * Build {@link ComponentName} describing this specific
94     * {@link AppWidgetProvider}
95     */
96    static ComponentName getComponentName(Context context) {
97        return new ComponentName(context, CalendarAppWidgetProvider.class);
98    }
99
100    /**
101     * Process and push out an update for the given appWidgetIds. This call
102     * actually fires an intent to start {@link CalendarAppWidgetService} as a
103     * background service which handles the actual update, to prevent ANR'ing
104     * during database queries.
105     *
106     * @param context Context to use when starting {@link CalendarAppWidgetService}.
107     * @param appWidgetIds List of specific appWidgetIds to update, or null for
108     *            all.
109     * @param changedEventIds Specific events known to be changed. If present,
110     *            we use it to decide if an update is necessary.
111     */
112    private void performUpdate(Context context,
113            AppWidgetManager appWidgetManager, int[] appWidgetIds,
114            long[] changedEventIds) {
115        // Launch over to service so it can perform update
116        for (int appWidgetId : appWidgetIds) {
117            if (LOGD) Log.d(TAG, "Building widget update...");
118            Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
119            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
120            if (changedEventIds != null) {
121                updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds);
122            }
123            updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)));
124
125            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
126            // Calendar header
127            Time time = new Time();
128            time.setToNow();
129            final String dayOfWeek = DateUtils.getDayOfWeekString(
130                    time.weekDay + 1, DateUtils.LENGTH_MEDIUM);
131            final String month =
132                    DateUtils.getMonthString(time.month, DateUtils.LENGTH_MEDIUM).toUpperCase();
133            views.setTextViewText(R.id.day_of_week, dayOfWeek);
134            final String dayOfMonth = Integer.toString(time.monthDay);
135            views.setTextViewText(R.id.day_of_month, dayOfMonth);
136            views.setTextViewText(R.id.month, month);
137            // Attach to list of events
138            views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent);
139            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list);
140
141
142            // Launch calendar app when the user taps on the header
143            final Intent launchCalendarIntent = new Intent(Intent.ACTION_VIEW);
144            launchCalendarIntent.setData(Uri.parse("content://com.android.calendar/time"));
145            final PendingIntent launchCalendarPendingIntent = PendingIntent.getActivity(
146                    context, 0 /* no requestCode */, launchCalendarIntent, 0 /* no flags */);
147            views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent);
148
149            // Each list item will call setOnClickExtra() to let the list know
150            // which item
151            // is selected by a user.
152            final PendingIntent updateEventIntent = getLaunchPendingIntentTemplate(context);
153            views.setPendingIntentTemplate(R.id.events_list, updateEventIntent);
154
155            // ImageButton is not a collection so we cannot/shouldn't call
156            // setPendingIntentTemplate().
157            final PendingIntent newEventIntent = getNewEventPendingIntent(context);
158            views.setOnClickPendingIntent(R.id.new_event_button, newEventIntent);
159
160            appWidgetManager.updateAppWidget(appWidgetId, views);
161        }
162    }
163
164    /**
165     * Build the {@link PendingIntent} used to trigger an update of all calendar
166     * widgets. Uses {@link #ACTION_CALENDAR_APPWIDGET_UPDATE} to directly target
167     * all widgets instead of using {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}.
168     *
169     * @param context Context to use when building broadcast.
170     */
171    static PendingIntent getUpdateIntent(Context context) {
172        Intent updateIntent = new Intent(ACTION_CALENDAR_APPWIDGET_UPDATE);
173        updateIntent.setComponent(new ComponentName(context, CalendarAppWidgetProvider.class));
174        return PendingIntent.getBroadcast(context, 0 /* no requestCode */,
175                updateIntent, 0 /* no flags */);
176    }
177
178    /**
179     * Build a {@link PendingIntent} to launch the Calendar app. This should be used
180     * in combination with {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)}.
181     */
182    static PendingIntent getLaunchPendingIntentTemplate(Context context) {
183        Intent launchIntent = new Intent();
184        launchIntent.setAction(Intent.ACTION_VIEW);
185        launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
186                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED |
187                Intent.FLAG_ACTIVITY_CLEAR_TOP);
188        return PendingIntent.getActivity(context, 0 /* no requestCode */,
189                launchIntent,  PendingIntent.FLAG_UPDATE_CURRENT);
190    }
191
192    /**
193     * Build an {@link Intent} available as FillInIntent to launch the Calendar app.
194     * This should be used in combination with
195     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
196     * If the go to time is 0, then calendar will be launched without a starting time.
197     *
198     * @param goToTime time that calendar should take the user to, or 0 to
199     *            indicate no specific start time.
200     */
201    static Intent getLaunchFillInIntent(long id, long start, long end) {
202        final Intent fillInIntent = new Intent();
203        String dataString = "content://com.android.calendar/events";
204        if (id != 0) {
205            fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true);
206            dataString += "/" + id;
207        }
208        Uri data = Uri.parse(dataString);
209        fillInIntent.setData(data);
210        fillInIntent.putExtra(EVENT_BEGIN_TIME, start);
211        fillInIntent.putExtra(EVENT_END_TIME, end);
212
213        return fillInIntent;
214    }
215
216    private static PendingIntent getNewEventPendingIntent(Context context) {
217        Intent newEventIntent = new Intent(Intent.ACTION_EDIT);
218        Builder builder = Calendar.CONTENT_URI.buildUpon();
219        builder.appendPath("events");
220        newEventIntent.setData(builder.build());
221        return PendingIntent.getActivity(context, 0, newEventIntent,
222                PendingIntent.FLAG_UPDATE_CURRENT);
223    }
224}
225