• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 implements ImportVCardDialogFragment.Listener {
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;
DialogDisplayer(int resId)116         public DialogDisplayer(int resId) {
117             mResId = resId;
118         }
DialogDisplayer(String errorMessage)119         public DialogDisplayer(String errorMessage) {
120             mResId = R.id.dialog_error_with_message;
121             mErrorMessage = errorMessage;
122         }
123         @Override
run()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
onClick(DialogInterface dialog, int which)134         public void onClick(DialogInterface dialog, int which) {
135             finish();
136         }
137         @Override
onCancel(DialogInterface dialog)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 
sendImportRequest(final List<ImportRequest> requests)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
onServiceConnected(ComponentName name, IBinder binder)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
onServiceDisconnected(ComponentName name)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 
VCardCacheThread(final Uri[] sourceUris, String[] sourceDisplayNames)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
finalize()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
run()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          */
constructImportRequest(final byte[] data, final Uri localDataUri, final String displayName)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 
getSourceUris()370         public Uri[] getSourceUris() {
371             return mSourceUris;
372         }
373 
cancel()374         public void cancel() {
375             mCanceled = true;
376             if (mVCardParser != null) {
377                 mVCardParser.cancel();
378             }
379         }
380 
381         @Override
onCancel(DialogInterface dialog)382         public void onCancel(DialogInterface dialog) {
383             Log.i(LOG_TAG, "Cancel request has come. Abort caching vCard.");
384             cancel();
385         }
386     }
387 
importVCard(final Uri uri, final String sourceDisplayName)388     private void importVCard(final Uri uri, final String sourceDisplayName) {
389         importVCard(new Uri[] {uri}, new String[] {sourceDisplayName});
390     }
391 
importVCard(final Uri[] uris, final String[] sourceDisplayNames)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 
getDisplayName(Uri sourceUri)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      */
copyTo(final Uri sourceUri, String filename)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      */
readUriToLocalFile(Uri sourceUri)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 
readUriToLocalUri(Uri sourceUri)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.
isStorageUri(Uri uri)534     private boolean isStorageUri(Uri uri) {
535         return uri != null && uri.toString().startsWith(STORAGE_VCARD_URI_PREFIX);
536     }
537 
538     @Override
onCreate(Bundle bundle)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) && RequestImportVCardPermissionsActivity
548                 .startPermissionActivity(this, isCallerSelf(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                 isCallerSelf(this))) {
574             return;
575         }
576 
577         String accountName = null;
578         String accountType = null;
579         String dataSet = null;
580         final Intent intent = getIntent();
581         if (intent != null) {
582             accountName = intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME);
583             accountType = intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE);
584             dataSet = intent.getStringExtra(SelectAccountActivity.DATA_SET);
585         } else {
586             Log.e(LOG_TAG, "intent does not exist");
587         }
588 
589         if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
590             mAccount = new AccountWithDataSet(accountName, accountType, dataSet);
591         } else {
592             final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
593             final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true);
594             if (accountList.size() == 0) {
595                 mAccount = null;
596             } else if (accountList.size() == 1) {
597                 mAccount = accountList.get(0);
598             } else {
599                 startActivityForResult(new Intent(this, SelectAccountActivity.class),
600                         SELECT_ACCOUNT);
601                 return;
602             }
603         }
604 
605         if (isCallerSelf(this)) {
606             startImport(sourceUri, sourceDisplayName);
607         } else {
608             ImportVCardDialogFragment.show(this, sourceUri, sourceDisplayName);
609         }
610     }
611 
isCallerSelf(Activity activity)612     private static boolean isCallerSelf(Activity activity) {
613         // {@link Activity#getCallingActivity()} is a safer alternative to
614         // {@link Activity#getCallingPackage()} that works around a
615         // framework bug where getCallingPackage() can sometimes return null even when the
616         // current activity *was* in fact launched via a startActivityForResult() call.
617         //
618         // (The bug happens if the task stack needs to be re-created by the framework after
619         // having been killed due to memory pressure or by the "Don't keep activities"
620         // developer option; see bug 7494866 for the full details.)
621         //
622         // Turns out that {@link Activity#getCallingActivity()} *does* return correct info
623         // even in the case where getCallingPackage() is broken, so the workaround is simply
624         // to get the package name from getCallingActivity().getPackageName() instead.
625         final ComponentName callingActivity = activity.getCallingActivity();
626         if (callingActivity == null) return false;
627         final String packageName = callingActivity.getPackageName();
628         if (packageName == null) return false;
629         return packageName.equals(activity.getApplicationContext().getPackageName());
630     }
631 
632     @Override
onImportVCardConfirmed(Uri sourceUri, String sourceDisplayName)633     public void onImportVCardConfirmed(Uri sourceUri, String sourceDisplayName) {
634         startImport(sourceUri, sourceDisplayName);
635     }
636 
637     @Override
onImportVCardDenied()638     public void onImportVCardDenied() {
639         finish();
640     }
641 
642     @Override
onActivityResult(int requestCode, int resultCode, Intent intent)643     public void onActivityResult(int requestCode, int resultCode, Intent intent) {
644         if (requestCode == SELECT_ACCOUNT) {
645             if (resultCode == Activity.RESULT_OK) {
646                 mAccount = new AccountWithDataSet(
647                         intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME),
648                         intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE),
649                         intent.getStringExtra(SelectAccountActivity.DATA_SET));
650                 final Uri sourceUri = getIntent().getData();
651                 if (sourceUri == null) {
652                     startImport(sourceUri, /* sourceDisplayName =*/ null);
653                 } else {
654                     final String sourceDisplayName = getIntent().getStringExtra(
655                             SOURCE_URI_DISPLAY_NAME);
656                     final String localFileName = getIntent().getStringExtra(
657                             LOCAL_TMP_FILE_NAME_EXTRA);
658                     final Uri localUri = Uri.parse(
659                             getFileStreamPath(localFileName).toURI().toString());
660                     startImport(localUri, sourceDisplayName);
661                 }
662             } else {
663                 if (resultCode != Activity.RESULT_CANCELED) {
664                     Log.w(LOG_TAG, "Result code was not OK nor CANCELED: " + resultCode);
665                 }
666                 finish();
667             }
668         } else if (requestCode == REQUEST_OPEN_DOCUMENT) {
669             if (resultCode == Activity.RESULT_OK) {
670                 final ClipData clipData = intent.getClipData();
671                 if (clipData != null) {
672                     final ArrayList<Uri> uris = new ArrayList<>();
673                     final ArrayList<String> sourceDisplayNames = new ArrayList<>();
674                     for (int i = 0; i < clipData.getItemCount(); i++) {
675                         ClipData.Item item = clipData.getItemAt(i);
676                         final Uri uri = item.getUri();
677                         if (uri != null) {
678                             final Uri localUri = readUriToLocalUri(uri);
679                             if (localUri != null) {
680                                 final String sourceDisplayName = getDisplayName(uri);
681                                 uris.add(localUri);
682                                 sourceDisplayNames.add(sourceDisplayName);
683                             }
684                         }
685                     }
686                     if (uris.isEmpty()) {
687                         Log.w(LOG_TAG, "No vCard was selected for import");
688                         finish();
689                     } else {
690                         Log.i(LOG_TAG, "Multiple vCards selected for import: " + uris);
691                         importVCard(uris.toArray(new Uri[0]),
692                                 sourceDisplayNames.toArray(new String[0]));
693                     }
694                 } else {
695                     final Uri uri = intent.getData();
696                     if (uri != null) {
697                         Log.i(LOG_TAG, "vCard selected for import: " + uri);
698                         final Uri localUri = readUriToLocalUri(uri);
699                         if (localUri != null) {
700                             final String sourceDisplayName = getDisplayName(uri);
701                             importVCard(localUri, sourceDisplayName);
702                         } else {
703                             Log.w(LOG_TAG, "No local URI for vCard import");
704                             finish();
705                         }
706                     } else {
707                         Log.w(LOG_TAG, "No vCard was selected for import");
708                         finish();
709                     }
710                 }
711             } else {
712                 if (resultCode != Activity.RESULT_CANCELED) {
713                     Log.w(LOG_TAG, "Result code was not OK nor CANCELED" + resultCode);
714                 }
715                 finish();
716             }
717         }
718     }
719 
startImport(Uri uri, String sourceDisplayName)720     private void startImport(Uri uri, String sourceDisplayName) {
721         // Handle inbound files
722         if (uri != null) {
723             Log.i(LOG_TAG, "Starting vCard import using Uri " + uri);
724             importVCard(uri, sourceDisplayName);
725         } else {
726             Log.i(LOG_TAG, "Start vCard without Uri. The user will select vCard manually.");
727             final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
728             intent.addCategory(Intent.CATEGORY_OPENABLE);
729             intent.setType(VCardService.X_VCARD_MIME_TYPE);
730             intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
731             startActivityForResult(intent, REQUEST_OPEN_DOCUMENT);
732         }
733     }
734 
735     @Override
onCreateDialog(int resId, Bundle bundle)736     protected Dialog onCreateDialog(int resId, Bundle bundle) {
737         if (resId == R.id.dialog_cache_vcard) {
738             if (mProgressDialogForCachingVCard == null) {
739                 final String title = getString(R.string.caching_vcard_title);
740                 final String message = getString(R.string.caching_vcard_message);
741                 mProgressDialogForCachingVCard = new ProgressDialog(this);
742                 mProgressDialogForCachingVCard.setTitle(title);
743                 mProgressDialogForCachingVCard.setMessage(message);
744                 mProgressDialogForCachingVCard.setProgressStyle(ProgressDialog.STYLE_SPINNER);
745                 mProgressDialogForCachingVCard.setOnCancelListener(mVCardCacheThread);
746                 startVCardService();
747             }
748             return mProgressDialogForCachingVCard;
749         } else if (resId == R.id.dialog_error_with_message) {
750             String message = mErrorMessage;
751             if (TextUtils.isEmpty(message)) {
752                 Log.e(LOG_TAG, "Error message is null while it must not.");
753                 message = getString(R.string.fail_reason_unknown);
754             }
755             final AlertDialog.Builder builder = new AlertDialog.Builder(this)
756                 .setTitle(getString(R.string.reading_vcard_failed_title))
757                 .setIconAttribute(android.R.attr.alertDialogIcon)
758                 .setMessage(message)
759                 .setOnCancelListener(mCancelListener)
760                 .setPositiveButton(android.R.string.ok, mCancelListener);
761             return builder.create();
762         }
763 
764         return super.onCreateDialog(resId, bundle);
765     }
766 
startVCardService()767     /* package */ void startVCardService() {
768         mConnection = new ImportRequestConnection();
769 
770         Log.i(LOG_TAG, "Bind to VCardService.");
771         // We don't want the service finishes itself just after this connection.
772         Intent intent = new Intent(this, VCardService.class);
773         startService(intent);
774         bindService(new Intent(this, VCardService.class),
775                 mConnection, Context.BIND_AUTO_CREATE);
776     }
777 
778     @Override
onRestoreInstanceState(Bundle savedInstanceState)779     protected void onRestoreInstanceState(Bundle savedInstanceState) {
780         super.onRestoreInstanceState(savedInstanceState);
781         if (mProgressDialogForCachingVCard != null) {
782             Log.i(LOG_TAG, "Cache thread is still running. Show progress dialog again.");
783             showDialog(R.id.dialog_cache_vcard);
784         }
785     }
786 
showFailureNotification(int reasonId)787     /* package */ void showFailureNotification(int reasonId) {
788         final NotificationManager notificationManager =
789                 (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
790         final Notification notification =
791                 NotificationImportExportListener.constructImportFailureNotification(
792                         ImportVCardActivity.this,
793                         getString(reasonId));
794         notificationManager.notify(NotificationImportExportListener.FAILURE_NOTIFICATION_TAG,
795                 FAILURE_NOTIFICATION_ID, notification);
796         mHandler.post(new Runnable() {
797             @Override
798             public void run() {
799                 Toast.makeText(ImportVCardActivity.this,
800                         getString(R.string.vcard_import_failed), Toast.LENGTH_LONG).show();
801             }
802         });
803     }
804 }
805