1 /* 2 * Copyright 2014, 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.server.telecom; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.location.Country; 23 import android.location.CountryDetector; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.os.Looper; 27 import android.os.UserHandle; 28 import android.os.PersistableBundle; 29 import android.provider.CallLog.Calls; 30 import android.telecom.DisconnectCause; 31 import android.telecom.Log; 32 import android.telecom.PhoneAccount; 33 import android.telecom.PhoneAccountHandle; 34 import android.telecom.VideoProfile; 35 import android.telephony.CarrierConfigManager; 36 import android.telephony.PhoneNumberUtils; 37 38 // TODO: Needed for move to system service: import com.android.internal.R; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.telephony.CallerInfo; 41 42 import java.util.Locale; 43 44 /** 45 * Helper class that provides functionality to write information about calls and their associated 46 * caller details to the call log. All logging activity will be performed asynchronously in a 47 * background thread to avoid blocking on the main thread. 48 */ 49 @VisibleForTesting 50 public final class CallLogManager extends CallsManagerListenerBase { 51 52 public interface LogCallCompletedListener { 53 void onLogCompleted(@Nullable Uri uri); 54 } 55 56 /** 57 * Parameter object to hold the arguments to add a call in the call log DB. 58 */ 59 private static class AddCallArgs { 60 /** 61 * @param callerInfo Caller details. 62 * @param number The phone number to be logged. 63 * @param presentation Number presentation of the phone number to be logged. 64 * @param callType The type of call (e.g INCOMING_TYPE). @see 65 * {@link android.provider.CallLog} for the list of values. 66 * @param features The features of the call (e.g. FEATURES_VIDEO). @see 67 * {@link android.provider.CallLog} for the list of values. 68 * @param creationDate Time when the call was created (milliseconds since epoch). 69 * @param durationInMillis Duration of the call (milliseconds). 70 * @param dataUsage Data usage in bytes, or null if not applicable. 71 * @param logCallCompletedListener optional callback called after the call is logged. 72 */ 73 public AddCallArgs(Context context, CallerInfo callerInfo, String number, 74 String postDialDigits, String viaNumber, int presentation, int callType, 75 int features, PhoneAccountHandle accountHandle, long creationDate, 76 long durationInMillis, Long dataUsage, UserHandle initiatingUser, 77 @Nullable LogCallCompletedListener logCallCompletedListener) { 78 this.context = context; 79 this.callerInfo = callerInfo; 80 this.number = number; 81 this.postDialDigits = postDialDigits; 82 this.viaNumber = viaNumber; 83 this.presentation = presentation; 84 this.callType = callType; 85 this.features = features; 86 this.accountHandle = accountHandle; 87 this.timestamp = creationDate; 88 this.durationInSec = (int)(durationInMillis / 1000); 89 this.dataUsage = dataUsage; 90 this.initiatingUser = initiatingUser; 91 this.logCallCompletedListener = logCallCompletedListener; 92 } 93 // Since the members are accessed directly, we don't use the 94 // mXxxx notation. 95 public final Context context; 96 public final CallerInfo callerInfo; 97 public final String number; 98 public final String postDialDigits; 99 public final String viaNumber; 100 public final int presentation; 101 public final int callType; 102 public final int features; 103 public final PhoneAccountHandle accountHandle; 104 public final long timestamp; 105 public final int durationInSec; 106 public final Long dataUsage; 107 public final UserHandle initiatingUser; 108 109 @Nullable 110 public final LogCallCompletedListener logCallCompletedListener; 111 } 112 113 private static final String TAG = CallLogManager.class.getSimpleName(); 114 115 private final Context mContext; 116 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 117 private final MissedCallNotifier mMissedCallNotifier; 118 private static final String ACTION_CALLS_TABLE_ADD_ENTRY = 119 "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY"; 120 private static final String PERMISSION_PROCESS_CALLLOG_INFO = 121 "android.permission.PROCESS_CALLLOG_INFO"; 122 private static final String CALL_TYPE = "callType"; 123 private static final String CALL_DURATION = "duration"; 124 125 private Object mLock; 126 private String mCurrentCountryIso; 127 128 public CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar, 129 MissedCallNotifier missedCallNotifier) { 130 mContext = context; 131 mPhoneAccountRegistrar = phoneAccountRegistrar; 132 mMissedCallNotifier = missedCallNotifier; 133 mLock = new Object(); 134 } 135 136 @Override 137 public void onCallStateChanged(Call call, int oldState, int newState) { 138 int disconnectCause = call.getDisconnectCause().getCode(); 139 boolean isNewlyDisconnected = 140 newState == CallState.DISCONNECTED || newState == CallState.ABORTED; 141 boolean isCallCanceled = isNewlyDisconnected && disconnectCause == DisconnectCause.CANCELED; 142 143 // Log newly disconnected calls only if: 144 // 1) It was not in the "choose account" phase when disconnected 145 // 2) It is a conference call 146 // 3) Call was not explicitly canceled 147 // 4) Call is not an external call 148 // 5) Call is not a self-managed call OR call is a self-managed call which has indicated it 149 // should be logged in its PhoneAccount 150 if (isNewlyDisconnected && 151 (oldState != CallState.SELECT_PHONE_ACCOUNT && 152 !call.isConference() && 153 !isCallCanceled) && 154 !call.isExternalCall() && 155 (!call.isSelfManaged() || 156 (call.isLoggedSelfManaged() && 157 (call.getHandoverState() == HandoverState.HANDOVER_NONE || 158 call.getHandoverState() == HandoverState.HANDOVER_COMPLETE)))) { 159 int type; 160 if (!call.isIncoming()) { 161 type = Calls.OUTGOING_TYPE; 162 } else if (disconnectCause == DisconnectCause.MISSED) { 163 type = Calls.MISSED_TYPE; 164 } else if (disconnectCause == DisconnectCause.ANSWERED_ELSEWHERE) { 165 type = Calls.ANSWERED_EXTERNALLY_TYPE; 166 } else if (disconnectCause == DisconnectCause.REJECTED) { 167 type = Calls.REJECTED_TYPE; 168 } else { 169 type = Calls.INCOMING_TYPE; 170 } 171 // Always show the notification for managed calls. For self-managed calls, it is up to 172 // the app to show the notification, so suppress the notification when logging the call. 173 boolean showNotification = !call.isSelfManaged(); 174 logCall(call, type, showNotification); 175 } 176 } 177 178 void logCall(Call call, int type, boolean showNotificationForMissedCall) { 179 if (type == Calls.MISSED_TYPE && showNotificationForMissedCall) { 180 logCall(call, Calls.MISSED_TYPE, 181 new LogCallCompletedListener() { 182 @Override 183 public void onLogCompleted(@Nullable Uri uri) { 184 mMissedCallNotifier.showMissedCallNotification( 185 new MissedCallNotifier.CallInfo(call)); 186 } 187 }); 188 } else { 189 logCall(call, type, null); 190 } 191 } 192 193 /** 194 * Logs a call to the call log based on the {@link Call} object passed in. 195 * 196 * @param call The call object being logged 197 * @param callLogType The type of call log entry to log this call as. See: 198 * {@link android.provider.CallLog.Calls#INCOMING_TYPE} 199 * {@link android.provider.CallLog.Calls#OUTGOING_TYPE} 200 * {@link android.provider.CallLog.Calls#MISSED_TYPE} 201 * @param logCallCompletedListener optional callback called after the call is logged. 202 */ 203 void logCall(Call call, int callLogType, 204 @Nullable LogCallCompletedListener logCallCompletedListener) { 205 final long creationTime = call.getCreationTimeMillis(); 206 final long age = call.getAgeMillis(); 207 208 final String logNumber = getLogNumber(call); 209 210 Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber)); 211 212 final PhoneAccountHandle emergencyAccountHandle = 213 TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle(); 214 215 String formattedViaNumber = PhoneNumberUtils.formatNumber(call.getViaNumber(), 216 getCountryIso()); 217 formattedViaNumber = (formattedViaNumber != null) ? 218 formattedViaNumber : call.getViaNumber(); 219 220 PhoneAccountHandle accountHandle = call.getTargetPhoneAccount(); 221 if (emergencyAccountHandle.equals(accountHandle)) { 222 accountHandle = null; 223 } 224 225 Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null : 226 call.getCallDataUsage(); 227 228 int callFeatures = getCallFeatures(call.getVideoStateHistory(), 229 call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED); 230 logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber, 231 call.getHandlePresentation(), callLogType, callFeatures, accountHandle, 232 creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser(), 233 logCallCompletedListener); 234 } 235 236 /** 237 * Inserts a call into the call log, based on the parameters passed in. 238 * 239 * @param callerInfo Caller details. 240 * @param number The number the call was made to or from. 241 * @param postDialDigits The post-dial digits that were dialed after the number, 242 * if it was an outgoing call. Otherwise ''. 243 * @param presentation 244 * @param callType The type of call. 245 * @param features The features of the call. 246 * @param start The start time of the call, in milliseconds. 247 * @param duration The duration of the call, in milliseconds. 248 * @param dataUsage The data usage for the call, null if not applicable. 249 * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise. 250 * @param logCallCompletedListener optional callback called after the call is logged. 251 */ 252 private void logCall( 253 CallerInfo callerInfo, 254 String number, 255 String postDialDigits, 256 String viaNumber, 257 int presentation, 258 int callType, 259 int features, 260 PhoneAccountHandle accountHandle, 261 long start, 262 long duration, 263 Long dataUsage, 264 boolean isEmergency, 265 UserHandle initiatingUser, 266 @Nullable LogCallCompletedListener logCallCompletedListener) { 267 268 // On some devices, to avoid accidental redialing of emergency numbers, we *never* log 269 // emergency calls to the Call Log. (This behavior is set on a per-product basis, based 270 // on carrier requirements.) 271 boolean okToLogEmergencyNumber = false; 272 CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService( 273 Context.CARRIER_CONFIG_SERVICE); 274 PersistableBundle configBundle = configManager.getConfigForSubId( 275 mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle)); 276 if (configBundle != null) { 277 okToLogEmergencyNumber = configBundle.getBoolean( 278 CarrierConfigManager.KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL); 279 } 280 281 // Don't log emergency numbers if the device doesn't allow it. 282 final boolean isOkToLogThisCall = !isEmergency || okToLogEmergencyNumber; 283 284 sendAddCallBroadcast(callType, duration); 285 286 if (isOkToLogThisCall) { 287 Log.d(TAG, "Logging Calllog entry: " + callerInfo + ", " 288 + Log.pii(number) + "," + presentation + ", " + callType 289 + ", " + start + ", " + duration); 290 AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits, 291 viaNumber, presentation, callType, features, accountHandle, start, duration, 292 dataUsage, initiatingUser, logCallCompletedListener); 293 logCallAsync(args); 294 } else { 295 Log.d(TAG, "Not adding emergency call to call log."); 296 } 297 } 298 299 /** 300 * Based on the video state of the call, determines the call features applicable for the call. 301 * 302 * @param videoState The video state. 303 * @param isPulledCall {@code true} if this call was pulled to another device. 304 * @return The call features. 305 */ 306 private static int getCallFeatures(int videoState, boolean isPulledCall) { 307 int features = 0; 308 if (VideoProfile.isVideo(videoState)) { 309 features |= Calls.FEATURES_VIDEO; 310 } 311 if (isPulledCall) { 312 features |= Calls.FEATURES_PULLED_EXTERNALLY; 313 } 314 return features; 315 } 316 317 /** 318 * Retrieve the phone number from the call, and then process it before returning the 319 * actual number that is to be logged. 320 * 321 * @param call The phone connection. 322 * @return the phone number to be logged. 323 */ 324 private String getLogNumber(Call call) { 325 Uri handle = call.getOriginalHandle(); 326 327 if (handle == null) { 328 return null; 329 } 330 331 String handleString = handle.getSchemeSpecificPart(); 332 if (!PhoneNumberUtils.isUriNumber(handleString)) { 333 handleString = PhoneNumberUtils.stripSeparators(handleString); 334 } 335 return handleString; 336 } 337 338 /** 339 * Adds the call defined by the parameters in the provided AddCallArgs to the CallLogProvider 340 * using an AsyncTask to avoid blocking the main thread. 341 * 342 * @param args Prepopulated call details. 343 * @return A handle to the AsyncTask that will add the call to the call log asynchronously. 344 */ 345 public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) { 346 return new LogCallAsyncTask().execute(args); 347 } 348 349 /** 350 * Helper AsyncTask to access the call logs database asynchronously since database operations 351 * can take a long time depending on the system's load. Since it extends AsyncTask, it uses 352 * its own thread pool. 353 */ 354 private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> { 355 356 private LogCallCompletedListener[] mListeners; 357 358 @Override 359 protected Uri[] doInBackground(AddCallArgs... callList) { 360 int count = callList.length; 361 Uri[] result = new Uri[count]; 362 mListeners = new LogCallCompletedListener[count]; 363 for (int i = 0; i < count; i++) { 364 AddCallArgs c = callList[i]; 365 mListeners[i] = c.logCallCompletedListener; 366 try { 367 // May block. 368 result[i] = addCall(c); 369 } catch (Exception e) { 370 // This is very rare but may happen in legitimate cases. 371 // E.g. If the phone is encrypted and thus write request fails, it may cause 372 // some kind of Exception (right now it is IllegalArgumentException, but this 373 // might change). 374 // 375 // We don't want to crash the whole process just because of that, so just log 376 // it instead. 377 Log.e(TAG, e, "Exception raised during adding CallLog entry."); 378 result[i] = null; 379 } 380 } 381 return result; 382 } 383 384 private Uri addCall(AddCallArgs c) { 385 PhoneAccount phoneAccount = mPhoneAccountRegistrar 386 .getPhoneAccountUnchecked(c.accountHandle); 387 if (phoneAccount != null && 388 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { 389 if (c.initiatingUser != null && 390 UserUtil.isManagedProfile(mContext, c.initiatingUser)) { 391 return addCall(c, c.initiatingUser); 392 } else { 393 return addCall(c, null); 394 } 395 } else { 396 return addCall(c, c.accountHandle == null ? null : c.accountHandle.getUserHandle()); 397 } 398 } 399 400 /** 401 * Insert the call to a specific user or all users except managed profile. 402 * @param c context 403 * @param userToBeInserted user handle of user that the call going be inserted to. null 404 * if insert to all users except managed profile. 405 */ 406 private Uri addCall(AddCallArgs c, UserHandle userToBeInserted) { 407 return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber, 408 c.presentation, c.callType, c.features, c.accountHandle, c.timestamp, 409 c.durationInSec, c.dataUsage, userToBeInserted == null, 410 userToBeInserted); 411 } 412 413 414 @Override 415 protected void onPostExecute(Uri[] result) { 416 for (int i = 0; i < result.length; i++) { 417 Uri uri = result[i]; 418 /* 419 Performs a simple sanity check to make sure the call was written in the database. 420 Typically there is only one result per call so it is easy to identify which one 421 failed. 422 */ 423 if (uri == null) { 424 Log.w(TAG, "Failed to write call to the log."); 425 } 426 if (mListeners[i] != null) { 427 mListeners[i].onLogCompleted(uri); 428 } 429 } 430 } 431 } 432 433 private void sendAddCallBroadcast(int callType, long duration) { 434 Intent callAddIntent = new Intent(ACTION_CALLS_TABLE_ADD_ENTRY); 435 callAddIntent.putExtra(CALL_TYPE, callType); 436 callAddIntent.putExtra(CALL_DURATION, duration); 437 mContext.sendBroadcast(callAddIntent, PERMISSION_PROCESS_CALLLOG_INFO); 438 } 439 440 private String getCountryIsoFromCountry(Country country) { 441 if(country == null) { 442 // Fallback to Locale if there are issues with CountryDetector 443 Log.w(TAG, "Value for country was null. Falling back to Locale."); 444 return Locale.getDefault().getCountry(); 445 } 446 447 return country.getCountryIso(); 448 } 449 450 /** 451 * Get the current country code 452 * 453 * @return the ISO 3166-1 two letters country code of current country. 454 */ 455 public String getCountryIso() { 456 synchronized (mLock) { 457 if (mCurrentCountryIso == null) { 458 Log.i(TAG, "Country cache is null. Detecting Country and Setting Cache..."); 459 final CountryDetector countryDetector = 460 (CountryDetector) mContext.getSystemService(Context.COUNTRY_DETECTOR); 461 Country country = null; 462 if (countryDetector != null) { 463 country = countryDetector.detectCountry(); 464 465 countryDetector.addCountryListener((newCountry) -> { 466 Log.startSession("CLM.oCD"); 467 try { 468 synchronized (mLock) { 469 Log.i(TAG, "Country ISO changed. Retrieving new ISO..."); 470 mCurrentCountryIso = getCountryIsoFromCountry(newCountry); 471 } 472 } finally { 473 Log.endSession(); 474 } 475 }, Looper.getMainLooper()); 476 } 477 mCurrentCountryIso = getCountryIsoFromCountry(country); 478 } 479 return mCurrentCountryIso; 480 } 481 } 482 } 483