Home | History | Annotate | Download | only in vcard
      1 /*
      2  * Copyright (C) 2009 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 
     17 package com.android.contacts.common.vcard;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.Notification;
     23 import android.app.NotificationManager;
     24 import android.app.ProgressDialog;
     25 import android.content.ClipData;
     26 import android.content.ComponentName;
     27 import android.content.ContentResolver;
     28 import android.content.Context;
     29 import android.content.DialogInterface;
     30 import android.content.Intent;
     31 import android.content.ServiceConnection;
     32 import android.database.Cursor;
     33 import android.net.Uri;
     34 import android.os.Bundle;
     35 import android.os.Handler;
     36 import android.os.IBinder;
     37 import android.os.PowerManager;
     38 import android.provider.OpenableColumns;
     39 import android.text.TextUtils;
     40 import android.util.Log;
     41 import android.widget.Toast;
     42 
     43 import com.android.contacts.common.R;
     44 import com.android.contacts.common.activity.RequestImportVCardPermissionsActivity;
     45 import com.android.contacts.common.model.AccountTypeManager;
     46 import com.android.contacts.common.model.account.AccountWithDataSet;
     47 import com.android.vcard.VCardEntryCounter;
     48 import com.android.vcard.VCardParser;
     49 import com.android.vcard.VCardParser_V21;
     50 import com.android.vcard.VCardParser_V30;
     51 import com.android.vcard.VCardSourceDetector;
     52 import com.android.vcard.exception.VCardException;
     53 import com.android.vcard.exception.VCardNestedException;
     54 import com.android.vcard.exception.VCardVersionException;
     55 
     56 import java.io.ByteArrayInputStream;
     57 import java.io.File;
     58 import java.io.IOException;
     59 import java.io.InputStream;
     60 import java.nio.ByteBuffer;
     61 import java.nio.channels.Channels;
     62 import java.nio.channels.ReadableByteChannel;
     63 import java.nio.channels.WritableByteChannel;
     64 import java.util.ArrayList;
     65 import java.util.Arrays;
     66 import java.util.List;
     67 
     68 /**
     69  * The class letting users to import vCard. This includes the UI part for letting them select
     70  * an Account and posssibly a file if there's no Uri is given from its caller Activity.
     71  *
     72  * Note that this Activity assumes that the instance is a "one-shot Activity", which will be
     73  * finished (with the method {@link Activity#finish()}) after the import and never reuse
     74  * any Dialog in the instance. So this code is careless about the management around managed
     75  * dialogs stuffs (like how onCreateDialog() is used).
     76  */
     77 public class ImportVCardActivity extends Activity {
     78     private static final String LOG_TAG = "VCardImport";
     79 
     80     private static final int SELECT_ACCOUNT = 0;
     81 
     82     /* package */ final static int VCARD_VERSION_AUTO_DETECT = 0;
     83     /* package */ final static int VCARD_VERSION_V21 = 1;
     84     /* package */ final static int VCARD_VERSION_V30 = 2;
     85 
     86     private static final int REQUEST_OPEN_DOCUMENT = 100;
     87 
     88     /**
     89      * Notification id used when error happened before sending an import request to VCardServer.
     90      */
     91     private static final int FAILURE_NOTIFICATION_ID = 1;
     92 
     93     private static final String LOCAL_TMP_FILE_NAME_EXTRA =
     94             "com.android.contacts.common.vcard.LOCAL_TMP_FILE_NAME";
     95 
     96     private static final String SOURCE_URI_DISPLAY_NAME =
     97             "com.android.contacts.common.vcard.SOURCE_URI_DISPLAY_NAME";
     98 
     99     private static final String STORAGE_VCARD_URI_PREFIX = "file:///storage";
    100 
    101     private AccountWithDataSet mAccount;
    102 
    103     private ProgressDialog mProgressDialogForCachingVCard;
    104 
    105     private VCardCacheThread mVCardCacheThread;
    106     private ImportRequestConnection mConnection;
    107     /* package */ VCardImportExportListener mListener;
    108 
    109     private String mErrorMessage;
    110 
    111     private Handler mHandler = new Handler();
    112 
    113     // Runs on the UI thread.
    114     private class DialogDisplayer implements Runnable {
    115         private final int mResId;
    116         public DialogDisplayer(int resId) {
    117             mResId = resId;
    118         }
    119         public DialogDisplayer(String errorMessage) {
    120             mResId = R.id.dialog_error_with_message;
    121             mErrorMessage = errorMessage;
    122         }
    123         @Override
    124         public void run() {
    125             if (!isFinishing()) {
    126                 showDialog(mResId);
    127             }
    128         }
    129     }
    130 
    131     private class CancelListener
    132         implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
    133         @Override
    134         public void onClick(DialogInterface dialog, int which) {
    135             finish();
    136         }
    137         @Override
    138         public void onCancel(DialogInterface dialog) {
    139             finish();
    140         }
    141     }
    142 
    143     private CancelListener mCancelListener = new CancelListener();
    144 
    145     private class ImportRequestConnection implements ServiceConnection {
    146         private VCardService mService;
    147 
    148         public void sendImportRequest(final List<ImportRequest> requests) {
    149             Log.i(LOG_TAG, "Send an import request");
    150             mService.handleImportRequest(requests, mListener);
    151         }
    152 
    153         @Override
    154         public void onServiceConnected(ComponentName name, IBinder binder) {
    155             mService = ((VCardService.MyBinder) binder).getService();
    156             Log.i(LOG_TAG,
    157                     String.format("Connected to VCardService. Kick a vCard cache thread (uri: %s)",
    158                             Arrays.toString(mVCardCacheThread.getSourceUris())));
    159             mVCardCacheThread.start();
    160         }
    161 
    162         @Override
    163         public void onServiceDisconnected(ComponentName name) {
    164             Log.i(LOG_TAG, "Disconnected from VCardService");
    165         }
    166     }
    167 
    168     /**
    169      * Caches given vCard files into a local directory, and sends actual import request to
    170      * {@link VCardService}.
    171      *
    172      * We need to cache given files into local storage. One of reasons is that some data (as Uri)
    173      * may have special permissions. Callers may allow only this Activity to access that content,
    174      * not what this Activity launched (like {@link VCardService}).
    175      */
    176     private class VCardCacheThread extends Thread
    177             implements DialogInterface.OnCancelListener {
    178         private boolean mCanceled;
    179         private PowerManager.WakeLock mWakeLock;
    180         private VCardParser mVCardParser;
    181         private final Uri[] mSourceUris;  // Given from a caller.
    182         private final String[] mSourceDisplayNames; // Display names for each Uri in mSourceUris.
    183         private final byte[] mSource;
    184         private final String mDisplayName;
    185 
    186         public VCardCacheThread(final Uri[] sourceUris, String[] sourceDisplayNames) {
    187             mSourceUris = sourceUris;
    188             mSourceDisplayNames = sourceDisplayNames;
    189             mSource = null;
    190             final Context context = ImportVCardActivity.this;
    191             final PowerManager powerManager =
    192                     (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    193             mWakeLock = powerManager.newWakeLock(
    194                     PowerManager.SCREEN_DIM_WAKE_LOCK |
    195                     PowerManager.ON_AFTER_RELEASE, LOG_TAG);
    196             mDisplayName = null;
    197         }
    198 
    199         @Override
    200         public void finalize() {
    201             if (mWakeLock != null && mWakeLock.isHeld()) {
    202                 Log.w(LOG_TAG, "WakeLock is being held.");
    203                 mWakeLock.release();
    204             }
    205         }
    206 
    207         @Override
    208         public void run() {
    209             Log.i(LOG_TAG, "vCard cache thread starts running.");
    210             if (mConnection == null) {
    211                 throw new NullPointerException("vCard cache thread must be launched "
    212                         + "after a service connection is established");
    213             }
    214 
    215             mWakeLock.acquire();
    216             try {
    217                 if (mCanceled == true) {
    218                     Log.i(LOG_TAG, "vCard cache operation is canceled.");
    219                     return;
    220                 }
    221 
    222                 final Context context = ImportVCardActivity.this;
    223                 // Uris given from caller applications may not be opened twice: consider when
    224                 // it is not from local storage (e.g. "file:///...") but from some special
    225                 // provider (e.g. "content://...").
    226                 // Thus we have to once copy the content of Uri into local storage, and read
    227                 // it after it.
    228                 //
    229                 // We may be able to read content of each vCard file during copying them
    230                 // to local storage, but currently vCard code does not allow us to do so.
    231                 int cache_index = 0;
    232                 ArrayList<ImportRequest> requests = new ArrayList<ImportRequest>();
    233                 if (mSource != null) {
    234                     try {
    235                         requests.add(constructImportRequest(mSource, null, mDisplayName));
    236                     } catch (VCardException e) {
    237                         Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
    238                         showFailureNotification(R.string.fail_reason_not_supported);
    239                         return;
    240                     }
    241                 } else {
    242                     int i = 0;
    243                     for (Uri sourceUri : mSourceUris) {
    244                         if (mCanceled) {
    245                             Log.i(LOG_TAG, "vCard cache operation is canceled.");
    246                             break;
    247                         }
    248 
    249                         String sourceDisplayName = mSourceDisplayNames[i++];
    250 
    251                         final ImportRequest request;
    252                         try {
    253                             request = constructImportRequest(null, sourceUri, sourceDisplayName);
    254                         } catch (VCardException e) {
    255                             Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
    256                             showFailureNotification(R.string.fail_reason_not_supported);
    257                             return;
    258                         } catch (IOException e) {
    259                             Log.e(LOG_TAG, "Unexpected IOException", e);
    260                             showFailureNotification(R.string.fail_reason_io_error);
    261                             return;
    262                         }
    263                         if (mCanceled) {
    264                             Log.i(LOG_TAG, "vCard cache operation is canceled.");
    265                             return;
    266                         }
    267                         requests.add(request);
    268                     }
    269                 }
    270                 if (!requests.isEmpty()) {
    271                     mConnection.sendImportRequest(requests);
    272                 } else {
    273                     Log.w(LOG_TAG, "Empty import requests. Ignore it.");
    274                 }
    275             } catch (OutOfMemoryError e) {
    276                 Log.e(LOG_TAG, "OutOfMemoryError occured during caching vCard");
    277                 System.gc();
    278                 runOnUiThread(new DialogDisplayer(
    279                         getString(R.string.fail_reason_low_memory_during_import)));
    280             } catch (IOException e) {
    281                 Log.e(LOG_TAG, "IOException during caching vCard", e);
    282                 runOnUiThread(new DialogDisplayer(
    283                         getString(R.string.fail_reason_io_error)));
    284             } finally {
    285                 Log.i(LOG_TAG, "Finished caching vCard.");
    286                 mWakeLock.release();
    287                 unbindService(mConnection);
    288                 mProgressDialogForCachingVCard.dismiss();
    289                 mProgressDialogForCachingVCard = null;
    290                 finish();
    291             }
    292         }
    293 
    294         /**
    295          * Reads localDataUri (possibly multiple times) and constructs {@link ImportRequest} from
    296          * its content.
    297          *
    298          * @arg localDataUri Uri actually used for the import. Should be stored in
    299          * app local storage, as we cannot guarantee other types of Uris can be read
    300          * multiple times. This variable populates {@link ImportRequest#uri}.
    301          * @arg displayName Used for displaying information to the user. This variable populates
    302          * {@link ImportRequest#displayName}.
    303          */
    304         private ImportRequest constructImportRequest(final byte[] data,
    305                 final Uri localDataUri, final String displayName)
    306                 throws IOException, VCardException {
    307             final ContentResolver resolver = ImportVCardActivity.this.getContentResolver();
    308             VCardEntryCounter counter = null;
    309             VCardSourceDetector detector = null;
    310             int vcardVersion = VCARD_VERSION_V21;
    311             try {
    312                 boolean shouldUseV30 = false;
    313                 InputStream is;
    314                 if (data != null) {
    315                     is = new ByteArrayInputStream(data);
    316                 } else {
    317                     is = resolver.openInputStream(localDataUri);
    318                 }
    319                 mVCardParser = new VCardParser_V21();
    320                 try {
    321                     counter = new VCardEntryCounter();
    322                     detector = new VCardSourceDetector();
    323                     mVCardParser.addInterpreter(counter);
    324                     mVCardParser.addInterpreter(detector);
    325                     mVCardParser.parse(is);
    326                 } catch (VCardVersionException e1) {
    327                     try {
    328                         is.close();
    329                     } catch (IOException e) {
    330                     }
    331 
    332                     shouldUseV30 = true;
    333                     if (data != null) {
    334                         is = new ByteArrayInputStream(data);
    335                     } else {
    336                         is = resolver.openInputStream(localDataUri);
    337                     }
    338                     mVCardParser = new VCardParser_V30();
    339                     try {
    340                         counter = new VCardEntryCounter();
    341                         detector = new VCardSourceDetector();
    342                         mVCardParser.addInterpreter(counter);
    343                         mVCardParser.addInterpreter(detector);
    344                         mVCardParser.parse(is);
    345                     } catch (VCardVersionException e2) {
    346                         throw new VCardException("vCard with unspported version.");
    347                     }
    348                 } finally {
    349                     if (is != null) {
    350                         try {
    351                             is.close();
    352                         } catch (IOException e) {
    353                         }
    354                     }
    355                 }
    356 
    357                 vcardVersion = shouldUseV30 ? VCARD_VERSION_V30 : VCARD_VERSION_V21;
    358             } catch (VCardNestedException e) {
    359                 Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
    360                 // Go through without throwing the Exception, as we may be able to detect the
    361                 // version before it
    362             }
    363             return new ImportRequest(mAccount,
    364                     data, localDataUri, displayName,
    365                     detector.getEstimatedType(),
    366                     detector.getEstimatedCharset(),
    367                     vcardVersion, counter.getCount());
    368         }
    369 
    370         public Uri[] getSourceUris() {
    371             return mSourceUris;
    372         }
    373 
    374         public void cancel() {
    375             mCanceled = true;
    376             if (mVCardParser != null) {
    377                 mVCardParser.cancel();
    378             }
    379         }
    380 
    381         @Override
    382         public void onCancel(DialogInterface dialog) {
    383             Log.i(LOG_TAG, "Cancel request has come. Abort caching vCard.");
    384             cancel();
    385         }
    386     }
    387 
    388     private void importVCard(final Uri uri, final String sourceDisplayName) {
    389         importVCard(new Uri[] {uri}, new String[] {sourceDisplayName});
    390     }
    391 
    392     private void importVCard(final Uri[] uris, final String[] sourceDisplayNames) {
    393         runOnUiThread(new Runnable() {
    394             @Override
    395             public void run() {
    396                 if (!isFinishing()) {
    397                     mVCardCacheThread = new VCardCacheThread(uris, sourceDisplayNames);
    398                     mListener = new NotificationImportExportListener(ImportVCardActivity.this);
    399                     showDialog(R.id.dialog_cache_vcard);
    400                 }
    401             }
    402         });
    403     }
    404 
    405     private String getDisplayName(Uri sourceUri) {
    406         if (sourceUri == null) {
    407             return null;
    408         }
    409         final ContentResolver resolver = ImportVCardActivity.this.getContentResolver();
    410         String displayName = null;
    411         Cursor cursor = null;
    412         // Try to get a display name from the given Uri. If it fails, we just
    413         // pick up the last part of the Uri.
    414         try {
    415             cursor = resolver.query(sourceUri,
    416                     new String[] { OpenableColumns.DISPLAY_NAME },
    417                     null, null, null);
    418             if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
    419                 if (cursor.getCount() > 1) {
    420                     Log.w(LOG_TAG, "Unexpected multiple rows: "
    421                             + cursor.getCount());
    422                 }
    423                 int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    424                 if (index >= 0) {
    425                     displayName = cursor.getString(index);
    426                 }
    427             }
    428         } finally {
    429             if (cursor != null) {
    430                 cursor.close();
    431             }
    432         }
    433         if (TextUtils.isEmpty(displayName)){
    434             displayName = sourceUri.getLastPathSegment();
    435         }
    436         return displayName;
    437     }
    438 
    439     /**
    440      * Copy the content of sourceUri to the destination.
    441      */
    442     private Uri copyTo(final Uri sourceUri, String filename) throws IOException {
    443         Log.i(LOG_TAG, String.format("Copy a Uri to app local storage (%s -> %s)",
    444                 sourceUri, filename));
    445         final Context context = ImportVCardActivity.this;
    446         final ContentResolver resolver = context.getContentResolver();
    447         ReadableByteChannel inputChannel = null;
    448         WritableByteChannel outputChannel = null;
    449         Uri destUri = null;
    450         try {
    451             inputChannel = Channels.newChannel(resolver.openInputStream(sourceUri));
    452             destUri = Uri.parse(context.getFileStreamPath(filename).toURI().toString());
    453             outputChannel = context.openFileOutput(filename, Context.MODE_PRIVATE).getChannel();
    454             final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
    455             while (inputChannel.read(buffer) != -1) {
    456                 buffer.flip();
    457                 outputChannel.write(buffer);
    458                 buffer.compact();
    459             }
    460             buffer.flip();
    461             while (buffer.hasRemaining()) {
    462                 outputChannel.write(buffer);
    463             }
    464         } finally {
    465             if (inputChannel != null) {
    466                 try {
    467                     inputChannel.close();
    468                 } catch (IOException e) {
    469                     Log.w(LOG_TAG, "Failed to close inputChannel.");
    470                 }
    471             }
    472             if (outputChannel != null) {
    473                 try {
    474                     outputChannel.close();
    475                 } catch(IOException e) {
    476                     Log.w(LOG_TAG, "Failed to close outputChannel");
    477                 }
    478             }
    479         }
    480         return destUri;
    481     }
    482 
    483     /**
    484      * Reads the file from {@param sourceUri} and copies it to local cache file.
    485      * Returns the local file name which stores the file from sourceUri.
    486      */
    487     private String readUriToLocalFile(Uri sourceUri) {
    488         // Read the uri to local first.
    489         int cache_index = 0;
    490         String localFilename = null;
    491         // Note: caches are removed by VCardService.
    492         while (true) {
    493             localFilename = VCardService.CACHE_FILE_PREFIX + cache_index + ".vcf";
    494             final File file = getFileStreamPath(localFilename);
    495             if (!file.exists()) {
    496                 break;
    497             } else {
    498                 if (cache_index == Integer.MAX_VALUE) {
    499                     throw new RuntimeException("Exceeded cache limit");
    500                 }
    501                 cache_index++;
    502             }
    503         }
    504         try {
    505             copyTo(sourceUri, localFilename);
    506         } catch (SecurityException e) {
    507             Log.e(LOG_TAG, "SecurityException", e);
    508             showFailureNotification(R.string.fail_reason_io_error);
    509             return null;
    510         } catch (IOException e) {
    511             Log.e(LOG_TAG, "IOException during caching vCard", e);
    512             showFailureNotification(R.string.fail_reason_io_error);
    513             return null;
    514         }
    515 
    516         if (localFilename == null) {
    517             Log.e(LOG_TAG, "Cannot load uri to local storage.");
    518             showFailureNotification(R.string.fail_reason_io_error);
    519             return null;
    520         }
    521 
    522         return localFilename;
    523     }
    524 
    525     private Uri readUriToLocalUri(Uri sourceUri) {
    526         final String fileName = readUriToLocalFile(sourceUri);
    527         if (fileName == null) {
    528             return null;
    529         }
    530         return Uri.parse(getFileStreamPath(fileName).toURI().toString());
    531     }
    532 
    533     // Returns true if uri is from Storage.
    534     private boolean isStorageUri(Uri uri) {
    535         return uri != null && uri.toString().startsWith(STORAGE_VCARD_URI_PREFIX);
    536     }
    537 
    538     @Override
    539     protected void onCreate(Bundle bundle) {
    540         super.onCreate(bundle);
    541 
    542         Uri sourceUri = getIntent().getData();
    543 
    544         // Reading uris from non-storage needs the permission granted from the source intent,
    545         // instead of permissions from RequestImportVCardPermissionActivity. So skipping requesting
    546         // permissions from RequestImportVCardPermissionActivity for uris from non-storage source.
    547         if (isStorageUri(sourceUri)
    548                 && RequestImportVCardPermissionsActivity.startPermissionActivity(this)) {
    549             return;
    550         }
    551 
    552         String sourceDisplayName = null;
    553         if (sourceUri != null) {
    554             // Read the uri to local first.
    555             String localTmpFileName = getIntent().getStringExtra(LOCAL_TMP_FILE_NAME_EXTRA);
    556             sourceDisplayName = getIntent().getStringExtra(SOURCE_URI_DISPLAY_NAME);
    557             if (TextUtils.isEmpty(localTmpFileName)) {
    558                 localTmpFileName = readUriToLocalFile(sourceUri);
    559                 sourceDisplayName = getDisplayName(sourceUri);
    560                 if (localTmpFileName == null) {
    561                     Log.e(LOG_TAG, "Cannot load uri to local storage.");
    562                     showFailureNotification(R.string.fail_reason_io_error);
    563                     return;
    564                 }
    565                 getIntent().putExtra(LOCAL_TMP_FILE_NAME_EXTRA, localTmpFileName);
    566                 getIntent().putExtra(SOURCE_URI_DISPLAY_NAME, sourceDisplayName);
    567             }
    568             sourceUri = Uri.parse(getFileStreamPath(localTmpFileName).toURI().toString());
    569         }
    570 
    571         // Always request required permission for contacts before importing the vcard.
    572         if (RequestImportVCardPermissionsActivity.startPermissionActivity(this)) {
    573             return;
    574         }
    575 
    576         String accountName = null;
    577         String accountType = null;
    578         String dataSet = null;
    579         final Intent intent = getIntent();
    580         if (intent != null) {
    581             accountName = intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME);
    582             accountType = intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE);
    583             dataSet = intent.getStringExtra(SelectAccountActivity.DATA_SET);
    584         } else {
    585             Log.e(LOG_TAG, "intent does not exist");
    586         }
    587 
    588         if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
    589             mAccount = new AccountWithDataSet(accountName, accountType, dataSet);
    590         } else {
    591             final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
    592             final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true);
    593             if (accountList.size() == 0) {
    594                 mAccount = null;
    595             } else if (accountList.size() == 1) {
    596                 mAccount = accountList.get(0);
    597             } else {
    598                 startActivityForResult(new Intent(this, SelectAccountActivity.class),
    599                         SELECT_ACCOUNT);
    600                 return;
    601             }
    602         }
    603 
    604         startImport(sourceUri, sourceDisplayName);
    605     }
    606 
    607     @Override
    608     public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    609         if (requestCode == SELECT_ACCOUNT) {
    610             if (resultCode == Activity.RESULT_OK) {
    611                 mAccount = new AccountWithDataSet(
    612                         intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME),
    613                         intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE),
    614                         intent.getStringExtra(SelectAccountActivity.DATA_SET));
    615                 final Uri sourceUri = getIntent().getData();
    616                 if (sourceUri == null) {
    617                     startImport(sourceUri, /* sourceDisplayName =*/ null);
    618                 } else {
    619                     final String sourceDisplayName = getIntent().getStringExtra(
    620                             SOURCE_URI_DISPLAY_NAME);
    621                     final String localFileName = getIntent().getStringExtra(
    622                             LOCAL_TMP_FILE_NAME_EXTRA);
    623                     final Uri localUri = Uri.parse(
    624                             getFileStreamPath(localFileName).toURI().toString());
    625                     startImport(localUri, sourceDisplayName);
    626                 }
    627             } else {
    628                 if (resultCode != Activity.RESULT_CANCELED) {
    629                     Log.w(LOG_TAG, "Result code was not OK nor CANCELED: " + resultCode);
    630                 }
    631                 finish();
    632             }
    633         } else if (requestCode == REQUEST_OPEN_DOCUMENT) {
    634             if (resultCode == Activity.RESULT_OK) {
    635                 final ClipData clipData = intent.getClipData();
    636                 if (clipData != null) {
    637                     final ArrayList<Uri> uris = new ArrayList<>();
    638                     final ArrayList<String> sourceDisplayNames = new ArrayList<>();
    639                     for (int i = 0; i < clipData.getItemCount(); i++) {
    640                         ClipData.Item item = clipData.getItemAt(i);
    641                         final Uri uri = item.getUri();
    642                         if (uri != null) {
    643                             final Uri localUri = readUriToLocalUri(uri);
    644                             if (localUri != null) {
    645                                 final String sourceDisplayName = getDisplayName(uri);
    646                                 uris.add(localUri);
    647                                 sourceDisplayNames.add(sourceDisplayName);
    648                             }
    649                         }
    650                     }
    651                     if (uris.isEmpty()) {
    652                         Log.w(LOG_TAG, "No vCard was selected for import");
    653                         finish();
    654                     } else {
    655                         Log.i(LOG_TAG, "Multiple vCards selected for import: " + uris);
    656                         importVCard(uris.toArray(new Uri[0]),
    657                                 sourceDisplayNames.toArray(new String[0]));
    658                     }
    659                 } else {
    660                     final Uri uri = intent.getData();
    661                     if (uri != null) {
    662                         Log.i(LOG_TAG, "vCard selected for import: " + uri);
    663                         final Uri localUri = readUriToLocalUri(uri);
    664                         if (localUri != null) {
    665                             final String sourceDisplayName = getDisplayName(uri);
    666                             importVCard(localUri, sourceDisplayName);
    667                         } else {
    668                             Log.w(LOG_TAG, "No local URI for vCard import");
    669                             finish();
    670                         }
    671                     } else {
    672                         Log.w(LOG_TAG, "No vCard was selected for import");
    673                         finish();
    674                     }
    675                 }
    676             } else {
    677                 if (resultCode != Activity.RESULT_CANCELED) {
    678                     Log.w(LOG_TAG, "Result code was not OK nor CANCELED" + resultCode);
    679                 }
    680                 finish();
    681             }
    682         }
    683     }
    684 
    685     private void startImport(Uri uri, String sourceDisplayName) {
    686         // Handle inbound files
    687         if (uri != null) {
    688             Log.i(LOG_TAG, "Starting vCard import using Uri " + uri);
    689             importVCard(uri, sourceDisplayName);
    690         } else {
    691             Log.i(LOG_TAG, "Start vCard without Uri. The user will select vCard manually.");
    692             final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    693             intent.addCategory(Intent.CATEGORY_OPENABLE);
    694             intent.setType(VCardService.X_VCARD_MIME_TYPE);
    695             intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
    696             startActivityForResult(intent, REQUEST_OPEN_DOCUMENT);
    697         }
    698     }
    699 
    700     @Override
    701     protected Dialog onCreateDialog(int resId, Bundle bundle) {
    702         if (resId == R.id.dialog_cache_vcard) {
    703             if (mProgressDialogForCachingVCard == null) {
    704                 final String title = getString(R.string.caching_vcard_title);
    705                 final String message = getString(R.string.caching_vcard_message);
    706                 mProgressDialogForCachingVCard = new ProgressDialog(this);
    707                 mProgressDialogForCachingVCard.setTitle(title);
    708                 mProgressDialogForCachingVCard.setMessage(message);
    709                 mProgressDialogForCachingVCard.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    710                 mProgressDialogForCachingVCard.setOnCancelListener(mVCardCacheThread);
    711                 startVCardService();
    712             }
    713             return mProgressDialogForCachingVCard;
    714         } else if (resId == R.id.dialog_error_with_message) {
    715             String message = mErrorMessage;
    716             if (TextUtils.isEmpty(message)) {
    717                 Log.e(LOG_TAG, "Error message is null while it must not.");
    718                 message = getString(R.string.fail_reason_unknown);
    719             }
    720             final AlertDialog.Builder builder = new AlertDialog.Builder(this)
    721                 .setTitle(getString(R.string.reading_vcard_failed_title))
    722                 .setIconAttribute(android.R.attr.alertDialogIcon)
    723                 .setMessage(message)
    724                 .setOnCancelListener(mCancelListener)
    725                 .setPositiveButton(android.R.string.ok, mCancelListener);
    726             return builder.create();
    727         }
    728 
    729         return super.onCreateDialog(resId, bundle);
    730     }
    731 
    732     /* package */ void startVCardService() {
    733         mConnection = new ImportRequestConnection();
    734 
    735         Log.i(LOG_TAG, "Bind to VCardService.");
    736         // We don't want the service finishes itself just after this connection.
    737         Intent intent = new Intent(this, VCardService.class);
    738         startService(intent);
    739         bindService(new Intent(this, VCardService.class),
    740                 mConnection, Context.BIND_AUTO_CREATE);
    741     }
    742 
    743     @Override
    744     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    745         super.onRestoreInstanceState(savedInstanceState);
    746         if (mProgressDialogForCachingVCard != null) {
    747             Log.i(LOG_TAG, "Cache thread is still running. Show progress dialog again.");
    748             showDialog(R.id.dialog_cache_vcard);
    749         }
    750     }
    751 
    752     /* package */ void showFailureNotification(int reasonId) {
    753         final NotificationManager notificationManager =
    754                 (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    755         final Notification notification =
    756                 NotificationImportExportListener.constructImportFailureNotification(
    757                         ImportVCardActivity.this,
    758                         getString(reasonId));
    759         notificationManager.notify(NotificationImportExportListener.FAILURE_NOTIFICATION_TAG,
    760                 FAILURE_NOTIFICATION_ID, notification);
    761         mHandler.post(new Runnable() {
    762             @Override
    763             public void run() {
    764                 Toast.makeText(ImportVCardActivity.this,
    765                         getString(R.string.vcard_import_failed), Toast.LENGTH_LONG).show();
    766             }
    767         });
    768     }
    769 }
    770