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; 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) 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 onActivityResult(int requestCode, int resultCode, Intent intent)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 startImport(Uri uri, String sourceDisplayName)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 onCreateDialog(int resId, Bundle bundle)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 startVCardService()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 onRestoreInstanceState(Bundle savedInstanceState)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 showFailureNotification(int reasonId)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