1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dialer.calllog; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.os.AsyncTask; 25 import android.provider.CallLog; 26 import android.provider.VoicemailContract.Voicemails; 27 import android.telecom.PhoneAccountHandle; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import com.android.contacts.common.GeoUtil; 32 import com.android.dialer.PhoneCallDetails; 33 import com.android.dialer.util.AsyncTaskExecutor; 34 import com.android.dialer.util.AsyncTaskExecutors; 35 import com.android.dialer.util.PhoneNumberUtil; 36 import com.android.dialer.util.TelecomUtil; 37 38 import com.google.common.annotations.VisibleForTesting; 39 40 public class CallLogAsyncTaskUtil { 41 private static String TAG = CallLogAsyncTaskUtil.class.getSimpleName(); 42 43 /** The enumeration of {@link AsyncTask} objects used in this class. */ 44 public enum Tasks { 45 DELETE_VOICEMAIL, 46 DELETE_CALL, 47 MARK_VOICEMAIL_READ, 48 GET_CALL_DETAILS, 49 } 50 51 private static class CallDetailQuery { 52 static final String[] CALL_LOG_PROJECTION = new String[] { 53 CallLog.Calls.DATE, 54 CallLog.Calls.DURATION, 55 CallLog.Calls.NUMBER, 56 CallLog.Calls.TYPE, 57 CallLog.Calls.COUNTRY_ISO, 58 CallLog.Calls.GEOCODED_LOCATION, 59 CallLog.Calls.NUMBER_PRESENTATION, 60 CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME, 61 CallLog.Calls.PHONE_ACCOUNT_ID, 62 CallLog.Calls.FEATURES, 63 CallLog.Calls.DATA_USAGE, 64 CallLog.Calls.TRANSCRIPTION 65 }; 66 67 static final int DATE_COLUMN_INDEX = 0; 68 static final int DURATION_COLUMN_INDEX = 1; 69 static final int NUMBER_COLUMN_INDEX = 2; 70 static final int CALL_TYPE_COLUMN_INDEX = 3; 71 static final int COUNTRY_ISO_COLUMN_INDEX = 4; 72 static final int GEOCODED_LOCATION_COLUMN_INDEX = 5; 73 static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6; 74 static final int ACCOUNT_COMPONENT_NAME = 7; 75 static final int ACCOUNT_ID = 8; 76 static final int FEATURES = 9; 77 static final int DATA_USAGE = 10; 78 static final int TRANSCRIPTION_COLUMN_INDEX = 11; 79 } 80 81 public interface CallLogAsyncTaskListener { 82 public void onDeleteCall(); 83 public void onDeleteVoicemail(); 84 public void onGetCallDetails(PhoneCallDetails[] details); 85 } 86 87 private static AsyncTaskExecutor sAsyncTaskExecutor; 88 89 private static void initTaskExecutor() { 90 sAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); 91 } 92 93 public static void getCallDetails( 94 final Context context, 95 final Uri[] callUris, 96 final CallLogAsyncTaskListener callLogAsyncTaskListener) { 97 if (sAsyncTaskExecutor == null) { 98 initTaskExecutor(); 99 } 100 101 sAsyncTaskExecutor.submit(Tasks.GET_CALL_DETAILS, 102 new AsyncTask<Void, Void, PhoneCallDetails[]>() { 103 @Override 104 public PhoneCallDetails[] doInBackground(Void... params) { 105 // TODO: All calls correspond to the same person, so make a single lookup. 106 final int numCalls = callUris.length; 107 PhoneCallDetails[] details = new PhoneCallDetails[numCalls]; 108 try { 109 for (int index = 0; index < numCalls; ++index) { 110 details[index] = 111 getPhoneCallDetailsForUri(context, callUris[index]); 112 } 113 return details; 114 } catch (IllegalArgumentException e) { 115 // Something went wrong reading in our primary data. 116 Log.w(TAG, "Invalid URI starting call details", e); 117 return null; 118 } 119 } 120 121 @Override 122 public void onPostExecute(PhoneCallDetails[] phoneCallDetails) { 123 if (callLogAsyncTaskListener != null) { 124 callLogAsyncTaskListener.onGetCallDetails(phoneCallDetails); 125 } 126 } 127 }); 128 } 129 130 /** 131 * Return the phone call details for a given call log URI. 132 */ 133 private static PhoneCallDetails getPhoneCallDetailsForUri(Context context, Uri callUri) { 134 Cursor cursor = context.getContentResolver().query( 135 callUri, CallDetailQuery.CALL_LOG_PROJECTION, null, null, null); 136 137 try { 138 if (cursor == null || !cursor.moveToFirst()) { 139 throw new IllegalArgumentException("Cannot find content: " + callUri); 140 } 141 142 // Read call log. 143 final String countryIso = cursor.getString(CallDetailQuery.COUNTRY_ISO_COLUMN_INDEX); 144 final String number = cursor.getString(CallDetailQuery.NUMBER_COLUMN_INDEX); 145 final int numberPresentation = 146 cursor.getInt(CallDetailQuery.NUMBER_PRESENTATION_COLUMN_INDEX); 147 148 final PhoneAccountHandle accountHandle = PhoneAccountUtils.getAccount( 149 cursor.getString(CallDetailQuery.ACCOUNT_COMPONENT_NAME), 150 cursor.getString(CallDetailQuery.ACCOUNT_ID)); 151 152 // If this is not a regular number, there is no point in looking it up in the contacts. 153 ContactInfoHelper contactInfoHelper = 154 new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)); 155 boolean isVoicemail = PhoneNumberUtil.isVoicemailNumber(context, accountHandle, number); 156 boolean shouldLookupNumber = 157 PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemail; 158 ContactInfo info = shouldLookupNumber 159 ? contactInfoHelper.lookupNumber(number, countryIso) 160 : ContactInfo.EMPTY; 161 PhoneCallDetails details = new PhoneCallDetails( 162 context, number, numberPresentation, info.formattedNumber, isVoicemail); 163 164 details.accountHandle = accountHandle; 165 details.contactUri = info.lookupUri; 166 details.name = info.name; 167 details.numberType = info.type; 168 details.numberLabel = info.label; 169 details.photoUri = info.photoUri; 170 details.sourceType = info.sourceType; 171 details.objectId = info.objectId; 172 173 details.callTypes = new int[] { 174 cursor.getInt(CallDetailQuery.CALL_TYPE_COLUMN_INDEX) 175 }; 176 details.date = cursor.getLong(CallDetailQuery.DATE_COLUMN_INDEX); 177 details.duration = cursor.getLong(CallDetailQuery.DURATION_COLUMN_INDEX); 178 details.features = cursor.getInt(CallDetailQuery.FEATURES); 179 details.geocode = cursor.getString(CallDetailQuery.GEOCODED_LOCATION_COLUMN_INDEX); 180 details.transcription = cursor.getString(CallDetailQuery.TRANSCRIPTION_COLUMN_INDEX); 181 182 details.countryIso = !TextUtils.isEmpty(countryIso) ? countryIso 183 : GeoUtil.getCurrentCountryIso(context); 184 185 if (!cursor.isNull(CallDetailQuery.DATA_USAGE)) { 186 details.dataUsage = cursor.getLong(CallDetailQuery.DATA_USAGE); 187 } 188 189 return details; 190 } finally { 191 if (cursor != null) { 192 cursor.close(); 193 } 194 } 195 } 196 197 198 /** 199 * Delete specified calls from the call log. 200 * 201 * @param context The context. 202 * @param callIds String of the callIds to delete from the call log, delimited by commas (","). 203 * @param callLogAsyncTaskListenerg The listener to invoke after the entries have been deleted. 204 */ 205 public static void deleteCalls( 206 final Context context, 207 final String callIds, 208 final CallLogAsyncTaskListener callLogAsyncTaskListener) { 209 if (sAsyncTaskExecutor == null) { 210 initTaskExecutor(); 211 } 212 213 sAsyncTaskExecutor.submit(Tasks.DELETE_CALL, 214 new AsyncTask<Void, Void, Void>() { 215 @Override 216 public Void doInBackground(Void... params) { 217 context.getContentResolver().delete( 218 TelecomUtil.getCallLogUri(context), 219 CallLog.Calls._ID + " IN (" + callIds + ")", null); 220 return null; 221 } 222 223 @Override 224 public void onPostExecute(Void result) { 225 if (callLogAsyncTaskListener != null) { 226 callLogAsyncTaskListener.onDeleteCall(); 227 } 228 } 229 }); 230 231 } 232 233 public static void markVoicemailAsRead(final Context context, final Uri voicemailUri) { 234 if (sAsyncTaskExecutor == null) { 235 initTaskExecutor(); 236 } 237 238 sAsyncTaskExecutor.submit(Tasks.MARK_VOICEMAIL_READ, new AsyncTask<Void, Void, Void>() { 239 @Override 240 public Void doInBackground(Void... params) { 241 ContentValues values = new ContentValues(); 242 values.put(Voicemails.IS_READ, true); 243 context.getContentResolver().update( 244 voicemailUri, values, Voicemails.IS_READ + " = 0", null); 245 246 Intent intent = new Intent(context, CallLogNotificationsService.class); 247 intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); 248 context.startService(intent); 249 return null; 250 } 251 }); 252 } 253 254 public static void deleteVoicemail( 255 final Context context, 256 final Uri voicemailUri, 257 final CallLogAsyncTaskListener callLogAsyncTaskListener) { 258 if (sAsyncTaskExecutor == null) { 259 initTaskExecutor(); 260 } 261 262 sAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL, 263 new AsyncTask<Void, Void, Void>() { 264 @Override 265 public Void doInBackground(Void... params) { 266 context.getContentResolver().delete(voicemailUri, null, null); 267 return null; 268 } 269 270 @Override 271 public void onPostExecute(Void result) { 272 if (callLogAsyncTaskListener != null) { 273 callLogAsyncTaskListener.onDeleteVoicemail(); 274 } 275 } 276 }); 277 } 278 279 @VisibleForTesting 280 public static void resetForTest() { 281 sAsyncTaskExecutor = null; 282 } 283 } 284