Merge "Moving contact saving to the service"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d09904a..4c75705 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -680,13 +680,13 @@
     -->
     <string name="description_plus_button">plus</string>
 
-    <!-- Dialog title shown when USB storage does not exist [CHAR LIMIT=25] -->
-    <string name="no_sdcard_title" product="nosdcard">USB storage unavailable</string>
+    <!-- Dialog title shown when (USB) storage does not exist [CHAR LIMIT=25] -->
+    <string name="no_sdcard_title" product="nosdcard">Storage unavailable</string>
     <!-- Dialog title shown when SD Card does not exist -->
     <string name="no_sdcard_title" product="default">No SD card</string>
 
-    <!-- Dialog message shown when USB storage does not exist [CHAR LIMIT=30] -->
-    <string name="no_sdcard_message" product="nosdcard">No USB storage detected</string>
+    <!-- Dialog message shown when (USB) storage does not exist [CHAR LIMIT=30] -->
+    <string name="no_sdcard_message" product="nosdcard">No storage detected</string>
     <!-- Dialog message shown when SDcard does not exist -->
     <string name="no_sdcard_message" product="default">No SD card detected</string>
 
@@ -696,13 +696,13 @@
     <!-- Action string for selecting SIM for importing contacts -->
     <string name="import_from_sim">Import from SIM card</string>
 
-    <!-- Action string for selecting USB storage for importing contacts [CHAR LIMIT=25] -->
-    <string name="import_from_sdcard" product="nosdcard">Import from USB storage</string>
+    <!-- Action string for selecting (USB) storage for importing contacts [CHAR LIMIT=25] -->
+    <string name="import_from_sdcard" product="nosdcard">Import from storage</string>
     <!-- Action string for selecting SD Card for importing contacts -->
     <string name="import_from_sdcard" product="default">Import from SD card</string>
 
-    <!-- Action that exports all contacts to USB storage [CHAR LIMIT=25] -->
-    <string name="export_to_sdcard" product="nosdcard">Export to USB storage</string>
+    <!-- Action that exports all contacts to (USB) storage [CHAR LIMIT=25] -->
+    <string name="export_to_sdcard" product="nosdcard">Export to storage</string>
     <!-- Action that exports all contacts to SD Card -->
     <string name="export_to_sdcard" product="default">Export to SD card</string>
 
@@ -724,19 +724,19 @@
          than one vCard files available in the system. -->
     <string name="import_all_vcard_string">Import all vCard files</string>
 
-    <!-- Dialog message shown when searching VCard data from SD Card [CHAR LIMIT=NONE] -->
-    <string name="searching_vcard_message" product="nosdcard">Searching for vCard data in USB storage</string>
+    <!-- Dialog message shown when searching VCard data from (USB) storage [CHAR LIMIT=NONE] -->
+    <string name="searching_vcard_message" product="nosdcard">Searching for vCard data in storage</string>
     <!-- Dialog message shown when searching VCard data from SD Card -->
     <string name="searching_vcard_message" product="default">Searching for vCard data on SD card</string>
 
     <!-- Dialog title shown when scanning VCard data failed. [CHAR LIMIT=NONE] -->
-    <string name="scanning_sdcard_failed_title" product="nosdcard">Scanning USB storage failed</string>
+    <string name="scanning_sdcard_failed_title" product="nosdcard">Scanning storage failed</string>
     <!-- Dialog title shown when scanning VCard data failed. -->
     <string name="scanning_sdcard_failed_title" product="default">Scanning SD card failed</string>
 
     <!-- Dialog message shown when searching VCard data failed.
          An exact reason for the failure should [CHAR LIMIT=NONE] -->
-    <string name="scanning_sdcard_failed_message" product="nosdcard">Scanning USB storage failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
+    <string name="scanning_sdcard_failed_message" product="nosdcard">Scanning storage failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
     <!-- Dialog message shown when searching VCard data failed.
          An exact reason for the failure should -->
     <string name="scanning_sdcard_failed_message" product="default">Scanning SD card failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
@@ -762,9 +762,9 @@
     <string name="vcard_import_failed">Failed to import vCard</string>
 
     <!-- The failure message shown when the system could not find any vCard file.
-         (with extension ".vcf" in USB storage.)
+         (with extension ".vcf" in (USB) storage.)
          [CHAR LIMIT=128] -->
-    <string name="import_failure_no_vcard_file" product="nosdcard">No vCard file found in the USB storage</string>
+    <string name="import_failure_no_vcard_file" product="nosdcard">No vCard file found in the storage</string>
     <!-- The failure message shown when the system could not find any vCard file.
          (with extension ".vcf" in SDCard.)
          [CHAR LIMIT=128] -->
@@ -864,7 +864,7 @@
 
     <!-- The failed reason shown when vCard exporter could not create a file for the vCard since
          there are too many files relevant to vCard. [CHAR LIMIT=NONE] -->
-    <string name="fail_reason_too_many_vcard" product="nosdcard">Too many vCard files in the USB storage</string>
+    <string name="fail_reason_too_many_vcard" product="nosdcard">Too many vCard files in the storage</string>
     <!-- The failed reason shown when vCard exporter could not create a file for the vCard since
          there are too many files relevant to vCard. -->
     <string name="fail_reason_too_many_vcard" product="default">Too many vCard files on the SD card</string>
diff --git a/src/com/android/contacts/vcard/ExportProcessor.java b/src/com/android/contacts/vcard/ExportProcessor.java
index c5f293b..3285a05 100644
--- a/src/com/android/contacts/vcard/ExportProcessor.java
+++ b/src/com/android/contacts/vcard/ExportProcessor.java
@@ -178,6 +178,11 @@
             }
             Log.i(LOG_TAG, "Successfully finished exporting vCard " + request.destUri);
 
