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