• 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.ContentResolver;
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.database.sqlite.SQLiteException;
22 import android.net.Uri;
23 import android.provider.CallLog;
24 import android.provider.CallLog.Calls;
25 import android.text.TextUtils;
26 import android.text.format.Time;
27 import android.util.Log;
28 
29 import com.android.bluetooth.R;
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.util.Arrays;
36 
37 /**
38  * VCard composer especially for Call Log used in Bluetooth.
39  */
40 public class BluetoothPbapCallLogComposer {
41     private static final String TAG = "CallLogComposer";
42 
43     private static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
44             "Failed to get database information";
45 
46     private static final String FAILURE_REASON_NO_ENTRY = "There's no exportable in the database";
47 
48     private static final String FAILURE_REASON_NOT_INITIALIZED =
49             "The vCard composer object is not correctly initialized";
50 
51     /** Should be visible only from developers... (no need to translate, hopefully) */
52     private static final String FAILURE_REASON_UNSUPPORTED_URI =
53             "The Uri vCard composer received is not supported by the composer.";
54 
55     private static final String NO_ERROR = "No error";
56 
57     /** The projection to use when querying the call log table */
58     private static final String[] sCallLogProjection = new String[]{
59             Calls.NUMBER,
60             Calls.DATE,
61             Calls.TYPE,
62             Calls.CACHED_NAME,
63             Calls.CACHED_NUMBER_TYPE,
64             Calls.CACHED_NUMBER_LABEL,
65             Calls.NUMBER_PRESENTATION
66     };
67     private static final int NUMBER_COLUMN_INDEX = 0;
68     private static final int DATE_COLUMN_INDEX = 1;
69     private static final int CALL_TYPE_COLUMN_INDEX = 2;
70     private static final int CALLER_NAME_COLUMN_INDEX = 3;
71     private static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 4;
72     private static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 5;
73     private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6;
74 
75     // Property for call log entry
76     private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
77     private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "RECEIVED";
78     private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "DIALED";
79     private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
80 
81     private final Context mContext;
82     private ContentResolver mContentResolver;
83     private Cursor mCursor;
84 
85     private boolean mTerminateIsCalled;
86 
87     private String mErrorReason = NO_ERROR;
88 
BluetoothPbapCallLogComposer(final Context context)89     public BluetoothPbapCallLogComposer(final Context context) {
90         mContext = context;
91         mContentResolver = context.getContentResolver();
92     }
93 
init(final Uri contentUri, final String selection, final String[] selectionArgs, final String sortOrder)94     public boolean init(final Uri contentUri, final String selection, final String[] selectionArgs,
95             final String sortOrder) {
96         final String[] projection;
97         if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
98             projection = sCallLogProjection;
99         } else {
100             mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
101             return false;
102         }
103 
104         mCursor =
105                 mContentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
106 
107         if (mCursor == null) {
108             mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
109             return false;
110         }
111 
112         if (mCursor.getCount() == 0 || !mCursor.moveToFirst()) {
113             try {
114                 mCursor.close();
115             } catch (SQLiteException e) {
116                 Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
117             } finally {
118                 mErrorReason = FAILURE_REASON_NO_ENTRY;
119                 mCursor = null;
120             }
121             return false;
122         }
123 
124         return true;
125     }
126 
createOneEntry(boolean vcardVer21)127     public String createOneEntry(boolean vcardVer21) {
128         if (mCursor == null || mCursor.isAfterLast()) {
129             mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
130             return null;
131         }
132         try {
133             return createOneCallLogEntryInternal(vcardVer21);
134         } finally {
135             mCursor.moveToNext();
136         }
137     }
138 
createOneCallLogEntryInternal(boolean vcardVer21)139     private String createOneCallLogEntryInternal(boolean vcardVer21) {
140         final int vcardType = (vcardVer21 ? VCardConfig.VCARD_TYPE_V21_GENERIC
141                 : VCardConfig.VCARD_TYPE_V30_GENERIC)
142                 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
143         final VCardBuilder builder = new VCardBuilder(vcardType);
144         String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
145         String number = mCursor.getString(NUMBER_COLUMN_INDEX);
146         final int numberPresentation = mCursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX);
147         if (TextUtils.isEmpty(name)) {
148             name = "";
149         }
150         if (numberPresentation != Calls.PRESENTATION_ALLOWED) {
151             // setting name to "" as FN/N must be empty fields in this case.
152             name = "";
153             // TODO: there are really 3 possible strings that could be set here:
154             // "unknown", "private", and "payphone".
155             number = mContext.getString(R.string.unknownNumber);
156         }
157         final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
158         builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false);
159         builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false);
160 
161         final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
162         String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
163         if (TextUtils.isEmpty(label)) {
164             label = Integer.toString(type);
165         }
166         builder.appendTelLine(type, label, number, false);
167         tryAppendCallHistoryTimeStampField(builder);
168 
169         return builder.toString();
170     }
171 
172     /**
173      * This static function is to compose vCard for phone own number
174      */
composeVCardForPhoneOwnNumber(int phonetype, String phoneName, String phoneNumber, boolean vcardVer21)175     public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName, String phoneNumber,
176             boolean vcardVer21) {
177         final int vcardType = (vcardVer21 ? 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 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.REJECTED_TYPE:
223             case Calls.INCOMING_TYPE: {
224                 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
225                 break;
226             }
227             case Calls.OUTGOING_TYPE: {
228                 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
229                 break;
230             }
231             case Calls.MISSED_TYPE: {
232                 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
233                 break;
234             }
235             default: {
236                 Log.w(TAG, "Call log type not correct.");
237                 return;
238             }
239         }
240 
241         final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
242         builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP, Arrays.asList(callLogTypeStr),
243                 toRfc2455Format(dateAsLong));
244     }
245 
terminate()246     public void terminate() {
247         if (mCursor != null) {
248             try {
249                 mCursor.close();
250             } catch (SQLiteException e) {
251                 Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
252             }
253             mCursor = null;
254         }
255 
256         mTerminateIsCalled = true;
257     }
258 
259     @Override
finalize()260     public void finalize() {
261         if (!mTerminateIsCalled) {
262             terminate();
263         }
264     }
265 
getCount()266     public int getCount() {
267         if (mCursor == null) {
268             return 0;
269         }
270         return mCursor.getCount();
271     }
272 
isAfterLast()273     public boolean isAfterLast() {
274         if (mCursor == null) {
275             return false;
276         }
277         return mCursor.isAfterLast();
278     }
279 
getErrorReason()280     public String getErrorReason() {
281         return mErrorReason;
282     }
283 }
284