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 
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.database.sqlite.SQLiteException;
     24 import android.net.Uri;
     25 import android.provider.CallLog;
     26 import android.provider.CallLog.Calls;
     27 import android.text.TextUtils;
     28 import android.text.format.Time;
     29 import android.util.Log;
     30 
     31 import com.android.vcard.VCardBuilder;
     32 import com.android.vcard.VCardConfig;
     33 import com.android.vcard.VCardConstants;
     34 import com.android.vcard.VCardUtils;
     35 
     36 import java.util.Arrays;
     37 
     38 /**
     39  * VCard composer especially for Call Log used in Bluetooth.
     40  */
     41 public 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, Calls.NUMBER_PRESENTATION
     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     private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6;
     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         String number = mCursor.getString(NUMBER_COLUMN_INDEX);
    143         final int numberPresentation = mCursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX);
    144         if (TextUtils.isEmpty(name)) {
    145             name = "";
    146         }
    147         if (numberPresentation != Calls.PRESENTATION_ALLOWED) {
    148             // setting name to "" as FN/N must be empty fields in this case.
    149             name = "";
    150             // TODO: there are really 3 possible strings that could be set here:
    151             // "unknown", "private", and "payphone".
    152             number = mContext.getString(R.string.unknownNumber);
    153         }
    154         final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
    155         builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false);
    156         builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false);
    157 
    158         final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
    159         String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
    160         if (TextUtils.isEmpty(label)) {
    161             label = Integer.toString(type);
    162         }
    163         builder.appendTelLine(type, label, number, false);
    164         tryAppendCallHistoryTimeStampField(builder);
    165 
    166         return builder.toString();
    167     }
    168 
    169     /**
    170      * This static function is to compose vCard for phone own number
    171      */
    172     public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
    173             String phoneNumber, boolean vcardVer21) {
    174         final int vcardType = (vcardVer21 ?
    175                 VCardConfig.VCARD_TYPE_V21_GENERIC :
    176                     VCardConfig.VCARD_TYPE_V30_GENERIC) |
    177                 VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
    178         final VCardBuilder builder = new VCardBuilder(vcardType);
    179         boolean needCharset = false;
    180         if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
    181             needCharset = true;
    182         }
    183         builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false);
    184         builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false);
    185 
    186         if (!TextUtils.isEmpty(phoneNumber)) {
    187             String label = Integer.toString(phonetype);
    188             builder.appendTelLine(phonetype, label, phoneNumber, false);
    189         }
    190 
    191         return builder.toString();
    192     }
    193 
    194     /**
    195      * Format according to RFC 2445 DATETIME type.
    196      * The format is: ("%Y%m%dT%H%M%S").
    197      */
    198     private final String toRfc2455Format(final long millSecs) {
    199         Time startDate = new Time();
    200         startDate.set(millSecs);
    201         return startDate.format2445();
    202     }
    203 
    204     /**
    205      * Try to append the property line for a call history time stamp field if possible.
    206      * Do nothing if the call log type gotton from the database is invalid.
    207      */
    208     private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) {
    209         // Extension for call history as defined in
    210         // in the Specification for Ic Mobile Communcation - ver 1.1,
    211         // Oct 2000. This is used to send the details of the call
    212         // history - missed, incoming, outgoing along with date and time
    213         // to the requesting device (For example, transferring phone book
    214         // when connected over bluetooth)
    215         //
    216         // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000"
    217         final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
    218         final String callLogTypeStr;
    219         switch (callLogType) {
    220             case Calls.INCOMING_TYPE: {
    221                 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
    222                 break;
    223             }
    224             case Calls.OUTGOING_TYPE: {
    225                 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
    226                 break;
    227             }
    228             case Calls.MISSED_TYPE: {
    229                 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
    230                 break;
    231             }
    232             default: {
    233                 Log.w(TAG, "Call log type not correct.");
    234                 return;
    235             }
    236         }
    237 
    238         final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
    239         builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP,
    240                 Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong));
    241     }
    242 
    243     public void terminate() {
    244         if (mCursor != null) {
    245             try {
    246                 mCursor.close();
    247             } catch (SQLiteException e) {
    248                 Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
    249             }
    250             mCursor = null;
    251         }
    252 
    253         mTerminateIsCalled = true;
    254     }
    255 
    256     @Override
    257     public void finalize() {
    258         if (!mTerminateIsCalled) {
    259             terminate();
    260         }
    261     }
    262 
    263     public int getCount() {
    264         if (mCursor == null) {
    265             return 0;
    266         }
    267         return mCursor.getCount();
    268     }
    269 
    270     public boolean isAfterLast() {
    271         if (mCursor == null) {
    272             return false;
    273         }
    274         return mCursor.isAfterLast();
    275     }
    276 
    277     public String getErrorReason() {
    278         return mErrorReason;
    279     }
    280 }
    281