blob: ba02aabf2691be7d8f5be1a68350db9d57cb1d2b [file] [log] [blame]
Yorke Leef98fb572014-03-05 10:56:55 -08001/*
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
Ben Gilad9f2bed32013-12-12 17:43:26 -080017package com.android.telecomm;
18
Yorke Leef98fb572014-03-05 10:56:55 -080019import android.content.Context;
20import android.net.Uri;
21import android.os.AsyncTask;
22import android.provider.CallLog.Calls;
Sailesh Nepal810735e2014-03-18 18:15:46 -070023import android.telecomm.CallState;
Evan Charlton89176372014-07-19 18:23:09 -070024import android.telecomm.PhoneAccountHandle;
Ihab Awad6fb37c82014-08-07 19:48:57 -070025import android.telecomm.VideoProfile;
Yorke Lee43df90f2014-08-20 10:56:09 -070026import android.telephony.DisconnectCause;
Yorke Leef98fb572014-03-05 10:56:55 -080027import android.telephony.PhoneNumberUtils;
28
Yorke Lee6f3f7af2014-07-11 10:59:46 -070029import com.android.internal.telephony.CallerInfo;
Yorke Leef98fb572014-03-05 10:56:55 -080030import com.android.internal.telephony.PhoneConstants;
31
32/**
33 * Helper class that provides functionality to write information about calls and their associated
34 * caller details to the call log. All logging activity will be performed asynchronously in a
35 * background thread to avoid blocking on the main thread.
36 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070037final class CallLogManager extends CallsManagerListenerBase {
Yorke Leef98fb572014-03-05 10:56:55 -080038 /**
39 * Parameter object to hold the arguments to add a call in the call log DB.
40 */
41 private static class AddCallArgs {
42 /**
Evan Charlton89176372014-07-19 18:23:09 -070043 * @param callerInfo Caller details.
Yorke Leef98fb572014-03-05 10:56:55 -080044 * @param number The phone number to be logged.
45 * @param presentation Number presentation of the phone number to be logged.
46 * @param callType The type of call (e.g INCOMING_TYPE). @see
47 * {@link android.provider.CallLog} for the list of values.
Tyler Gunn765c35c2014-07-10 08:17:53 -070048 * @param features The features of the call (e.g. FEATURES_VIDEO). @see
49 * {@link android.provider.CallLog} for the list of values.
Yorke Leef98fb572014-03-05 10:56:55 -080050 * @param creationDate Time when the call was created (milliseconds since epoch).
51 * @param durationInMillis Duration of the call (milliseconds).
Tyler Gunn765c35c2014-07-10 08:17:53 -070052 * @param dataUsage Data usage in bytes, or null if not applicable.
Yorke Leef98fb572014-03-05 10:56:55 -080053 */
Yorke Lee6f3f7af2014-07-11 10:59:46 -070054 public AddCallArgs(Context context, CallerInfo callerInfo, String number,
Evan Charlton89176372014-07-19 18:23:09 -070055 int presentation, int callType, int features, PhoneAccountHandle accountHandle,
Tyler Gunn765c35c2014-07-10 08:17:53 -070056 long creationDate, long durationInMillis, Long dataUsage) {
Yorke Leef98fb572014-03-05 10:56:55 -080057 this.context = context;
Yorke Lee6f3f7af2014-07-11 10:59:46 -070058 this.callerInfo = callerInfo;
Yorke Leef98fb572014-03-05 10:56:55 -080059 this.number = number;
60 this.presentation = presentation;
61 this.callType = callType;
Tyler Gunn765c35c2014-07-10 08:17:53 -070062 this.features = features;
Evan Charlton89176372014-07-19 18:23:09 -070063 this.accountHandle = accountHandle;
Yorke Leef98fb572014-03-05 10:56:55 -080064 this.timestamp = creationDate;
65 this.durationInSec = (int)(durationInMillis / 1000);
Tyler Gunn765c35c2014-07-10 08:17:53 -070066 this.dataUsage = dataUsage;
Yorke Leef98fb572014-03-05 10:56:55 -080067 }
68 // Since the members are accessed directly, we don't use the
69 // mXxxx notation.
70 public final Context context;
Yorke Lee6f3f7af2014-07-11 10:59:46 -070071 public final CallerInfo callerInfo;
Yorke Leef98fb572014-03-05 10:56:55 -080072 public final String number;
73 public final int presentation;
74 public final int callType;
Tyler Gunn765c35c2014-07-10 08:17:53 -070075 public final int features;
Evan Charlton89176372014-07-19 18:23:09 -070076 public final PhoneAccountHandle accountHandle;
Yorke Leef98fb572014-03-05 10:56:55 -080077 public final long timestamp;
78 public final int durationInSec;
Tyler Gunn765c35c2014-07-10 08:17:53 -070079 public final Long dataUsage;
Yorke Leef98fb572014-03-05 10:56:55 -080080 }
81
82 private static final String TAG = CallLogManager.class.getSimpleName();
83
84 private final Context mContext;
85
86 public CallLogManager(Context context) {
87 mContext = context;
88 }
89
Sailesh Nepal810735e2014-03-18 18:15:46 -070090 @Override
Ihab Awad6fb37c82014-08-07 19:48:57 -070091 public void onCallStateChanged(Call call, int oldState, int newState) {
Santos Cordon672321e2014-08-21 14:38:58 -070092 boolean isNewlyDisconnected =
93 newState == CallState.DISCONNECTED || newState == CallState.ABORTED;
94 boolean isCallCanceled = isNewlyDisconnected &&
95 call.getDisconnectCause() == DisconnectCause.OUTGOING_CANCELED;
96
97 // Log newly disconnected calls only if:
98 // 1) It was not in the "choose account" phase when disconnected
99 // 2) It is a conference call
100 // 3) Call was not explicitly canceled
101 if (isNewlyDisconnected &&
102 (oldState != CallState.PRE_DIAL_WAIT &&
103 !call.isConference() &&
104 !isCallCanceled)) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700105 int type;
106 if (!call.isIncoming()) {
107 type = Calls.OUTGOING_TYPE;
108 } else if (oldState == CallState.RINGING) {
109 type = Calls.MISSED_TYPE;
110 } else {
111 type = Calls.INCOMING_TYPE;
112 }
113 logCall(call, type);
114 }
Yorke Leef98fb572014-03-05 10:56:55 -0800115 }
116
117 /**
118 * Logs a call to the call log based on the {@link Call} object passed in.
119 *
120 * @param call The call object being logged
121 * @param callLogType The type of call log entry to log this call as. See:
122 * {@link android.provider.CallLog.Calls#INCOMING_TYPE}
123 * {@link android.provider.CallLog.Calls#OUTGOING_TYPE}
124 * {@link android.provider.CallLog.Calls#MISSED_TYPE}
125 */
126 private void logCall(Call call, int callLogType) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700127 final long creationTime = call.getCreationTimeMillis();
128 final long age = call.getAgeMillis();
Yorke Leef98fb572014-03-05 10:56:55 -0800129
Yorke Leef98fb572014-03-05 10:56:55 -0800130 final String logNumber = getLogNumber(call);
131
Yorke Lee33501632014-03-17 19:24:12 -0700132 Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber));
Yorke Leef98fb572014-03-05 10:56:55 -0800133
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700134 final int presentation = getPresentation(call);
Ihab Awadb78b2762014-07-25 15:16:23 -0700135 final PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
Yorke Leef98fb572014-03-05 10:56:55 -0800136
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700137 // TODO(vt): Once data usage is available, wire it up here.
138 int callFeatures = getCallFeatures(call.getVideoStateHistory());
Evan Charlton94d01622014-07-20 12:32:05 -0700139 logCall(call.getCallerInfo(), logNumber, presentation, callLogType, callFeatures,
140 accountHandle, creationTime, age, null);
Yorke Leef98fb572014-03-05 10:56:55 -0800141 }
142
143 /**
144 * Inserts a call into the call log, based on the parameters passed in.
145 *
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700146 * @param callerInfo Caller details.
Yorke Leef98fb572014-03-05 10:56:55 -0800147 * @param number The number the call was made to or from.
148 * @param presentation
149 * @param callType The type of call.
Tyler Gunn765c35c2014-07-10 08:17:53 -0700150 * @param features The features of the call.
Yorke Leef98fb572014-03-05 10:56:55 -0800151 * @param start The start time of the call, in milliseconds.
152 * @param duration The duration of the call, in milliseconds.
Tyler Gunn765c35c2014-07-10 08:17:53 -0700153 * @param dataUsage The data usage for the call, null if not applicable.
Yorke Leef98fb572014-03-05 10:56:55 -0800154 */
155 private void logCall(
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700156 CallerInfo callerInfo,
Yorke Leef98fb572014-03-05 10:56:55 -0800157 String number,
158 int presentation,
159 int callType,
Tyler Gunn765c35c2014-07-10 08:17:53 -0700160 int features,
Evan Charlton89176372014-07-19 18:23:09 -0700161 PhoneAccountHandle accountHandle,
Yorke Leef98fb572014-03-05 10:56:55 -0800162 long start,
Tyler Gunn765c35c2014-07-10 08:17:53 -0700163 long duration,
164 Long dataUsage) {
Yorke Lee66255452014-06-05 08:09:24 -0700165 boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(mContext, number);
Yorke Leef98fb572014-03-05 10:56:55 -0800166
167 // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
168 // emergency calls to the Call Log. (This behavior is set on a per-product basis, based
169 // on carrier requirements.)
170 final boolean okToLogEmergencyNumber =
171 mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);
172
173 // Don't log emergency numbers if the device doesn't allow it.
174 final boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;
175
176 if (isOkToLogThisCall) {
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700177 Log.d(TAG, "Logging Calllog entry: " + callerInfo + ", "
Yorke Leef98fb572014-03-05 10:56:55 -0800178 + Log.pii(number) + "," + presentation + ", " + callType
179 + ", " + start + ", " + duration);
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700180 AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, presentation,
Evan Charlton89176372014-07-19 18:23:09 -0700181 callType, features, accountHandle, start, duration, dataUsage);
Yorke Leef98fb572014-03-05 10:56:55 -0800182 logCallAsync(args);
183 } else {
184 Log.d(TAG, "Not adding emergency call to call log.");
185 }
186 }
187
188 /**
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700189 * Based on the video state of the call, determines the call features applicable for the call.
190 *
191 * @param videoState The video state.
192 * @return The call features.
193 */
194 private static int getCallFeatures(int videoState) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700195 if ((videoState & VideoProfile.VideoState.TX_ENABLED)
196 == VideoProfile.VideoState.TX_ENABLED) {
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700197 return Calls.FEATURES_VIDEO;
198 }
199 return Calls.FEATURES_NONE;
200 }
201
202 /**
Yorke Leef98fb572014-03-05 10:56:55 -0800203 * Retrieve the phone number from the call, and then process it before returning the
204 * actual number that is to be logged.
205 *
206 * @param call The phone connection.
207 * @return the phone number to be logged.
208 */
209 private String getLogNumber(Call call) {
Yorke Lee33501632014-03-17 19:24:12 -0700210 Uri handle = call.getOriginalHandle();
Yorke Leef98fb572014-03-05 10:56:55 -0800211
212 if (handle == null) {
213 return null;
214 }
215
Yorke Lee060d1d62014-03-19 13:24:15 -0700216 String handleString = handle.getSchemeSpecificPart();
Sailesh Nepalce704b92014-03-17 18:31:43 -0700217 if (!PhoneNumberUtils.isUriNumber(handleString)) {
218 handleString = PhoneNumberUtils.stripSeparators(handleString);
Yorke Leef98fb572014-03-05 10:56:55 -0800219 }
Sailesh Nepalce704b92014-03-17 18:31:43 -0700220 return handleString;
Yorke Leef98fb572014-03-05 10:56:55 -0800221 }
222
223 /**
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700224 * Gets the presentation from the {@link Call}.
Yorke Leef98fb572014-03-05 10:56:55 -0800225 *
226 * TODO: There needs to be a way to pass information from
227 * Connection.getNumberPresentation() into a {@link Call} object. Until then, always return
228 * PhoneConstants.PRESENTATION_ALLOWED. On top of that, we might need to introduce
229 * getNumberPresentation to the ContactInfo object as well.
230 *
231 * @param call The call object to retrieve caller details from.
Yorke Leef98fb572014-03-05 10:56:55 -0800232 * @return The number presentation constant to insert into the call logs.
233 */
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700234 private int getPresentation(Call call) {
Yorke Leef98fb572014-03-05 10:56:55 -0800235 return PhoneConstants.PRESENTATION_ALLOWED;
236 }
237
238 /**
239 * Adds the call defined by the parameters in the provided AddCallArgs to the CallLogProvider
240 * using an AsyncTask to avoid blocking the main thread.
241 *
242 * @param args Prepopulated call details.
243 * @return A handle to the AsyncTask that will add the call to the call log asynchronously.
244 */
245 public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {
246 return new LogCallAsyncTask().execute(args);
247 }
248
249 /**
250 * Helper AsyncTask to access the call logs database asynchronously since database operations
251 * can take a long time depending on the system's load. Since it extends AsyncTask, it uses
252 * its own thread pool.
253 */
254 private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> {
255 @Override
256 protected Uri[] doInBackground(AddCallArgs... callList) {
257 int count = callList.length;
258 Uri[] result = new Uri[count];
259 for (int i = 0; i < count; i++) {
260 AddCallArgs c = callList[i];
261
262 try {
263 // May block.
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700264 result[i] = Calls.addCall(c.callerInfo, c.context, c.number, c.presentation,
Evan Charlton89176372014-07-19 18:23:09 -0700265 c.callType, c.features, c.accountHandle, c.timestamp, c.durationInSec,
Yorke Leea38f3292014-07-16 17:31:02 -0700266 c.dataUsage, true /* addForAllUsers */);
Yorke Leef98fb572014-03-05 10:56:55 -0800267 } catch (Exception e) {
268 // This is very rare but may happen in legitimate cases.
269 // E.g. If the phone is encrypted and thus write request fails, it may cause
270 // some kind of Exception (right now it is IllegalArgumentException, but this
271 // might change).
272 //
273 // We don't want to crash the whole process just because of that, so just log
274 // it instead.
275 Log.e(TAG, e, "Exception raised during adding CallLog entry.");
276 result[i] = null;
277 }
278 }
279 return result;
280 }
281
282 /**
283 * Performs a simple sanity check to make sure the call was written in the database.
284 * Typically there is only one result per call so it is easy to identify which one failed.
285 */
286 @Override
287 protected void onPostExecute(Uri[] result) {
288 for (Uri uri : result) {
289 if (uri == null) {
290 Log.w(TAG, "Failed to write call to the log.");
291 }
292 }
293 }
294 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800295}