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);