blob: 7862afdb06bf2f32ed2f7a891d59f37de88a5da2 [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;
Yorke Leef98fb572014-03-05 10:56:55 -080024import android.telephony.PhoneNumberUtils;
25
26import com.android.internal.telephony.PhoneConstants;
27
Yorke Lee060d1d62014-03-19 13:24:15 -070028import java.io.UnsupportedEncodingException;
29import java.net.URLDecoder;
30
Yorke Leef98fb572014-03-05 10:56:55 -080031/**
32 * Helper class that provides functionality to write information about calls and their associated
33 * caller details to the call log. All logging activity will be performed asynchronously in a
34 * background thread to avoid blocking on the main thread.
35 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070036final class CallLogManager extends CallsManagerListenerBase {
Yorke Leef98fb572014-03-05 10:56:55 -080037 /**
38 * Parameter object to hold the arguments to add a call in the call log DB.
39 */
40 private static class AddCallArgs {
41 /**
42 * @param contactInfo Caller details.
43 * @param number The phone number to be logged.
44 * @param presentation Number presentation of the phone number to be logged.
45 * @param callType The type of call (e.g INCOMING_TYPE). @see
46 * {@link android.provider.CallLog} for the list of values.
47 * @param creationDate Time when the call was created (milliseconds since epoch).
48 * @param durationInMillis Duration of the call (milliseconds).
49 */
50 public AddCallArgs(Context context, ContactInfo contactInfo, String number,
51 int presentation, int callType, long creationDate, long durationInMillis) {
52 this.context = context;
53 this.contactInfo = contactInfo;
54 this.number = number;
55 this.presentation = presentation;
56 this.callType = callType;
57 this.timestamp = creationDate;
58 this.durationInSec = (int)(durationInMillis / 1000);
59 }
60 // Since the members are accessed directly, we don't use the
61 // mXxxx notation.
62 public final Context context;
63 public final ContactInfo contactInfo;
64 public final String number;
65 public final int presentation;
66 public final int callType;
67 public final long timestamp;
68 public final int durationInSec;
69 }
70
71 private static final String TAG = CallLogManager.class.getSimpleName();
72
73 private final Context mContext;
74
75 public CallLogManager(Context context) {
76 mContext = context;
77 }
78
Sailesh Nepal810735e2014-03-18 18:15:46 -070079 @Override
80 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
81 if (newState == CallState.DISCONNECTED || newState == CallState.ABORTED) {
82 int type;
83 if (!call.isIncoming()) {
84 type = Calls.OUTGOING_TYPE;
85 } else if (oldState == CallState.RINGING) {
86 type = Calls.MISSED_TYPE;
87 } else {
88 type = Calls.INCOMING_TYPE;
89 }
90 logCall(call, type);
91 }
Yorke Leef98fb572014-03-05 10:56:55 -080092 }
93
94 /**
95 * Logs a call to the call log based on the {@link Call} object passed in.
96 *
97 * @param call The call object being logged
98 * @param callLogType The type of call log entry to log this call as. See:
99 * {@link android.provider.CallLog.Calls#INCOMING_TYPE}
100 * {@link android.provider.CallLog.Calls#OUTGOING_TYPE}
101 * {@link android.provider.CallLog.Calls#MISSED_TYPE}
102 */
103 private void logCall(Call call, int callLogType) {
Sailesh Nepalce704b92014-03-17 18:31:43 -0700104 Uri number = call.getHandle();
Yorke Leef98fb572014-03-05 10:56:55 -0800105 final long creationTime = call.getCreationTimeInMilliseconds();
106 final long age = call.getAgeInMilliseconds();
107
108 final ContactInfo contactInfo = call.getContactInfo(); // May be null.
109 final String logNumber = getLogNumber(call);
110
111 Log.d(TAG, "logNumber set to:" + Log.pii(logNumber) + ", number set to: "
112 + Log.pii(number));
113
114 final int presentation = getPresentation(call, contactInfo);
115
116 logCall(contactInfo, logNumber, presentation, callLogType, creationTime, age);
117 }
118
119 /**
120 * Inserts a call into the call log, based on the parameters passed in.
121 *
122 * @param contactInfo Caller details.
123 * @param number The number the call was made to or from.
124 * @param presentation
125 * @param callType The type of call.
126 * @param start The start time of the call, in milliseconds.
127 * @param duration The duration of the call, in milliseconds.
128 */
129 private void logCall(
130 ContactInfo contactInfo,
131 String number,
132 int presentation,
133 int callType,
134 long start,
135 long duration) {
136 boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number, mContext);
137
138 // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
139 // emergency calls to the Call Log. (This behavior is set on a per-product basis, based
140 // on carrier requirements.)
141 final boolean okToLogEmergencyNumber =
142 mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);
143
144 // Don't log emergency numbers if the device doesn't allow it.
145 final boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;
146
147 if (isOkToLogThisCall) {
148 Log.d(TAG, "Logging Calllog entry: " + contactInfo + ", "
149 + Log.pii(number) + "," + presentation + ", " + callType
150 + ", " + start + ", " + duration);
151 AddCallArgs args = new AddCallArgs(mContext, contactInfo, number, presentation,
152 callType, start, duration);
153 logCallAsync(args);
154 } else {
155 Log.d(TAG, "Not adding emergency call to call log.");
156 }
157 }
158
159 /**
160 * Retrieve the phone number from the call, and then process it before returning the
161 * actual number that is to be logged.
162 *
163 * @param call The phone connection.
164 * @return the phone number to be logged.
165 */
166 private String getLogNumber(Call call) {
Sailesh Nepalce704b92014-03-17 18:31:43 -0700167 Uri handle = call.getHandle();
Yorke Leef98fb572014-03-05 10:56:55 -0800168
169 if (handle == null) {
170 return null;
171 }
172
Yorke Lee060d1d62014-03-19 13:24:15 -0700173 String handleString = handle.getSchemeSpecificPart();
Sailesh Nepalce704b92014-03-17 18:31:43 -0700174 if (!PhoneNumberUtils.isUriNumber(handleString)) {
175 handleString = PhoneNumberUtils.stripSeparators(handleString);
Yorke Leef98fb572014-03-05 10:56:55 -0800176 }
Sailesh Nepalce704b92014-03-17 18:31:43 -0700177 return handleString;
Yorke Leef98fb572014-03-05 10:56:55 -0800178 }
179
180 /**
181 * Gets the presentation from the {@link ContactInfo} if not null. Otherwise, gets it from the
182 * {@link Call}.
183 *
184 * TODO: There needs to be a way to pass information from
185 * Connection.getNumberPresentation() into a {@link Call} object. Until then, always return
186 * PhoneConstants.PRESENTATION_ALLOWED. On top of that, we might need to introduce
187 * getNumberPresentation to the ContactInfo object as well.
188 *
189 * @param call The call object to retrieve caller details from.
190 * @param contactInfo The CallerInfo. May be null.
191 * @return The number presentation constant to insert into the call logs.
192 */
193 private int getPresentation(Call call, ContactInfo contactInfo) {
194 return PhoneConstants.PRESENTATION_ALLOWED;
195 }
196
197 /**
198 * Adds the call defined by the parameters in the provided AddCallArgs to the CallLogProvider
199 * using an AsyncTask to avoid blocking the main thread.
200 *
201 * @param args Prepopulated call details.
202 * @return A handle to the AsyncTask that will add the call to the call log asynchronously.
203 */
204 public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {
205 return new LogCallAsyncTask().execute(args);
206 }
207
208 /**
209 * Helper AsyncTask to access the call logs database asynchronously since database operations
210 * can take a long time depending on the system's load. Since it extends AsyncTask, it uses
211 * its own thread pool.
212 */
213 private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> {
214 @Override
215 protected Uri[] doInBackground(AddCallArgs... callList) {
216 int count = callList.length;
217 Uri[] result = new Uri[count];
218 for (int i = 0; i < count; i++) {
219 AddCallArgs c = callList[i];
220
221 try {
222 // May block.
223 result[i] = Calls.addCall(null, c.context, c.number, c.presentation,
224 c.callType, c.timestamp, c.durationInSec);
225 } catch (Exception e) {
226 // This is very rare but may happen in legitimate cases.
227 // E.g. If the phone is encrypted and thus write request fails, it may cause
228 // some kind of Exception (right now it is IllegalArgumentException, but this
229 // might change).
230 //
231 // We don't want to crash the whole process just because of that, so just log
232 // it instead.
233 Log.e(TAG, e, "Exception raised during adding CallLog entry.");
234 result[i] = null;
235 }
236 }
237 return result;
238 }
239
240 /**
241 * Performs a simple sanity check to make sure the call was written in the database.
242 * Typically there is only one result per call so it is easy to identify which one failed.
243 */
244 @Override
245 protected void onPostExecute(Uri[] result) {
246 for (Uri uri : result) {
247 if (uri == null) {
248 Log.w(TAG, "Failed to write call to the log.");
249 }
250 }
251 }
252 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800253}