[DO NOT MERGE] Display missed call count for call log.

The number of unread missed calls are displayed beside the Call Log as a
badge. All unread missed calls are bolded under the call log tab. When
exiting the call log tab, all unread calls are marked as read. When
interacting with an unread missed call card while in the call log tab,
it will subtract 1 from the badge.

Cherry-picked from d118a8e0d8a68499398504f350f81175118e2ecf

BUG=24953555

Change-Id: If91a3bc89197465934cb773955e4bec005d6d14e
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 372c7bd..7c2e80d 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -169,6 +169,9 @@
                 mCurrentlyExpandedPosition = RecyclerView.NO_POSITION;
                 mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM;
             } else {
+                if (viewHolder.callType == CallLog.Calls.MISSED_TYPE) {
+                    CallLogAsyncTaskUtil.markCallAsRead(mContext, viewHolder.callIds);
+                }
                 expandViewHolderActions(viewHolder);
             }
 
@@ -506,7 +509,8 @@
         details.features = getCallFeatures(c, count);
         details.geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
         details.transcription = c.getString(CallLogQuery.TRANSCRIPTION);
-        if (details.callTypes[0] == CallLog.Calls.VOICEMAIL_TYPE) {
+        if (details.callTypes[0] == CallLog.Calls.VOICEMAIL_TYPE ||
+                details.callTypes[0] == CallLog.Calls.MISSED_TYPE) {
             details.isRead = c.getInt(CallLogQuery.IS_READ) == 1;
         }
 
diff --git a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
index df6dc7d..9825918 100644
--- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
+++ b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
@@ -29,6 +29,7 @@
 import android.util.Log;
 
 import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.DialtactsActivity;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.util.AppCompatConstants;
@@ -51,6 +52,7 @@
         DELETE_CALL,
         DELETE_BLOCKED_CALL,
         MARK_VOICEMAIL_READ,
+        MARK_CALL_READ,
         GET_CALL_DETAILS,
         UPDATE_DURATION
     }
@@ -349,8 +351,6 @@
                 Intent intent = new Intent(context, CallLogNotificationsService.class);
                 intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
                 context.startService(intent);
-
-                ((DialtactsActivity) context).updateTabUnreadCounts();
                 return null;
             }
         });
@@ -380,6 +380,37 @@
         });
     }
 
+    public static void markCallAsRead(final Context context, final long[] callIds) {
+        if (!PermissionsUtil.hasPhonePermissions(context)) {
+            return;
+        }
+        if (sAsyncTaskExecutor == null) {
+            initTaskExecutor();
+        }
+
+        sAsyncTaskExecutor.submit(Tasks.MARK_CALL_READ, new AsyncTask<Void, Void, Void>() {
+            @Override
+            public Void doInBackground(Void... params) {
+
+                StringBuilder where = new StringBuilder();
+                where.append(CallLog.Calls.TYPE).append(" = ").append(CallLog.Calls.MISSED_TYPE);
+                where.append(" AND ");
+
+                Long[] callIdLongs = new Long[callIds.length];
+                for (int i = 0; i < callIds.length; i++) {
+                    callIdLongs[i] = callIds[i];
+                }
+                where.append(CallLog.Calls._ID).append(
+                        " IN (" + TextUtils.join(",", callIdLongs) + ")");
+
+                ContentValues values = new ContentValues(1);
+                values.put(CallLog.Calls.IS_READ, "1");
+                context.getContentResolver().update(
+                        CallLog.Calls.CONTENT_URI, values, where.toString(), null);
+                return null;
+            }
+        });
+    }
 
     /**
      * Updates the duration of a voicemail call log entry.
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 41ff7d3..ac8dfda 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -41,6 +41,7 @@
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.R;
+import com.android.dialer.list.ListsFragment;
 import com.android.dialer.util.EmptyLoader;
 import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
 import com.android.dialer.widget.EmptyContentView;
@@ -278,6 +279,9 @@
     public void onVoicemailUnreadCountFetched(Cursor cursor) {}
 
     @Override
+    public void onMissedCallsUnreadCountFetched(Cursor cursor) {}
+
+    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
         View view = inflater.inflate(R.layout.call_log_fragment, container, false);
         setupView(view, null);
@@ -443,7 +447,7 @@
 
             fetchCalls();
             mCallLogQueryHandler.fetchVoicemailStatus();
-
+            mCallLogQueryHandler.fetchMissedCallsUnreadCount();
             updateOnTransition(true /* onEntry */);
             mRefreshDataRequired = false;
         } else {
@@ -467,13 +471,8 @@
             // On either of the transitions we update the missed call and voicemail notifications.
             // While exiting we additionally consume all missed calls (by marking them as read).
             mCallLogQueryHandler.markNewCallsAsOld();
-            if (!onEntry) {
-                mCallLogQueryHandler.markMissedCallsAsRead();
-            }
             if (mCallTypeFilter == Calls.VOICEMAIL_TYPE) {
                 CallLogNotificationsHelper.updateVoicemailNotifications(getActivity());
-            } else {
-                CallLogNotificationsHelper.removeMissedCallNotifications(getActivity());
             }
         }
     }
diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java
index 4cb835b..9ff74cf 100644
--- a/src/com/android/dialer/calllog/CallLogQueryHandler.java
+++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java
@@ -62,6 +62,8 @@
     private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 57;
     /** The token for the query to fetch the number of unread voicemails. */
     private static final int QUERY_VOICEMAIL_UNREAD_COUNT_TOKEN = 58;
+    /** The token for the query to fetch the number of missed calls. */
+    private static final int QUERY_MISSED_CALLS_UNREAD_COUNT_TOKEN = 59;
 
     private final int mLogLimit;
 
@@ -225,19 +227,25 @@
         if (!PermissionsUtil.hasPhonePermissions(mContext)) {
             return;
         }
