From 838988bf34229b33ed39bb7dc7109427da264c81 Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Fri, 25 Sep 2015 17:12:58 -0700 Subject: Add BLOCKED call type and util to mark in call log. To support call blocking, added BLOCKED call type. Also added utility to be able to mark the most recent call from a number as BLOCKEd in the call log database. Added REJECTED call type as well. This is not presently used within our application, but we want to reserve the integer, anticipating changes in the framework to add this constant. Introduced AppCompConstants, because BLOCKED and REJECTED call type values will not be defined as part of the API on M devices. Change existing call type constants to reference this compatability class. Bug: 24341350 Change-Id: I523ebd8dd1844a3b71a69a14bd38073be5940804 --- res/values/strings.xml | 12 +- .../dialer/calllog/CallLogAsyncTaskUtil.java | 144 ++++++++++++++++----- .../dialer/calllog/CallLogListItemHelper.java | 6 +- .../dialer/calllog/CallLogQueryHandler.java | 3 +- src/com/android/dialer/calllog/CallTypeHelper.java | 35 +++-- .../android/dialer/calllog/CallTypeIconsView.java | 10 +- .../android/dialer/util/AppCompatConstants.java | 30 +++++ .../com/android/dialer/CallDetailActivityTest.java | 3 +- .../android/dialer/calllog/CallLogAdapterTest.java | 19 ++- .../dialer/calllog/CallLogGroupBuilderTest.java | 116 +++++++++++------ .../dialer/calllog/CallLogListItemHelperTest.java | 49 ++++--- .../dialer/calllog/PhoneCallDetailsHelperTest.java | 48 ++++--- .../tests/calllog/FillCallLogTestActivity.java | 17 ++- 13 files changed, 345 insertions(+), 147 deletions(-) create mode 100644 src/com/android/dialer/util/AppCompatConstants.java diff --git a/res/values/strings.xml b/res/values/strings.xml index c8b238f03..216a12a4a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -309,13 +309,13 @@ is already in progress.) --> Add call - + Incoming call - + Outgoing call - + Missed call @@ -330,6 +330,12 @@ Voicemail + + Declined call + + + Blocked call + Incoming calls diff --git a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java index aa994d25a..89932ffce 100644 --- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java +++ b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java @@ -31,6 +31,7 @@ import android.util.Log; import com.android.contacts.common.GeoUtil; import com.android.dialer.DialtactsActivity; import com.android.dialer.PhoneCallDetails; +import com.android.dialer.util.AppCompatConstants; import com.android.dialer.util.AsyncTaskExecutor; import com.android.dialer.util.AsyncTaskExecutors; import com.android.dialer.util.PhoneNumberUtil; @@ -45,6 +46,7 @@ public class CallLogAsyncTaskUtil { public enum Tasks { DELETE_VOICEMAIL, DELETE_CALL, + MARK_BLOCKED, MARK_VOICEMAIL_READ, GET_CALL_DETAILS, } @@ -79,12 +81,31 @@ public class CallLogAsyncTaskUtil { static final int TRANSCRIPTION_COLUMN_INDEX = 11; } + private static class CallLogMarkBlockedQuery { + static final String[] PROJECTION = new String[] { + CallLog.Calls._ID, + CallLog.Calls.DATE + }; + + static final int ID_COLUMN_INDEX = 0; + static final int DATE_COLUMN_INDEX = 1; + } + public interface CallLogAsyncTaskListener { public void onDeleteCall(); public void onDeleteVoicemail(); public void onGetCallDetails(PhoneCallDetails[] details); } + public interface OnCallLogQueryFinishedListener { + public void onQueryFinished(boolean hasEntry); + } + + // Try to identify if a call log entry corresponds to a number which was blocked. We match by + // by comparing its creation time to the time it was added in the InCallUi and seeing if they + // fall within a certain threshold. + private static final int MATCH_BLOCKED_CALL_THRESHOLD_MS = 1500; + private static AsyncTaskExecutor sAsyncTaskExecutor; private static void initTaskExecutor() { @@ -156,8 +177,8 @@ public class CallLogAsyncTaskUtil { boolean isVoicemail = PhoneNumberUtil.isVoicemailNumber(context, accountHandle, number); boolean shouldLookupNumber = PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemail; - ContactInfo info = ContactInfo.EMPTY; + if (shouldLookupNumber) { ContactInfo lookupInfo = contactInfoHelper.lookupNumber(number, countryIso); info = lookupInfo != null ? lookupInfo : ContactInfo.EMPTY; @@ -205,7 +226,7 @@ public class CallLogAsyncTaskUtil { * * @param context The context. * @param callIds String of the callIds to delete from the call log, delimited by commas (","). - * @param callLogAsyncTaskListenerg The listener to invoke after the entries have been deleted. + * @param callLogAsyncTaskListener The listener to invoke after the entries have been deleted. */ public static void deleteCalls( final Context context, @@ -215,26 +236,88 @@ public class CallLogAsyncTaskUtil { initTaskExecutor(); } - sAsyncTaskExecutor.submit(Tasks.DELETE_CALL, - new AsyncTask() { - @Override - public Void doInBackground(Void... params) { - context.getContentResolver().delete( - TelecomUtil.getCallLogUri(context), - CallLog.Calls._ID + " IN (" + callIds + ")", null); - return null; - } + sAsyncTaskExecutor.submit(Tasks.DELETE_CALL, new AsyncTask() { + @Override + public Void doInBackground(Void... params) { + context.getContentResolver().delete( + TelecomUtil.getCallLogUri(context), + CallLog.Calls._ID + " IN (" + callIds + ")", null); + return null; + } - @Override - public void onPostExecute(Void result) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onDeleteCall(); - } + @Override + public void onPostExecute(Void result) { + if (callLogAsyncTaskListener != null) { + callLogAsyncTaskListener.onDeleteCall(); + } + } + }); + } + + /** + * Marks last call made by the number the call type of the specified call as BLOCKED in the call log. + * + * @param context The context. + * @param number Number of which to mark the most recent call as BLOCKED. + * @param timeAddedMs The time the number was added to InCall, in milliseconds. + * @param listener The listener to invoke after looking up for a call log entry matching the + * number and time added. + */ + public static void markCallAsBlocked( + final Context context, + final String number, + final long timeAddedMs, + final OnCallLogQueryFinishedListener listener) { + if (sAsyncTaskExecutor == null) { + initTaskExecutor(); + } + + sAsyncTaskExecutor.submit(Tasks.MARK_BLOCKED, new AsyncTask() { + @Override + public Long doInBackground(Void... params) { + // First, lookup the call log entry of the most recent call with this number. + Cursor cursor = context.getContentResolver().query( + TelecomUtil.getCallLogUri(context), + CallLogMarkBlockedQuery.PROJECTION, + CallLog.Calls.NUMBER + "= ?", + new String[] { number }, + CallLog.Calls.DATE + " DESC LIMIT 1"); + + // If found, return the call log entry id. + if (cursor.moveToFirst()) { + long creationTime = cursor.getLong(CallLogMarkBlockedQuery.DATE_COLUMN_INDEX); + if (timeAddedMs > creationTime + && timeAddedMs - creationTime < MATCH_BLOCKED_CALL_THRESHOLD_MS) { + return cursor.getLong(CallLogMarkBlockedQuery.ID_COLUMN_INDEX); } - }); + } + return (long) -1; + } + + @Override + public void onPostExecute(Long callLogEntryId) { + if (listener != null) { + listener.onQueryFinished(callLogEntryId >= 0); + } + + if (callLogEntryId < 0) { + return; + } + // Then, update that call log entry to have type BLOCKED. + final ContentValues values = new ContentValues(); + values.put(CallLog.Calls.TYPE, AppCompatConstants.CALLS_BLOCKED_TYPE); + + context.getContentResolver().update( + TelecomUtil.getCallLogUri(context), + values, + CallLog.Calls._ID + "= ?", + new String[] { Long.toString(callLogEntryId) }); + } + }); } + public static void markVoicemailAsRead(final Context context, final Uri voicemailUri) { if (sAsyncTaskExecutor == null) { initTaskExecutor(); @@ -266,21 +349,20 @@ public class CallLogAsyncTaskUtil { initTaskExecutor(); } - sAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL, - new AsyncTask() { - @Override - public Void doInBackground(Void... params) { - context.getContentResolver().delete(voicemailUri, null, null); - return null; - } + sAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL, new AsyncTask() { + @Override + public Void doInBackground(Void... params) { + context.getContentResolver().delete(voicemailUri, null, null); + return null; + } - @Override - public void onPostExecute(Void result) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onDeleteVoicemail(); - } - } - }); + @Override + public void onPostExecute(Void result) { + if (callLogAsyncTaskListener != null) { + callLogAsyncTaskListener.onDeleteVoicemail(); + } + } + }); } @VisibleForTesting diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java index d18e274ba..8e45dd36f 100644 --- a/src/com/android/dialer/calllog/CallLogListItemHelper.java +++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java @@ -24,6 +24,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.dialer.PhoneCallDetails; +import com.android.dialer.util.AppCompatConstants; import com.android.dialer.R; /** @@ -220,11 +221,12 @@ import com.android.dialer.R; int lastCallType = getLastCallType(callTypes); int stringID; - if (lastCallType == Calls.VOICEMAIL_TYPE || lastCallType == Calls.MISSED_TYPE) { + if (lastCallType == AppCompatConstants.CALLS_VOICEMAIL_TYPE + || lastCallType == AppCompatConstants.CALLS_MISSED_TYPE) { //Message: Missed call from , , , //. stringID = R.string.description_incoming_missed_call; - } else if (lastCallType == Calls.INCOMING_TYPE) { + } else if (lastCallType == AppCompatConstants.CALLS_INCOMING_TYPE) { //Message: Answered call from , , , //. stringID = R.string.description_incoming_answered_call; diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java index 771df99e2..81e49d2d4 100644 --- a/src/com/android/dialer/calllog/CallLogQueryHandler.java +++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java @@ -36,6 +36,7 @@ import android.util.Log; import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; import com.android.contacts.common.util.PermissionsUtil; +import com.android.dialer.util.AppCompatConstants; import com.android.dialer.util.TelecomUtil; import com.android.dialer.voicemail.VoicemailStatusHelperImpl; @@ -180,7 +181,7 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { selectionArgs.add(Integer.toString(callType)); } else { where.append(" AND NOT "); - where.append("(" + Calls.TYPE + " = " + Calls.VOICEMAIL_TYPE + ")"); + where.append("(" + Calls.TYPE + " = " + AppCompatConstants.CALLS_VOICEMAIL_TYPE + ")"); } if (newerThan > 0) { diff --git a/src/com/android/dialer/calllog/CallTypeHelper.java b/src/com/android/dialer/calllog/CallTypeHelper.java index 36c0975bd..acc114c5c 100644 --- a/src/com/android/dialer/calllog/CallTypeHelper.java +++ b/src/com/android/dialer/calllog/CallTypeHelper.java @@ -17,9 +17,9 @@ package com.android.dialer.calllog; import android.content.res.Resources; -import android.provider.CallLog.Calls; import com.android.dialer.R; +import com.android.dialer.util.AppCompatConstants; /** * Helper class to perform operations related to call types. @@ -39,6 +39,10 @@ public class CallTypeHelper { private final CharSequence mMissedVideoName; /** Name used to identify voicemail calls. */ private final CharSequence mVoicemailName; + /** Name used to identify rejected calls. */ + private final CharSequence mRejectedName; + /** Name used to identify blocked calls. */ + private final CharSequence mBlockedName; /** Color used to identify new missed calls. */ private final int mNewMissedColor; /** Color used to identify new voicemail calls. */ @@ -53,6 +57,8 @@ public class CallTypeHelper { mOutgoingVideoName = resources.getString(R.string.type_outgoing_video); mMissedVideoName = resources.getString(R.string.type_missed_video); mVoicemailName = resources.getString(R.string.type_voicemail); + mRejectedName = resources.getString(R.string.type_rejected); + mBlockedName = resources.getString(R.string.type_blocked); mNewMissedColor = resources.getColor(R.color.call_log_missed_call_highlight_color); mNewVoicemailColor = resources.getColor(R.color.call_log_voicemail_highlight_color); } @@ -60,30 +66,36 @@ public class CallTypeHelper { /** Returns the text used to represent the given call type. */ public CharSequence getCallTypeText(int callType, boolean isVideoCall) { switch (callType) { - case Calls.INCOMING_TYPE: + case AppCompatConstants.CALLS_INCOMING_TYPE: if (isVideoCall) { return mIncomingVideoName; } else { return mIncomingName; } - case Calls.OUTGOING_TYPE: + case AppCompatConstants.CALLS_OUTGOING_TYPE: if (isVideoCall) { return mOutgoingVideoName; } else { return mOutgoingName; } - case Calls.MISSED_TYPE: + case AppCompatConstants.CALLS_MISSED_TYPE: if (isVideoCall) { return mMissedVideoName; } else { return mMissedName; } - case Calls.VOICEMAIL_TYPE: + case AppCompatConstants.CALLS_VOICEMAIL_TYPE: return mVoicemailName; + case AppCompatConstants.CALLS_REJECTED_TYPE: + return mRejectedName; + + case AppCompatConstants.CALLS_BLOCKED_TYPE: + return mBlockedName; + default: return mMissedName; } @@ -92,18 +104,18 @@ public class CallTypeHelper { /** Returns the color used to highlight the given call type, null if not highlight is needed. */ public Integer getHighlightedColor(int callType) { switch (callType) { - case Calls.INCOMING_TYPE: + case AppCompatConstants.CALLS_INCOMING_TYPE: // New incoming calls are not highlighted. return null; - case Calls.OUTGOING_TYPE: + case AppCompatConstants.CALLS_OUTGOING_TYPE: // New outgoing calls are not highlighted. return null; - case Calls.MISSED_TYPE: + case AppCompatConstants.CALLS_MISSED_TYPE: return mNewMissedColor; - case Calls.VOICEMAIL_TYPE: + case AppCompatConstants.CALLS_VOICEMAIL_TYPE: return mNewVoicemailColor; default: @@ -115,7 +127,8 @@ public class CallTypeHelper { } public static boolean isMissedCallType(int callType) { - return (callType != Calls.INCOMING_TYPE && callType != Calls.OUTGOING_TYPE && - callType != Calls.VOICEMAIL_TYPE); + return (callType != AppCompatConstants.CALLS_INCOMING_TYPE + && callType != AppCompatConstants.CALLS_OUTGOING_TYPE + && callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE); } } diff --git a/src/com/android/dialer/calllog/CallTypeIconsView.java b/src/com/android/dialer/calllog/CallTypeIconsView.java index 31d4f4b0e..d680cfe42 100644 --- a/src/com/android/dialer/calllog/CallTypeIconsView.java +++ b/src/com/android/dialer/calllog/CallTypeIconsView.java @@ -23,13 +23,13 @@ import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.provider.CallLog.Calls; import android.util.AttributeSet; import android.view.View; import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.util.BitmapUtil; import com.android.dialer.R; +import com.android.dialer.util.AppCompatConstants; import com.google.common.collect.Lists; import java.util.List; @@ -106,13 +106,13 @@ public class CallTypeIconsView extends View { private Drawable getCallTypeDrawable(int callType) { switch (callType) { - case Calls.INCOMING_TYPE: + case AppCompatConstants.CALLS_INCOMING_TYPE: return mResources.incoming; - case Calls.OUTGOING_TYPE: + case AppCompatConstants.CALLS_OUTGOING_TYPE: return mResources.outgoing; - case Calls.MISSED_TYPE: + case AppCompatConstants.CALLS_MISSED_TYPE: return mResources.missed; - case Calls.VOICEMAIL_TYPE: + case AppCompatConstants.CALLS_VOICEMAIL_TYPE: return mResources.voicemail; default: // It is possible for users to end up with calls with unknown call types in their diff --git a/src/com/android/dialer/util/AppCompatConstants.java b/src/com/android/dialer/util/AppCompatConstants.java new file mode 100644 index 000000000..1d52eee1d --- /dev/null +++ b/src/com/android/dialer/util/AppCompatConstants.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 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.dialer.util; + +import android.provider.CallLog.Calls; + +public final class AppCompatConstants { + + public static final int CALLS_INCOMING_TYPE = Calls.INCOMING_TYPE; + public static final int CALLS_OUTGOING_TYPE = Calls.OUTGOING_TYPE; + public static final int CALLS_MISSED_TYPE = Calls.MISSED_TYPE; + public static final int CALLS_VOICEMAIL_TYPE = Calls.VOICEMAIL_TYPE; + // Added to android.provider.CallLog.Calls in N+. + public static final int CALLS_REJECTED_TYPE = 5; + // Added to android.provider.CallLog.Calls in N+. + public static final int CALLS_BLOCKED_TYPE = 6; +} diff --git a/tests/src/com/android/dialer/CallDetailActivityTest.java b/tests/src/com/android/dialer/CallDetailActivityTest.java index 59c2434f2..27fbc304e 100644 --- a/tests/src/com/android/dialer/CallDetailActivityTest.java +++ b/tests/src/com/android/dialer/CallDetailActivityTest.java @@ -33,6 +33,7 @@ import android.widget.PopupMenu; import android.widget.TextView; import com.android.dialer.calllog.CallLogAsyncTaskUtil; +import com.android.dialer.util.AppCompatConstants; import com.android.dialer.util.AsyncTaskExecutors; import com.android.dialer.util.FakeAsyncTaskExecutor; @@ -121,7 +122,7 @@ public class CallDetailActivityTest extends ActivityInstrumentationTestCase2