Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 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 | |
Ben Gilad | 9f2bed3 | 2013-12-12 17:43:26 -0800 | [diff] [blame] | 17 | package com.android.telecomm; |
| 18 | |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 19 | import android.content.Context; |
| 20 | import android.net.Uri; |
| 21 | import android.os.AsyncTask; |
| 22 | import android.provider.CallLog.Calls; |
Sailesh Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 23 | import android.telecomm.CallState; |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 24 | import android.telecomm.PhoneAccountHandle; |
Ihab Awad | 6fb37c8 | 2014-08-07 19:48:57 -0700 | [diff] [blame] | 25 | import android.telecomm.VideoProfile; |
Yorke Lee | 43df90f | 2014-08-20 10:56:09 -0700 | [diff] [blame] | 26 | import android.telephony.DisconnectCause; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 27 | import android.telephony.PhoneNumberUtils; |
| 28 | |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 29 | import com.android.internal.telephony.CallerInfo; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 30 | import 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 Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 37 | final class CallLogManager extends CallsManagerListenerBase { |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 38 | /** |
| 39 | * Parameter object to hold the arguments to add a call in the call log DB. |
| 40 | */ |
| 41 | private static class AddCallArgs { |
| 42 | /** |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 43 | * @param callerInfo Caller details. |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 44 | * @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 Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 48 | * @param features The features of the call (e.g. FEATURES_VIDEO). @see |
| 49 | * {@link android.provider.CallLog} for the list of values. |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 50 | * @param creationDate Time when the call was created (milliseconds since epoch). |
| 51 | * @param durationInMillis Duration of the call (milliseconds). |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 52 | * @param dataUsage Data usage in bytes, or null if not applicable. |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 53 | */ |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 54 | public AddCallArgs(Context context, CallerInfo callerInfo, String number, |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 55 | int presentation, int callType, int features, PhoneAccountHandle accountHandle, |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 56 | long creationDate, long durationInMillis, Long dataUsage) { |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 57 | this.context = context; |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 58 | this.callerInfo = callerInfo; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 59 | this.number = number; |
| 60 | this.presentation = presentation; |
| 61 | this.callType = callType; |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 62 | this.features = features; |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 63 | this.accountHandle = accountHandle; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 64 | this.timestamp = creationDate; |
| 65 | this.durationInSec = (int)(durationInMillis / 1000); |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 66 | this.dataUsage = dataUsage; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 67 | } |
| 68 | // Since the members are accessed directly, we don't use the |
| 69 | // mXxxx notation. |
| 70 | public final Context context; |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 71 | public final CallerInfo callerInfo; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 72 | public final String number; |
| 73 | public final int presentation; |
| 74 | public final int callType; |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 75 | public final int features; |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 76 | public final PhoneAccountHandle accountHandle; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 77 | public final long timestamp; |
| 78 | public final int durationInSec; |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 79 | public final Long dataUsage; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 80 | } |
| 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 Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 90 | @Override |
Ihab Awad | 6fb37c8 | 2014-08-07 19:48:57 -0700 | [diff] [blame] | 91 | public void onCallStateChanged(Call call, int oldState, int newState) { |
Santos Cordon | 672321e | 2014-08-21 14:38:58 -0700 | [diff] [blame^] | 92 | 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 Nepal | 810735e | 2014-03-18 18:15:46 -0700 | [diff] [blame] | 105 | 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 Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 115 | } |
| 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 Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 127 | final long creationTime = call.getCreationTimeMillis(); |
| 128 | final long age = call.getAgeMillis(); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 129 | |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 130 | final String logNumber = getLogNumber(call); |
| 131 | |
Yorke Lee | 3350163 | 2014-03-17 19:24:12 -0700 | [diff] [blame] | 132 | Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber)); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 133 | |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 134 | final int presentation = getPresentation(call); |
Ihab Awad | b78b276 | 2014-07-25 15:16:23 -0700 | [diff] [blame] | 135 | final PhoneAccountHandle accountHandle = call.getTargetPhoneAccount(); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 136 | |
Tyler Gunn | 0a388fc | 2014-07-17 12:21:17 -0700 | [diff] [blame] | 137 | // TODO(vt): Once data usage is available, wire it up here. |
| 138 | int callFeatures = getCallFeatures(call.getVideoStateHistory()); |
Evan Charlton | 94d0162 | 2014-07-20 12:32:05 -0700 | [diff] [blame] | 139 | logCall(call.getCallerInfo(), logNumber, presentation, callLogType, callFeatures, |
| 140 | accountHandle, creationTime, age, null); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Inserts a call into the call log, based on the parameters passed in. |
| 145 | * |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 146 | * @param callerInfo Caller details. |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 147 | * @param number The number the call was made to or from. |
| 148 | * @param presentation |
| 149 | * @param callType The type of call. |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 150 | * @param features The features of the call. |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 151 | * @param start The start time of the call, in milliseconds. |
| 152 | * @param duration The duration of the call, in milliseconds. |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 153 | * @param dataUsage The data usage for the call, null if not applicable. |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 154 | */ |
| 155 | private void logCall( |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 156 | CallerInfo callerInfo, |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 157 | String number, |
| 158 | int presentation, |
| 159 | int callType, |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 160 | int features, |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 161 | PhoneAccountHandle accountHandle, |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 162 | long start, |
Tyler Gunn | 765c35c | 2014-07-10 08:17:53 -0700 | [diff] [blame] | 163 | long duration, |
| 164 | Long dataUsage) { |
Yorke Lee | 6625545 | 2014-06-05 08:09:24 -0700 | [diff] [blame] | 165 | boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(mContext, number); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 166 | |
| 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 Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 177 | Log.d(TAG, "Logging Calllog entry: " + callerInfo + ", " |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 178 | + Log.pii(number) + "," + presentation + ", " + callType |
| 179 | + ", " + start + ", " + duration); |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 180 | AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, presentation, |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 181 | callType, features, accountHandle, start, duration, dataUsage); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 182 | logCallAsync(args); |
| 183 | } else { |
| 184 | Log.d(TAG, "Not adding emergency call to call log."); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | /** |
Tyler Gunn | 0a388fc | 2014-07-17 12:21:17 -0700 | [diff] [blame] | 189 | * 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 Awad | 6fb37c8 | 2014-08-07 19:48:57 -0700 | [diff] [blame] | 195 | if ((videoState & VideoProfile.VideoState.TX_ENABLED) |
| 196 | == VideoProfile.VideoState.TX_ENABLED) { |
Tyler Gunn | 0a388fc | 2014-07-17 12:21:17 -0700 | [diff] [blame] | 197 | return Calls.FEATURES_VIDEO; |
| 198 | } |
| 199 | return Calls.FEATURES_NONE; |
| 200 | } |
| 201 | |
| 202 | /** |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 203 | * 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 Lee | 3350163 | 2014-03-17 19:24:12 -0700 | [diff] [blame] | 210 | Uri handle = call.getOriginalHandle(); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 211 | |
| 212 | if (handle == null) { |
| 213 | return null; |
| 214 | } |
| 215 | |
Yorke Lee | 060d1d6 | 2014-03-19 13:24:15 -0700 | [diff] [blame] | 216 | String handleString = handle.getSchemeSpecificPart(); |
Sailesh Nepal | ce704b9 | 2014-03-17 18:31:43 -0700 | [diff] [blame] | 217 | if (!PhoneNumberUtils.isUriNumber(handleString)) { |
| 218 | handleString = PhoneNumberUtils.stripSeparators(handleString); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 219 | } |
Sailesh Nepal | ce704b9 | 2014-03-17 18:31:43 -0700 | [diff] [blame] | 220 | return handleString; |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | /** |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 224 | * Gets the presentation from the {@link Call}. |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 225 | * |
| 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 Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 232 | * @return The number presentation constant to insert into the call logs. |
| 233 | */ |
Yorke Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 234 | private int getPresentation(Call call) { |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 235 | 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 Lee | 6f3f7af | 2014-07-11 10:59:46 -0700 | [diff] [blame] | 264 | result[i] = Calls.addCall(c.callerInfo, c.context, c.number, c.presentation, |
Evan Charlton | 8917637 | 2014-07-19 18:23:09 -0700 | [diff] [blame] | 265 | c.callType, c.features, c.accountHandle, c.timestamp, c.durationInSec, |
Yorke Lee | a38f329 | 2014-07-16 17:31:02 -0700 | [diff] [blame] | 266 | c.dataUsage, true /* addForAllUsers */); |
Yorke Lee | f98fb57 | 2014-03-05 10:56:55 -0800 | [diff] [blame] | 267 | } 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 Gilad | 9f2bed3 | 2013-12-12 17:43:26 -0800 | [diff] [blame] | 295 | } |