blob: e9822b1c6d176ed23d9286a9be811a38a71e0120 [file] [log] [blame]
The Android Open Source Project146de362009-03-03 19:32:18 -08001/*
2 * Copyright (C) 2007 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
Mason Tang23e7da32010-07-27 15:24:02 -070017package com.android.calendar.alerts;
18
The Android Open Source Project146de362009-03-03 19:32:18 -080019import android.app.Notification;
The Android Open Source Project146de362009-03-03 19:32:18 -080020import android.app.PendingIntent;
21import android.app.Service;
22import android.content.BroadcastReceiver;
Sara Ting42ba5ef2012-05-04 10:28:56 -070023import android.content.ContentUris;
The Android Open Source Project146de362009-03-03 19:32:18 -080024import android.content.Context;
25import android.content.Intent;
The Android Open Source Project146de362009-03-03 19:32:18 -080026import android.content.res.Resources;
Sara Ting1946e272012-05-07 16:24:44 -070027import android.database.Cursor;
The Android Open Source Project146de362009-03-03 19:32:18 -080028import android.net.Uri;
Sara Ting247a2f12012-05-14 01:07:39 -070029import android.os.Handler;
30import android.os.HandlerThread;
The Android Open Source Project146de362009-03-03 19:32:18 -080031import android.os.PowerManager;
Sara Ting1946e272012-05-07 16:24:44 -070032import android.provider.CalendarContract.Attendees;
33import android.provider.CalendarContract.Calendars;
Sara Ting42ba5ef2012-05-04 10:28:56 -070034import android.provider.CalendarContract.Events;
Sam Blitzstein7e19bf92012-11-13 10:02:41 -080035import android.telephony.TelephonyManager;
36import android.text.Spannable;
Sara Ting4e954452012-04-27 17:27:35 -070037import android.text.SpannableStringBuilder;
Michael Chanea388ed2011-08-22 16:58:53 -070038import android.text.TextUtils;
Sara Tingf6deec22012-05-13 22:52:10 -070039import android.text.style.RelativeSizeSpan;
Sara Ting42ba5ef2012-05-04 10:28:56 -070040import android.text.style.TextAppearanceSpan;
Sam Blitzstein7e19bf92012-11-13 10:02:41 -080041import android.text.style.URLSpan;
Michael Chane2ae1ef2009-11-18 18:37:09 -080042import android.util.Log;
Sara Ting855078e2012-07-20 10:12:39 -070043import android.view.View;
44import android.widget.RemoteViews;
The Android Open Source Project146de362009-03-03 19:32:18 -080045
Michael Chan25b09db2012-05-15 22:44:25 -070046import com.android.calendar.R;
47import com.android.calendar.Utils;
48import com.android.calendar.alerts.AlertService.NotificationWrapper;
49
Sara Ting1946e272012-05-07 16:24:44 -070050import java.util.ArrayList;
Sara Ting4e954452012-04-27 17:27:35 -070051import java.util.List;
Sara Ting477442a2012-05-16 10:34:02 -070052import java.util.regex.Pattern;
RoboErika99feb92011-02-09 18:03:24 -080053
The Android Open Source Project146de362009-03-03 19:32:18 -080054/**
55 * Receives android.intent.action.EVENT_REMINDER intents and handles
Michael Chane2ae1ef2009-11-18 18:37:09 -080056 * event reminders. The intent URI specifies an alert id in the
The Android Open Source Project146de362009-03-03 19:32:18 -080057 * CalendarAlerts database table. This class also receives the
58 * BOOT_COMPLETED intent so that it can add a status bar notification
59 * if there are Calendar event alarms that have not been dismissed.
60 * It also receives the TIME_CHANGED action so that it can fire off
61 * snoozed alarms that have become ready. The real work is done in
62 * the AlertService class.
Michael Chan7321a062011-01-20 20:45:22 -080063 *
64 * To trigger this code after pushing the apk to device:
65 * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER"
66 * -n "com.android.calendar/.alerts.AlertReceiver"
The Android Open Source Project146de362009-03-03 19:32:18 -080067 */
68public class AlertReceiver extends BroadcastReceiver {
Michael Chane2ae1ef2009-11-18 18:37:09 -080069 private static final String TAG = "AlertReceiver";
70
Sara Ting247a2f12012-05-14 01:07:39 -070071 private static final String DELETE_ALL_ACTION = "com.android.calendar.DELETEALL";
Sam Blitzstein7e19bf92012-11-13 10:02:41 -080072 private static final String MAP_ACTION = "com.android.calendar.MAP";
73 private static final String CALL_ACTION = "com.android.calendar.CALL";
Sara Ting247a2f12012-05-14 01:07:39 -070074 private static final String MAIL_ACTION = "com.android.calendar.MAIL";
75 private static final String EXTRA_EVENT_ID = "eventid";
Michael Chane2ae1ef2009-11-18 18:37:09 -080076
Sara Ting3a07a682012-10-31 13:19:38 -070077 // The broadcast for notification refreshes scheduled by the app. This is to
78 // distinguish the EVENT_REMINDER broadcast sent by the provider.
79 public static final String EVENT_REMINDER_APP_ACTION =
80 "com.android.calendar.EVENT_REMINDER_APP";
81
The Android Open Source Project146de362009-03-03 19:32:18 -080082 static final Object mStartingServiceSync = new Object();
83 static PowerManager.WakeLock mStartingService;
Sara Ting477442a2012-05-16 10:34:02 -070084 private static final Pattern mBlankLinePattern = Pattern.compile("^\\s*$[\n\r]",
85 Pattern.MULTILINE);
Michael Chane2ae1ef2009-11-18 18:37:09 -080086
RoboErik43ffa462011-10-31 16:01:58 -070087 public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders";
Sara Ting4e954452012-04-27 17:27:35 -070088 private static final int NOTIFICATION_DIGEST_MAX_LENGTH = 3;
RoboErik43ffa462011-10-31 16:01:58 -070089
Sam Blitzstein7e19bf92012-11-13 10:02:41 -080090 private static final String GEO_PREFIX = "geo:";
91 private static final String TEL_PREFIX = "tel:";
92 private static final int MAX_NOTIF_ACTIONS = 3;
93
Sara Ting247a2f12012-05-14 01:07:39 -070094 private static Handler sAsyncHandler;
95 static {
96 HandlerThread thr = new HandlerThread("AlertReceiver async");
97 thr.start();
98 sAsyncHandler = new Handler(thr.getLooper());
99 }
100
The Android Open Source Project146de362009-03-03 19:32:18 -0800101 @Override
Sara Ting247a2f12012-05-14 01:07:39 -0700102 public void onReceive(final Context context, final Intent intent) {
Michael Chane2ae1ef2009-11-18 18:37:09 -0800103 if (AlertService.DEBUG) {
Michael Chane2ae1ef2009-11-18 18:37:09 -0800104 Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString());
105 }
Sara Ting247a2f12012-05-14 01:07:39 -0700106 if (DELETE_ALL_ACTION.equals(intent.getAction())) {
Michael Chane2ae1ef2009-11-18 18:37:09 -0800107
The Android Open Source Project146de362009-03-03 19:32:18 -0800108 /* The user has clicked the "Clear All Notifications"
109 * buttons so dismiss all Calendar alerts.
110 */
The Android Open Source Project3cfe2e52009-03-13 13:04:24 -0700111 // TODO Grab a wake lock here?
Isaac Katzenelson98819072012-03-01 15:58:30 -0800112 Intent serviceIntent = new Intent(context, DismissAlarmsService.class);
The Android Open Source Project3cfe2e52009-03-13 13:04:24 -0700113 context.startService(serviceIntent);
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800114 } else if (MAP_ACTION.equals(intent.getAction())) {
115 // Try starting the map action.
116 // If no map location is found (something changed since the notification was originally
117 // fired), update the notifications to express this change.
118 final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
119 if (eventId != -1) {
120 URLSpan[] urlSpans = getURLSpans(context, eventId);
121 Intent geoIntent = createMapActivityIntent(context, urlSpans);
122 if (geoIntent != null) {
123 // Location was successfully found, so dismiss the shade and start maps.
124 context.startActivity(geoIntent);
125 closeNotificationShade(context);
126 } else {
127 // No location was found, so update all notifications.
128 // Our alert service does not currently allow us to specify only one
129 // specific notification to refresh.
130 AlertService.updateAlertNotification(context);
131 }
132 }
133 } else if (CALL_ACTION.equals(intent.getAction())) {
134 // Try starting the call action.
135 // If no call location is found (something changed since the notification was originally
136 // fired), update the notifications to express this change.
137 final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
138 if (eventId != -1) {
139 URLSpan[] urlSpans = getURLSpans(context, eventId);
140 Intent callIntent = createCallActivityIntent(context, urlSpans);
141 if (callIntent != null) {
142 // Call location was successfully found, so dismiss the shade and start dialer.
143 context.startActivity(callIntent);
144 closeNotificationShade(context);
145 } else {
146 // No call location was found, so update all notifications.
147 // Our alert service does not currently allow us to specify only one
148 // specific notification to refresh.
149 AlertService.updateAlertNotification(context);
150 }
151 }
Sara Ting247a2f12012-05-14 01:07:39 -0700152 } else if (MAIL_ACTION.equals(intent.getAction())) {
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800153 closeNotificationShade(context);
Sara Ting247a2f12012-05-14 01:07:39 -0700154
155 // Now start the email intent.
156 final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
157 if (eventId != -1) {
Michael Chane98dca72012-06-16 08:22:47 -0700158 Intent i = new Intent(context, QuickResponseActivity.class);
159 i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, eventId);
160 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
161 context.startActivity(i);
Sara Ting247a2f12012-05-14 01:07:39 -0700162 }
The Android Open Source Project146de362009-03-03 19:32:18 -0800163 } else {
164 Intent i = new Intent();
165 i.setClass(context, AlertService.class);
166 i.putExtras(intent);
167 i.putExtra("action", intent.getAction());
168 Uri uri = intent.getData();
Michael Chane2ae1ef2009-11-18 18:37:09 -0800169
The Android Open Source Project146de362009-03-03 19:32:18 -0800170 // This intent might be a BOOT_COMPLETED so it might not have a Uri.
171 if (uri != null) {
172 i.putExtra("uri", uri.toString());
The Android Open Source Project146de362009-03-03 19:32:18 -0800173 }
174 beginStartingService(context, i);
175 }
176 }
The Android Open Source Project146de362009-03-03 19:32:18 -0800177
178 /**
179 * Start the service to process the current event notifications, acquiring
180 * the wake lock before returning to ensure that the service will run.
181 */
182 public static void beginStartingService(Context context, Intent intent) {
183 synchronized (mStartingServiceSync) {
184 if (mStartingService == null) {
185 PowerManager pm =
186 (PowerManager)context.getSystemService(Context.POWER_SERVICE);
187 mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
188 "StartingAlertService");
189 mStartingService.setReferenceCounted(false);
190 }
191 mStartingService.acquire();
192 context.startService(intent);
193 }
194 }
Michael Chane2ae1ef2009-11-18 18:37:09 -0800195
The Android Open Source Project146de362009-03-03 19:32:18 -0800196 /**
197 * Called back by the service when it has finished processing notifications,
198 * releasing the wake lock if the service is now stopping.
199 */
200 public static void finishStartingService(Service service, int startId) {
201 synchronized (mStartingServiceSync) {
202 if (mStartingService != null) {
203 if (service.stopSelfResult(startId)) {
204 mStartingService.release();
205 }
206 }
207 }
208 }
The Android Open Source Project146de362009-03-03 19:32:18 -0800209
Sara Ting42ba5ef2012-05-04 10:28:56 -0700210 private static PendingIntent createClickEventIntent(Context context, long eventId,
211 long startMillis, long endMillis, int notificationId) {
212 return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId,
213 "com.android.calendar.CLICK", true);
214 }
215
216 private static PendingIntent createDeleteEventIntent(Context context, long eventId,
217 long startMillis, long endMillis, int notificationId) {
218 return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId,
219 "com.android.calendar.DELETE", false);
220 }
221
222 private static PendingIntent createDismissAlarmsIntent(Context context, long eventId,
223 long startMillis, long endMillis, int notificationId, String action,
224 boolean showEvent) {
225 Intent intent = new Intent();
226 intent.setClass(context, DismissAlarmsService.class);
227 intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId);
228 intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis);
229 intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis);
230 intent.putExtra(AlertUtils.SHOW_EVENT_KEY, showEvent);
231 intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId);
232
233 // Must set a field that affects Intent.filterEquals so that the resulting
234 // PendingIntent will be a unique instance (the 'extras' don't achieve this).
235 // This must be unique for the click event across all reminders (so using
236 // event ID + startTime should be unique). This also must be unique from
237 // the delete event (which also uses DismissAlarmsService).
238 Uri.Builder builder = Events.CONTENT_URI.buildUpon();
239 ContentUris.appendId(builder, eventId);
240 ContentUris.appendId(builder, startMillis);
241 intent.setData(builder.build());
242 intent.setAction(action);
243 return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
244 }
245
246 private static PendingIntent createSnoozeIntent(Context context, long eventId,
247 long startMillis, long endMillis, int notificationId) {
248 Intent intent = new Intent();
249 intent.setClass(context, SnoozeAlarmsService.class);
250 intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId);
251 intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis);
252 intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis);
253 intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId);
254
255 Uri.Builder builder = Events.CONTENT_URI.buildUpon();
256 ContentUris.appendId(builder, eventId);
257 ContentUris.appendId(builder, startMillis);
258 intent.setData(builder.build());
259 return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
260 }
261
Sara Ting567b6262012-05-31 13:45:20 -0700262 private static PendingIntent createAlertActivityIntent(Context context) {
263 Intent clickIntent = new Intent();
264 clickIntent.setClass(context, AlertActivity.class);
265 clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
266 return PendingIntent.getActivity(context, 0, clickIntent,
267 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
268 }
269
Michael Chan25b09db2012-05-15 22:44:25 -0700270 public static NotificationWrapper makeBasicNotification(Context context, String title,
Sara Ting42ba5ef2012-05-04 10:28:56 -0700271 String summaryText, long startMillis, long endMillis, long eventId,
Sara Ting487d52c2012-05-25 10:26:49 -0700272 int notificationId, boolean doPopup, int priority) {
Sara Ting855078e2012-07-20 10:12:39 -0700273 Notification n = buildBasicNotification(new Notification.Builder(context),
274 context, title, summaryText, startMillis, endMillis, eventId, notificationId,
Sara Ting487d52c2012-05-25 10:26:49 -0700275 doPopup, priority, false);
Michael Chan25b09db2012-05-15 22:44:25 -0700276 return new NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700277 }
278
Sara Ting855078e2012-07-20 10:12:39 -0700279 private static Notification buildBasicNotification(Notification.Builder notificationBuilder,
280 Context context, String title, String summaryText, long startMillis, long endMillis,
Sara Ting487d52c2012-05-25 10:26:49 -0700281 long eventId, int notificationId, boolean doPopup, int priority,
Sara Ting855078e2012-07-20 10:12:39 -0700282 boolean addActionButtons) {
Sara Ting42ba5ef2012-05-04 10:28:56 -0700283 Resources resources = context.getResources();
284 if (title == null || title.length() == 0) {
285 title = resources.getString(R.string.no_title_label);
286 }
287
288 // Create an intent triggered by clicking on the status icon, that dismisses the
289 // notification and shows the event.
290 PendingIntent clickIntent = createClickEventIntent(context, eventId, startMillis,
291 endMillis, notificationId);
292
293 // Create a delete intent triggered by dismissing the notification.
294 PendingIntent deleteIntent = createDeleteEventIntent(context, eventId, startMillis,
295 endMillis, notificationId);
296
297 // Create the base notification.
Sara Ting42ba5ef2012-05-04 10:28:56 -0700298 notificationBuilder.setContentTitle(title);
299 notificationBuilder.setContentText(summaryText);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700300 notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar);
301 notificationBuilder.setContentIntent(clickIntent);
302 notificationBuilder.setDeleteIntent(deleteIntent);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700303 if (doPopup) {
Sara Ting567b6262012-05-31 13:45:20 -0700304 notificationBuilder.setFullScreenIntent(createAlertActivityIntent(context), true);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700305 }
306
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800307 PendingIntent mapIntent = null, callIntent = null, snoozeIntent = null, emailIntent = null;
Sara Ting855078e2012-07-20 10:12:39 -0700308 if (addActionButtons) {
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800309 // Send map, call, and email intent back to ourself first for a couple reasons:
310 // 1) Workaround issue where clicking action button in notification does
311 // not automatically close the notification shade.
312 // 2) Event information will always be up to date.
313
314 // Create map and/or call intents.
315 URLSpan[] urlSpans = getURLSpans(context, eventId);
316 mapIntent = createMapBroadcastIntent(context, urlSpans, eventId);
317 callIntent = createCallBroadcastIntent(context, urlSpans, eventId);
Sara Ting855078e2012-07-20 10:12:39 -0700318
319 // Create email intent for emailing attendees.
Sara Tingaf589fb2012-09-20 14:18:32 -0700320 emailIntent = createBroadcastMailIntent(context, eventId, title);
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800321
322 // Create snooze intent. TODO: change snooze to 10 minutes.
323 snoozeIntent = createSnoozeIntent(context, eventId, startMillis, endMillis,
324 notificationId);
Sara Tingaf589fb2012-09-20 14:18:32 -0700325 }
Sara Ting487d52c2012-05-25 10:26:49 -0700326
Sara Tingfac2d152012-05-31 14:59:57 -0700327 if (Utils.isJellybeanOrLater()) {
Sara Tingaf589fb2012-09-20 14:18:32 -0700328 // Turn off timestamp.
329 notificationBuilder.setWhen(0);
330
Sara Ting487d52c2012-05-25 10:26:49 -0700331 // Should be one of the values in Notification (ie. Notification.PRIORITY_HIGH, etc).
332 // A higher priority will encourage notification manager to expand it.
333 notificationBuilder.setPriority(priority);
Sara Ting855078e2012-07-20 10:12:39 -0700334
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800335 // Add action buttons. Show at most three, using the following priority ordering:
336 // 1. Map
337 // 2. Call
338 // 3. Email
339 // 4. Snooze
340 // Actions will only be shown if they are applicable; i.e. with no location, map will
341 // not be shown, and with no recipients, snooze will not be shown.
342 // TODO: Get icons, get strings. Maybe show preview of actual location/number?
343 int numActions = 0;
344 if (mapIntent != null && numActions < MAX_NOTIF_ACTIONS) {
345 notificationBuilder.addAction(R.drawable.ic_map,
346 resources.getString(R.string.map_label), mapIntent);
347 numActions++;
Sara Tingaf589fb2012-09-20 14:18:32 -0700348 }
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800349 if (callIntent != null && numActions < MAX_NOTIF_ACTIONS) {
350 notificationBuilder.addAction(R.drawable.ic_call,
351 resources.getString(R.string.call_label), callIntent);
352 numActions++;
353 }
354 if (emailIntent != null && numActions < MAX_NOTIF_ACTIONS) {
Sara Tingaf589fb2012-09-20 14:18:32 -0700355 notificationBuilder.addAction(R.drawable.ic_menu_email_holo_dark,
356 resources.getString(R.string.email_guests_label), emailIntent);
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800357 numActions++;
358 }
359 if (snoozeIntent != null && numActions < MAX_NOTIF_ACTIONS) {
360 notificationBuilder.addAction(R.drawable.ic_alarm_holo_dark,
361 resources.getString(R.string.snooze_label), snoozeIntent);
362 numActions++;
Sara Tingaf589fb2012-09-20 14:18:32 -0700363 }
364 return notificationBuilder.getNotification();
Sara Ting855078e2012-07-20 10:12:39 -0700365
Sara Tingaf589fb2012-09-20 14:18:32 -0700366 } else {
367 // Old-style notification (pre-JB). Use custom view with buttons to provide
368 // JB-like functionality (snooze/email).
369 Notification n = notificationBuilder.getNotification();
370
371 // Use custom view with buttons to provide JB-like functionality (snooze/email).
372 RemoteViews contentView = new RemoteViews(context.getPackageName(),
373 R.layout.notification);
374 contentView.setImageViewResource(R.id.image, R.drawable.stat_notify_calendar);
375 contentView.setTextViewText(R.id.title, title);
376 contentView.setTextViewText(R.id.text, summaryText);
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800377
378 int numActions = 0;
379 if (mapIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
380 contentView.setViewVisibility(R.id.map_button, View.GONE);
Sara Tingaf589fb2012-09-20 14:18:32 -0700381 } else {
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800382 contentView.setViewVisibility(R.id.map_button, View.VISIBLE);
383 contentView.setOnClickPendingIntent(R.id.map_button, mapIntent);
Sara Tingaf589fb2012-09-20 14:18:32 -0700384 contentView.setViewVisibility(R.id.end_padding, View.GONE);
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800385 numActions++;
Sara Ting855078e2012-07-20 10:12:39 -0700386 }
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800387 if (callIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
388 contentView.setViewVisibility(R.id.call_button, View.GONE);
389 } else {
390 contentView.setViewVisibility(R.id.call_button, View.VISIBLE);
391 contentView.setOnClickPendingIntent(R.id.call_button, callIntent);
392 contentView.setViewVisibility(R.id.end_padding, View.GONE);
393 numActions++;
394 }
395 if (emailIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
Sara Tingaf589fb2012-09-20 14:18:32 -0700396 contentView.setViewVisibility(R.id.email_button, View.GONE);
397 } else {
398 contentView.setViewVisibility(R.id.email_button, View.VISIBLE);
399 contentView.setOnClickPendingIntent(R.id.email_button, emailIntent);
400 contentView.setViewVisibility(R.id.end_padding, View.GONE);
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800401 numActions++;
Sara Tingaf589fb2012-09-20 14:18:32 -0700402 }
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800403 if (snoozeIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
404 contentView.setViewVisibility(R.id.snooze_button, View.GONE);
405 } else {
406 contentView.setViewVisibility(R.id.snooze_button, View.VISIBLE);
407 contentView.setOnClickPendingIntent(R.id.snooze_button, snoozeIntent);
408 contentView.setViewVisibility(R.id.end_padding, View.GONE);
409 numActions++;
410 }
411
Sara Tingaf589fb2012-09-20 14:18:32 -0700412 n.contentView = contentView;
413
414 return n;
Sara Ting855078e2012-07-20 10:12:39 -0700415 }
Sara Ting42ba5ef2012-05-04 10:28:56 -0700416 }
417
Mason Tangd23299d2010-06-28 10:42:17 -0700418 /**
Sara Ting42ba5ef2012-05-04 10:28:56 -0700419 * Creates an expanding notification. The initial expanded state is decided by
420 * the notification manager based on the priority.
421 */
Michael Chan25b09db2012-05-15 22:44:25 -0700422 public static NotificationWrapper makeExpandingNotification(Context context, String title,
Sara Ting42ba5ef2012-05-04 10:28:56 -0700423 String summaryText, String description, long startMillis, long endMillis, long eventId,
Sara Ting487d52c2012-05-25 10:26:49 -0700424 int notificationId, boolean doPopup, int priority) {
Sara Ting855078e2012-07-20 10:12:39 -0700425 Notification.Builder basicBuilder = new Notification.Builder(context);
426 Notification notification = buildBasicNotification(basicBuilder, context, title,
427 summaryText, startMillis, endMillis, eventId, notificationId, doPopup,
Sara Ting487d52c2012-05-25 10:26:49 -0700428 priority, true);
Sara Tingfac2d152012-05-31 14:59:57 -0700429 if (Utils.isJellybeanOrLater()) {
430 // Create a new-style expanded notification
431 Notification.BigTextStyle expandedBuilder = new Notification.BigTextStyle(
432 basicBuilder);
433 if (description != null) {
434 description = mBlankLinePattern.matcher(description).replaceAll("");
435 description = description.trim();
436 }
437 CharSequence text;
438 if (TextUtils.isEmpty(description)) {
439 text = summaryText;
440 } else {
441 SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
442 stringBuilder.append(summaryText);
443 stringBuilder.append("\n\n");
444 stringBuilder.setSpan(new RelativeSizeSpan(0.5f), summaryText.length(),
445 stringBuilder.length(), 0);
446 stringBuilder.append(description);
447 text = stringBuilder;
448 }
449 expandedBuilder.bigText(text);
Sara Ting855078e2012-07-20 10:12:39 -0700450 notification = expandedBuilder.build();
Sara Tingc90ed732012-05-10 12:44:45 -0700451 }
Sara Ting855078e2012-07-20 10:12:39 -0700452 return new NotificationWrapper(notification, notificationId, eventId, startMillis,
453 endMillis, doPopup);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700454 }
455
456 /**
457 * Creates an expanding digest notification for expired events.
Mason Tangd23299d2010-06-28 10:42:17 -0700458 */
Michael Chan25b09db2012-05-15 22:44:25 -0700459 public static NotificationWrapper makeDigestNotification(Context context,
Sara Ting660f1b42012-05-16 23:40:12 -0700460 ArrayList<AlertService.NotificationInfo> notificationInfos, String digestTitle,
Sara Ting87487242012-05-09 22:16:07 -0700461 boolean expandable) {
Sara Ting4e954452012-04-27 17:27:35 -0700462 if (notificationInfos == null || notificationInfos.size() < 1) {
463 return null;
464 }
465
The Android Open Source Project146de362009-03-03 19:32:18 -0800466 Resources res = context.getResources();
Sara Ting4e954452012-04-27 17:27:35 -0700467 int numEvents = notificationInfos.size();
Sara Ting660f1b42012-05-16 23:40:12 -0700468 long[] eventIds = new long[notificationInfos.size()];
469 for (int i = 0; i < notificationInfos.size(); i++) {
470 eventIds[i] = notificationInfos.get(i).eventId;
471 }
Sara Ting4e954452012-04-27 17:27:35 -0700472
Sara Ting42ba5ef2012-05-04 10:28:56 -0700473 // Create an intent triggered by clicking on the status icon that shows the alerts list.
Sara Ting567b6262012-05-31 13:45:20 -0700474 PendingIntent pendingClickIntent = createAlertActivityIntent(context);
Michael Chane2ae1ef2009-11-18 18:37:09 -0800475
Sara Ting42ba5ef2012-05-04 10:28:56 -0700476 // Create an intent triggered by dismissing the digest notification that clears all
477 // expired events.
The Android Open Source Project146de362009-03-03 19:32:18 -0800478 Intent deleteIntent = new Intent();
Sara Ting42ba5ef2012-05-04 10:28:56 -0700479 deleteIntent.setClass(context, DismissAlarmsService.class);
Sara Ting247a2f12012-05-14 01:07:39 -0700480 deleteIntent.setAction(DELETE_ALL_ACTION);
Sara Ting660f1b42012-05-16 23:40:12 -0700481 deleteIntent.putExtra(AlertUtils.EVENT_IDS_KEY, eventIds);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700482 PendingIntent pendingDeleteIntent = PendingIntent.getService(context, 0, deleteIntent,
483 PendingIntent.FLAG_UPDATE_CURRENT);
Michael Chane2ae1ef2009-11-18 18:37:09 -0800484
Sara Ting4e954452012-04-27 17:27:35 -0700485 if (digestTitle == null || digestTitle.length() == 0) {
486 digestTitle = res.getString(R.string.no_title_label);
The Android Open Source Project146de362009-03-03 19:32:18 -0800487 }
Michael Chane2ae1ef2009-11-18 18:37:09 -0800488
Sara Ting4e954452012-04-27 17:27:35 -0700489 Notification.Builder notificationBuilder = new Notification.Builder(context);
Sara Tingab94b742012-05-15 14:11:12 -0700490 notificationBuilder.setContentText(digestTitle);
Sara Ting5797bd02012-05-17 10:54:02 -0700491 notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar_multiple);
Sara Ting4e954452012-04-27 17:27:35 -0700492 notificationBuilder.setContentIntent(pendingClickIntent);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700493 notificationBuilder.setDeleteIntent(pendingDeleteIntent);
Sara Ting42ba5ef2012-05-04 10:28:56 -0700494 String nEventsStr = res.getQuantityString(R.plurals.Nevents, numEvents, numEvents);
Sara Tingab94b742012-05-15 14:11:12 -0700495 notificationBuilder.setContentTitle(nEventsStr);
Michael Chan7321a062011-01-20 20:45:22 -0800496
Michael Chan25b09db2012-05-15 22:44:25 -0700497 Notification n;
Sara Tingfac2d152012-05-31 14:59:57 -0700498 if (Utils.isJellybeanOrLater()) {
499 // New-style notification...
Michael Chan25b09db2012-05-15 22:44:25 -0700500
Sara Tingfac2d152012-05-31 14:59:57 -0700501 // Set to min priority to encourage the notification manager to collapse it.
502 notificationBuilder.setPriority(Notification.PRIORITY_MIN);
503
504 if (expandable) {
505 // Multiple reminders. Combine into an expanded digest notification.
506 Notification.InboxStyle expandedBuilder = new Notification.InboxStyle(
507 notificationBuilder);
508 int i = 0;
509 for (AlertService.NotificationInfo info : notificationInfos) {
510 if (i < NOTIFICATION_DIGEST_MAX_LENGTH) {
511 String name = info.eventName;
512 if (TextUtils.isEmpty(name)) {
513 name = context.getResources().getString(R.string.no_title_label);
514 }
515 String timeLocation = AlertUtils.formatTimeLocation(context,
516 info.startMillis, info.allDay, info.location);
517
518 TextAppearanceSpan primaryTextSpan = new TextAppearanceSpan(context,
519 R.style.NotificationPrimaryText);
520 TextAppearanceSpan secondaryTextSpan = new TextAppearanceSpan(context,
521 R.style.NotificationSecondaryText);
522
523 // Event title in bold.
524 SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
525 stringBuilder.append(name);
526 stringBuilder.setSpan(primaryTextSpan, 0, stringBuilder.length(), 0);
527 stringBuilder.append(" ");
528
529 // Followed by time and location.
530 int secondaryIndex = stringBuilder.length();
531 stringBuilder.append(timeLocation);
532 stringBuilder.setSpan(secondaryTextSpan, secondaryIndex,
533 stringBuilder.length(), 0);
534 expandedBuilder.addLine(stringBuilder);
535 i++;
536 } else {
537 break;
Sara Ting87487242012-05-09 22:16:07 -0700538 }
Sara Ting87487242012-05-09 22:16:07 -0700539 }
Sara Tingfac2d152012-05-31 14:59:57 -0700540
541 // If there are too many to display, add "+X missed events" for the last line.
542 int remaining = numEvents - i;
543 if (remaining > 0) {
544 String nMoreEventsStr = res.getQuantityString(R.plurals.N_remaining_events,
545 remaining, remaining);
546 // TODO: Add highlighting and icon to this last entry once framework allows it.
547 expandedBuilder.setSummaryText(nMoreEventsStr);
548 }
549
550 // Remove the title in the expanded form (redundant with the listed items).
551 expandedBuilder.setBigContentTitle("");
552
553 n = expandedBuilder.build();
554 } else {
555 n = notificationBuilder.build();
Sara Ting87487242012-05-09 22:16:07 -0700556 }
Sara Ting87487242012-05-09 22:16:07 -0700557 } else {
Sara Tingaf589fb2012-09-20 14:18:32 -0700558 // Old-style notification (pre-JB). We only need a standard notification (no
559 // buttons) but use a custom view so it is consistent with the others.
560 n = notificationBuilder.getNotification();
561
562 // Use custom view with buttons to provide JB-like functionality (snooze/email).
563 RemoteViews contentView = new RemoteViews(context.getPackageName(),
564 R.layout.notification);
565 contentView.setImageViewResource(R.id.image, R.drawable.stat_notify_calendar_multiple);
566 contentView.setTextViewText(R.id.title, nEventsStr);
567 contentView.setTextViewText(R.id.text, digestTitle);
568 contentView.setViewVisibility(R.id.time, View.VISIBLE);
Sam Blitzstein321948d2012-11-28 10:48:21 -0800569 contentView.setViewVisibility(R.id.map_button, View.GONE);
570 contentView.setViewVisibility(R.id.call_button, View.GONE);
Sara Tingaf589fb2012-09-20 14:18:32 -0700571 contentView.setViewVisibility(R.id.email_button, View.GONE);
572 contentView.setViewVisibility(R.id.snooze_button, View.GONE);
573 contentView.setViewVisibility(R.id.end_padding, View.VISIBLE);
574 n.contentView = contentView;
575
576 // Use timestamp to force expired digest notification to the bottom (there is no
577 // priority setting before JB release). This is hidden by the custom view.
578 n.when = 1;
Sara Ting87487242012-05-09 22:16:07 -0700579 }
Michael Chan25b09db2012-05-15 22:44:25 -0700580
581 NotificationWrapper nw = new NotificationWrapper(n);
582 if (AlertService.DEBUG) {
583 for (AlertService.NotificationInfo info : notificationInfos) {
584 nw.add(new NotificationWrapper(null, 0, info.eventId, info.startMillis,
585 info.endMillis, false));
586 }
587 }
588 return nw;
The Android Open Source Project146de362009-03-03 19:32:18 -0800589 }
Sara Ting1946e272012-05-07 16:24:44 -0700590
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800591 private void closeNotificationShade(Context context) {
592 Intent closeNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
593 context.sendBroadcast(closeNotificationShadeIntent);
594 }
595
Sara Ting1946e272012-05-07 16:24:44 -0700596 private static final String[] ATTENDEES_PROJECTION = new String[] {
597 Attendees.ATTENDEE_EMAIL, // 0
598 Attendees.ATTENDEE_STATUS, // 1
599 };
600 private static final int ATTENDEES_INDEX_EMAIL = 0;
601 private static final int ATTENDEES_INDEX_STATUS = 1;
602 private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
603 private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
604 + Attendees.ATTENDEE_EMAIL + " ASC";
605
606 private static final String[] EVENT_PROJECTION = new String[] {
607 Calendars.OWNER_ACCOUNT, // 0
Sara Ting247a2f12012-05-14 01:07:39 -0700608 Calendars.ACCOUNT_NAME, // 1
609 Events.TITLE, // 2
Sara Ting7ffa24c2012-08-28 15:44:37 -0700610 Events.ORGANIZER, // 3
Sara Ting1946e272012-05-07 16:24:44 -0700611 };
612 private static final int EVENT_INDEX_OWNER_ACCOUNT = 0;
613 private static final int EVENT_INDEX_ACCOUNT_NAME = 1;
Sara Ting247a2f12012-05-14 01:07:39 -0700614 private static final int EVENT_INDEX_TITLE = 2;
Sara Ting7ffa24c2012-08-28 15:44:37 -0700615 private static final int EVENT_INDEX_ORGANIZER = 3;
Sara Ting247a2f12012-05-14 01:07:39 -0700616
617 private static Cursor getEventCursor(Context context, long eventId) {
618 return context.getContentResolver().query(
619 ContentUris.withAppendedId(Events.CONTENT_URI, eventId), EVENT_PROJECTION,
620 null, null, null);
621 }
622
623 private static Cursor getAttendeesCursor(Context context, long eventId) {
624 return context.getContentResolver().query(Attendees.CONTENT_URI,
625 ATTENDEES_PROJECTION, ATTENDEES_WHERE, new String[] { Long.toString(eventId) },
626 ATTENDEES_SORT_ORDER);
627 }
628
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800629 private static Cursor getLocationCursor(Context context, long eventId) {
630 return context.getContentResolver().query(
631 ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
632 new String[] { Events.EVENT_LOCATION }, null, null, null);
633 }
634
Sara Ting247a2f12012-05-14 01:07:39 -0700635 /**
636 * Creates a broadcast pending intent that fires to AlertReceiver when the email button
637 * is clicked.
638 */
639 private static PendingIntent createBroadcastMailIntent(Context context, long eventId,
640 String eventTitle) {
641 // Query for viewer account.
642 String syncAccount = null;
643 Cursor eventCursor = getEventCursor(context, eventId);
644 try {
645 if (eventCursor != null && eventCursor.moveToFirst()) {
646 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME);
647 }
648 } finally {
649 if (eventCursor != null) {
650 eventCursor.close();
651 }
652 }
653
654 // Query attendees to see if there are any to email.
655 Cursor attendeesCursor = getAttendeesCursor(context, eventId);
656 try {
657 if (attendeesCursor != null && attendeesCursor.moveToFirst()) {
658 do {
659 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
660 if (Utils.isEmailableFrom(email, syncAccount)) {
Sara Ting247a2f12012-05-14 01:07:39 -0700661 Intent broadcastIntent = new Intent(MAIL_ACTION);
662 broadcastIntent.setClass(context, AlertReceiver.class);
663 broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId);
664 return PendingIntent.getBroadcast(context,
665 Long.valueOf(eventId).hashCode(), broadcastIntent,
666 PendingIntent.FLAG_CANCEL_CURRENT);
667 }
668 } while (attendeesCursor.moveToNext());
669 }
670 return null;
671
672 } finally {
673 if (attendeesCursor != null) {
674 attendeesCursor.close();
675 }
676 }
677 }
Sara Ting1946e272012-05-07 16:24:44 -0700678
679 /**
680 * Creates an Intent for emailing the attendees of the event. Returns null if there
681 * are no emailable attendees.
682 */
Michael Chane98dca72012-06-16 08:22:47 -0700683 static Intent createEmailIntent(Context context, long eventId, String body) {
Sara Ting1946e272012-05-07 16:24:44 -0700684 // TODO: Refactor to move query part into Utils.createEmailAttendeeIntent, to
685 // be shared with EventInfoFragment.
686
687 // Query for the owner account(s).
688 String ownerAccount = null;
689 String syncAccount = null;
Sara Ting247a2f12012-05-14 01:07:39 -0700690 String eventTitle = null;
Sara Ting7ffa24c2012-08-28 15:44:37 -0700691 String eventOrganizer = null;
Sara Ting247a2f12012-05-14 01:07:39 -0700692 Cursor eventCursor = getEventCursor(context, eventId);
693 try {
694 if (eventCursor != null && eventCursor.moveToFirst()) {
695 ownerAccount = eventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
696 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME);
697 eventTitle = eventCursor.getString(EVENT_INDEX_TITLE);
Sara Ting7ffa24c2012-08-28 15:44:37 -0700698 eventOrganizer = eventCursor.getString(EVENT_INDEX_ORGANIZER);
Sara Ting247a2f12012-05-14 01:07:39 -0700699 }
700 } finally {
701 if (eventCursor != null) {
702 eventCursor.close();
703 }
704 }
705 if (TextUtils.isEmpty(eventTitle)) {
706 eventTitle = context.getResources().getString(R.string.no_title_label);
Sara Ting1946e272012-05-07 16:24:44 -0700707 }
708
709 // Query for the attendees.
710 List<String> toEmails = new ArrayList<String>();
711 List<String> ccEmails = new ArrayList<String>();
Sara Ting247a2f12012-05-14 01:07:39 -0700712 Cursor attendeesCursor = getAttendeesCursor(context, eventId);
713 try {
714 if (attendeesCursor != null && attendeesCursor.moveToFirst()) {
715 do {
716 int status = attendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
717 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
718 switch(status) {
719 case Attendees.ATTENDEE_STATUS_DECLINED:
720 addIfEmailable(ccEmails, email, syncAccount);
721 break;
722 default:
723 addIfEmailable(toEmails, email, syncAccount);
724 }
725 } while (attendeesCursor.moveToNext());
726 }
727 } finally {
728 if (attendeesCursor != null) {
729 attendeesCursor.close();
730 }
Sara Ting1946e272012-05-07 16:24:44 -0700731 }
732
Sara Ting7ffa24c2012-08-28 15:44:37 -0700733 // Add organizer only if no attendees to email (the case when too many attendees
734 // in the event to sync or show).
735 if (toEmails.size() == 0 && ccEmails.size() == 0 && eventOrganizer != null) {
736 addIfEmailable(toEmails, eventOrganizer, syncAccount);
737 }
738
Sara Ting1946e272012-05-07 16:24:44 -0700739 Intent intent = null;
740 if (ownerAccount != null && (toEmails.size() > 0 || ccEmails.size() > 0)) {
Michael Chane98dca72012-06-16 08:22:47 -0700741 intent = Utils.createEmailAttendeesIntent(context.getResources(), eventTitle, body,
Sara Ting1946e272012-05-07 16:24:44 -0700742 toEmails, ccEmails, ownerAccount);
743 }
744
745 if (intent == null) {
746 return null;
747 }
748 else {
Sara Ting247a2f12012-05-14 01:07:39 -0700749 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
750 return intent;
Sara Ting1946e272012-05-07 16:24:44 -0700751 }
752 }
753
754 private static void addIfEmailable(List<String> emailList, String email, String syncAccount) {
Sara Ting247a2f12012-05-14 01:07:39 -0700755 if (Utils.isEmailableFrom(email, syncAccount)) {
Sara Ting1946e272012-05-07 16:24:44 -0700756 emailList.add(email);
757 }
758 }
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800759
760 /**
761 * Using the linkify magic, get a list of URLs from the event's location. If no such links
762 * are found, we should end up with a single geo link of the entire string.
763 */
764 private static URLSpan[] getURLSpans(Context context, long eventId) {
765 Cursor locationCursor = getLocationCursor(context, eventId);
766 if (locationCursor != null && locationCursor.moveToFirst()) {
767 String location = locationCursor.getString(0); // Only one item in this cursor.
768 if (location == null || location.isEmpty()) {
769 // Return an empty list if we know there was nothing in the location field.
770 return new URLSpan[0];
771 }
772
Sam Blitzsteindcf6e772012-12-04 16:17:24 -0800773 Spannable text = Utils.extendedLinkify(location, true);
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800774
775 // The linkify method should have found at least one link, at the very least.
776 // If no smart links were found, it should have set the whole string as a geo link.
Sam Blitzstein29dc76a2012-11-19 10:46:54 -0800777 URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
778 return urlSpans;
Sam Blitzstein7e19bf92012-11-13 10:02:41 -0800779 }
780
781 // If no links were found or location was empty, return an empty list.
782 return new URLSpan[0];
783 }
784
785 /**
786 * Create a pending intent to send ourself a broadcast to start maps, using the first map
787 * link available.
788 * If no links are found, return null.
789 */
790 private static PendingIntent createMapBroadcastIntent(Context context, URLSpan[] urlSpans,
791 long eventId) {
792 for (int span_i = 0; span_i < urlSpans.length; span_i++) {
793 URLSpan urlSpan = urlSpans[span_i];
794 String urlString = urlSpan.getURL();
795 if (urlString.startsWith(GEO_PREFIX)) {
796 Intent broadcastIntent = new Intent(MAP_ACTION);
797 broadcastIntent.setClass(context, AlertReceiver.class);
798 broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId);
799 return PendingIntent.getBroadcast(context,
800 Long.valueOf(eventId).hashCode(), broadcastIntent,
801 PendingIntent.FLAG_CANCEL_CURRENT);
802 }
803 }
804
805 // No geo link was found, so return null;
806 return null;
807 }
808
809 /**
810 * Create an intent to take the user to maps, using the first map link available.
811 * If no links are found, return null.
812 */
813 private static Intent createMapActivityIntent(Context context, URLSpan[] urlSpans) {
814 for (int span_i = 0; span_i < urlSpans.length; span_i++) {
815 URLSpan urlSpan = urlSpans[span_i];
816 String urlString = urlSpan.getURL();
817 if (urlString.startsWith(GEO_PREFIX)) {
818 Intent geoIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlString));
819 geoIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
820 return geoIntent;
821 }
822 }
823
824 // No geo link was found, so return null;
825 return null;
826 }
827
828 /**
829 * Create a pending intent to send ourself a broadcast to take the user to dialer, or any other
830 * app capable of making phone calls. Use the first phone number available. If no phone number
831 * is found, or if the device is not capable of making phone calls (i.e. a tablet), return null.
832 */
833 private static PendingIntent createCallBroadcastIntent(Context context, URLSpan[] urlSpans,
834 long eventId) {
835 // Return null if the device is unable to make phone calls.
836 TelephonyManager tm =
837 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
838 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
839 return null;
840 }
841
842 for (int span_i = 0; span_i < urlSpans.length; span_i++) {
843 URLSpan urlSpan = urlSpans[span_i];
844 String urlString = urlSpan.getURL();
845 if (urlString.startsWith(TEL_PREFIX)) {
846 Intent broadcastIntent = new Intent(CALL_ACTION);
847 broadcastIntent.setClass(context, AlertReceiver.class);
848 broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId);
849 return PendingIntent.getBroadcast(context,
850 Long.valueOf(eventId).hashCode(), broadcastIntent,
851 PendingIntent.FLAG_CANCEL_CURRENT);
852 }
853 }
854
855 // No tel link was found, so return null;
856 return null;
857 }
858
859 /**
860 * Create an intent to take the user to dialer, or any other app capable of making phone calls.
861 * Use the first phone number available. If no phone number is found, or if the device is
862 * not capable of making phone calls (i.e. a tablet), return null.
863 */
864 private static Intent createCallActivityIntent(Context context, URLSpan[] urlSpans) {
865 // Return null if the device is unable to make phone calls.
866 TelephonyManager tm =
867 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
868 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
869 return null;
870 }
871
872 for (int span_i = 0; span_i < urlSpans.length; span_i++) {
873 URLSpan urlSpan = urlSpans[span_i];
874 String urlString = urlSpan.getURL();
875 if (urlString.startsWith(TEL_PREFIX)) {
876 Intent callIntent = new Intent(Intent.ACTION_DIAL, Uri.parse(urlString));
877 callIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
878 return callIntent;
879 }
880 }
881
882 // No tel link was found, so return null;
883 return null;
884 }
The Android Open Source Project146de362009-03-03 19:32:18 -0800885}