Merge "Initial commit of basic call logging code in Telecomm" into master-nova
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 295e3dc..780906c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -23,6 +23,8 @@
the user presses "home". -->
<!-- TODO(gilad): Better understand/document this use case. -->
<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"></uses-permission>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..6905860
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<!-- Telecomm resources that may need to be customized for different hardware or product
+ builds. -->
+<resources>
+ <!-- Determines if the current device should allow emergency numbers to be logged in the
+ call log. Some carriers require that emergency calls *not* be logged, presumably to
+ avoid the risk of accidental redialing from the call log UI.
+ The default is false. -->
+ <bool name="allow_emergency_numbers_in_call_log">false</bool>
+</resources>
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 1463c51..d54072d 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -132,6 +132,14 @@
return new Date().getTime() - mCreationTime.getTime();
}
+ /**
+ * @return The time when this call object was created and added to the set of pending outgoing
+ * calls.
+ */
+ long getCreationTimeInMilliseconds() {
+ return mCreationTime.getTime();
+ }
+
CallServiceWrapper getCallService() {
return mCallService;
}
diff --git a/src/com/android/telecomm/CallLogManager.java b/src/com/android/telecomm/CallLogManager.java
index eb3b0f5..de9b301 100644
--- a/src/com/android/telecomm/CallLogManager.java
+++ b/src/com/android/telecomm/CallLogManager.java
@@ -1,5 +1,248 @@
+/*
+ * 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;
-/** Package private */
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.CallLog.Calls;
+import android.telephony.PhoneNumberUtils;
+
+import com.android.internal.telephony.PhoneConstants;
+
+/**
+ * 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.
+ */
class CallLogManager {
+ /**
+ * 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;
+ }
+
+ void logDisconnectedCall(Call call) {
+ // TODO: Until we add more state to the Call object to track whether this disconnected
+ // call orginated as an incoming or outgoing call, always log it as an incoming call.
+ // See b/13420887.
+ logCall(call, Calls.INCOMING_TYPE);
+ }
+
+ void logFailedOutgoingCall(Call call) {
+ logCall(call, Calls.OUTGOING_TYPE);
+ }
+
+ void logMissedCall(Call call) {
+ logCall(call, Calls.MISSED_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) {
+ String number = call.getHandle();
+ final long creationTime = call.getCreationTimeInMilliseconds();
+ final long age = call.getAgeInMilliseconds();
+
+ final ContactInfo contactInfo = call.getContactInfo(); // May be null.
+ final String logNumber = getLogNumber(call);
+
+ Log.d(TAG, "logNumber set to:" + Log.pii(logNumber) + ", number set to: "
+ + Log.pii(number));
+
+ 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) {
+ String handle = call.getHandle();
+
+ if (handle == null) {
+ return null;
+ }
+
+ if (!PhoneNumberUtils.isUriNumber(handle)) {
+ handle = PhoneNumberUtils.stripSeparators(handle);
+ }
+ return handle;
+ }
+
+ /**
+ * 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.");
+ }
+ }
+ }
+ }
}
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index b8150fd..61f331b 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -94,6 +94,7 @@
mSwitchboard = new Switchboard(this);
mInCallController = new InCallController(this);
mRinger = new Ringer();
+ mCallLogManager = new CallLogManager(TelecommApp.getInstance());
}
static CallsManager getInstance() {
@@ -186,6 +187,15 @@
}
/**
+ * Informs mCallLogManager about the outgoing call that failed, so that it can be logged.
+ *
+ * @param call The failed outgoing call.
+ */
+ void handleFailedOutgoingCall(Call call) {
+ mCallLogManager.logFailedOutgoingCall(call);
+ }
+
+ /**
* Instructs Telecomm to answer the specified call. Intended to be invoked by the in-call
* app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
* the user opting to answer said call.
@@ -291,6 +301,9 @@
if (mCalls.isEmpty()) {
mInCallController.unbind();
}
+
+ // Log the call in the call log.
+ mCallLogManager.logDisconnectedCall(call);
}
/**
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 040eb5f..af8d380 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -211,8 +211,6 @@
void handleFailedOutgoingCall(Call call) {
Log.d(this, "handleFailedOutgoingCall");
- // TODO(gilad): Notify mCallsManager.
-
finalizeOutgoingCall(call);
}