Move missed-call notifications out of services/Telephony.
Change-Id: I6a54574d95f3d586b22eb515b4a8cc7e6c4abe0c
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 7435747..39df173 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -74,31 +74,19 @@
// Do not check in with VDBG = true, since that may write PII to the system log.
private static final boolean VDBG = false;
- private static final String[] CALL_LOG_PROJECTION = new String[] {
- Calls._ID,
- Calls.NUMBER,
- Calls.NUMBER_PRESENTATION,
- Calls.DATE,
- Calls.DURATION,
- Calls.TYPE,
- };
-
// notification types
- static final int MISSED_CALL_NOTIFICATION = 1;
- static final int IN_CALL_NOTIFICATION = 2;
- static final int MMI_NOTIFICATION = 3;
- static final int NETWORK_SELECTION_NOTIFICATION = 4;
- static final int VOICEMAIL_NOTIFICATION = 5;
- static final int CALL_FORWARD_NOTIFICATION = 6;
- static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 7;
- static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 8;
+ static final int MMI_NOTIFICATION = 1;
+ static final int NETWORK_SELECTION_NOTIFICATION = 2;
+ static final int VOICEMAIL_NOTIFICATION = 3;
+ static final int CALL_FORWARD_NOTIFICATION = 4;
+ static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 5;
+ static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 6;
/** The singleton NotificationMgr instance. */
private static NotificationMgr sInstance;
private PhoneGlobals mApp;
private Phone mPhone;
- private CallManager mCM;
private Context mContext;
private NotificationManager mNotificationManager;
@@ -107,9 +95,6 @@
public StatusBarHelper statusBarHelper;
- // used to track the missed call counter, default to 0.
- private int mNumberMissedCalls = 0;
-
// used to track the notification of selected network unavailable
private boolean mSelectedUnavailableNotify = false;
@@ -118,14 +103,9 @@
private static final int VM_NUMBER_RETRY_DELAY_MILLIS = 10000;
private int mVmNumberRetriesRemaining = MAX_VM_NUMBER_RETRIES;
- // Query used to look up caller-id info for the "call log" notification.
- private QueryHandler mQueryHandler = null;
- private static final int CALL_LOG_TOKEN = -1;
- private static final int CONTACT_TOKEN = -2;
-
/**
* Private constructor (this is a singleton).
- * @see init()
+ * @see #init(PhoneGlobals)
*/
private NotificationMgr(PhoneGlobals app) {
mApp = app;
@@ -135,7 +115,6 @@
mStatusBarManager =
(StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
mPhone = app.phone; // TODO: better style to use mCM.getDefaultPhone() everywhere instead
- mCM = app.mCM;
statusBarHelper = new StatusBarHelper();
}
@@ -151,8 +130,6 @@
synchronized (NotificationMgr.class) {
if (sInstance == null) {
sInstance = new NotificationMgr(app);
- // Update the notifications that need to be touched at startup.
- sInstance.updateNotificationsAtStartup();
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -255,31 +232,6 @@
}
}
- /**
- * Makes sure phone-related notifications are up to date on a
- * freshly-booted device.
- */
- private void updateNotificationsAtStartup() {
- if (DBG) log("updateNotificationsAtStartup()...");
-
- // instantiate query handler
- mQueryHandler = new QueryHandler(mContext.getContentResolver());
-
- // setup query spec, look for all Missed calls that are new.
- StringBuilder where = new StringBuilder("type=");
- where.append(Calls.MISSED_TYPE);
- where.append(" AND new=1");
-
- // start the query
- if (DBG) log("- start call log query...");
- mQueryHandler.startQuery(CALL_LOG_TOKEN, null, Calls.CONTENT_URI, CALL_LOG_PROJECTION,
- where.toString(), null, Calls.DEFAULT_SORT_ORDER);
-
- // Depend on android.app.StatusBarManager to be set to
- // disable(DISABLE_NONE) upon startup. This will be the
- // case even if the phone app crashes.
- }
-
/** The projection to use when querying the phones table */
static final String[] PHONES_PROJECTION = new String[] {
PhoneLookup.NUMBER,
@@ -288,306 +240,6 @@
};
/**
- * Class used to run asynchronous queries to re-populate the notifications we care about.
- * There are really 3 steps to this:
- * 1. Find the list of missed calls
- * 2. For each call, run a query to retrieve the caller's name.
- * 3. For each caller, try obtaining photo.
- */
- private class QueryHandler extends AsyncQueryHandler
- implements ContactsAsyncHelper.OnImageLoadCompleteListener {
-
- /**
- * Used to store relevant fields for the Missed Call
- * notifications.
- */
- private class NotificationInfo {
- public String name;
- public String number;
- public int presentation;
- /**
- * Type of the call. {@link android.provider.CallLog.Calls#INCOMING_TYPE}
- * {@link android.provider.CallLog.Calls#OUTGOING_TYPE}, or
- * {@link android.provider.CallLog.Calls#MISSED_TYPE}.
- */
- public String type;
- public long date;
- }
-
- public QueryHandler(ContentResolver cr) {
- super(cr);
- }
-
- /**
- * Handles the query results.
- */
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- // TODO: it would be faster to use a join here, but for the purposes
- // of this small record set, it should be ok.
-
- // Note that CursorJoiner is not useable here because the number
- // comparisons are not strictly equals; the comparisons happen in
- // the SQL function PHONE_NUMBERS_EQUAL, which is not available for
- // the CursorJoiner.
-
- // Executing our own query is also feasible (with a join), but that
- // will require some work (possibly destabilizing) in Contacts
- // Provider.
-
- // At this point, we will execute subqueries on each row just as
- // CallLogActivity.java does.
- switch (token) {
- case CALL_LOG_TOKEN:
- if (DBG) log("call log query complete.");
-
- // initial call to retrieve the call list.
- if (cursor != null) {
- while (cursor.moveToNext()) {
- // for each call in the call log list, create
- // the notification object and query contacts
- NotificationInfo n = getNotificationInfo (cursor);
-
- if (DBG) log("query contacts for number: " + n.number);
-
- mQueryHandler.startQuery(CONTACT_TOKEN, n,
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, n.number),
- PHONES_PROJECTION, null, null, PhoneLookup.NUMBER);
- }
-
- if (DBG) log("closing call log cursor.");
- cursor.close();
- }
- break;
- case CONTACT_TOKEN:
- if (DBG) log("contact query complete.");
-
- // subqueries to get the caller name.
- if ((cursor != null) && (cookie != null)){
- NotificationInfo n = (NotificationInfo) cookie;
-
- Uri personUri = null;
- if (cursor.moveToFirst()) {
- n.name = cursor.getString(
- cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
- long person_id = cursor.getLong(
- cursor.getColumnIndexOrThrow(PhoneLookup._ID));
- if (DBG) {
- log("contact :" + n.name + " found for phone: " + n.number
- + ". id : " + person_id);
- }
- personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, person_id);
- }
-
- if (personUri != null) {
- if (DBG) {
- log("Start obtaining picture for the missed call. Uri: "
- + personUri);
- }
- // Now try to obtain a photo for this person.
- // ContactsAsyncHelper will do that and call onImageLoadComplete()
- // after that.
- ContactsAsyncHelper.startObtainPhotoAsync(
- 0, mContext, personUri, this, n);
- } else {
- if (DBG) {
- log("Failed to find Uri for obtaining photo."
- + " Just send notification without it.");
- }
- // We couldn't find person Uri, so we're sure we cannot obtain a photo.
- // Call notifyMissedCall() right now.
- notifyMissedCall(n.name, n.number, n.presentation, n.type, null, null,
- n.date);
- }
-
- if (DBG) log("closing contact cursor.");
- cursor.close();
- }
- break;
- default:
- }
- }
-
- @Override
- public void onImageLoadComplete(
- int token, Drawable photo, Bitmap photoIcon, Object cookie) {
- if (DBG) log("Finished loading image: " + photo);
- NotificationInfo n = (NotificationInfo) cookie;
- notifyMissedCall(n.name, n.number, n.presentation, n.type, photo, photoIcon, n.date);
- }
-
- /**
- * Factory method to generate a NotificationInfo object given a
- * cursor from the call log table.
- */
- private final NotificationInfo getNotificationInfo(Cursor cursor) {
- NotificationInfo n = new NotificationInfo();
- n.name = null;
- n.number = cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER));
- n.presentation = cursor.getInt(cursor.getColumnIndexOrThrow(Calls.NUMBER_PRESENTATION));
- n.type = cursor.getString(cursor.getColumnIndexOrThrow(Calls.TYPE));
- n.date = cursor.getLong(cursor.getColumnIndexOrThrow(Calls.DATE));
-
- // make sure we update the number depending upon saved values in
- // CallLog.addCall(). If either special values for unknown or
- // private number are detected, we need to hand off the message
- // to the missed call notification.
- if (n.presentation != Calls.PRESENTATION_ALLOWED) {
- n.number = null;
- }
-
- if (DBG) log("NotificationInfo constructed for number: " + n.number);
-
- return n;
- }
- }
-
- /**
- * Configures a Notification to emit the blinky green message-waiting/
- * missed-call signal.
- */
- private static void configureLedNotification(Notification note) {
- note.flags |= Notification.FLAG_SHOW_LIGHTS;
- note.defaults |= Notification.DEFAULT_LIGHTS;
- }
-
- /**
- * Displays a notification about a missed call.
- *
- * @param name the contact name.
- * @param number the phone number. Note that this may be a non-callable String like "Unknown",
- * or "Private Number", which possibly come from methods like
- * {@link PhoneUtils#modifyForSpecialCnapCases(Context, CallerInfo, String, int)}.
- * @param type the type of the call. {@link android.provider.CallLog.Calls#INCOMING_TYPE}
- * {@link android.provider.CallLog.Calls#OUTGOING_TYPE}, or
- * {@link android.provider.CallLog.Calls#MISSED_TYPE}
- * @param photo picture which may be used for the notification (when photoIcon is null).
- * This also can be null when the picture itself isn't available. If photoIcon is available
- * it should be prioritized (because this may be too huge for notification).
- * See also {@link ContactsAsyncHelper}.
- * @param photoIcon picture which should be used for the notification. Can be null. This is
- * the most suitable for {@link android.app.Notification.Builder#setLargeIcon(Bitmap)}, this
- * should be used when non-null.
- * @param date the time when the missed call happened
- */
- /* package */ void notifyMissedCall(String name, String number, int presentation, String type,
- Drawable photo, Bitmap photoIcon, long date) {
-
- // When the user clicks this notification, we go to the call log.
- final PendingIntent pendingCallLogIntent = PhoneGlobals.createPendingCallLogIntent(
- mContext);
-
- // Never display the missed call notification on non-voice-capable
- // devices, even if the device does somehow manage to get an
- // incoming call.
- if (!PhoneGlobals.sVoiceCapable) {
- if (DBG) log("notifyMissedCall: non-voice-capable device, not posting notification");
- return;
- }
-
- if (VDBG) {
- log("notifyMissedCall(). name: " + name + ", number: " + number
- + ", label: " + type + ", photo: " + photo + ", photoIcon: " + photoIcon
- + ", date: " + date);
- }
-
- // title resource id
- int titleResId;
- // the text in the notification's line 1 and 2.
- String expandedText, callName;
-
- // increment number of missed calls.
- mNumberMissedCalls++;
-
- // get the name for the ticker text
- // i.e. "Missed call from <caller name or number>"
- if (name != null && TextUtils.isGraphic(name)) {
- callName = name;
- } else if (!TextUtils.isEmpty(number)){
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- // A number should always be displayed LTR using {@link BidiFormatter}
- // regardless of the content of the rest of the notification.
- callName = bidiFormatter.unicodeWrap(number, TextDirectionHeuristics.LTR);
- } else {
- // use "unknown" if the caller is unidentifiable.
- callName = mContext.getString(R.string.unknown);
- }
-
- // display the first line of the notification:
- // 1 missed call: call name
- // more than 1 missed call: <number of calls> + "missed calls"
- if (mNumberMissedCalls == 1) {
- titleResId = R.string.notification_missedCallTitle;
- expandedText = callName;
- } else {
- titleResId = R.string.notification_missedCallsTitle;
- expandedText = mContext.getString(R.string.notification_missedCallsMsg,
- mNumberMissedCalls);
- }
-
- Notification.Builder builder = new Notification.Builder(mContext);
- builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
- .setTicker(mContext.getString(R.string.notification_missedCallTicker, callName))
- .setWhen(date)
- .setContentTitle(mContext.getText(titleResId))
- .setContentText(expandedText)
- .setContentIntent(pendingCallLogIntent)
- .setAutoCancel(true)
- .setDeleteIntent(createClearMissedCallsIntent());
-
- // Simple workaround for issue 6476275; refrain having actions when the given number seems
- // not a real one but a non-number which was embedded by methods outside (like
- // PhoneUtils#modifyForSpecialCnapCases()).
- // TODO: consider removing equals() checks here, and modify callers of this method instead.
- if (mNumberMissedCalls == 1
- && !TextUtils.isEmpty(number)
- && (presentation == PhoneConstants.PRESENTATION_ALLOWED ||
- presentation == PhoneConstants.PRESENTATION_PAYPHONE)) {
- if (DBG) log("Add actions with the number " + number);
-
- builder.addAction(R.drawable.stat_sys_phone_call,
- mContext.getString(R.string.notification_missedCall_call_back),
- PhoneGlobals.getCallBackPendingIntent(mContext, number));
-
- builder.addAction(R.drawable.ic_text_holo_dark,
- mContext.getString(R.string.notification_missedCall_message),
- PhoneGlobals.getSendSmsFromNotificationPendingIntent(mContext, number));
-
- if (photoIcon != null) {
- builder.setLargeIcon(photoIcon);
- } else if (photo instanceof BitmapDrawable) {
- builder.setLargeIcon(((BitmapDrawable) photo).getBitmap());
- }
- } else {
- if (DBG) {
- log("Suppress actions. number: " + number + ", missedCalls: " + mNumberMissedCalls);
- }
- }
-
- Notification notification = builder.getNotification();
- configureLedNotification(notification);
- mNotificationManager.notify(MISSED_CALL_NOTIFICATION, notification);
- }
-
- /** Returns an intent to be invoked when the missed call notification is cleared. */
- private PendingIntent createClearMissedCallsIntent() {
- Intent intent = new Intent(mContext, ClearMissedCallsService.class);
- intent.setAction(ClearMissedCallsService.ACTION_CLEAR_MISSED_CALLS);
- return PendingIntent.getService(mContext, 0, intent, 0);
- }
-
- /**
- * Cancels the "missed call" notification.
- *
- * @see ITelephony.cancelMissedCallsNotification()
- */
- void cancelMissedCallNotification() {
- // reset the number of missed calls to 0.
- mNumberMissedCalls = 0;
- mNotificationManager.cancel(MISSED_CALL_NOTIFICATION);
- }
-
- /**
* Updates the message waiting indicator (voicemail) notification.
*
* @param visible true if there are messages waiting
@@ -699,7 +351,6 @@
notification.defaults |= Notification.DEFAULT_VIBRATE;
}
notification.flags |= Notification.FLAG_NO_CLEAR;
- configureLedNotification(notification);
mNotificationManager.notify(VOICEMAIL_NOTIFICATION, notification);
} else {
mNotificationManager.cancel(VOICEMAIL_NOTIFICATION);