1/*
2 * Copyright (C) 2013 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
17package com.android.phone;
18
19import com.android.internal.telephony.CallerInfo;
20import com.android.internal.telephony.Connection;
21import com.android.internal.telephony.Phone;
22import com.android.internal.telephony.PhoneConstants;
23import com.android.internal.telephony.TelephonyCapabilities;
24import com.android.phone.common.CallLogAsync;
25
26import android.net.Uri;
27import android.os.SystemProperties;
28import android.provider.CallLog.Calls;
29import android.telephony.PhoneNumberUtils;
30import android.text.TextUtils;
31import android.util.Log;
32
33/**
34 * Helper class for interacting with the call log.
35 */
36class CallLogger {
37    private static final String LOG_TAG = CallLogger.class.getSimpleName();
38    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) &&
39        (SystemProperties.getInt("ro.debuggable", 0) == 1);
40    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
41
42    private PhoneGlobals mApplication;
43    private CallLogAsync mCallLog;
44
45    public CallLogger(PhoneGlobals application, CallLogAsync callLogAsync) {
46        mApplication = application;
47        mCallLog = callLogAsync;
48    }
49
50    /**
51     * Logs a call to the call log based on the connection object passed in.
52     *
53     * @param c The connection object for the call being logged.
54     * @param callLogType The type of call log entry.
55     */
56    public void logCall(Connection c, int callLogType) {
57        final String number = c.getAddress();
58        final long date = c.getCreateTime();
59        final long duration = c.getDurationMillis();
60        final Phone phone = c.getCall().getPhone();
61
62        final CallerInfo ci = getCallerInfoFromConnection(c);  // May be null.
63        final String logNumber = getLogNumber(c, ci);
64
65        if (DBG) {
66            log("- onDisconnect(): logNumber set to:" + PhoneUtils.toLogSafePhoneNumber(logNumber) +
67                ", number set to: " + PhoneUtils.toLogSafePhoneNumber(number));
68        }
69
70        // TODO: In getLogNumber we use the presentation from
71        // the connection for the CNAP. Should we use the one
72        // below instead? (comes from caller info)
73
74        // For international calls, 011 needs to be logged as +
75        final int presentation = getPresentation(c, ci);
76
77        final boolean isOtaspNumber = TelephonyCapabilities.supportsOtasp(phone)
78                && phone.isOtaSpNumber(number);
79
80        // Don't log OTASP calls.
81        if (!isOtaspNumber) {
82            logCall(ci, logNumber, presentation, callLogType, date, duration);
83        }
84    }
85
86    /**
87     * Came as logCall(Connection,int) but calculates the call type from the connection object.
88     */
89    public void logCall(Connection c) {
90        final Connection.DisconnectCause cause = c.getDisconnectCause();
91
92        // Set the "type" to be displayed in the call log (see constants in CallLog.Calls)
93        final int callLogType;
94
95        if (c.isIncoming()) {
96            callLogType = (cause == Connection.DisconnectCause.INCOMING_MISSED ?
97                           Calls.MISSED_TYPE : Calls.INCOMING_TYPE);
98        } else {
99            callLogType = Calls.OUTGOING_TYPE;
100        }
101        if (VDBG) log("- callLogType: " + callLogType + ", UserData: " + c.getUserData());
102
103        logCall(c, callLogType);
104    }
105
106    /**
107     * Logs a call to the call from the parameters passed in.
108     */
109    public void logCall(CallerInfo ci, String number, int presentation, int callType, long start,
110                        long duration) {
111        final boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number,
112                mApplication);
113
114        // On some devices, to avoid accidental redialing of
115        // emergency numbers, we *never* log emergency calls to
116        // the Call Log.  (This behavior is set on a per-product
117        // basis, based on carrier requirements.)
118        final boolean okToLogEmergencyNumber =
119            mApplication.getResources().getBoolean(
120                        R.bool.allow_emergency_numbers_in_call_log);
121
122        // Don't log emergency numbers if the device doesn't allow it,
123        boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;
124
125        if (isOkToLogThisCall) {
126            if (DBG) {
127                log("sending Calllog entry: " + ci + ", " + PhoneUtils.toLogSafePhoneNumber(number)
128                    + "," + presentation + ", " + callType + ", " + start + ", " + duration);
129            }
130
131            CallLogAsync.AddCallArgs args = new CallLogAsync.AddCallArgs(mApplication, ci, number,
132                    presentation, callType, start, duration);
133            mCallLog.addCall(args);
134        }
135    }
136
137    /**
138     * Get the caller info.
139     *
140     * @param conn The phone connection.
141     * @return The CallerInfo associated with the connection. Maybe null.
142     */
143    private CallerInfo getCallerInfoFromConnection(Connection conn) {
144        CallerInfo ci = null;
145        Object o = conn.getUserData();
146
147        if ((o == null) || (o instanceof CallerInfo)) {
148            ci = (CallerInfo) o;
149        } else if (o instanceof Uri) {
150            ci = CallerInfo.getCallerInfo(mApplication.getApplicationContext(), (Uri) o);
151        } else {
152            ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
153        }
154        return ci;
155    }
156
157    /**
158     * Retrieve the phone number from the caller info or the connection.
159     *
160     * For incoming call the number is in the Connection object. For
161     * outgoing call we use the CallerInfo phoneNumber field if
162     * present. All the processing should have been done already (CDMA vs GSM numbers).
163     *
164     * If CallerInfo is missing the phone number, get it from the connection.
165     * Apply the Call Name Presentation (CNAP) transform in the connection on the number.
166     *
167     * @param conn The phone connection.
168     * @param callerInfo The CallerInfo. Maybe null.
169     * @return the phone number.
170     */
171    private String getLogNumber(Connection conn, CallerInfo callerInfo) {
172        String number = null;
173
174        if (conn.isIncoming()) {
175            number = conn.getAddress();
176        } else {
177            // For emergency and voicemail calls,
178            // CallerInfo.phoneNumber does *not* contain a valid phone
179            // number.  Instead it contains an I18N'd string such as
180            // "Emergency Number" or "Voice Mail" so we get the number
181            // from the connection.
182            if (null == callerInfo || TextUtils.isEmpty(callerInfo.phoneNumber) ||
183                callerInfo.isEmergencyNumber() || callerInfo.isVoiceMailNumber()) {
184                if (conn.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
185                    // In cdma getAddress() is not always equals to getOrigDialString().
186                    number = conn.getOrigDialString();
187                } else {
188                    number = conn.getAddress();
189                }
190            } else {
191                number = callerInfo.phoneNumber;
192            }
193        }
194
195        if (null == number) {
196            return null;
197        } else {
198            int presentation = conn.getNumberPresentation();
199
200            // Do final CNAP modifications.
201            String newNumber = PhoneUtils.modifyForSpecialCnapCases(mApplication, callerInfo,
202                                                          number, presentation);
203
204            if (!PhoneNumberUtils.isUriNumber(number)) {
205                number = PhoneNumberUtils.stripSeparators(number);
206            }
207            if (VDBG) log("getLogNumber: " + number);
208            return number;
209        }
210    }
211
212    /**
213     * Get the presentation from the callerinfo if not null otherwise,
214     * get it from the connection.
215     *
216     * @param conn The phone connection.
217     * @param callerInfo The CallerInfo. Maybe null.
218     * @return The presentation to use in the logs.
219     */
220    private int getPresentation(Connection conn, CallerInfo callerInfo) {
221        int presentation;
222
223        if (null == callerInfo) {
224            presentation = conn.getNumberPresentation();
225        } else {
226            presentation = callerInfo.numberPresentation;
227            if (DBG) log("- getPresentation(): ignoring connection's presentation: " +
228                         conn.getNumberPresentation());
229        }
230        if (DBG) log("- getPresentation: presentation: " + presentation);
231        return presentation;
232    }
233
234    private void log(String msg) {
235        Log.d(LOG_TAG, msg);
236    }
237}
238