Home | History | Annotate | Download | only in pbap
      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 
     86     public BluetoothPbapCallLogComposer(final Context context) {
     87         mContext = context;
     88         mContentResolver = context.getContentResolver();
     89     }
     90 
     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 
    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 
    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      */
    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      */
    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      */
    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 
    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
    259     public void finalize() {
    260         if (!mTerminateIsCalled) {
    261             terminate();
    262         }
    263     }
    264 
    265     public int getCount() {
    266         if (mCursor == null) {
    267             return 0;
    268         }
    269         return mCursor.getCount();
    270     }
    271 
    272     public boolean isAfterLast() {
    273         if (mCursor == null) {
    274             return false;
    275         }
    276         return mCursor.isAfterLast();
    277     }
    278 
    279     public String getErrorReason() {
    280         return mErrorReason;
    281     }
    282 }
    283