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