-        // Mark all "new" calls as not new anymore.
-        StringBuilder where = new StringBuilder();
-        where.append(Calls.IS_READ).append(" = 0");
-        where.append(" AND ");
-        where.append(Calls.TYPE).append(" = ").append(Calls.MISSED_TYPE);
 
         ContentValues values = new ContentValues(1);
         values.put(Calls.IS_READ, "1");
 
         startUpdate(UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN, null, Calls.CONTENT_URI, values,
-                where.toString(), null);
+                getUnreadMissedCallsQuery(), null);
     }
 
+    /** Fetch all missed calls received since last time the tab was opened. */
+    public void fetchMissedCallsUnreadCount() {
+        if (!PermissionsUtil.hasPhonePermissions(mContext)) {
+            return;
+        }
+
+        startQuery(QUERY_MISSED_CALLS_UNREAD_COUNT_TOKEN, null, Calls.CONTENT_URI,
+                new String[]{Calls._ID}, getUnreadMissedCallsQuery(), null, null);
+    }
+
+
     @Override
     protected synchronized void onNotNullableQueryComplete(int token, Object cookie,
             Cursor cursor) {
@@ -253,6 +261,8 @@
                 updateVoicemailStatus(cursor);
             } else if (token == QUERY_VOICEMAIL_UNREAD_COUNT_TOKEN) {
                 updateVoicemailUnreadCount(cursor);
+            } else if (token == QUERY_MISSED_CALLS_UNREAD_COUNT_TOKEN) {
+                updateMissedCallsUnreadCount(cursor);
             } else {
                 Log.w(TAG, "Unknown query completed: ignoring: " + token);
             }
@@ -276,6 +286,17 @@
 
     }
 
+    /**
+     * @return Query string to get all unread missed calls.
+     */
+    private String getUnreadMissedCallsQuery() {
+        StringBuilder where = new StringBuilder();
+        where.append(Calls.IS_READ).append(" = 0");
+        where.append(" AND ");
+        where.append(Calls.TYPE).append(" = ").append(Calls.MISSED_TYPE);
+        return where.toString();
+    }
+
     private void updateVoicemailStatus(Cursor statusCursor) {
         final Listener listener = mListener.get();
         if (listener != null) {
@@ -290,6 +311,13 @@
         }
     }
 
+    private void updateMissedCallsUnreadCount(Cursor statusCursor) {
+        final Listener listener = mListener.get();
+        if (listener != null) {
+            listener.onMissedCallsUnreadCountFetched(statusCursor);
+        }
+    }
+
     /** Listener to completion of various queries. */
     public interface Listener {
         /** Called when {@link CallLogQueryHandler#fetchVoicemailStatus()} completes. */
@@ -298,6 +326,9 @@
         /** Called when {@link CallLogQueryHandler#fetchVoicemailUnreadCount()} completes. */
         void onVoicemailUnreadCountFetched(Cursor cursor);
 
+        /** Called when {@link CallLogQueryHandler#fetchMissedCallsUnreadCount()} completes. */
+        void onMissedCallsUnreadCountFetched(Cursor cursor);
+
         /**
          * Called when {@link CallLogQueryHandler#fetchCalls(int)} complete.
          * Returns true if takes ownership of cursor.
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java
index d5caa1a..9f76148 100644
--- a/src/com/android/dialer/list/ListsFragment.java
+++ b/src/com/android/dialer/list/ListsFragment.java
@@ -192,6 +192,7 @@
         mCallLogQueryHandler =
                 new CallLogQueryHandler(getActivity(), getActivity().getContentResolver(), this);
         mCallLogQueryHandler.fetchVoicemailStatus();
+        mCallLogQueryHandler.fetchMissedCallsUnreadCount();
         Trace.endSection();
     }
 
@@ -347,6 +348,23 @@
     }
 
     @Override
+    public void onMissedCallsUnreadCountFetched(Cursor cursor) {
+        if (getActivity() == null || getActivity().isFinishing() || cursor == null) {
+            return;
+        }
+
+        int count = 0;
+        try {
+            count = cursor.getCount();
+        } finally {
+            cursor.close();
+        }
+
+        mViewPagerTabs.setUnreadCount(count, TAB_INDEX_HISTORY);
+        mViewPagerTabs.updateTab(TAB_INDEX_HISTORY);
+    }
+
+    @Override
     public boolean onCallsFetched(Cursor statusCursor) {
         // Return false; did not take ownership of cursor
         return false;
@@ -361,8 +379,11 @@
      * expands a voicemail in the call log.
      */
     public void updateTabUnreadCounts() {
-        if (mHasActiveVoicemailProvider && mCallLogQueryHandler != null) {
-            mCallLogQueryHandler.fetchVoicemailUnreadCount();
+        if (mCallLogQueryHandler != null) {
+            mCallLogQueryHandler.fetchMissedCallsUnreadCount();
+            if (mHasActiveVoicemailProvider) {
+                mCallLogQueryHandler.fetchVoicemailUnreadCount();
+            }
         }
     }
 
diff --git a/src/com/android/dialer/voicemail/VisualVoicemailEnabledChecker.java b/src/com/android/dialer/voicemail/VisualVoicemailEnabledChecker.java
index 3134b14..80a0368 100644
--- a/src/com/android/dialer/voicemail/VisualVoicemailEnabledChecker.java
+++ b/src/com/android/dialer/voicemail/VisualVoicemailEnabledChecker.java
@@ -91,6 +91,11 @@
     }
 
     @Override
+    public void onMissedCallsUnreadCountFetched(Cursor cursor) {
+        // Do nothing
+    }
+
+    @Override
     public boolean onCallsFetched(Cursor combinedCursor) {
         // Do nothing
         return false;