CalendarAppWidgetProvider.java revision bdbf15078ad5efdf27c021d7aca8c8aa4693878c
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 onEnabled(Context context) {
77        // Enable updates for timezone, date, and provider changes
78        PackageManager pm = context.getPackageManager();
79        pm.setComponentEnabledSetting(
80                new ComponentName(context, CalendarAppWidgetReceiver.class),
81                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
82                PackageManager.DONT_KILL_APP);
83    }
84
85    /**
86     * {@inheritDoc}
87     */
88    @Override
89    public void onDisabled(Context context) {
90        // Unsubscribe from all AlarmManager updates
91        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
92        PendingIntent pendingUpdate = getUpdateIntent(context);
93        am.cancel(pendingUpdate);
94
95        // Disable updates for timezone, date, and provider changes
96        PackageManager pm = context.getPackageManager();
97        pm.setComponentEnabledSetting(
98                new ComponentName(context, CalendarAppWidgetReceiver.class),
99                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
100                PackageManager.DONT_KILL_APP);
101    }
102
103    /**
104     * {@inheritDoc}
105     */
106    @Override
107    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
108        performUpdate(context, appWidgetManager, appWidgetIds, null /* no eventIds */);
109    }
110
111
112    /**
113     * Build {@link ComponentName} describing this specific
114     * {@link AppWidgetProvider}
115     */
116    static ComponentName getComponentName(Context context) {
117        return new ComponentName(context, CalendarAppWidgetProvider.class);
118    }
119
120    /**
121     * Process and push out an update for the given appWidgetIds. This call
122     * actually fires an intent to start {@link CalendarAppWidgetService} as a
123     * background service which handles the actual update, to prevent ANR'ing
124     * during database queries.
125     *
126     * @param context Context to use when starting {@link CalendarAppWidgetService}.
127     * @param appWidgetIds List of specific appWidgetIds to update, or null for
128     *            all.
129     * @param changedEventIds Specific events known to be changed. If present,
130     *            we use it to decide if an update is necessary.
131     */
132    private void performUpdate(Context context,
133            AppWidgetManager appWidgetManager, int[] appWidgetIds,
134            long[] changedEventIds) {
135        // Launch over to service so it can perform update
136        for (int appWidgetId : appWidgetIds) {
137            if (LOGD) Log.d(TAG, "Building widget update...");
138            Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
139            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
140            if (changedEventIds != null) {
141                updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds);
142            }
143            updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)));
144
145            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
146            // Calendar header
147            Time time = new Time();
148            time.setToNow();
149            final String dayOfWeek = DateUtils.getDayOfWeekString(
150                    time.weekDay + 1, DateUtils.LENGTH_MEDIUM);
151            final String month =
152                    DateUtils.getMonthString(time.month, DateUtils.LENGTH_MEDIUM).toUpperCase();
153            views.setTextViewText(R.id.day_of_week, dayOfWeek);
154            final String dayOfMonth = Integer.toString(time.monthDay);
155            views.setTextViewText(R.id.day_of_month, dayOfMonth);
156            views.setTextViewText(R.id.month, month);
157            // Attach to list of events
158            views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent);
159            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list);
160
161
162            // Launch calendar app when the user taps on the header
163            final Intent launchCalendarIntent = new Intent(Intent.ACTION_VIEW);
164            launchCalendarIntent.setData(Uri.parse("content://com.android.calendar/time"));
165            final PendingIntent launchCalendarPendingIntent = PendingIntent.getActivity(context,
166                    0 /* no requestCode */, launchCalendarIntent, 0 /* no flags */);
167            views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent);
168
169            // Each list item will call setOnClickExtra() to let the list know which item
170            // is selected by a user.
171            final PendingIntent updateEventIntent = getLaunchPendingIntentTemplate(context);
172            views.setPendingIntentTemplate(R.id.events_list, updateEventIntent);
173
174            // ImageButton is not a collection so we cannot/shouldn't call
175            // setPendingIntentTemplate().
176            final PendingIntent newEventIntent = getNewEventPendingIntent(context);
177            views.setOnClickPendingIntent(R.id.new_event_button, newEventIntent);
178
179            appWidgetManager.updateAppWidget(appWidgetId, views);
180        }
181    }
182
183    /**
184     * Build the {@link PendingIntent} used to trigger an update of all calendar
185     * widgets. Uses {@link #ACTION_CALENDAR_APPWIDGET_UPDATE} to directly target
186     * all widgets instead of using {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}.
187     *
188     * @param context Context to use when building broadcast.
189     */
190    static PendingIntent getUpdateIntent(Context context) {
191        Intent updateIntent = new Intent(ACTION_CALENDAR_APPWIDGET_UPDATE);
192        updateIntent.setComponent(new ComponentName(context, CalendarAppWidgetProvider.class));
193        return PendingIntent.getBroadcast(context, 0 /* no requestCode */,
194                updateIntent, 0 /* no flags */);
195    }
196
197    /**
198     * Build a {@link PendingIntent} to launch the Calendar app. This should be used
199     * in combination with {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)}.
200     */
201    static PendingIntent getLaunchPendingIntentTemplate(Context context) {
202        Intent launchIntent = new Intent();
203        launchIntent.setAction(Intent.ACTION_VIEW);
204        launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
205                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED |
206                Intent.FLAG_ACTIVITY_CLEAR_TOP);
207        return PendingIntent.getActivity(context, 0 /* no requestCode */,
208                launchIntent,  PendingIntent.FLAG_UPDATE_CURRENT);
209    }
210
211    /**
212     * Build an {@link Intent} available as FillInIntent to launch the Calendar app.
213     * This should be used in combination with
214     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
215     * If the go to time is 0, then calendar will be launched without a starting time.
216     *
217     * @param goToTime time that calendar should take the user to, or 0 to
218     *            indicate no specific start time.
219     */
220    static Intent getLaunchFillInIntent(long id, long start, long end) {
221        final Intent fillInIntent = new Intent();
222        String dataString = "content://com.android.calendar/events";
223        if (id != 0) {
224            fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true);
225            dataString += "/" + id;
226        }
227        Uri data = Uri.parse(dataString);
228        fillInIntent.setData(data);
229        fillInIntent.putExtra(EVENT_BEGIN_TIME, start);
230        fillInIntent.putExtra(EVENT_END_TIME, end);
231
232        return fillInIntent;
233    }
234
235    private static PendingIntent getNewEventPendingIntent(Context context) {
236        Intent newEventIntent = new Intent(Intent.ACTION_EDIT);
237        Builder builder = Calendar.CONTENT_URI.buildUpon();
238        builder.appendPath("events");
239        newEventIntent.setData(builder.build());
240        return PendingIntent.getActivity(context, 0, newEventIntent,
241                PendingIntent.FLAG_UPDATE_CURRENT);
242    }
243}
244