blob: 83dcc9fc658f0495050dbf8885bc5bc19af860b3 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.calendar.alerts;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PowerManager;
import android.provider.CalendarContract.Attendees;
import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.Events;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import com.android.calendar.R;
import com.android.calendar.Utils;
import com.android.calendar.alerts.AlertService.NotificationWrapper;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* Receives android.intent.action.EVENT_REMINDER intents and handles
* event reminders. The intent URI specifies an alert id in the
* CalendarAlerts database table. This class also receives the
* BOOT_COMPLETED intent so that it can add a status bar notification
* if there are Calendar event alarms that have not been dismissed.
* It also receives the TIME_CHANGED action so that it can fire off
* snoozed alarms that have become ready. The real work is done in
* the AlertService class.
*
* To trigger this code after pushing the apk to device:
* adb shell am broadcast -a "android.intent.action.EVENT_REMINDER"
* -n "com.android.calendar/.alerts.AlertReceiver"
*/
public class AlertReceiver extends BroadcastReceiver {
private static final String TAG = "AlertReceiver";
private static final String MAP_ACTION = "com.android.calendar.MAP";
private static final String CALL_ACTION = "com.android.calendar.CALL";
private static final String MAIL_ACTION = "com.android.calendar.MAIL";
private static final String EXTRA_EVENT_ID = "eventid";
// The broadcast for notification refreshes scheduled by the app. This is to
// distinguish the EVENT_REMINDER broadcast sent by the provider.
public static final String EVENT_REMINDER_APP_ACTION =
"com.android.calendar.EVENT_REMINDER_APP";
static final Object mStartingServiceSync = new Object();
static PowerManager.WakeLock mStartingService;
private static final Pattern mBlankLinePattern = Pattern.compile("^\\s*$[\n\r]",
Pattern.MULTILINE);
public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders";
private static final int NOTIFICATION_DIGEST_MAX_LENGTH = 3;
private static final String GEO_PREFIX = "geo:";
private static final String TEL_PREFIX = "tel:";
private static final int MAX_NOTIF_ACTIONS = 3;
private static Handler sAsyncHandler;
static {
HandlerThread thr = new HandlerThread("AlertReceiver async");
thr.start();
sAsyncHandler = new Handler(thr.getLooper());
}
@Override
public void onReceive(final Context context, final Intent intent) {
if (AlertService.DEBUG) {
Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString());
}
if (MAP_ACTION.equals(intent.getAction())) {
// Try starting the map action.
// If no map location is found (something changed since the notification was originally
// fired), update the notifications to express this change.
final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
if (eventId != -1) {
URLSpan[] urlSpans = getURLSpans(context, eventId);
Intent geoIntent = createMapActivityIntent(context, urlSpans);
if (geoIntent != null) {
// Location was successfully found, so dismiss the shade and start maps.
context.startActivity(geoIntent);
closeNotificationShade(context);
} else {
// No location was found, so update all notifications.
// Our alert service does not currently allow us to specify only one
// specific notification to refresh.
AlertService.updateAlertNotification(context);
}
}
} else if (CALL_ACTION.equals(intent.getAction())) {
// Try starting the call action.
// If no call location is found (something changed since the notification was originally
// fired), update the notifications to express this change.
final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
if (eventId != -1) {
URLSpan[] urlSpans = getURLSpans(context, eventId);
Intent callIntent = createCallActivityIntent(context, urlSpans);
if (callIntent != null) {
// Call location was successfully found, so dismiss the shade and start dialer.
context.startActivity(callIntent);
closeNotificationShade(context);
} else {
// No call location was found, so update all notifications.
// Our alert service does not currently allow us to specify only one
// specific notification to refresh.
AlertService.updateAlertNotification(context);
}
}
} else if (MAIL_ACTION.equals(intent.getAction())) {
closeNotificationShade(context);
// Now start the email intent.
final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
if (eventId != -1) {
Intent i = new Intent(context, QuickResponseActivity.class);
i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, eventId);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
} else {
Intent i = new Intent();
i.setClass(context, AlertService.class);
i.putExtras(intent);
i.putExtra("action", intent.getAction());
Uri uri = intent.getData();
// This intent might be a BOOT_COMPLETED so it might not have a Uri.
if (uri != null) {
i.putExtra("uri", uri.toString());
}
beginStartingService(context, i);
}
}
/**
* Start the service to process the current event notifications, acquiring
* the wake lock before returning to ensure that the service will run.
*/
public static void beginStartingService(Context context, Intent intent) {
synchronized (mStartingServiceSync) {
if (mStartingService == null) {
PowerManager pm =
(PowerManager)context.getSystemService(Context.POWER_SERVICE);
mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"StartingAlertService");
mStartingService.setReferenceCounted(false);
}
mStartingService.acquire();
context.startService(intent);
}
}
/**
* Called back by the service when it has finished processing notifications,
* releasing the wake lock if the service is now stopping.
*/
public static void finishStartingService(Service service, int startId) {
synchronized (mStartingServiceSync) {
if (mStartingService != null) {
if (service.stopSelfResult(startId)) {
mStartingService.release();
}
}
}
}
private static PendingIntent createClickEventIntent(Context context, long eventId,
long startMillis, long endMillis, int notificationId) {
return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId,
DismissAlarmsService.SHOW_ACTION);
}
private static PendingIntent createDeleteEventIntent(Context context, long eventId,
long startMillis, long endMillis, int notificationId) {
return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId,
DismissAlarmsService.DISMISS_ACTION);
}
private static PendingIntent createDismissAlarmsIntent(Context context, long eventId,
long startMillis, long endMillis, int notificationId, String action) {
Intent intent = new Intent();
intent.setClass(context, DismissAlarmsService.class);
intent.setAction(action);
intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId);
intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis);
intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis);
intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId);
// Must set a field that affects Intent.filterEquals so that the resulting
// PendingIntent will be a unique instance (the 'extras' don't achieve this).
// This must be unique for the click event across all reminders (so using
// event ID + startTime should be unique). This also must be unique from
// the delete event (which also uses DismissAlarmsService).
Uri.Builder builder = Events.CONTENT_URI.buildUpon();
ContentUris.appendId(builder, eventId);
ContentUris.appendId(builder, startMillis);
intent.setData(builder.build());
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private static PendingIntent createSnoozeIntent(Context context, long eventId,
long startMillis, long endMillis, int notificationId) {
Intent intent = new Intent();
intent.setClass(context, SnoozeAlarmsService.class);
intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId);
intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis);
intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis);
intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId);
Uri.Builder builder = Events.CONTENT_URI.buildUpon();
ContentUris.appendId(builder, eventId);
ContentUris.appendId(builder, startMillis);
intent.setData(builder.build());
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private static PendingIntent createAlertActivityIntent(Context context) {
Intent clickIntent = new Intent();
clickIntent.setClass(context, AlertActivity.class);
clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return PendingIntent.getActivity(context, 0, clickIntent,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
}
public static NotificationWrapper makeBasicNotification(Context context, String title,
String summaryText, long startMillis, long endMillis, long eventId,
int notificationId, boolean doPopup, int priority) {
Notification n = buildBasicNotification(new Notification.Builder(context),
context, title, summaryText, startMillis, endMillis, eventId, notificationId,
doPopup, priority, false);
return new NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup);
}
private static Notification buildBasicNotification(Notification.Builder notificationBuilder,
Context context, String title, String summaryText, long startMillis, long endMillis,
long eventId, int notificationId, boolean doPopup, int priority,
boolean addActionButtons) {
Resources resources = context.getResources();
if (title == null || title.length() == 0) {
title = resources.getString(R.string.no_title_label);
}
// Create an intent triggered by clicking on the status icon, that dismisses the
// notification and shows the event.
PendingIntent clickIntent = createClickEventIntent(context, eventId, startMillis,
endMillis, notificationId);
// Create a delete intent triggered by dismissing the notification.
PendingIntent deleteIntent = createDeleteEventIntent(context, eventId, startMillis,
endMillis, notificationId);
// Create the base notification.
notificationBuilder.setContentTitle(title);
notificationBuilder.setContentText(summaryText);
notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar);
notificationBuilder.setContentIntent(clickIntent);
notificationBuilder.setDeleteIntent(deleteIntent);
if (doPopup) {
notificationBuilder.setFullScreenIntent(createAlertActivityIntent(context), true);
}
PendingIntent mapIntent = null, callIntent = null, snoozeIntent = null, emailIntent = null;
if (addActionButtons) {
// Send map, call, and email intent back to ourself first for a couple reasons:
// 1) Workaround issue where clicking action button in notification does
// not automatically close the notification shade.
// 2) Event information will always be up to date.
// Create map and/or call intents.
URLSpan[] urlSpans = getURLSpans(context, eventId);
mapIntent = createMapBroadcastIntent(context, urlSpans, eventId);
callIntent = createCallBroadcastIntent(context, urlSpans, eventId);
// Create email intent for emailing attendees.
emailIntent = createBroadcastMailIntent(context, eventId, title);
// Create snooze intent. TODO: change snooze to 10 minutes.
snoozeIntent = createSnoozeIntent(context, eventId, startMillis, endMillis,
notificationId);
}
if (Utils.isJellybeanOrLater()) {
// Turn off timestamp.
notificationBuilder.setWhen(0);
// Should be one of the values in Notification (ie. Notification.PRIORITY_HIGH, etc).
// A higher priority will encourage notification manager to expand it.
notificationBuilder.setPriority(priority);
// Add action buttons. Show at most three, using the following priority ordering:
// 1. Map
// 2. Call
// 3. Email
// 4. Snooze
// Actions will only be shown if they are applicable; i.e. with no location, map will
// not be shown, and with no recipients, snooze will not be shown.
// TODO: Get icons, get strings. Maybe show preview of actual location/number?
int numActions = 0;
if (mapIntent != null && numActions < MAX_NOTIF_ACTIONS) {
notificationBuilder.addAction(R.drawable.ic_map,
resources.getString(R.string.map_label), mapIntent);
numActions++;
}
if (callIntent != null && numActions < MAX_NOTIF_ACTIONS) {
notificationBuilder.addAction(R.drawable.ic_call,
resources.getString(R.string.call_label), callIntent);
numActions++;
}
if (emailIntent != null && numActions < MAX_NOTIF_ACTIONS) {
notificationBuilder.addAction(R.drawable.ic_menu_email_holo_dark,
resources.getString(R.string.email_guests_label), emailIntent);
numActions++;
}
if (snoozeIntent != null && numActions < MAX_NOTIF_ACTIONS) {
notificationBuilder.addAction(R.drawable.ic_alarm_holo_dark,
resources.getString(R.string.snooze_label), snoozeIntent);
numActions++;
}
return notificationBuilder.getNotification();
} else {
// Old-style notification (pre-JB). Use custom view with buttons to provide
// JB-like functionality (snooze/email).
Notification n = notificationBuilder.getNotification();
// Use custom view with buttons to provide JB-like functionality (snooze/email).
RemoteViews contentView = new RemoteViews(context.getPackageName(),
R.layout.notification);
contentView.setImageViewResource(R.id.image, R.drawable.stat_notify_calendar);
contentView.setTextViewText(R.id.title, title);
contentView.setTextViewText(R.id.text, summaryText);
int numActions = 0;
if (mapIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
contentView.setViewVisibility(R.id.map_button, View.GONE);
} else {
contentView.setViewVisibility(R.id.map_button, View.VISIBLE);
contentView.setOnClickPendingIntent(R.id.map_button, mapIntent);
contentView.setViewVisibility(R.id.end_padding, View.GONE);
numActions++;
}
if (callIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
contentView.setViewVisibility(R.id.call_button, View.GONE);
} else {
contentView.setViewVisibility(R.id.call_button, View.VISIBLE);
contentView.setOnClickPendingIntent(R.id.call_button, callIntent);
contentView.setViewVisibility(R.id.end_padding, View.GONE);
numActions++;
}
if (emailIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
contentView.setViewVisibility(R.id.email_button, View.GONE);
} else {
contentView.setViewVisibility(R.id.email_button, View.VISIBLE);
contentView.setOnClickPendingIntent(R.id.email_button, emailIntent);
contentView.setViewVisibility(R.id.end_padding, View.GONE);
numActions++;
}
if (snoozeIntent == null || numActions >= MAX_NOTIF_ACTIONS) {
contentView.setViewVisibility(R.id.snooze_button, View.GONE);
} else {
contentView.setViewVisibility(R.id.snooze_button, View.VISIBLE);
contentView.setOnClickPendingIntent(R.id.snooze_button, snoozeIntent);
contentView.setViewVisibility(R.id.end_padding, View.GONE);
numActions++;
}
n.contentView = contentView;
return n;
}
}
/**
* Creates an expanding notification. The initial expanded state is decided by
* the notification manager based on the priority.
*/
public static NotificationWrapper makeExpandingNotification(Context context, String title,
String summaryText, String description, long startMillis, long endMillis, long eventId,
int notificationId, boolean doPopup, int priority) {
Notification.Builder basicBuilder = new Notification.Builder(context);
Notification notification = buildBasicNotification(basicBuilder, context, title,
summaryText, startMillis, endMillis, eventId, notificationId, doPopup,
priority, true);
if (Utils.isJellybeanOrLater()) {
// Create a new-style expanded notification
Notification.BigTextStyle expandedBuilder = new Notification.BigTextStyle();
if (description != null) {
description = mBlankLinePattern.matcher(description).replaceAll("");
description = description.trim();
}
CharSequence text;
if (TextUtils.isEmpty(description)) {
text = summaryText;
} else {
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
stringBuilder.append(summaryText);
stringBuilder.append("\n\n");
stringBuilder.setSpan(new RelativeSizeSpan(0.5f), summaryText.length(),
stringBuilder.length(), 0);
stringBuilder.append(description);
text = stringBuilder;
}
expandedBuilder.bigText(text);
basicBuilder.setStyle(expandedBuilder);
notification = basicBuilder.build();
}
return new NotificationWrapper(notification, notificationId, eventId, startMillis,
endMillis, doPopup);
}
/**
* Creates an expanding digest notification for expired events.
*/
public static NotificationWrapper makeDigestNotification(Context context,
ArrayList<AlertService.NotificationInfo> notificationInfos, String digestTitle,
boolean expandable) {
if (notificationInfos == null || notificationInfos.size() < 1) {
return null;
}
Resources res = context.getResources();
int numEvents = notificationInfos.size();
long[] eventIds = new long[notificationInfos.size()];
long[] startMillis = new long[notificationInfos.size()];
for (int i = 0; i < notificationInfos.size(); i++) {
eventIds[i] = notificationInfos.get(i).eventId;
startMillis[i] = notificationInfos.get(i).startMillis;
}
// Create an intent triggered by clicking on the status icon that shows the alerts list.
PendingIntent pendingClickIntent = createAlertActivityIntent(context);
// Create an intent triggered by dismissing the digest notification that clears all
// expired events.
Intent deleteIntent = new Intent();
deleteIntent.setClass(context, DismissAlarmsService.class);
deleteIntent.setAction(DismissAlarmsService.DISMISS_ACTION);
deleteIntent.putExtra(AlertUtils.EVENT_IDS_KEY, eventIds);
deleteIntent.putExtra(AlertUtils.EVENT_STARTS_KEY, startMillis);
PendingIntent pendingDeleteIntent = PendingIntent.getService(context, 0, deleteIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
if (digestTitle == null || digestTitle.length() == 0) {
digestTitle = res.getString(R.string.no_title_label);
}
Notification.Builder notificationBuilder = new Notification.Builder(context);
notificationBuilder.setContentText(digestTitle);
notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar_multiple);
notificationBuilder.setContentIntent(pendingClickIntent);
notificationBuilder.setDeleteIntent(pendingDeleteIntent);
String nEventsStr = res.getQuantityString(R.plurals.Nevents, numEvents, numEvents);
notificationBuilder.setContentTitle(nEventsStr);
Notification n;
if (Utils.isJellybeanOrLater()) {
// New-style notification...
// Set to min priority to encourage the notification manager to collapse it.
notificationBuilder.setPriority(Notification.PRIORITY_MIN);
if (expandable) {
// Multiple reminders. Combine into an expanded digest notification.
Notification.InboxStyle expandedBuilder = new Notification.InboxStyle();
int i = 0;
for (AlertService.NotificationInfo info : notificationInfos) {
if (i < NOTIFICATION_DIGEST_MAX_LENGTH) {
String name = info.eventName;
if (TextUtils.isEmpty(name)) {
name = context.getResources().getString(R.string.no_title_label);
}
String timeLocation = AlertUtils.formatTimeLocation(context,
info.startMillis, info.allDay, info.location);
TextAppearanceSpan primaryTextSpan = new TextAppearanceSpan(context,
R.style.NotificationPrimaryText);
TextAppearanceSpan secondaryTextSpan = new TextAppearanceSpan(context,
R.style.NotificationSecondaryText);
// Event title in bold.
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
stringBuilder.append(name);
stringBuilder.setSpan(primaryTextSpan, 0, stringBuilder.length(), 0);
stringBuilder.append(" ");
// Followed by time and location.
int secondaryIndex = stringBuilder.length();
stringBuilder.append(timeLocation);
stringBuilder.setSpan(secondaryTextSpan, secondaryIndex,
stringBuilder.length(), 0);
expandedBuilder.addLine(stringBuilder);
i++;
} else {
break;
}
}
// If there are too many to display, add "+X missed events" for the last line.
int remaining = numEvents - i;
if (remaining > 0) {
String nMoreEventsStr = res.getQuantityString(R.plurals.N_remaining_events,
remaining, remaining);
// TODO: Add highlighting and icon to this last entry once framework allows it.
expandedBuilder.setSummaryText(nMoreEventsStr);
}
// Remove the title in the expanded form (redundant with the listed items).
expandedBuilder.setBigContentTitle("");
notificationBuilder.setStyle(expandedBuilder);
}
n = notificationBuilder.build();
} else {
// Old-style notification (pre-JB). We only need a standard notification (no
// buttons) but use a custom view so it is consistent with the others.
n = notificationBuilder.getNotification();
// Use custom view with buttons to provide JB-like functionality (snooze/email).
RemoteViews contentView = new RemoteViews(context.getPackageName(),
R.layout.notification);
contentView.setImageViewResource(R.id.image, R.drawable.stat_notify_calendar_multiple);
contentView.setTextViewText(R.id.title, nEventsStr);
contentView.setTextViewText(R.id.text, digestTitle);
contentView.setViewVisibility(R.id.time, View.VISIBLE);
contentView.setViewVisibility(R.id.map_button, View.GONE);
contentView.setViewVisibility(R.id.call_button, View.GONE);
contentView.setViewVisibility(R.id.email_button, View.GONE);
contentView.setViewVisibility(R.id.snooze_button, View.GONE);
contentView.setViewVisibility(R.id.end_padding, View.VISIBLE);
n.contentView = contentView;
// Use timestamp to force expired digest notification to the bottom (there is no
// priority setting before JB release). This is hidden by the custom view.
n.when = 1;
}
NotificationWrapper nw = new NotificationWrapper(n);
if (AlertService.DEBUG) {
for (AlertService.NotificationInfo info : notificationInfos) {
nw.add(new NotificationWrapper(null, 0, info.eventId, info.startMillis,
info.endMillis, false));
}
}
return nw;
}
private void closeNotificationShade(Context context) {
Intent closeNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.sendBroadcast(closeNotificationShadeIntent);
}
private static final String[] ATTENDEES_PROJECTION = new String[] {
Attendees.ATTENDEE_EMAIL, // 0
Attendees.ATTENDEE_STATUS, // 1
};
private static final int ATTENDEES_INDEX_EMAIL = 0;
private static final int ATTENDEES_INDEX_STATUS = 1;
private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
+ Attendees.ATTENDEE_EMAIL + " ASC";
private static final String[] EVENT_PROJECTION = new String[] {
Calendars.OWNER_ACCOUNT, // 0
Calendars.ACCOUNT_NAME, // 1
Events.TITLE, // 2
Events.ORGANIZER, // 3
};
private static final int EVENT_INDEX_OWNER_ACCOUNT = 0;
private static final int EVENT_INDEX_ACCOUNT_NAME = 1;
private static final int EVENT_INDEX_TITLE = 2;
private static final int EVENT_INDEX_ORGANIZER = 3;
private static Cursor getEventCursor(Context context, long eventId) {
return context.getContentResolver().query(
ContentUris.withAppendedId(Events.CONTENT_URI, eventId), EVENT_PROJECTION,
null, null, null);
}
private static Cursor getAttendeesCursor(Context context, long eventId) {
return context.getContentResolver().query(Attendees.CONTENT_URI,
ATTENDEES_PROJECTION, ATTENDEES_WHERE, new String[] { Long.toString(eventId) },
ATTENDEES_SORT_ORDER);
}
private static Cursor getLocationCursor(Context context, long eventId) {
return context.getContentResolver().query(
ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
new String[] { Events.EVENT_LOCATION }, null, null, null);
}
/**
* Creates a broadcast pending intent that fires to AlertReceiver when the email button
* is clicked.
*/
private static PendingIntent createBroadcastMailIntent(Context context, long eventId,
String eventTitle) {
// Query for viewer account.
String syncAccount = null;
Cursor eventCursor = getEventCursor(context, eventId);
try {
if (eventCursor != null && eventCursor.moveToFirst()) {
syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME);
}
} finally {
if (eventCursor != null) {
eventCursor.close();
}
}
// Query attendees to see if there are any to email.
Cursor attendeesCursor = getAttendeesCursor(context, eventId);
try {
if (attendeesCursor != null && attendeesCursor.moveToFirst()) {
do {
String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
if (Utils.isEmailableFrom(email, syncAccount)) {
Intent broadcastIntent = new Intent(MAIL_ACTION);
broadcastIntent.setClass(context, AlertReceiver.class);
broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId);
return PendingIntent.getBroadcast(context,
Long.valueOf(eventId).hashCode(), broadcastIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
} while (attendeesCursor.moveToNext());
}
return null;
} finally {
if (attendeesCursor != null) {
attendeesCursor.close();
}
}
}
/**
* Creates an Intent for emailing the attendees of the event. Returns null if there
* are no emailable attendees.
*/
static Intent createEmailIntent(Context context, long eventId, String body) {
// TODO: Refactor to move query part into Utils.createEmailAttendeeIntent, to
// be shared with EventInfoFragment.
// Query for the owner account(s).
String ownerAccount = null;
String syncAccount = null;
String eventTitle = null;
String eventOrganizer = null;
Cursor eventCursor = getEventCursor(context, eventId);
try {
if (eventCursor != null && eventCursor.moveToFirst()) {
ownerAccount = eventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME);
eventTitle = eventCursor.getString(EVENT_INDEX_TITLE);
eventOrganizer = eventCursor.getString(EVENT_INDEX_ORGANIZER);
}
} finally {
if (eventCursor != null) {
eventCursor.close();
}
}
if (TextUtils.isEmpty(eventTitle)) {
eventTitle = context.getResources().getString(R.string.no_title_label);
}
// Query for the attendees.
List<String> toEmails = new ArrayList<String>();
List<String> ccEmails = new ArrayList<String>();
Cursor attendeesCursor = getAttendeesCursor(context, eventId);
try {
if (attendeesCursor != null && attendeesCursor.moveToFirst()) {
do {
int status = attendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
switch(status) {
case Attendees.ATTENDEE_STATUS_DECLINED:
addIfEmailable(ccEmails, email, syncAccount);
break;
default:
addIfEmailable(toEmails, email, syncAccount);
}
} while (attendeesCursor.moveToNext());
}
} finally {
if (attendeesCursor != null) {
attendeesCursor.close();
}
}
// Add organizer only if no attendees to email (the case when too many attendees
// in the event to sync or show).
if (toEmails.size() == 0 && ccEmails.size() == 0 && eventOrganizer != null) {
addIfEmailable(toEmails, eventOrganizer, syncAccount);
}
Intent intent = null;
if (ownerAccount != null && (toEmails.size() > 0 || ccEmails.size() > 0)) {
intent = Utils.createEmailAttendeesIntent(context.getResources(), eventTitle, body,
toEmails, ccEmails, ownerAccount);
}
if (intent == null) {
return null;
}
else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return intent;
}
}
private static void addIfEmailable(List<String> emailList, String email, String syncAccount) {
if (Utils.isEmailableFrom(email, syncAccount)) {
emailList.add(email);
}
}
/**
* Using the linkify magic, get a list of URLs from the event's location. If no such links
* are found, we should end up with a single geo link of the entire string.
*/
private static URLSpan[] getURLSpans(Context context, long eventId) {
Cursor locationCursor = getLocationCursor(context, eventId);
// Default to empty list
URLSpan[] urlSpans = new URLSpan[0];
if (locationCursor != null && locationCursor.moveToFirst()) {
String location = locationCursor.getString(0); // Only one item in this cursor.
if (location != null && !location.isEmpty()) {
Spannable text = Utils.extendedLinkify(location, true);
// The linkify method should have found at least one link, at the very least.
// If no smart links were found, it should have set the whole string as a geo link.
urlSpans = text.getSpans(0, text.length(), URLSpan.class);
}
locationCursor.close();
}
return urlSpans;
}
/**
* Create a pending intent to send ourself a broadcast to start maps, using the first map
* link available.
* If no links are found, return null.
*/
private static PendingIntent createMapBroadcastIntent(Context context, URLSpan[] urlSpans,
long eventId) {
for (int span_i = 0; span_i < urlSpans.length; span_i++) {
URLSpan urlSpan = urlSpans[span_i];
String urlString = urlSpan.getURL();
if (urlString.startsWith(GEO_PREFIX)) {
Intent broadcastIntent = new Intent(MAP_ACTION);
broadcastIntent.setClass(context, AlertReceiver.class);
broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId);
return PendingIntent.getBroadcast(context,
Long.valueOf(eventId).hashCode(), broadcastIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
}
// No geo link was found, so return null;
return null;
}
/**
* Create an intent to take the user to maps, using the first map link available.
* If no links are found, return null.
*/
private static Intent createMapActivityIntent(Context context, URLSpan[] urlSpans) {
for (int span_i = 0; span_i < urlSpans.length; span_i++) {
URLSpan urlSpan = urlSpans[span_i];
String urlString = urlSpan.getURL();
if (urlString.startsWith(GEO_PREFIX)) {
Intent geoIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlString));
geoIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return geoIntent;
}
}
// No geo link was found, so return null;
return null;
}
/**
* Create a pending intent to send ourself a broadcast to take the user to dialer, or any other
* app capable of making phone calls. Use the first phone number available. If no phone number
* is found, or if the device is not capable of making phone calls (i.e. a tablet), return null.
*/
private static PendingIntent createCallBroadcastIntent(Context context, URLSpan[] urlSpans,
long eventId) {
// Return null if the device is unable to make phone calls.
TelephonyManager tm =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
return null;
}
for (int span_i = 0; span_i < urlSpans.length; span_i++) {
URLSpan urlSpan = urlSpans[span_i];
String urlString = urlSpan.getURL();
if (urlString.startsWith(TEL_PREFIX)) {
Intent broadcastIntent = new Intent(CALL_ACTION);
broadcastIntent.setClass(context, AlertReceiver.class);
broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId);
return PendingIntent.getBroadcast(context,
Long.valueOf(eventId).hashCode(), broadcastIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
}
// No tel link was found, so return null;
return null;
}
/**
* Create an intent to take the user to dialer, or any other app capable of making phone calls.
* Use the first phone number available. If no phone number is found, or if the device is
* not capable of making phone calls (i.e. a tablet), return null.
*/
private static Intent createCallActivityIntent(Context context, URLSpan[] urlSpans) {
// Return null if the device is unable to make phone calls.
TelephonyManager tm =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
return null;
}
for (int span_i = 0; span_i < urlSpans.length; span_i++) {
URLSpan urlSpan = urlSpans[span_i];
String urlString = urlSpan.getURL();
if (urlString.startsWith(TEL_PREFIX)) {
Intent callIntent = new Intent(Intent.ACTION_DIAL, Uri.parse(urlString));
callIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return callIntent;
}
}
// No tel link was found, so return null;
return null;
}
}