Home | History | Annotate | Download | only in vcard
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.contacts.common.vcard;
     17 
     18 import android.accounts.Account;
     19 import android.content.ContentResolver;
     20 import android.net.Uri;
     21 import android.util.Log;
     22 
     23 import com.android.vcard.VCardEntry;
     24 import com.android.vcard.VCardEntryCommitter;
     25 import com.android.vcard.VCardEntryConstructor;
     26 import com.android.vcard.VCardEntryHandler;
     27 import com.android.vcard.VCardInterpreter;
     28 import com.android.vcard.VCardParser;
     29 import com.android.vcard.VCardParser_V21;
     30 import com.android.vcard.VCardParser_V30;
     31 import com.android.vcard.exception.VCardException;
     32 import com.android.vcard.exception.VCardNestedException;
     33 import com.android.vcard.exception.VCardNotSupportedException;
     34 import com.android.vcard.exception.VCardVersionException;
     35 
     36 import java.io.ByteArrayInputStream;
     37 import java.io.IOException;
     38 import java.io.InputStream;
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 
     42 /**
     43  * Class for processing one import request from a user. Dropped after importing requested Uri(s).
     44  * {@link VCardService} will create another object when there is another import request.
     45  */
     46 public class ImportProcessor extends ProcessorBase implements VCardEntryHandler {
     47     private static final String LOG_TAG = "VCardImport";
     48     private static final boolean DEBUG = VCardService.DEBUG;
     49 
     50     private final VCardService mService;
     51     private final ContentResolver mResolver;
     52     private final ImportRequest mImportRequest;
     53     private final int mJobId;
     54     private final VCardImportExportListener mListener;
     55 
     56     // TODO: remove and show appropriate message instead.
     57     private final List<Uri> mFailedUris = new ArrayList<Uri>();
     58 
     59     private VCardParser mVCardParser;
     60 
     61     private volatile boolean mCanceled;
     62     private volatile boolean mDone;
     63 
     64     private int mCurrentCount = 0;
     65     private int mTotalCount = 0;
     66 
     67     public ImportProcessor(final VCardService service, final VCardImportExportListener listener,
     68             final ImportRequest request, final int jobId) {
     69         mService = service;
     70         mResolver = mService.getContentResolver();
     71         mListener = listener;
     72 
     73         mImportRequest = request;
     74         mJobId = jobId;
     75     }
     76 
     77     @Override
     78     public void onStart() {
     79         // do nothing
     80     }
     81 
     82     @Override
     83     public void onEnd() {
     84         // do nothing
     85     }
     86 
     87     @Override
     88     public void onEntryCreated(VCardEntry entry) {
     89         mCurrentCount++;
     90         if (mListener != null) {
     91             mListener.onImportParsed(mImportRequest, mJobId, entry, mCurrentCount, mTotalCount);
     92         }
     93     }
     94 
     95     @Override
     96     public final int getType() {
     97         return VCardService.TYPE_IMPORT;
     98     }
     99 
    100     @Override
    101     public void run() {
    102         // ExecutorService ignores RuntimeException, so we need to show it here.
    103         try {
    104             runInternal();
    105 
    106             if (isCancelled() && mListener != null) {
    107                 mListener.onImportCanceled(mImportRequest, mJobId);
    108             }
    109         } catch (OutOfMemoryError e) {
    110             Log.e(LOG_TAG, "OutOfMemoryError thrown during import", e);
    111             throw e;
    112         } catch (RuntimeException e) {
    113             Log.e(LOG_TAG, "RuntimeException thrown during import", e);
    114             throw e;
    115         } finally {
    116             synchronized (this) {
    117                 mDone = true;
    118             }
    119         }
    120     }
    121 
    122     private void runInternal() {
    123         Log.i(LOG_TAG, String.format("vCard import (id: %d) has started.", mJobId));
    124         final ImportRequest request = mImportRequest;
    125         if (isCancelled()) {
    126             Log.i(LOG_TAG, "Canceled before actually handling parameter (" + request.uri + ")");
    127             return;
    128         }
    129         final int[] possibleVCardVersions;
    130         if (request.vcardVersion == ImportVCardActivity.VCARD_VERSION_AUTO_DETECT) {
    131             /**
    132              * Note: this code assumes that a given Uri is able to be opened more than once,
    133              * which may not be true in certain conditions.
    134              */
    135             possibleVCardVersions = new int[] {
    136                     ImportVCardActivity.VCARD_VERSION_V21,
    137                     ImportVCardActivity.VCARD_VERSION_V30
    138             };
    139         } else {
    140             possibleVCardVersions = new int[] {
    141                     request.vcardVersion
    142             };
    143         }
    144 
    145         final Uri uri = request.uri;
    146         final Account account = request.account;
    147         final int estimatedVCardType = request.estimatedVCardType;
    148         final String estimatedCharset = request.estimatedCharset;
    149         final int entryCount = request.entryCount;
    150         mTotalCount += entryCount;
    151 
    152         final VCardEntryConstructor constructor =
    153                 new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
    154         final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
    155         constructor.addEntryHandler(committer);
    156         constructor.addEntryHandler(this);
    157 
    158         InputStream is = null;
    159         boolean successful = false;
    160         try {
    161             if (uri != null) {
    162                 Log.i(LOG_TAG, "start importing one vCard (Uri: " + uri + ")");
    163                 is = mResolver.openInputStream(uri);
    164             } else if (request.data != null){
    165                 Log.i(LOG_TAG, "start importing one vCard (byte[])");
    166                 is = new ByteArrayInputStream(request.data);
    167             }
    168 
    169             if (is != null) {
    170                 successful = readOneVCard(is, estimatedVCardType, estimatedCharset, constructor,
    171                         possibleVCardVersions);
    172             }
    173         } catch (IOException e) {
    174             successful = false;
    175         } finally {
    176             if (is != null) {
    177                 try {
    178                     is.close();
    179                 } catch (Exception e) {
    180                     // ignore
    181                 }
    182             }
    183         }
    184 
    185         mService.handleFinishImportNotification(mJobId, successful);
    186 
    187         if (successful) {
    188             // TODO: successful becomes true even when cancelled. Should return more appropriate
    189             // value
    190             if (isCancelled()) {
    191                 Log.i(LOG_TAG, "vCard import has been canceled (uri: " + uri + ")");
    192                 // Cancel notification will be done outside this method.
    193             } else {
    194                 Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
    195                 List<Uri> uris = committer.getCreatedUris();
    196                 if (mListener != null) {
    197                     if (uris != null && uris.size() > 0) {
    198                         // TODO: construct intent showing a list of imported contact list.
    199                         mListener.onImportFinished(mImportRequest, mJobId, uris.get(0));
    200                     } else {
    201                         // Not critical, but suspicious.
    202                         Log.w(LOG_TAG,
    203                                 "Created Uris is null or 0 length " +
    204                                 "though the creation itself is successful.");
    205                         mListener.onImportFinished(mImportRequest, mJobId, null);
    206                     }
    207                 }
    208             }
    209         } else {
    210             Log.w(LOG_TAG, "Failed to read one vCard file: " + uri);
    211             mFailedUris.add(uri);
    212         }
    213     }
    214 
    215     private boolean readOneVCard(InputStream is, int vcardType, String charset,
    216             final VCardInterpreter interpreter,
    217             final int[] possibleVCardVersions) {
    218         boolean successful = false;
    219         final int length = possibleVCardVersions.length;
    220         for (int i = 0; i < length; i++) {
    221             final int vcardVersion = possibleVCardVersions[i];
    222             try {
    223                 if (i > 0 && (interpreter instanceof VCardEntryConstructor)) {
    224                     // Let the object clean up internal temporary objects,
    225                     ((VCardEntryConstructor) interpreter).clear();
    226                 }
    227 
    228                 // We need synchronized block here,
    229                 // since we need to handle mCanceled and mVCardParser at once.
    230                 // In the worst case, a user may call cancel() just before creating
    231                 // mVCardParser.
    232                 synchronized (this) {
    233                     mVCardParser = (vcardVersion == ImportVCardActivity.VCARD_VERSION_V30 ?
    234                             new VCardParser_V30(vcardType) :
    235                                 new VCardParser_V21(vcardType));
    236                     if (isCancelled()) {
    237                         Log.i(LOG_TAG, "ImportProcessor already recieves cancel request, so " +
    238                                 "send cancel request to vCard parser too.");
    239                         mVCardParser.cancel();
    240                     }
    241                 }
    242                 mVCardParser.parse(is, interpreter);
    243 
    244                 successful = true;
    245                 break;
    246             } catch (IOException e) {
    247                 Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
    248             } catch (VCardNestedException e) {
    249                 // This exception should not be thrown here. We should instead handle it
    250                 // in the preprocessing session in ImportVCardActivity, as we don't try
    251                 // to detect the type of given vCard here.
    252                 //
    253                 // TODO: Handle this case appropriately, which should mean we have to have
    254                 // code trying to auto-detect the type of given vCard twice (both in
    255                 // ImportVCardActivity and ImportVCardService).
    256                 Log.e(LOG_TAG, "Nested Exception is found.");
    257             } catch (VCardNotSupportedException e) {
    258                 Log.e(LOG_TAG, e.toString());
    259             } catch (VCardVersionException e) {
    260                 if (i == length - 1) {
    261                     Log.e(LOG_TAG, "Appropriate version for this vCard is not found.");
    262                 } else {
    263                     // We'll try the other (v30) version.
    264                 }
    265             } catch (VCardException e) {
    266                 Log.e(LOG_TAG, e.toString());
    267             } finally {
    268                 if (is != null) {
    269                     try {
    270                         is.close();
    271                     } catch (IOException e) {
    272                     }
    273                 }
    274             }
    275         }
    276 
    277         return successful;
    278     }
    279 
    280     @Override
    281     public synchronized boolean cancel(boolean mayInterruptIfRunning) {
    282         if (DEBUG) Log.d(LOG_TAG, "ImportProcessor received cancel request");
    283         if (mDone || mCanceled) {
    284             return false;
    285         }
    286         mCanceled = true;
    287         synchronized (this) {
    288             if (mVCardParser != null) {
    289                 mVCardParser.cancel();
    290             }
    291         }
    292         return true;
    293     }
    294 
    295     @Override
    296     public synchronized boolean isCancelled() {
    297         return mCanceled;
    298     }
    299 
    300 
    301     @Override
    302     public synchronized boolean isDone() {
    303         return mDone;
    304     }
    305 }
    306