blob: e7cdab47f8298c77bcfbd2f84b276abed5fea501 [file] [log] [blame]
/*
* Copyright 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.telecomm;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.CallLog.Calls;
import android.telecomm.CallState;
import android.telephony.PhoneNumberUtils;
import com.android.internal.telephony.PhoneConstants;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
/**
* Helper class that provides functionality to write information about calls and their associated
* caller details to the call log. All logging activity will be performed asynchronously in a
* background thread to avoid blocking on the main thread.
*/
final class CallLogManager extends CallsManagerListenerBase {
/**
* Parameter object to hold the arguments to add a call in the call log DB.
*/
private static class AddCallArgs {
/**
* @param contactInfo Caller details.
* @param number The phone number to be logged.
* @param presentation Number presentation of the phone number to be logged.
* @param callType The type of call (e.g INCOMING_TYPE). @see
* {@link android.provider.CallLog} for the list of values.
* @param creationDate Time when the call was created (milliseconds since epoch).
* @param durationInMillis Duration of the call (milliseconds).
*/
public AddCallArgs(Context context, ContactInfo contactInfo, String number,
int presentation, int callType, long creationDate, long durationInMillis) {
this.context = context;
this.contactInfo = contactInfo;
this.number = number;
this.presentation = presentation;
this.callType = callType;
this.timestamp = creationDate;
this.durationInSec = (int)(durationInMillis / 1000);
}
// Since the members are accessed directly, we don't use the
// mXxxx notation.
public final Context context;
public final ContactInfo contactInfo;
public final String number;
public final int presentation;
public final int callType;
public final long timestamp;
public final int durationInSec;
}
private static final String TAG = CallLogManager.class.getSimpleName();
private final Context mContext;
public CallLogManager(Context context) {
mContext = context;
}
@Override
public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
if (newState == CallState.DISCONNECTED || newState == CallState.ABORTED) {
int type;
if (!call.isIncoming()) {
type = Calls.OUTGOING_TYPE;
} else if (oldState == CallState.RINGING) {
type = Calls.MISSED_TYPE;
} else {
type = Calls.INCOMING_TYPE;
}
logCall(call, type);
}
}
/**
* Logs a call to the call log based on the {@link Call} object passed in.
*
* @param call The call object being logged
* @param callLogType The type of call log entry to log this call as. See:
* {@link android.provider.CallLog.Calls#INCOMING_TYPE}
* {@link android.provider.CallLog.Calls#OUTGOING_TYPE}
* {@link android.provider.CallLog.Calls#MISSED_TYPE}
*/
private void logCall(Call call, int callLogType) {
final long creationTime = call.getCreationTimeMillis();
final long age = call.getAgeMillis();
// TODO(santoscordon): Replace with use of call.getCallerInfo() or similar.
final ContactInfo contactInfo = null;
final String logNumber = getLogNumber(call);
Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber));
final int presentation = getPresentation(call, contactInfo);
logCall(contactInfo, logNumber, presentation, callLogType, creationTime, age);
}
/**
* Inserts a call into the call log, based on the parameters passed in.
*
* @param contactInfo Caller details.
* @param number The number the call was made to or from.
* @param presentation
* @param callType The type of call.
* @param start The start time of the call, in milliseconds.
* @param duration The duration of the call, in milliseconds.
*/
private void logCall(
ContactInfo contactInfo,
String number,
int presentation,
int callType,
long start,
long duration) {
boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number, mContext);
// On some devices, to avoid accidental redialing of emergency numbers, we *never* log
// emergency calls to the Call Log. (This behavior is set on a per-product basis, based
// on carrier requirements.)
final boolean okToLogEmergencyNumber =
mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);
// Don't log emergency numbers if the device doesn't allow it.
final boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;
if (isOkToLogThisCall) {
Log.d(TAG, "Logging Calllog entry: " + contactInfo + ", "
+ Log.pii(number) + "," + presentation + ", " + callType
+ ", " + start + ", " + duration);
AddCallArgs args = new AddCallArgs(mContext, contactInfo, number, presentation,
callType, start, duration);
logCallAsync(args);
} else {
Log.d(TAG, "Not adding emergency call to call log.");
}
}
/**
* Retrieve the phone number from the call, and then process it before returning the
* actual number that is to be logged.
*
* @param call The phone connection.
* @return the phone number to be logged.
*/
private String getLogNumber(Call call) {
Uri handle = call.getOriginalHandle();
if (handle == null) {
return null;
}
String handleString = handle.getSchemeSpecificPart();
if (!PhoneNumberUtils.isUriNumber(handleString)) {
handleString = PhoneNumberUtils.stripSeparators(handleString);
}
return handleString;
}
/**
* Gets the presentation from the {@link ContactInfo} if not null. Otherwise, gets it from the
* {@link Call}.
*
* TODO: There needs to be a way to pass information from
* Connection.getNumberPresentation() into a {@link Call} object. Until then, always return
* PhoneConstants.PRESENTATION_ALLOWED. On top of that, we might need to introduce
* getNumberPresentation to the ContactInfo object as well.
*
* @param call The call object to retrieve caller details from.
* @param contactInfo The CallerInfo. May be null.
* @return The number presentation constant to insert into the call logs.
*/
private int getPresentation(Call call, ContactInfo contactInfo) {
return PhoneConstants.PRESENTATION_ALLOWED;
}
/**
* Adds the call defined by the parameters in the provided AddCallArgs to the CallLogProvider
* using an AsyncTask to avoid blocking the main thread.
*
* @param args Prepopulated call details.
* @return A handle to the AsyncTask that will add the call to the call log asynchronously.
*/
public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {
return new LogCallAsyncTask().execute(args);
}
/**
* Helper AsyncTask to access the call logs database asynchronously since database operations
* can take a long time depending on the system's load. Since it extends AsyncTask, it uses
* its own thread pool.
*/
private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> {
@Override
protected Uri[] doInBackground(AddCallArgs... callList) {
int count = callList.length;
Uri[] result = new Uri[count];
for (int i = 0; i < count; i++) {
AddCallArgs c = callList[i];
try {
// May block.
result[i] = Calls.addCall(null, c.context, c.number, c.presentation,
c.callType, c.timestamp, c.durationInSec);
} catch (Exception e) {
// This is very rare but may happen in legitimate cases.
// E.g. If the phone is encrypted and thus write request fails, it may cause
// some kind of Exception (right now it is IllegalArgumentException, but this
// might change).
//
// We don't want to crash the whole process just because of that, so just log
// it instead.
Log.e(TAG, e, "Exception raised during adding CallLog entry.");
result[i] = null;
}
}
return result;
}
/**
* Performs a simple sanity check to make sure the call was written in the database.
* Typically there is only one result per call so it is easy to identify which one failed.
*/
@Override
protected void onPostExecute(Uri[] result) {
for (Uri uri : result) {
if (uri == null) {
Log.w(TAG, "Failed to write call to the log.");
}
}
}
}
}