1f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa/*
2f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * Copyright (C) 2010 The Android Open Source Project
3f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa *
4f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * Licensed under the Apache License, Version 2.0 (the "License");
5f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * you may not use this file except in compliance with the License.
6f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * You may obtain a copy of the License at
7f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa *
8f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa *      http://www.apache.org/licenses/LICENSE-2.0
9f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa *
10f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * Unless required by applicable law or agreed to in writing, software
11f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * distributed under the License is distributed on an "AS IS" BASIS,
12f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * See the License for the specific language governing permissions and
14f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa * limitations under the License.
15f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa */
161b918e58f4a3ae8d32af83f6f69bbf2de57a94f9Daisuke Miyakawapackage com.android.contacts.vcard;
17f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
18f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.app.Service;
19f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.content.Intent;
20783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.content.res.Resources;
21fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport android.media.MediaScannerConnection;
22fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport android.media.MediaScannerConnection.MediaScannerConnectionClient;
23ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.net.Uri;
241167da421b68952a590b050c32def7e0eff7cca6Jeff Hamiltonimport android.os.Binder;
2562152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmannimport android.os.Environment;
26f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.os.IBinder;
27476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Message;
28476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Messenger;
29783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.os.RemoteException;
30783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.text.TextUtils;
31f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.util.Log;
32208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawaimport android.util.SparseArray;
33208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa
34208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawaimport com.android.contacts.R;
35f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
36783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.io.File;
37fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport java.util.ArrayList;
38783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.util.HashSet;
39fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport java.util.List;
40783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.util.Set;
417c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.ExecutorService;
427c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.Executors;
437c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.RejectedExecutionException;
441b918e58f4a3ae8d32af83f6f69bbf2de57a94f9Daisuke Miyakawa
45f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa/**
467c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * The class responsible for handling vCard import/export requests.
477c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa *
487c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * This Service creates one ImportRequest/ExportRequest object (as Runnable) per request and push
497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * it to {@link ExecutorService} with single thread executor. The executor handles each request
507c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * one by one, and notifies users when needed.
51f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa */
527c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// TODO: Using IntentService looks simpler than using Service + ServiceConnection though this
537c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// works fine enough. Investigate the feasibility.
54d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawapublic class VCardService extends Service {
557c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    private final static String LOG_TAG = "VCardService";
5653d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda
576d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa    /* package */ final static boolean DEBUG = false;
58f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
59ef41f8866e8e7d52e04907f7282adcf5f4749f25Daisuke Miyakawa    /* package */ static final int MSG_IMPORT_REQUEST = 1;
60d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa    /* package */ static final int MSG_EXPORT_REQUEST = 2;
61ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static final int MSG_CANCEL_REQUEST = 3;
62783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* package */ static final int MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION = 4;
63783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* package */ static final int MSG_SET_AVAILABLE_EXPORT_DESTINATION = 5;
64f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
65ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /**
661167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton     * Specifies the type of operation. Used when constructing a notification, canceling
67ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * some operation, etc.
68ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     */
69ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static final int TYPE_IMPORT = 1;
70ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static final int TYPE_EXPORT = 2;
71f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
72aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    /* package */ static final String CACHE_FILE_PREFIX = "import_tmp_";
73f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
74f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
75fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private class CustomMediaScannerConnectionClient implements MediaScannerConnectionClient {
76fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        final MediaScannerConnection mConnection;
77fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        final String mPath;
78fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
79fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public CustomMediaScannerConnectionClient(String path) {
80fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection = new MediaScannerConnection(VCardService.this, this);
81fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mPath = path;
82fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
83fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
84fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public void start() {
85fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection.connect();
86fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
87fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
88fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        @Override
89fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public void onMediaScannerConnected() {
90fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            if (DEBUG) { Log.d(LOG_TAG, "Connected to MediaScanner. Start scanning."); }
91fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection.scanFile(mPath, null);
92fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
93fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
94fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        @Override
95fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public void onScanCompleted(String path, Uri uri) {
96fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            if (DEBUG) { Log.d(LOG_TAG, "scan completed: " + path); }
97fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection.disconnect();
98fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            removeConnectionClient(this);
99fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
100fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    }
101fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
1027c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    // Should be single thread, as we don't want to simultaneously handle import and export
1037c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    // requests.
104910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
1057c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
1067c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    private int mCurrentJobId;
107910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa
108910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    // Stores all unfinished import/export jobs which will be executed by mExecutorService.
109910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    // Key is jobId.
110208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa    private final SparseArray<ProcessorBase> mRunningJobMap = new SparseArray<ProcessorBase>();
111fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    // Stores ScannerConnectionClient objects until they finish scanning requested files.
112fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    // Uses List class for simplicity. It's not costly as we won't have multiple objects in
113fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    // almost all cases.
114fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private final List<CustomMediaScannerConnectionClient> mRemainingScannerConnections =
115fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            new ArrayList<CustomMediaScannerConnectionClient>();
116476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa
117783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* ** vCard exporter params ** */
118783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    // If true, VCardExporter is able to emits files longer than 8.3 format.
119783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private static final boolean ALLOW_LONG_FILE_NAME = false;
12053d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda
12162152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann    private File mTargetDirectory;
122783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mFileNamePrefix;
123783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mFileNameSuffix;
124783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private int mFileIndexMinimum;
125783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private int mFileIndexMaximum;
126783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mFileNameExtension;
127783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private Set<String> mExtensionsToConsider;
128783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mErrorReason;
1291167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton    private MyBinder mBinder;
130783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
131783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    // File names currently reserved by some export job.
132783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private final Set<String> mReservedDestination = new HashSet<String>();
133783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* ** end of vCard exporter params ** */
134783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
1351167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton    public class MyBinder extends Binder {
1361167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton        public VCardService getService() {
1371167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            return VCardService.this;
1381167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton        }
1391167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton    }
1401167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton
1411167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton   @Override
142ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    public void onCreate() {
143ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        super.onCreate();
1441167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton        mBinder = new MyBinder();
145783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, "vCard Service is being created.");
146783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        initExporterParams();
147783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    }
148783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
149783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private void initExporterParams() {
15062152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann        mTargetDirectory = Environment.getExternalStorageDirectory();
151783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileNamePrefix = getString(R.string.config_export_file_prefix);
152783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileNameSuffix = getString(R.string.config_export_file_suffix);
153783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileNameExtension = getString(R.string.config_export_file_extension);
154783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
155783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mExtensionsToConsider = new HashSet<String>();
156783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mExtensionsToConsider.add(mFileNameExtension);
157783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
158783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final String additionalExtensions =
159783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            getString(R.string.config_export_extensions_to_consider);
160783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (!TextUtils.isEmpty(additionalExtensions)) {
161783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            for (String extension : additionalExtensions.split(",")) {
162783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                String trimed = extension.trim();
163783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                if (trimed.length() > 0) {
164783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    mExtensionsToConsider.add(trimed);
165783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
166783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
167783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
168783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
169783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final Resources resources = getResources();
170783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileIndexMinimum = resources.getInteger(R.integer.config_export_file_min_index);
171783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileIndexMaximum = resources.getInteger(R.integer.config_export_file_max_index);
172ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    }
173ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
174ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    @Override
175476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa    public int onStartCommand(Intent intent, int flags, int id) {
176476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa        return START_STICKY;
177f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    }
178f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
179f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    @Override
180f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    public IBinder onBind(Intent intent) {
1811167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton        return mBinder;
182f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    }
183aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa
184aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    @Override
185aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    public void onDestroy() {
186783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, "VCardService is being destroyed.");
187910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        cancelAllRequestsAndShutdown();
188aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa        clearCache();
189aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa        super.onDestroy();
190aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    }
191aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa
1921167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton    public synchronized void handleImportRequest(List<ImportRequest> requests,
1931167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            VCardImportExportListener listener) {
194783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) {
1956d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            final ArrayList<String> uris = new ArrayList<String>();
196e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton            final ArrayList<String> displayNames = new ArrayList<String>();
1976d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            for (ImportRequest request : requests) {
1986d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                uris.add(request.uri.toString());
199e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                displayNames.add(request.displayName);
2006d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            }
201783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.d(LOG_TAG,
202e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    String.format("received multiple import request (uri: %s, displayName: %s)",
203e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                            uris.toString(), displayNames.toString()));
204783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
2056d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa        final int size = requests.size();
2066d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa        for (int i = 0; i < size; i++) {
2076d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            ImportRequest request = requests.get(i);
2086d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa
2091167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            if (tryExecute(new ImportProcessor(this, listener, request, mCurrentJobId))) {
2101167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                if (listener != null) {
2111167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                    listener.onImportProcessed(request, mCurrentJobId, i);
2126d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                }
2136d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                mCurrentJobId++;
2146d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            } else {
2151167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                if (listener != null) {
2161167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                    listener.onImportFailed(request);
2171167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                }
2186d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                // A rejection means executor doesn't run any more. Exit.
2196d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                break;
2206d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            }
221ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        }
2227c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
2237c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
2241167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton    public synchronized void handleExportRequest(ExportRequest request,
2251167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            VCardImportExportListener listener) {
226ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        if (tryExecute(new ExportProcessor(this, request, mCurrentJobId))) {
227783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            final String path = request.destUri.getEncodedPath();
228783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (DEBUG) Log.d(LOG_TAG, "Reserve the path " + path);
229783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (!mReservedDestination.add(path)) {
230783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.w(LOG_TAG,
231783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        String.format("The path %s is already reserved. Reject export request",
232783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                                path));
2331167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                if (listener != null) {
2341167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                    listener.onExportFailed(request);
2351167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                }
236783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                return;
237783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
238783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
2391167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            if (listener != null) {
2401167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                listener.onExportProcessed(request, mCurrentJobId);
2411167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            }
242ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            mCurrentJobId++;
243ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        } else {
2441167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            if (listener != null) {
2451167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                listener.onExportFailed(request);
2461167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            }
247ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        }
248910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    }
249910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa
250910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    /**
251ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor.
252ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @return true when successful.
253910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa     */
254ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    private synchronized boolean tryExecute(ProcessorBase processor) {
2557c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        try {
2566d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            if (DEBUG) {
2576d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                Log.d(LOG_TAG, "Executor service status: shutdown: " + mExecutorService.isShutdown()
2586d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                        + ", terminated: " + mExecutorService.isTerminated());
2596d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            }
260910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            mExecutorService.execute(processor);
261910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            mRunningJobMap.put(mCurrentJobId, processor);
262ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            return true;
2637c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        } catch (RejectedExecutionException e) {
264910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            Log.w(LOG_TAG, "Failed to excetute a job.", e);
265ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            return false;
2667c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
2677c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
2687c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
2691167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton    public synchronized void handleCancelRequest(CancelRequest request,
2701167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            VCardImportExportListener listener) {
271ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final int jobId = request.jobId;
272783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, String.format("Received cancel request. (id: %d)", jobId));
273208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa
274208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa        final ProcessorBase processor = mRunningJobMap.get(jobId);
275208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa        mRunningJobMap.remove(jobId);
2767c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
277ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        if (processor != null) {
278ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            processor.cancel(true);
2791167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            final int type = processor.getType();
2801167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            if (listener != null) {
2811167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton                listener.onCancelRequest(request, type);
2821167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            }
2831167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton            if (type == TYPE_EXPORT) {
284783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                final String path =
285783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        ((ExportProcessor)processor).getRequest().destUri.getEncodedPath();
286783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.i(LOG_TAG,
287783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        String.format("Cancel reservation for the path %s if appropriate", path));
288783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                if (!mReservedDestination.remove(path)) {
289783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    Log.w(LOG_TAG, "Not reserved.");
290783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
291783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
292ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        } else {
293ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
2947c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
295fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
296f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa    }
297f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa
2981167da421b68952a590b050c32def7e0eff7cca6Jeff Hamilton    public synchronized void handleRequestAvailableExportDestination(final Messenger messenger) {
299783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, "Received available export destination request.");
300783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final String path = getAppropriateDestination(mTargetDirectory);
301783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final Message message;
302783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (path != null) {
303783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            message = Message.obtain(null,
304783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    VCardService.MSG_SET_AVAILABLE_EXPORT_DESTINATION, 0, 0, path);
305783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } else {
306783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            message = Message.obtain(null,
307783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    VCardService.MSG_SET_AVAILABLE_EXPORT_DESTINATION,
308783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    R.id.dialog_fail_to_export_with_reason, 0, mErrorReason);
309783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
310783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        try {
311783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            messenger.send(message);
312783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } catch (RemoteException e) {
313783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.w(LOG_TAG, "Failed to send reply for available export destination request.", e);
314783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
315783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    }
316783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
317f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa    /**
318fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa     * Checks job list and call {@link #stopSelf()} when there's no job and no scanner connection
319fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa     * is remaining.
320fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa     * A new job (import/export) cannot be submitted any more after this call.
321f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     */
322fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private synchronized void stopServiceIfAppropriate() {
323910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        if (mRunningJobMap.size() > 0) {
324208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            final int size = mRunningJobMap.size();
325208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa
326208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // Check if there are processors which aren't finished yet. If we still have ones to
327208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // process, we cannot stop the service yet. Also clean up already finished processors
328208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // here.
329208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa
330208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // Job-ids to be removed. At first all elements in the array are invalid and will
331208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // be filled with real job-ids from the array's top. When we find a not-yet-finished
332208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // processor, then we start removing those finished jobs. In that case latter half of
333208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // this array will be invalid.
334208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            final int[] toBeRemoved = new int[size];
335208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            for (int i = 0; i < size; i++) {
336208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                final int jobId = mRunningJobMap.keyAt(i);
337208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                final ProcessorBase processor = mRunningJobMap.valueAt(i);
338208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                if (!processor.isDone()) {
339f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                    Log.i(LOG_TAG, String.format("Found unfinished job (id: %d)", jobId));
340208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa
341208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                    // Remove processors which are already "done", all of which should be before
342208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                    // processors which aren't done yet.
343208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                    for (int j = 0; j < i; j++) {
344208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                        mRunningJobMap.remove(toBeRemoved[j]);
345208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                    }
346f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                    return;
347f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                }
348208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa
349208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                // Remember the finished processor.
350208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa                toBeRemoved[i] = jobId;
351f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa            }
352208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa
353208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            // We're sure we can remove all. Instead of removing one by one, just call clear().
354208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            mRunningJobMap.clear();
355f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        }
356f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa
357fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (!mRemainingScannerConnections.isEmpty()) {
358fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.i(LOG_TAG, "MediaScanner update is in progress.");
359fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            return;
360fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
361fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
362f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        Log.i(LOG_TAG, "No unfinished job. Stop this service.");
363f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        mExecutorService.shutdown();
364f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        stopSelf();
3657c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
3667c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
367fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    /* package */ synchronized void updateMediaScanner(String path) {
368fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (DEBUG) {
369fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.d(LOG_TAG, "MediaScanner is being updated: " + path);
370fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
371fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
372fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (mExecutorService.isShutdown()) {
373fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.w(LOG_TAG, "MediaScanner update is requested after executor's being shut down. " +
374fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa                    "Ignoring the update request");
375fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            return;
376fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
377fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        final CustomMediaScannerConnectionClient client =
378fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa                new CustomMediaScannerConnectionClient(path);
379fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        mRemainingScannerConnections.add(client);
380fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        client.start();
381fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    }
382fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
383fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private synchronized void removeConnectionClient(
384fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            CustomMediaScannerConnectionClient client) {
385fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (DEBUG) {
386fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.d(LOG_TAG, "Removing custom MediaScannerConnectionClient.");
387fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
388fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        mRemainingScannerConnections.remove(client);
389fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
390fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    }
391fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
3927c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /* package */ synchronized void handleFinishImportNotification(
3937c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa            int jobId, boolean successful) {
394783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) {
395783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.d(LOG_TAG, String.format("Received vCard import finish notification (id: %d). "
396783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    + "Result: %b", jobId, (successful ? "success" : "failure")));
397783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
398208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa        mRunningJobMap.remove(jobId);
399fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
4007c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
4017c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
4027c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /* package */ synchronized void handleFinishExportNotification(
4037c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa            int jobId, boolean successful) {
404783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) {
405783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.d(LOG_TAG, String.format("Received vCard export finish notification (id: %d). "
406783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    + "Result: %b", jobId, (successful ? "success" : "failure")));
407783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
408208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa        final ProcessorBase job = mRunningJobMap.get(jobId);
409208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa        mRunningJobMap.remove(jobId);
410783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (job == null) {
4117c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa            Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
412783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } else if (!(job instanceof ExportProcessor)) {
413783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.w(LOG_TAG,
414783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    String.format("Removed job (id: %s) isn't ExportProcessor", jobId));
415783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } else {
416783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            final String path = ((ExportProcessor)job).getRequest().destUri.getEncodedPath();
417783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (DEBUG) Log.d(LOG_TAG, "Remove reserved path " + path);
418783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            mReservedDestination.remove(path);
4197c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
420783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
421fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
4227c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
4237c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
4247c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /**
425910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa     * Cancels all the import/export requests and calls {@link ExecutorService#shutdown()}, which
426f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     * means this Service becomes no longer ready for import/export requests.
427f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     *
428f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     * Mainly called from onDestroy().
4297c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa     */
430910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    private synchronized void cancelAllRequestsAndShutdown() {
431208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa        for (int i = 0; i < mRunningJobMap.size(); i++) {
432208f0be85dbc00b30e300f22556c30af2580e699Daisuke Miyakawa            mRunningJobMap.valueAt(i).cancel(true);
4337c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
434910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        mRunningJobMap.clear();
435f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        mExecutorService.shutdown();
4367c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
4377c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
4387c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /**
4397c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa     * Removes import caches stored locally.
4407c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa     */
441aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    private void clearCache() {
442910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        for (final String fileName : fileList()) {
443aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa            if (fileName.startsWith(CACHE_FILE_PREFIX)) {
444aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa                // We don't want to keep all the caches so we remove cache files old enough.
445aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa                Log.i(LOG_TAG, "Remove a temporary file: " + fileName);
446aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa                deleteFile(fileName);
447aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa            }
448aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa        }
449aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    }
450ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
451ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /**
452783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     * Returns an appropriate file name for vCard export. Returns null when impossible.
453783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     *
454783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     * @return destination path for a vCard file to be exported. null on error and mErrorReason
455783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     * is correctly set.
456783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     */
45762152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann    private String getAppropriateDestination(final File destDirectory) {
458783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        /*
459783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * Here, file names have 5 parts: directory, prefix, index, suffix, and extension.
460783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * e.g. "/mnt/sdcard/prfx00001sfx.vcf" -> "/mnt/sdcard", "prfx", "00001", "sfx", and ".vcf"
461783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *      (In default, prefix and suffix is empty, so usually the destination would be
462783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *       /mnt/sdcard/00001.vcf.)
463783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *
464783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * This method increments "index" part from 1 to maximum, and checks whether any file name
465783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * following naming rule is available. If there's no file named /mnt/sdcard/00001.vcf, the
466783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * name will be returned to a caller. If there are 00001.vcf 00002.vcf, 00003.vcf is
467783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * returned.
468783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *
469783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * There may not be any appropriate file name. If there are 99999 vCard files in the
470783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * storage, for example, there's no appropriate name, so this method returns
471783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * null.
472783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         */
473783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
474783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        // Count the number of digits of mFileIndexMaximum
475783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        // e.g. When mFileIndexMaximum is 99999, fileIndexDigit becomes 5, as we will count the
476783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        int fileIndexDigit = 0;
477783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        {
478783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            // Calling Math.Log10() is costly.
479783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            int tmp;
480783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            for (fileIndexDigit = 0, tmp = mFileIndexMaximum; tmp > 0;
481783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                fileIndexDigit++, tmp /= 10) {
482783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
483783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
484783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
485783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        // %s05d%s (e.g. "p00001s")
486783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final String bodyFormat = "%s%0" + fileIndexDigit + "d%s";
487783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
488783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (!ALLOW_LONG_FILE_NAME) {
489783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            final String possibleBody =
490783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    String.format(bodyFormat, mFileNamePrefix, 1, mFileNameSuffix);
491783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) {
492783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.e(LOG_TAG, "This code does not allow any long file name.");
493783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                mErrorReason = getString(R.string.fail_reason_too_long_filename,
494783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        String.format("%s.%s", possibleBody, mFileNameExtension));
495783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.w(LOG_TAG, "File name becomes too long.");
496783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                return null;
497783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
498783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
499783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
500783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) {
501783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            boolean numberIsAvailable = true;
50262152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann            final String body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
50362152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann            // Make sure that none of the extensions of mExtensionsToConsider matches. If this
50462152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann            // number is free, we'll go ahead with mFileNameExtension (which is included in
50562152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann            // mExtensionsToConsider)
506783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            for (String possibleExtension : mExtensionsToConsider) {
50762152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann                final File file = new File(destDirectory, body + "." + possibleExtension);
50862152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann                final String path = file.getAbsolutePath();
509783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                synchronized (this) {
51062152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann                    // Is this being exported right now? Skip this number
511783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    if (mReservedDestination.contains(path)) {
512783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        if (DEBUG) {
51362152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann                            Log.d(LOG_TAG, String.format("%s is already being exported.", path));
514783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        }
515783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        numberIsAvailable = false;
516783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        break;
517783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    }
518783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
519783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                if (file.exists()) {
520783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    numberIsAvailable = false;
521783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    break;
522783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
523783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
524783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (numberIsAvailable) {
52562152c3097758e7084eed393556b86bd2fd795b7Daniel Lehmann                return new File(destDirectory, body + "." + mFileNameExtension).getAbsolutePath();
526783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
527783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
528783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
529783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        Log.w(LOG_TAG, "Reached vCard number limit. Maybe there are too many vCard in the storage");
530783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mErrorReason = getString(R.string.fail_reason_too_many_vcard);
531783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        return null;
532ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    }
533f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa}
534