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