BluetoothPbapCallLogComposer.java revision 77ba5f6684f4dd7e4b7fc37982271da5654aec07
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package com.android.bluetooth.pbap;
17
18import com.android.bluetooth.R;
19import com.android.internal.telephony.CallerInfo;
20
21import android.content.ContentResolver;
22import android.content.Context;
23import android.database.Cursor;
24import android.database.sqlite.SQLiteException;
25import android.net.Uri;
26import android.provider.CallLog;
27import android.provider.CallLog.Calls;
28import android.text.TextUtils;
29import android.text.format.Time;
30import android.util.Log;
31
32import com.android.vcard.VCardBuilder;
33import com.android.vcard.VCardConfig;
34import com.android.vcard.VCardConstants;
35import com.android.vcard.VCardUtils;
36
37import java.util.Arrays;
38
39/**
40 * VCard composer especially for Call Log used in Bluetooth.
41 */
42public class BluetoothPbapCallLogComposer {
43    private static final String TAG = "CallLogComposer";
44
45    private static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
46        "Failed to get database information";
47
48    private static final String FAILURE_REASON_NO_ENTRY =
49        "There's no exportable in the database";
50
51    private static final String FAILURE_REASON_NOT_INITIALIZED =
52        "The vCard composer object is not correctly initialized";
53
54    /** Should be visible only from developers... (no need to translate, hopefully) */
55    private static final String FAILURE_REASON_UNSUPPORTED_URI =
56        "The Uri vCard composer received is not supported by the composer.";
57
58    private static final String NO_ERROR = "No error";
59
60    /** The projection to use when querying the call log table */
61    private static final String[] sCallLogProjection = new String[] {
62            Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
63            Calls.CACHED_NUMBER_LABEL
64    };
65    private static final int NUMBER_COLUMN_INDEX = 0;
66    private static final int DATE_COLUMN_INDEX = 1;
67    private static final int CALL_TYPE_COLUMN_INDEX = 2;
68    private static final int CALLER_NAME_COLUMN_INDEX = 3;
69    private static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 4;
70    private static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 5;
71
72    // Property for call log entry
73    private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
74    private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "RECEIVED";
75    private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "DIALED";
76    private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
77
78    private static final String FLAG_TIMEZONE_UTC = "Z";
79
80    private final Context mContext;
81    private ContentResolver mContentResolver;
82    private Cursor mCursor;
83
84    private boolean mTerminateIsCalled;
85
86    private String mErrorReason = NO_ERROR;
87
88    public BluetoothPbapCallLogComposer(final Context context) {
89        mContext = context;
90        mContentResolver = context.getContentResolver();
91    }
92
93    public boolean init(final Uri contentUri, final String selection,
94            final String[] selectionArgs, final String sortOrder) {
95        final String[] projection;
96        if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
97            projection = sCallLogProjection;
98        } else {
99            mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
100            return false;
101        }
102
103        mCursor = mContentResolver.query(
104                contentUri, projection, selection, selectionArgs, sortOrder);
105
106        if (mCursor == null) {
107            mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
108            return false;
109        }
110
111        if (mCursor.getCount() == 0 || !mCursor.moveToFirst()) {
112            try {
113                mCursor.close();
114            } catch (SQLiteException e) {
115                Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
116            } finally {
117                mErrorReason = FAILURE_REASON_NO_ENTRY;
118                mCursor = null;
119            }
120            return false;
121        }
122
123        return true;
124    }
125
126    public String createOneEntry(boolean vcardVer21) {
127        if (mCursor == null || mCursor.isAfterLast()) {
128            mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
129            return null;
130        }
131        try {
132            return createOneCallLogEntryInternal(vcardVer21);
133        } finally {
134            mCursor.moveToNext();
135        }
136    }
137
138    private String createOneCallLogEntryInternal(boolean vcardVer21) {
139        final int vcardType = (vcardVer21 ? VCardConfig.VCARD_TYPE_V21_GENERIC :
140                VCardConfig.VCARD_TYPE_V30_GENERIC) |
141                VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
142        final VCardBuilder builder = new VCardBuilder(vcardType);
143        String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
144        if (TextUtils.isEmpty(name)) {
145            name = "";
146        }
147        if (CallerInfo.UNKNOWN_NUMBER.equals(name) || CallerInfo.PRIVATE_NUMBER.equals(name) ||
148                CallerInfo.PAYPHONE_NUMBER.equals(name)) {
149            // setting name to "" as FN/N must be empty fields in this case.
150            name = "";
151        }
152        final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
153        builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false);
154        builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false);
155
156        String number = mCursor.getString(NUMBER_COLUMN_INDEX);
157        if (CallerInfo.UNKNOWN_NUMBER.equals(number) ||
158                CallerInfo.PRIVATE_NUMBER.equals(number) ||
159                CallerInfo.PAYPHONE_NUMBER.equals(number)) {
160            number = mContext.getString(R.string.unknownNumber);
161        }
162        final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
163        String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
164        if (TextUtils.isEmpty(label)) {
165            label = Integer.toString(type);
166        }
167        builder.appendTelLine(type, label, number, false);
168        tryAppendCallHistoryTimeStampField(builder);
169
170        return builder.toString();
171    }
172
173    /**
174     * This static function is to compose vCard for phone own number
175     */
176    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
177            String phoneNumber, boolean vcardVer21) {
178        final int vcardType = (vcardVer21 ?
179                VCardConfig.VCARD_TYPE_V21_GENERIC :
180                    VCardConfig.VCARD_TYPE_V30_GENERIC) |
181                VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
182        final VCardBuilder builder = new VCardBuilder(vcardType);
183        boolean needCharset = false;
184        if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
185            needCharset = true;
186        }
187        builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false);
188        builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false);
189
190        if (!TextUtils.isEmpty(phoneNumber)) {
191            String label = Integer.toString(phonetype);
192            builder.appendTelLine(phonetype, label, phoneNumber, false);
193        }
194
195        return builder.toString();
196    }
197
198    /**
199     * Format according to RFC 2445 DATETIME type.
200     * The format is: ("%Y%m%dT%H%M%SZ").
201     */
202    private final String toRfc2455Format(final long millSecs) {
203        Time startDate = new Time();
204        startDate.set(millSecs);
205        String date = startDate.format2445();
206        return date + FLAG_TIMEZONE_UTC;
207    }
208
209    /**
210     * Try to append the property line for a call history time stamp field if possible.
211     * Do nothing if the call log type gotton from the database is invalid.
212     */
213    private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) {
214        // Extension for call history as defined in
215        // in the Specification for Ic Mobile Communcation - ver 1.1,
216        // Oct 2000. This is used to send the details of the call
217        // history - missed, incoming, outgoing along with date and time
218        // to the requesting device (For example, transferring phone book
219        // when connected over bluetooth)
220        //
221        // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
222        final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
223        final String callLogTypeStr;
224        switch (callLogType) {
225            case Calls.INCOMING_TYPE: {
226                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
227                break;
228            }
229            case Calls.OUTGOING_TYPE: {
230                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
231                break;
232            }
233            case Calls.MISSED_TYPE: {
234                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
235                break;
236            }
237            default: {
238                Log.w(TAG, "Call log type not correct.");
239                return;
240            }
241        }
242
243        final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
244        builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP,
245                Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong));
246    }
247
248    public void terminate() {
249        if (mCursor != null) {
250            try {
251                mCursor.close();
252            } catch (SQLiteException e) {
253                Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
254            }
255            mCursor = null;
256        }
257
258        mTerminateIsCalled = true;
259    }
260
261    @Override
262    public void finalize() {
263        if (!mTerminateIsCalled) {
264            terminate();
265        }
266    }
267
268    public int getCount() {
269        if (mCursor == null) {
270            return 0;
271        }
272        return mCursor.getCount();
273    }
274
275    public boolean isAfterLast() {
276        if (mCursor == null) {
277            return false;
278        }
279        return mCursor.isAfterLast();
280    }
281
282    public String getErrorReason() {
283        return mErrorReason;
284    }
285}
286