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