• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL;
20 
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.location.Country;
25 import android.location.CountryDetector;
26 import android.net.Uri;
27 import android.os.AsyncTask;
28 import android.os.Looper;
29 import android.os.UserHandle;
30 import android.os.PersistableBundle;
31 import android.provider.CallLog.Calls;
32 import android.telecom.Connection;
33 import android.telecom.DisconnectCause;
34 import android.telecom.Log;
35 import android.telecom.PhoneAccount;
36 import android.telecom.PhoneAccountHandle;
37 import android.telecom.VideoProfile;
38 import android.telephony.CarrierConfigManager;
39 import android.telephony.PhoneNumberUtils;
40 import android.telephony.SubscriptionManager;
41 
42 // TODO: Needed for move to system service: import com.android.internal.R;
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.telephony.CallerInfo;
45 import com.android.internal.telephony.SubscriptionController;
46 import com.android.server.telecom.callfiltering.CallFilteringResult;
47 
48 import java.util.Arrays;
49 import java.util.Locale;
50 import java.util.Objects;
51 import java.util.stream.Stream;
52 
53 /**
54  * Helper class that provides functionality to write information about calls and their associated
55  * caller details to the call log. All logging activity will be performed asynchronously in a
56  * background thread to avoid blocking on the main thread.
57  */
58 @VisibleForTesting
59 public final class CallLogManager extends CallsManagerListenerBase {
60 
61     public interface LogCallCompletedListener {
onLogCompleted(@ullable Uri uri)62         void onLogCompleted(@Nullable Uri uri);
63     }
64 
65     /**
66      * Parameter object to hold the arguments to add a call in the call log DB.
67      */
68     private static class AddCallArgs {
69         /**
70          * @param callerInfo Caller details.
71          * @param number The phone number to be logged.
72          * @param presentation Number presentation of the phone number to be logged.
73          * @param callType The type of call (e.g INCOMING_TYPE). @see
74          *     {@link android.provider.CallLog} for the list of values.
75          * @param features The features of the call (e.g. FEATURES_VIDEO). @see
76          *     {@link android.provider.CallLog} for the list of values.
77          * @param creationDate Time when the call was created (milliseconds since epoch).
78          * @param durationInMillis Duration of the call (milliseconds).
79          * @param dataUsage Data usage in bytes, or null if not applicable.
80          * @param isRead Indicates if the entry has been read or not.
81          * @param logCallCompletedListener optional callback called after the call is logged.
82          */
AddCallArgs(Context context, CallerInfo callerInfo, String number, String postDialDigits, String viaNumber, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long creationDate, long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead, @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName)83         public AddCallArgs(Context context, CallerInfo callerInfo, String number,
84                 String postDialDigits, String viaNumber, int presentation, int callType,
85                 int features, PhoneAccountHandle accountHandle, long creationDate,
86                 long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead,
87                 @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason,
88                 CharSequence callScreeningAppName, String callScreeningComponentName) {
89             this.context = context;
90             this.callerInfo = callerInfo;
91             this.number = number;
92             this.postDialDigits = postDialDigits;
93             this.viaNumber = viaNumber;
94             this.presentation = presentation;
95             this.callType = callType;
96             this.features = features;
97             this.accountHandle = accountHandle;
98             this.timestamp = creationDate;
99             this.durationInSec = (int)(durationInMillis / 1000);
100             this.dataUsage = dataUsage;
101             this.initiatingUser = initiatingUser;
102             this.isRead = isRead;
103             this.logCallCompletedListener = logCallCompletedListener;
104             this.callBockReason = callBlockReason;
105             this.callScreeningAppName = callScreeningAppName;
106             this.callScreeningComponentName = callScreeningComponentName;
107         }
108         // Since the members are accessed directly, we don't use the
109         // mXxxx notation.
110         public final Context context;
111         public final CallerInfo callerInfo;
112         public final String number;
113         public final String postDialDigits;
114         public final String viaNumber;
115         public final int presentation;
116         public final int callType;
117         public final int features;
118         public final PhoneAccountHandle accountHandle;
119         public final long timestamp;
120         public final int durationInSec;
121         public final Long dataUsage;
122         public final UserHandle initiatingUser;
123         public final boolean isRead;
124 
125         @Nullable
126         public final LogCallCompletedListener logCallCompletedListener;
127 
128         public final int callBockReason;
129         public final CharSequence callScreeningAppName;
130         public final String callScreeningComponentName;
131     }
132 
133     private static final String TAG = CallLogManager.class.getSimpleName();
134 
135     private final Context mContext;
136     private final CarrierConfigManager mCarrierConfigManager;
137     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
138     private final MissedCallNotifier mMissedCallNotifier;
139     private static final String ACTION_CALLS_TABLE_ADD_ENTRY =
140                 "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
141     private static final String PERMISSION_PROCESS_CALLLOG_INFO =
142                 "android.permission.PROCESS_CALLLOG_INFO";
143     private static final String CALL_TYPE = "callType";
144     private static final String CALL_DURATION = "duration";
145 
146     private Object mLock;
147     private String mCurrentCountryIso;
148 
CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar, MissedCallNotifier missedCallNotifier)149     public CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
150             MissedCallNotifier missedCallNotifier) {
151         mContext = context;
152         mCarrierConfigManager = (CarrierConfigManager) mContext
153                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
154         mPhoneAccountRegistrar = phoneAccountRegistrar;
155         mMissedCallNotifier = missedCallNotifier;
156         mLock = new Object();
157     }
158 
159     @Override
onCallStateChanged(Call call, int oldState, int newState)160     public void onCallStateChanged(Call call, int oldState, int newState) {
161         int disconnectCause = call.getDisconnectCause().getCode();
162         boolean isNewlyDisconnected =
163                 newState == CallState.DISCONNECTED || newState == CallState.ABORTED;
164         boolean isCallCanceled = isNewlyDisconnected && disconnectCause == DisconnectCause.CANCELED;
165 
166         if (!isNewlyDisconnected) {
167             return;
168         }
169 
170         if (shouldLogDisconnectedCall(call, oldState, isCallCanceled)) {
171             int type;
172             if (!call.isIncoming()) {
173                 type = Calls.OUTGOING_TYPE;
174             } else if (disconnectCause == DisconnectCause.MISSED) {
175                 type = Calls.MISSED_TYPE;
176             } else if (disconnectCause == DisconnectCause.ANSWERED_ELSEWHERE) {
177                 type = Calls.ANSWERED_EXTERNALLY_TYPE;
178             } else if (disconnectCause == DisconnectCause.REJECTED) {
179                 type = Calls.REJECTED_TYPE;
180             } else {
181                 type = Calls.INCOMING_TYPE;
182             }
183             // Always show the notification for managed calls. For self-managed calls, it is up to
184             // the app to show the notification, so suppress the notification when logging the call.
185             boolean showNotification = !call.isSelfManaged();
186             logCall(call, type, showNotification, null /*result*/);
187         }
188     }
189 
190     /**
191      * Log newly disconnected calls only if all of below conditions are met:
192      * Call was NOT in the "choose account" phase when disconnected
193      * Call is NOT a conference call which had children (unless it was remotely hosted).
194      * Call is NOT a child call from a conference which was remotely hosted.
195      * Call is NOT simulating a single party conference.
196      * Call was NOT explicitly canceled, except for disconnecting from a conference.
197      * Call is NOT an external call
198      * Call is NOT disconnected because of merging into a conference.
199      * Call is NOT a self-managed call OR call is a self-managed call which has indicated it
200      * should be logged in its PhoneAccount
201      */
202     @VisibleForTesting
shouldLogDisconnectedCall(Call call, int oldState, boolean isCallCancelled)203     public boolean shouldLogDisconnectedCall(Call call, int oldState, boolean isCallCancelled) {
204         // "Choose account" phase when disconnected
205         if (oldState == CallState.SELECT_PHONE_ACCOUNT) {
206             return false;
207         }
208         // A conference call which had children should not be logged, unless it was remotely hosted.
209         if (call.isConference() && call.hadChildren() &&
210                 !call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
211             return false;
212         }
213 
214         // A child call of a conference which was remotely hosted; these didn't originate on this
215         // device and should not be logged.
216         if (call.getParentCall() != null && call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
217             return false;
218         }
219 
220         DisconnectCause cause = call.getDisconnectCause();
221         if (isCallCancelled) {
222             // No log when disconnecting to simulate a single party conference.
223             if (cause != null
224                     && DisconnectCause.REASON_EMULATING_SINGLE_CALL.equals(cause.getReason())) {
225                 return false;
226             }
227             // Explicitly canceled
228             // Conference children connections only have CAPABILITY_DISCONNECT_FROM_CONFERENCE.
229             // Log them when they are disconnected from conference.
230             return Connection.can(call.getConnectionCapabilities(),
231                     Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE);
232         }
233         // An external call
234         if (call.isExternalCall()) {
235             return false;
236         }
237 
238         // Call merged into conferences and marked with IMS_MERGED_SUCCESSFULLY.
239         if (cause != null && android.telephony.DisconnectCause.toString(
240                 android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY)
241                 .equals(cause.getReason())) {
242             int subscriptionId = mPhoneAccountRegistrar
243                     .getSubscriptionIdForPhoneAccount(call.getTargetPhoneAccount());
244             // By default, the conference should return a list of participants.
245             if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
246                 return false;
247             }
248 
249             PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subscriptionId);
250             if (b == null) {
251                 return false;
252             }
253 
254             if (b.getBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL, true)) {
255                 return false;
256             }
257         }
258 
259         boolean shouldCallSelfManagedLogged = call.isLoggedSelfManaged()
260                 && (call.getHandoverState() == HandoverState.HANDOVER_NONE
261                 || call.getHandoverState() == HandoverState.HANDOVER_COMPLETE);
262         // Call is NOT a self-managed call OR call is a self-managed call which has indicated it
263         // should be logged in its PhoneAccount
264         return !call.isSelfManaged() || shouldCallSelfManagedLogged;
265     }
266 
logCall(Call call, int type, boolean showNotificationForMissedCall, CallFilteringResult result)267     void logCall(Call call, int type, boolean showNotificationForMissedCall, CallFilteringResult
268             result) {
269         if ((type == Calls.MISSED_TYPE || type == Calls.BLOCKED_TYPE) &&
270                 showNotificationForMissedCall) {
271             logCall(call, type, new LogCallCompletedListener() {
272                 @Override
273                 public void onLogCompleted(@Nullable Uri uri) {
274                     mMissedCallNotifier.showMissedCallNotification(
275                             new MissedCallNotifier.CallInfo(call));
276                 }
277             }, result);
278         } else {
279             logCall(call, type, null, result);
280         }
281     }
282 
283     /**
284      * Logs a call to the call log based on the {@link Call} object passed in.
285      *
286      * @param call The call object being logged
287      * @param callLogType The type of call log entry to log this call as. See:
288      *     {@link android.provider.CallLog.Calls#INCOMING_TYPE}
289      *     {@link android.provider.CallLog.Calls#OUTGOING_TYPE}
290      *     {@link android.provider.CallLog.Calls#MISSED_TYPE}
291      *     {@link android.provider.CallLog.Calls#BLOCKED_TYPE}
292      * @param logCallCompletedListener optional callback called after the call is logged.
293      * @param result is generated when call type is
294      *     {@link android.provider.CallLog.Calls#BLOCKED_TYPE}.
295      */
logCall(Call call, int callLogType, @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result)296     void logCall(Call call, int callLogType,
297         @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
298         final long creationTime = call.getCreationTimeMillis();
299         final long age = call.getAgeMillis();
300 
301         final String logNumber = getLogNumber(call);
302 
303         Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber));
304 
305         final PhoneAccountHandle emergencyAccountHandle =
306                 TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle();
307 
308         String formattedViaNumber = PhoneNumberUtils.formatNumber(call.getViaNumber(),
309                 getCountryIso());
310         formattedViaNumber = (formattedViaNumber != null) ?
311                 formattedViaNumber : call.getViaNumber();
312 
313         PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
314         if (emergencyAccountHandle.equals(accountHandle)) {
315             accountHandle = null;
316         }
317 
318         Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null :
319                 call.getCallDataUsage();
320 
321         int callFeatures = getCallFeatures(call.getVideoStateHistory(),
322                 call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED,
323                 shouldSaveHdInfo(call, accountHandle),
324                 (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING_USED) ==
325                         Connection.PROPERTY_ASSISTED_DIALING_USED,
326                 call.wasEverRttCall());
327 
328         if (callLogType == Calls.BLOCKED_TYPE) {
329             logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
330                     call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
331                     creationTime, age, callDataUsage, call.isEmergencyCall(),
332                     call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
333                     result.mCallBlockReason, result.mCallScreeningAppName,
334                     result.mCallScreeningComponentName);
335         } else {
336             logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
337                     call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
338                     creationTime, age, callDataUsage, call.isEmergencyCall(),
339                     call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
340                     Calls.BLOCK_REASON_NOT_BLOCKED, null /*callScreeningAppName*/,
341                     null /*callScreeningComponentName*/);
342         }
343     }
344 
345     /**
346      * Inserts a call into the call log, based on the parameters passed in.
347      *
348      * @param callerInfo Caller details.
349      * @param number The number the call was made to or from.
350      * @param postDialDigits The post-dial digits that were dialed after the number,
351      *                       if it was an outgoing call. Otherwise ''.
352      * @param presentation
353      * @param callType The type of call.
354      * @param features The features of the call.
355      * @param start The start time of the call, in milliseconds.
356      * @param duration The duration of the call, in milliseconds.
357      * @param dataUsage The data usage for the call, null if not applicable.
358      * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise.
359      * @param logCallCompletedListener optional callback called after the call is logged.
360      * @param initiatingUser The user the call was initiated under.
361      * @param isSelfManaged {@code true} if this is a self-managed call, {@code false} otherwise.
362      * @param callBlockReason The reason why the call is blocked.
363      * @param callScreeningAppName The call screening application name which block the call.
364      * @param callScreeningComponentName The call screening component name which block the call.
365      */
logCall( CallerInfo callerInfo, String number, String postDialDigits, String viaNumber, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, long duration, Long dataUsage, boolean isEmergency, UserHandle initiatingUser, boolean isSelfManaged, @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName)366     private void logCall(
367             CallerInfo callerInfo,
368             String number,
369             String postDialDigits,
370             String viaNumber,
371             int presentation,
372             int callType,
373             int features,
374             PhoneAccountHandle accountHandle,
375             long start,
376             long duration,
377             Long dataUsage,
378             boolean isEmergency,
379             UserHandle initiatingUser,
380             boolean isSelfManaged,
381             @Nullable LogCallCompletedListener logCallCompletedListener,
382             int callBlockReason,
383             CharSequence callScreeningAppName,
384             String callScreeningComponentName) {
385 
386         // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
387         // emergency calls to the Call Log.  (This behavior is set on a per-product basis, based
388         // on carrier requirements.)
389         boolean okToLogEmergencyNumber = false;
390         CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
391                 Context.CARRIER_CONFIG_SERVICE);
392         PersistableBundle configBundle = configManager.getConfigForSubId(
393                 mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle));
394         if (configBundle != null) {
395             okToLogEmergencyNumber = configBundle.getBoolean(
396                     CarrierConfigManager.KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL);
397         }
398 
399         // Don't log emergency numbers if the device doesn't allow it.
400         final boolean isOkToLogThisCall = (!isEmergency || okToLogEmergencyNumber)
401                 && !isUnloggableNumber(number, configBundle);
402 
403         sendAddCallBroadcast(callType, duration);
404 
405         if (isOkToLogThisCall) {
406             Log.d(TAG, "Logging Call log entry: " + callerInfo + ", "
407                     + Log.pii(number) + "," + presentation + ", " + callType
408                     + ", " + start + ", " + duration);
409             boolean isRead = false;
410             if (isSelfManaged) {
411                 // Mark self-managed calls are read since they're being handled by their own app.
412                 // Their inclusion in the call log is informational only.
413                 isRead = true;
414             }
415             AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
416                     viaNumber, presentation, callType, features, accountHandle, start, duration,
417                     dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason,
418                     callScreeningAppName, callScreeningComponentName);
419             logCallAsync(args);
420         } else {
421           Log.d(TAG, "Not adding emergency call to call log.");
422         }
423     }
424 
isUnloggableNumber(String callNumber, PersistableBundle carrierConfig)425     private boolean isUnloggableNumber(String callNumber, PersistableBundle carrierConfig) {
426         String normalizedNumber = PhoneNumberUtils.normalizeNumber(callNumber);
427         String[] unloggableNumbersFromCarrierConfig = carrierConfig == null ? null
428                 : carrierConfig.getStringArray(
429                         CarrierConfigManager.KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY);
430         String[] unloggableNumbersFromMccConfig = mContext.getResources()
431                 .getStringArray(com.android.internal.R.array.unloggable_phone_numbers);
432         return Stream.concat(
433                 unloggableNumbersFromCarrierConfig == null ?
434                         Stream.empty() : Arrays.stream(unloggableNumbersFromCarrierConfig),
435                 unloggableNumbersFromMccConfig == null ?
436                         Stream.empty() : Arrays.stream(unloggableNumbersFromMccConfig)
437         ).anyMatch(unloggableNumber -> Objects.equals(unloggableNumber, normalizedNumber));
438     }
439 
440     /**
441      * Based on the video state of the call, determines the call features applicable for the call.
442      *
443      * @param videoState The video state.
444      * @param isPulledCall {@code true} if this call was pulled to another device.
445      * @param isStoreHd {@code true} if this call was used HD.
446      * @param isUsingAssistedDialing {@code true} if this call used assisted dialing.
447      * @return The call features.
448      */
getCallFeatures(int videoState, boolean isPulledCall, boolean isStoreHd, boolean isUsingAssistedDialing, boolean isRtt)449     private static int getCallFeatures(int videoState, boolean isPulledCall, boolean isStoreHd,
450             boolean isUsingAssistedDialing, boolean isRtt) {
451         int features = 0;
452         if (VideoProfile.isVideo(videoState)) {
453             features |= Calls.FEATURES_VIDEO;
454         }
455         if (isPulledCall) {
456             features |= Calls.FEATURES_PULLED_EXTERNALLY;
457         }
458         if (isStoreHd) {
459             features |= Calls.FEATURES_HD_CALL;
460         }
461         if (isUsingAssistedDialing) {
462             features |= Calls.FEATURES_ASSISTED_DIALING_USED;
463         }
464         if (isRtt) {
465             features |= Calls.FEATURES_RTT;
466         }
467         return features;
468     }
469 
shouldSaveHdInfo(Call call, PhoneAccountHandle accountHandle)470     private boolean shouldSaveHdInfo(Call call, PhoneAccountHandle accountHandle) {
471         CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
472                 Context.CARRIER_CONFIG_SERVICE);
473         PersistableBundle configBundle = null;
474         if (configManager != null) {
475             configBundle = configManager.getConfigForSubId(
476                     mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle));
477         }
478         if (configBundle != null && configBundle.getBoolean(
479                 CarrierConfigManager.KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL)
480                 && call.wasHighDefAudio()) {
481             return true;
482         }
483         return false;
484     }
485 
486     /**
487      * Retrieve the phone number from the call, and then process it before returning the
488      * actual number that is to be logged.
489      *
490      * @param call The phone connection.
491      * @return the phone number to be logged.
492      */
getLogNumber(Call call)493     private String getLogNumber(Call call) {
494         Uri handle = call.getOriginalHandle();
495 
496         if (handle == null) {
497             return null;
498         }
499 
500         String handleString = handle.getSchemeSpecificPart();
501         if (!PhoneNumberUtils.isUriNumber(handleString)) {
502             handleString = PhoneNumberUtils.stripSeparators(handleString);
503         }
504         return handleString;
505     }
506 
507     /**
508      * Adds the call defined by the parameters in the provided AddCallArgs to the CallLogProvider
509      * using an AsyncTask to avoid blocking the main thread.
510      *
511      * @param args Prepopulated call details.
512      * @return A handle to the AsyncTask that will add the call to the call log asynchronously.
513      */
logCallAsync(AddCallArgs args)514     public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {
515         return new LogCallAsyncTask().execute(args);
516     }
517 
518     /**
519      * Helper AsyncTask to access the call logs database asynchronously since database operations
520      * can take a long time depending on the system's load. Since it extends AsyncTask, it uses
521      * its own thread pool.
522      */
523     private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> {
524 
525         private LogCallCompletedListener[] mListeners;
526 
527         @Override
doInBackground(AddCallArgs... callList)528         protected Uri[] doInBackground(AddCallArgs... callList) {
529             int count = callList.length;
530             Uri[] result = new Uri[count];
531             mListeners = new LogCallCompletedListener[count];
532             for (int i = 0; i < count; i++) {
533                 AddCallArgs c = callList[i];
534                 mListeners[i] = c.logCallCompletedListener;
535                 try {
536                     // May block.
537                     result[i] = addCall(c);
538                 } catch (Exception e) {
539                     // This is very rare but may happen in legitimate cases.
540                     // E.g. If the phone is encrypted and thus write request fails, it may cause
541                     // some kind of Exception (right now it is IllegalArgumentException, but this
542                     // might change).
543                     //
544                     // We don't want to crash the whole process just because of that, so just log
545                     // it instead.
546                     Log.e(TAG, e, "Exception raised during adding CallLog entry.");
547                     result[i] = null;
548                 }
549             }
550             return result;
551         }
552 
addCall(AddCallArgs c)553         private Uri addCall(AddCallArgs c) {
554             PhoneAccount phoneAccount = mPhoneAccountRegistrar
555                     .getPhoneAccountUnchecked(c.accountHandle);
556             if (phoneAccount != null &&
557                     phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
558                 if (c.initiatingUser != null &&
559                         UserUtil.isManagedProfile(mContext, c.initiatingUser)) {
560                     return addCall(c, c.initiatingUser);
561                 } else {
562                     return addCall(c, null);
563                 }
564             } else {
565                 return addCall(c, c.accountHandle == null ? null : c.accountHandle.getUserHandle());
566             }
567         }
568 
569         /**
570          * Insert the call to a specific user or all users except managed profile.
571          * @param c context
572          * @param userToBeInserted user handle of user that the call going be inserted to. null
573          *                         if insert to all users except managed profile.
574          */
addCall(AddCallArgs c, UserHandle userToBeInserted)575         private Uri addCall(AddCallArgs c, UserHandle userToBeInserted) {
576             return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber,
577                     c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
578                     c.durationInSec, c.dataUsage, userToBeInserted == null,
579                     userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName,
580                     c.callScreeningComponentName);
581         }
582 
583 
584         @Override
onPostExecute(Uri[] result)585         protected void onPostExecute(Uri[] result) {
586             for (int i = 0; i < result.length; i++) {
587                 Uri uri = result[i];
588                 /*
589                  Performs a simple sanity check to make sure the call was written in the database.
590                  Typically there is only one result per call so it is easy to identify which one
591                  failed.
592                  */
593                 if (uri == null) {
594                     Log.w(TAG, "Failed to write call to the log.");
595                 }
596                 if (mListeners[i] != null) {
597                     mListeners[i].onLogCompleted(uri);
598                 }
599             }
600         }
601     }
602 
sendAddCallBroadcast(int callType, long duration)603     private void sendAddCallBroadcast(int callType, long duration) {
604         Intent callAddIntent = new Intent(ACTION_CALLS_TABLE_ADD_ENTRY);
605         callAddIntent.putExtra(CALL_TYPE, callType);
606         callAddIntent.putExtra(CALL_DURATION, duration);
607         mContext.sendBroadcast(callAddIntent, PERMISSION_PROCESS_CALLLOG_INFO);
608     }
609 
getCountryIsoFromCountry(Country country)610     private String getCountryIsoFromCountry(Country country) {
611         if(country == null) {
612             // Fallback to Locale if there are issues with CountryDetector
613             Log.w(TAG, "Value for country was null. Falling back to Locale.");
614             return Locale.getDefault().getCountry();
615         }
616 
617         return country.getCountryIso();
618     }
619 
620     /**
621      * Get the current country code
622      *
623      * @return the ISO 3166-1 two letters country code of current country.
624      */
getCountryIso()625     public String getCountryIso() {
626         synchronized (mLock) {
627             if (mCurrentCountryIso == null) {
628                 Log.i(TAG, "Country cache is null. Detecting Country and Setting Cache...");
629                 final CountryDetector countryDetector =
630                         (CountryDetector) mContext.getSystemService(Context.COUNTRY_DETECTOR);
631                 Country country = null;
632                 if (countryDetector != null) {
633                     country = countryDetector.detectCountry();
634 
635                     countryDetector.addCountryListener((newCountry) -> {
636                         Log.startSession("CLM.oCD");
637                         try {
638                             synchronized (mLock) {
639                                 Log.i(TAG, "Country ISO changed. Retrieving new ISO...");
640                                 mCurrentCountryIso = getCountryIsoFromCountry(newCountry);
641                             }
642                         } finally {
643                             Log.endSession();
644                         }
645                     }, Looper.getMainLooper());
646                 }
647                 mCurrentCountryIso = getCountryIsoFromCountry(country);
648             }
649             return mCurrentCountryIso;
650         }
651     }
652 }
653