+            if (DEBUG) {
+                Log.d(LOG_TAG, "Ask MediaScanner to scan the file: " + request.destUri.getPath());
+            }
+            mService.updateMediaScanner(request.destUri.getPath());
+
             successful = true;
             final String filename = uri.getLastPathSegment();
             final String title = mService.getString(R.string.exporting_vcard_finished_title,
diff --git a/src/com/android/contacts/vcard/ProcessorBase.java b/src/com/android/contacts/vcard/ProcessorBase.java
index 833090d..073bcbb 100644
--- a/src/com/android/contacts/vcard/ProcessorBase.java
+++ b/src/com/android/contacts/vcard/ProcessorBase.java
@@ -39,6 +39,7 @@
      */
     public abstract int getType();
 
+    @Override
     public abstract void run();
 
     /**
@@ -49,8 +50,11 @@
      *
      * @see Future#cancel(boolean)
      */
+    @Override
     public abstract boolean cancel(boolean mayInterruptIfRunning);
+    @Override
     public abstract boolean isCancelled();
+    @Override
     public abstract boolean isDone();
 
     /**
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
index 6422163..a4c0480 100644
--- a/src/com/android/contacts/vcard/VCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -36,8 +38,10 @@
 import android.widget.Toast;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -101,6 +105,33 @@
         }
     });
 
+    private class CustomMediaScannerConnectionClient implements MediaScannerConnectionClient {
+        final MediaScannerConnection mConnection;
+        final String mPath;
+
+        public CustomMediaScannerConnectionClient(String path) {
+            mConnection = new MediaScannerConnection(VCardService.this, this);
+            mPath = path;
+        }
+
+        public void start() {
+            mConnection.connect();
+        }
+
+        @Override
+        public void onMediaScannerConnected() {
+            if (DEBUG) { Log.d(LOG_TAG, "Connected to MediaScanner. Start scanning."); }
+            mConnection.scanFile(mPath, null);
+        }
+
+        @Override
+        public void onScanCompleted(String path, Uri uri) {
+            if (DEBUG) { Log.d(LOG_TAG, "scan completed: " + path); }
+            mConnection.disconnect();
+            removeConnectionClient(this);
+        }
+    }
+
     private NotificationManager mNotificationManager;
 
     // Should be single thread, as we don't want to simultaneously handle import and export
@@ -113,6 +144,11 @@
     // Key is jobId.
     private final Map<Integer, ProcessorBase> mRunningJobMap =
             new HashMap<Integer, ProcessorBase>();
+    // Stores ScannerConnectionClient objects until they finish scanning requested files.
+    // Uses List class for simplicity. It's not costly as we won't have multiple objects in
+    // almost all cases.
+    private final List<CustomMediaScannerConnectionClient> mRemainingScannerConnections =
+            new ArrayList<CustomMediaScannerConnectionClient>();
 
     /* ** vCard exporter params ** */
     // If true, VCardExporter is able to emits files longer than 8.3 format.
@@ -286,7 +322,7 @@
         } else {
             Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
         }
-        stopServiceWhenNoJob();
+        stopServiceIfAppropriate();
     }
 
     private synchronized void handleRequestAvailableExportDestination(Message msg) {
@@ -310,10 +346,11 @@
     }
 
     /**
-     * Checks job list and call {@link #stopSelf()} when there's no job now.
-     * A new job cannot be submitted any more after this call.
+     * Checks job list and call {@link #stopSelf()} when there's no job and no scanner connection
+     * is remaining.
+     * A new job (import/export) cannot be submitted any more after this call.
      */
-    private synchronized void stopServiceWhenNoJob() {
+    private synchronized void stopServiceIfAppropriate() {
         if (mRunningJobMap.size() > 0) {
             for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) {
                 final int jobId = entry.getKey();
@@ -327,11 +364,41 @@
             }
         }
 
+        if (!mRemainingScannerConnections.isEmpty()) {
+            Log.i(LOG_TAG, "MediaScanner update is in progress.");
+            return;
+        }
+
         Log.i(LOG_TAG, "No unfinished job. Stop this service.");
         mExecutorService.shutdown();
         stopSelf();
     }
 
+    /* package */ synchronized void updateMediaScanner(String path) {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "MediaScanner is being updated: " + path);
+        }
+
+        if (mExecutorService.isShutdown()) {
+            Log.w(LOG_TAG, "MediaScanner update is requested after executor's being shut down. " +
+                    "Ignoring the update request");
+            return;
+        }
+        final CustomMediaScannerConnectionClient client =
+                new CustomMediaScannerConnectionClient(path);
+        mRemainingScannerConnections.add(client);
+        client.start();
+    }
+
+    private synchronized void removeConnectionClient(
+            CustomMediaScannerConnectionClient client) {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Removing custom MediaScannerConnectionClient.");
+        }
+        mRemainingScannerConnections.remove(client);
+        stopServiceIfAppropriate();
+    }
+
     /* package */ synchronized void handleFinishImportNotification(
             int jobId, boolean successful) {
         if (DEBUG) {
@@ -341,7 +408,7 @@
         if (mRunningJobMap.remove(jobId) == null) {
             Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
         }
-        stopServiceWhenNoJob();
+        stopServiceIfAppropriate();
     }
 
     /* package */ synchronized void handleFinishExportNotification(
@@ -362,7 +429,7 @@
             mReservedDestination.remove(path);
         }
 
-        stopServiceWhenNoJob();
+        stopServiceIfAppropriate();
     }
 
     /**