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.CalendarContract.EXTRA_EVENT_ALL_DAY;
20import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
21import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
22
23import android.app.AlarmManager;
24import android.app.PendingIntent;
25import android.appwidget.AppWidgetManager;
26import android.appwidget.AppWidgetProvider;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.net.Uri;
31import android.provider.CalendarContract;
32import android.text.format.DateUtils;
33import android.text.format.Time;
34import android.util.Log;
35import android.widget.RemoteViews;
36
37import com.android.calendar.AllInOneActivity;
38import com.android.calendar.EventInfoActivity;
39import com.android.calendar.R;
40import com.android.calendar.Utils;
41
42/**
43 * Simple widget to show next upcoming calendar event.
44 */
45public 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
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
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
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     */
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     */
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     */
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     */
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     */
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