VCardService.java revision e967f7cb12e02f7c852670c315a284aed1310dc1
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
187c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport com.android.contacts.R;
197c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
20ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.app.Notification;
21ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.app.NotificationManager;
22ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.app.PendingIntent;
23f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.app.Service;
24ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.content.Context;
25f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.content.Intent;
26783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.content.res.Resources;
27fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport android.media.MediaScannerConnection;
28fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport android.media.MediaScannerConnection.MediaScannerConnectionClient;
29ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.net.Uri;
30476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Handler;
31f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.os.IBinder;
32476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Message;
33476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Messenger;
34783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.os.RemoteException;
35783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.text.TextUtils;
36f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.util.Log;
37ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.widget.RemoteViews;
38f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.widget.Toast;
39f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
40783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.io.File;
41fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport java.util.ArrayList;
427c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.HashMap;
43783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.util.HashSet;
44fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawaimport java.util.List;
457c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.Map;
46783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.util.Set;
477c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.ExecutorService;
487c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.Executors;
497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.RejectedExecutionException;
501b918e58f4a3ae8d32af83f6f69bbf2de57a94f9Daisuke Miyakawa
51f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa/**
527c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * The class responsible for handling vCard import/export requests.
537c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa *
547c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * This Service creates one ImportRequest/ExportRequest object (as Runnable) per request and push
557c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * it to {@link ExecutorService} with single thread executor. The executor handles each request
567c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * one by one, and notifies users when needed.
57f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa */
587c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// TODO: Using IntentService looks simpler than using Service + ServiceConnection though this
597c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// works fine enough. Investigate the feasibility.
60d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawapublic class VCardService extends Service {
617c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    private final static String LOG_TAG = "VCardService";
6253d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda
6353d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda    /** The tag used by vCard-related notifications. */
6453d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda    /* package */ static final String DEFAULT_NOTIFICATION_TAG = "VCardServiceProgress";
6553d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda    /**
6653d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda     * The tag used by vCard-related failure notifications.
6753d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda     * <p>
6853d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda     * Use a different tag from {@link #DEFAULT_NOTIFICATION_TAG} so that failures do not get
6953d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda     * replaced by other notifications and vice-versa.
7053d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda     */
7153d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda    /* package */ static final String FAILURE_NOTIFICATION_TAG = "VCardServiceFailure";
7253d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda
736d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa    /* package */ final static boolean DEBUG = false;
74f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
75ef41f8866e8e7d52e04907f7282adcf5f4749f25Daisuke Miyakawa    /* package */ static final int MSG_IMPORT_REQUEST = 1;
76d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa    /* package */ static final int MSG_EXPORT_REQUEST = 2;
77ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static final int MSG_CANCEL_REQUEST = 3;
78783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* package */ static final int MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION = 4;
79783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* package */ static final int MSG_SET_AVAILABLE_EXPORT_DESTINATION = 5;
80f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
81ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /**
82ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Specifies the type of operation. Used when constructing a {@link Notification}, canceling
83ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * some operation, etc.
84ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     */
85ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static final int TYPE_IMPORT = 1;
86ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static final int TYPE_EXPORT = 2;
87f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
88aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    /* package */ static final String CACHE_FILE_PREFIX = "import_tmp_";
89f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
90910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    private final Messenger mMessenger = new Messenger(new Handler() {
91476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa        @Override
92476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa        public void handleMessage(Message msg) {
93476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa            switch (msg.what) {
94ef41f8866e8e7d52e04907f7282adcf5f4749f25Daisuke Miyakawa                case MSG_IMPORT_REQUEST: {
956d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                    handleImportRequest((List<ImportRequest>)msg.obj);
96476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa                    break;
97915723665339c73c9bdebc7bf8ef56c414602c2cDaisuke Miyakawa                }
98d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa                case MSG_EXPORT_REQUEST: {
997c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa                    handleExportRequest((ExportRequest)msg.obj);
100d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa                    break;
101d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa                }
102ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                case MSG_CANCEL_REQUEST: {
103ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                    handleCancelRequest((CancelRequest)msg.obj);
10418b5190d6ed37be04d153a5d6f205076b38ac479Daisuke Miyakawa                    break;
10518b5190d6ed37be04d153a5d6f205076b38ac479Daisuke Miyakawa                }
106783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                case MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION: {
107783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    handleRequestAvailableExportDestination(msg);
108783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    break;
109783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
1107c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa                // TODO: add cancel capability for export..
111d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa                default: {
112aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa                    Log.w(LOG_TAG, "Received unknown request, ignoring it.");
113476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa                    super.hasMessages(msg.what);
114d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa                }
115476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa            }
116f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa        }
117910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    });
118f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
119fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private class CustomMediaScannerConnectionClient implements MediaScannerConnectionClient {
120fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        final MediaScannerConnection mConnection;
121fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        final String mPath;
122fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
123fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public CustomMediaScannerConnectionClient(String path) {
124fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection = new MediaScannerConnection(VCardService.this, this);
125fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mPath = path;
126fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
127fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
128fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public void start() {
129fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection.connect();
130fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
131fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
132fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        @Override
133fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public void onMediaScannerConnected() {
134fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            if (DEBUG) { Log.d(LOG_TAG, "Connected to MediaScanner. Start scanning."); }
135fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection.scanFile(mPath, null);
136fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
137fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
138fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        @Override
139fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        public void onScanCompleted(String path, Uri uri) {
140fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            if (DEBUG) { Log.d(LOG_TAG, "scan completed: " + path); }
141fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            mConnection.disconnect();
142fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            removeConnectionClient(this);
143fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
144fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    }
145fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
146ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    private NotificationManager mNotificationManager;
147ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
1487c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    // Should be single thread, as we don't want to simultaneously handle import and export
1497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    // requests.
150910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
1517c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
1527c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    private int mCurrentJobId;
153910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa
154910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    // Stores all unfinished import/export jobs which will be executed by mExecutorService.
155910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    // Key is jobId.
156910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    private final Map<Integer, ProcessorBase> mRunningJobMap =
157910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            new HashMap<Integer, ProcessorBase>();
158fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    // Stores ScannerConnectionClient objects until they finish scanning requested files.
159fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    // Uses List class for simplicity. It's not costly as we won't have multiple objects in
160fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    // almost all cases.
161fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private final List<CustomMediaScannerConnectionClient> mRemainingScannerConnections =
162fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            new ArrayList<CustomMediaScannerConnectionClient>();
163476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa
164783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* ** vCard exporter params ** */
165783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    // If true, VCardExporter is able to emits files longer than 8.3 format.
166783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private static final boolean ALLOW_LONG_FILE_NAME = false;
16753d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda
168783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mTargetDirectory;
169783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mFileNamePrefix;
170783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mFileNameSuffix;
171783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private int mFileIndexMinimum;
172783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private int mFileIndexMaximum;
173783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mFileNameExtension;
174783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private Set<String> mExtensionsToConsider;
175783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String mErrorReason;
176783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
177783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    // File names currently reserved by some export job.
178783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private final Set<String> mReservedDestination = new HashSet<String>();
179783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /* ** end of vCard exporter params ** */
180783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
181476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa    @Override
182ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    public void onCreate() {
183ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        super.onCreate();
184783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, "vCard Service is being created.");
185ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
186783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        initExporterParams();
187783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    }
188783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
189783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private void initExporterParams() {
190783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mTargetDirectory = getString(R.string.config_export_dir);
191783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileNamePrefix = getString(R.string.config_export_file_prefix);
192783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileNameSuffix = getString(R.string.config_export_file_suffix);
193783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileNameExtension = getString(R.string.config_export_file_extension);
194783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
195783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mExtensionsToConsider = new HashSet<String>();
196783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mExtensionsToConsider.add(mFileNameExtension);
197783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
198783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final String additionalExtensions =
199783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            getString(R.string.config_export_extensions_to_consider);
200783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (!TextUtils.isEmpty(additionalExtensions)) {
201783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            for (String extension : additionalExtensions.split(",")) {
202783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                String trimed = extension.trim();
203783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                if (trimed.length() > 0) {
204783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    mExtensionsToConsider.add(trimed);
205783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
206783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
207783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
208783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
209783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final Resources resources = getResources();
210783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileIndexMinimum = resources.getInteger(R.integer.config_export_file_min_index);
211783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mFileIndexMaximum = resources.getInteger(R.integer.config_export_file_max_index);
212ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    }
213ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
214ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    @Override
215476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa    public int onStartCommand(Intent intent, int flags, int id) {
216476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa        return START_STICKY;
217f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    }
218f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa
219f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    @Override
220f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    public IBinder onBind(Intent intent) {
221476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa        return mMessenger.getBinder();
222f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa    }
223aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa
224aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    @Override
225aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    public void onDestroy() {
226783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, "VCardService is being destroyed.");
227910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        cancelAllRequestsAndShutdown();
228aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa        clearCache();
229aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa        super.onDestroy();
230aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    }
231aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa
2326d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa    private synchronized void handleImportRequest(List<ImportRequest> requests) {
233783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) {
2346d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            final ArrayList<String> uris = new ArrayList<String>();
235e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton            final ArrayList<String> displayNames = new ArrayList<String>();
2366d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            for (ImportRequest request : requests) {
2376d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                uris.add(request.uri.toString());
238e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                displayNames.add(request.displayName);
2396d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            }
240783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.d(LOG_TAG,
241e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    String.format("received multiple import request (uri: %s, displayName: %s)",
242e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                            uris.toString(), displayNames.toString()));
243783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
2446d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa        final int size = requests.size();
2456d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa        for (int i = 0; i < size; i++) {
2466d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            ImportRequest request = requests.get(i);
2476d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa
2486d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            if (tryExecute(new ImportProcessor(this, request, mCurrentJobId))) {
249e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                if (!request.showImmediately) {
250e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    // Show a notification about the status
251e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    final String displayName;
252e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    final String message;
253e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    if (request.displayName != null) {
254e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        displayName = request.displayName;
255e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        message = getString(R.string.vcard_import_will_start_message, displayName);
256e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    } else {
257e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        displayName = getString(R.string.vcard_unknown_filename);
258e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        message = getString(
259e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                                R.string.vcard_import_will_start_message_with_default_name);
260e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    }
261e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton
262e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    // We just want to show notification for the first vCard.
263e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    if (i == 0) {
264e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        // TODO: Ideally we should detect the current status of import/export and
265e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        // show "started" when we can import right now and show "will start" when
266e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        // we cannot.
267e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
268e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    }
269e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton
270e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    final Notification notification = constructProgressNotification(this,
271e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                            TYPE_IMPORT, message, message, mCurrentJobId, displayName, -1, 0);
272e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                    mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG,
273e967f7cb12e02f7c852670c315a284aed1310dc1Jeff Hamilton                            mCurrentJobId, notification);
2746d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                }
2756d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                mCurrentJobId++;
2766d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            } else {
2776d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                // TODO: a little unkind to show Toast in this case, which is shown just a moment.
2786d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                // Ideally we should show some persistent something users can notice more easily.
2796d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                Toast.makeText(this, getString(R.string.vcard_import_request_rejected_message),
2806d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                        Toast.LENGTH_LONG).show();
2816d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                // A rejection means executor doesn't run any more. Exit.
2826d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                break;
2836d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            }
284ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        }
2857c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
2867c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
2877c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    private synchronized void handleExportRequest(ExportRequest request) {
288ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        if (tryExecute(new ExportProcessor(this, request, mCurrentJobId))) {
289ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            final String displayName = request.destUri.getLastPathSegment();
290ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            final String message = getString(R.string.vcard_export_will_start_message,
291ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                    displayName);
292783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
293783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            final String path = request.destUri.getEncodedPath();
294783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (DEBUG) Log.d(LOG_TAG, "Reserve the path " + path);
295783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (!mReservedDestination.add(path)) {
296783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.w(LOG_TAG,
297783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        String.format("The path %s is already reserved. Reject export request",
298783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                                path));
299783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message),
300783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        Toast.LENGTH_LONG).show();
301783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                return;
302783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
303783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
304ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            Toast.makeText(this, message, Toast.LENGTH_LONG).show();
305ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            final Notification notification =
306ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                    constructProgressNotification(this, TYPE_EXPORT, message, message,
307ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                            mCurrentJobId, displayName, -1, 0);
30853d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda            mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mCurrentJobId, notification);
309ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            mCurrentJobId++;
310ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        } else {
311ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message),
312ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                    Toast.LENGTH_LONG).show();
313ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        }
314910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    }
315910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa
316910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    /**
317ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor.
318ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @return true when successful.
319910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa     */
320ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    private synchronized boolean tryExecute(ProcessorBase processor) {
3217c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        try {
3226d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            if (DEBUG) {
3236d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                Log.d(LOG_TAG, "Executor service status: shutdown: " + mExecutorService.isShutdown()
3246d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa                        + ", terminated: " + mExecutorService.isTerminated());
3256d42e4933d42fc2805f921adf3a5e65d64e16238Daisuke Miyakawa            }
326910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            mExecutorService.execute(processor);
327910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            mRunningJobMap.put(mCurrentJobId, processor);
328ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            return true;
3297c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        } catch (RejectedExecutionException e) {
330910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            Log.w(LOG_TAG, "Failed to excetute a job.", e);
331ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            return false;
3327c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
3337c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
3347c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
335783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private synchronized void handleCancelRequest(CancelRequest request) {
336ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final int jobId = request.jobId;
337783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, String.format("Received cancel request. (id: %d)", jobId));
338ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final ProcessorBase processor = mRunningJobMap.remove(jobId);
3397c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
340ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        if (processor != null) {
341ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            processor.cancel(true);
342ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            final String description = processor.getType() == TYPE_IMPORT ?
343ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                    getString(R.string.importing_vcard_canceled_title, request.displayName) :
344ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                            getString(R.string.exporting_vcard_canceled_title, request.displayName);
345ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            final Notification notification = constructCancelNotification(this, description);
34653d4b8eef85ccdc2e750a36307492c97cfbb225cFlavio Lerda            mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, jobId, notification);
347783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (processor.getType() == TYPE_EXPORT) {
348783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                final String path =
349783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        ((ExportProcessor)processor).getRequest().destUri.getEncodedPath();
350783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.i(LOG_TAG,
351783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        String.format("Cancel reservation for the path %s if appropriate", path));
352783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                if (!mReservedDestination.remove(path)) {
353783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    Log.w(LOG_TAG, "Not reserved.");
354783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
355783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
356ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        } else {
357ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
3587c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
359fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
360f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa    }
361f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa
362783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private synchronized void handleRequestAvailableExportDestination(Message msg) {
363783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) Log.d(LOG_TAG, "Received available export destination request.");
364783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final Messenger messenger = msg.replyTo;
365783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final String path = getAppropriateDestination(mTargetDirectory);
366783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final Message message;
367783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (path != null) {
368783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            message = Message.obtain(null,
369783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    VCardService.MSG_SET_AVAILABLE_EXPORT_DESTINATION, 0, 0, path);
370783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } else {
371783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            message = Message.obtain(null,
372783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    VCardService.MSG_SET_AVAILABLE_EXPORT_DESTINATION,
373783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    R.id.dialog_fail_to_export_with_reason, 0, mErrorReason);
374783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
375783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        try {
376783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            messenger.send(message);
377783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } catch (RemoteException e) {
378783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.w(LOG_TAG, "Failed to send reply for available export destination request.", e);
379783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
380783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    }
381783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
382f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa    /**
383fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa     * Checks job list and call {@link #stopSelf()} when there's no job and no scanner connection
384fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa     * is remaining.
385fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa     * A new job (import/export) cannot be submitted any more after this call.
386f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     */
387fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private synchronized void stopServiceIfAppropriate() {
388910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        if (mRunningJobMap.size() > 0) {
389910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) {
390f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                final int jobId = entry.getKey();
391910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa                final ProcessorBase processor = entry.getValue();
392910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa                if (processor.isDone()) {
393910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa                    mRunningJobMap.remove(jobId);
394f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                } else {
395f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                    Log.i(LOG_TAG, String.format("Found unfinished job (id: %d)", jobId));
396f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                    return;
397f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa                }
398f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa            }
399f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        }
400f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa
401fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (!mRemainingScannerConnections.isEmpty()) {
402fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.i(LOG_TAG, "MediaScanner update is in progress.");
403fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            return;
404fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
405fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
406f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        Log.i(LOG_TAG, "No unfinished job. Stop this service.");
407f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        mExecutorService.shutdown();
408f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        stopSelf();
4097c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
4107c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
411fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    /* package */ synchronized void updateMediaScanner(String path) {
412fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (DEBUG) {
413fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.d(LOG_TAG, "MediaScanner is being updated: " + path);
414fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
415fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
416fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (mExecutorService.isShutdown()) {
417fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.w(LOG_TAG, "MediaScanner update is requested after executor's being shut down. " +
418fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa                    "Ignoring the update request");
419fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            return;
420fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
421fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        final CustomMediaScannerConnectionClient client =
422fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa                new CustomMediaScannerConnectionClient(path);
423fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        mRemainingScannerConnections.add(client);
424fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        client.start();
425fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    }
426fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
427fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    private synchronized void removeConnectionClient(
428fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            CustomMediaScannerConnectionClient client) {
429fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        if (DEBUG) {
430fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa            Log.d(LOG_TAG, "Removing custom MediaScannerConnectionClient.");
431fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        }
432fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        mRemainingScannerConnections.remove(client);
433fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
434fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa    }
435fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa
4367c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /* package */ synchronized void handleFinishImportNotification(
4377c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa            int jobId, boolean successful) {
438783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) {
439783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.d(LOG_TAG, String.format("Received vCard import finish notification (id: %d). "
440783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    + "Result: %b", jobId, (successful ? "success" : "failure")));
441783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
442910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        if (mRunningJobMap.remove(jobId) == null) {
4437c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa            Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
4447c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
445fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
4467c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
4477c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
4487c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /* package */ synchronized void handleFinishExportNotification(
4497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa            int jobId, boolean successful) {
450783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (DEBUG) {
451783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.d(LOG_TAG, String.format("Received vCard export finish notification (id: %d). "
452783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    + "Result: %b", jobId, (successful ? "success" : "failure")));
453783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
454783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final ProcessorBase job = mRunningJobMap.remove(jobId);
455783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (job == null) {
4567c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa            Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
457783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } else if (!(job instanceof ExportProcessor)) {
458783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Log.w(LOG_TAG,
459783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    String.format("Removed job (id: %s) isn't ExportProcessor", jobId));
460783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        } else {
461783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            final String path = ((ExportProcessor)job).getRequest().destUri.getEncodedPath();
462783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (DEBUG) Log.d(LOG_TAG, "Remove reserved path " + path);
463783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            mReservedDestination.remove(path);
4647c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
465783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
466fcc7e386cf027b1fe31fe551f365843218f6fb35Daisuke Miyakawa        stopServiceIfAppropriate();
4677c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
4687c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
4697c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /**
470910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa     * Cancels all the import/export requests and calls {@link ExecutorService#shutdown()}, which
471f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     * means this Service becomes no longer ready for import/export requests.
472f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     *
473f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa     * Mainly called from onDestroy().
4747c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa     */
475910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa    private synchronized void cancelAllRequestsAndShutdown() {
476910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) {
477910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa            entry.getValue().cancel(true);
4787c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa        }
479910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        mRunningJobMap.clear();
480f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa        mExecutorService.shutdown();
4817c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    }
4827c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa
4837c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa    /**
4847c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa     * Removes import caches stored locally.
4857c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa     */
486aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    private void clearCache() {
487910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa        for (final String fileName : fileList()) {
488aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa            if (fileName.startsWith(CACHE_FILE_PREFIX)) {
489aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa                // We don't want to keep all the caches so we remove cache files old enough.
490aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa                Log.i(LOG_TAG, "Remove a temporary file: " + fileName);
491aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa                deleteFile(fileName);
492aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa            }
493aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa        }
494aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa    }
495ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
496ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /**
497ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Constructs a {@link Notification} showing the current status of import/export.
498ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Users can cancel the process with the Notification.
499ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     *
500ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param context
501ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param type import/export
502ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param description Content of the Notification.
503ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param tickerText
504ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param jobId
505ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX").
506ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Typycally a file name.
507ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param totalCount The number of vCard entries to be imported. Used to show progress bar.
508ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * -1 lets the system show the progress bar with "indeterminate" state.
509ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param currentCount The index of current vCard. Used to show progress bar.
510ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     */
511ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static Notification constructProgressNotification(
512ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            Context context, int type, String description, String tickerText,
513ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            int jobId, String displayName, int totalCount, int currentCount) {
514ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final RemoteViews remoteViews =
515ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                new RemoteViews(context.getPackageName(),
516ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                        R.layout.status_bar_ongoing_event_progress_bar);
517ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        remoteViews.setTextViewText(R.id.status_description, description);
518ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount,
519ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                totalCount == -1);
520ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final String percentage;
521ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        if (totalCount > 0) {
522ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            percentage = context.getString(R.string.percentage,
523ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                    String.valueOf(currentCount * 100/totalCount));
524ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        } else {
525ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            percentage = "";
526ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        }
527ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        remoteViews.setTextViewText(R.id.status_progress_text, percentage);
528ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final int icon = (type == TYPE_IMPORT ? android.R.drawable.stat_sys_download :
529ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                android.R.drawable.stat_sys_upload);
530ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        remoteViews.setImageViewResource(R.id.status_icon, icon);
531ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
532ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final Notification notification = new Notification();
533ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        notification.icon = icon;
534ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        notification.tickerText = tickerText;
535ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        notification.contentView = remoteViews;
536ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        notification.flags |= Notification.FLAG_ONGOING_EVENT;
537ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
538ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't
539ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        // preserve them across multiple Notifications. PendingIntent preserves the first extras
540ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        // (when flag is not set), or update them when PendingIntent#getActivity() is called
541ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        // (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we
542ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        // expect (for each vCard import/export request).
543ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        //
544ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        // We use query parameter in Uri instead.
545ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        // Scheme and Authority is arbitorary, assuming CancelActivity never refers them.
546ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final Intent intent = new Intent(context, CancelActivity.class);
547ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        final Uri uri = (new Uri.Builder())
548ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                .scheme("invalidscheme")
549ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                .authority("invalidauthority")
550ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                .appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId))
551ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                .appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName)
552ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa                .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build();
553ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        intent.setData(uri);
554ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
555ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
556ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa        return notification;
557ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    }
558ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
559ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /**
560ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Constructs a Notification telling users the process is canceled.
561ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     *
562ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param context
563ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param description Content of the Notification
564ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     */
565ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static Notification constructCancelNotification(
566ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa            Context context, String description) {
567783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        return new Notification.Builder(context)
568783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setAutoCancel(true)
569783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setSmallIcon(android.R.drawable.stat_notify_error)
570783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setContentTitle(description)
571783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setContentText(description)
572783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0))
573783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .getNotification();
574ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    }
575ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa
576ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /**
577ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * Constructs a Notification telling users the process is finished.
578ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     *
579ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param context
580ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param description Content of the Notification
581ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     * @param intent Intent to be launched when the Notification is clicked. Can be null.
582ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa     */
583ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    /* package */ static Notification constructFinishNotification(
584783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            Context context, String title, String description, Intent intent) {
585783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        return new Notification.Builder(context)
586783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setAutoCancel(true)
587783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setSmallIcon(android.R.drawable.stat_sys_download_done)
588783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setContentTitle(title)
589783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .setContentText(description)
590e9ec5ddb542787b7b5d94d9ea59b05d1f405c5aaDaisuke Miyakawa                .setContentIntent(PendingIntent.getActivity(context, 0,
591e9ec5ddb542787b7b5d94d9ea59b05d1f405c5aaDaisuke Miyakawa                        (intent != null ? intent : new Intent()), 0))
592783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                .getNotification();
593783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    }
594783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
595783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    /**
596ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa     * Constructs a Notification telling the vCard import has failed.
597ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa     *
598ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa     * @param context
599ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa     * @param reason The reason why the import has failed. Shown in description field.
600ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa     */
601ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa    /* package */ static Notification constructImportFailureNotification(
602ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa            Context context, String reason) {
603ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa        return new Notification.Builder(context)
604ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa                .setAutoCancel(true)
605ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa                .setSmallIcon(android.R.drawable.stat_notify_error)
606ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa                .setContentTitle(context.getString(R.string.vcard_import_failed))
607ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa                .setContentText(reason)
608ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa                .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0))
609ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa                .getNotification();
610ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa    }
611ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa
612ddf4270c7c09562dc4eff6f66940ceb89ac8995fDaisuke Miyakawa    /**
613783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     * Returns an appropriate file name for vCard export. Returns null when impossible.
614783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     *
615783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     * @return destination path for a vCard file to be exported. null on error and mErrorReason
616783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     * is correctly set.
617783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa     */
618783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa    private String getAppropriateDestination(final String destDirectory) {
619783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        /*
620783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * Here, file names have 5 parts: directory, prefix, index, suffix, and extension.
621783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * e.g. "/mnt/sdcard/prfx00001sfx.vcf" -> "/mnt/sdcard", "prfx", "00001", "sfx", and ".vcf"
622783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *      (In default, prefix and suffix is empty, so usually the destination would be
623783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *       /mnt/sdcard/00001.vcf.)
624783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *
625783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * This method increments "index" part from 1 to maximum, and checks whether any file name
626783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * following naming rule is available. If there's no file named /mnt/sdcard/00001.vcf, the
627783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * name will be returned to a caller. If there are 00001.vcf 00002.vcf, 00003.vcf is
628783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * returned.
629783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         *
630783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * There may not be any appropriate file name. If there are 99999 vCard files in the
631783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * storage, for example, there's no appropriate name, so this method returns
632783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         * null.
633783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa         */
634783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
635783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        // Count the number of digits of mFileIndexMaximum
636783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        // e.g. When mFileIndexMaximum is 99999, fileIndexDigit becomes 5, as we will count the
637783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        int fileIndexDigit = 0;
638783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        {
639783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            // Calling Math.Log10() is costly.
640783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            int tmp;
641783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            for (fileIndexDigit = 0, tmp = mFileIndexMaximum; tmp > 0;
642783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                fileIndexDigit++, tmp /= 10) {
643783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
644783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
645783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
646783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        // %s05d%s (e.g. "p00001s")
647783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        final String bodyFormat = "%s%0" + fileIndexDigit + "d%s";
648783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
649783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        if (!ALLOW_LONG_FILE_NAME) {
650783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            final String possibleBody =
651783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    String.format(bodyFormat, mFileNamePrefix, 1, mFileNameSuffix);
652783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) {
653783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.e(LOG_TAG, "This code does not allow any long file name.");
654783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                mErrorReason = getString(R.string.fail_reason_too_long_filename,
655783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        String.format("%s.%s", possibleBody, mFileNameExtension));
656783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                Log.w(LOG_TAG, "File name becomes too long.");
657783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                return null;
658783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
659783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
660783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
661783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) {
662783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            boolean numberIsAvailable = true;
663783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            String body = null;
664783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            for (String possibleExtension : mExtensionsToConsider) {
665783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
666783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                final String path =
667783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        String.format("%s/%s.%s", destDirectory, body, possibleExtension);
668783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                synchronized (this) {
669783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    if (mReservedDestination.contains(path)) {
670783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        if (DEBUG) {
671783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                            Log.d(LOG_TAG, String.format("The path %s is reserved.", path));
672783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        }
673783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        numberIsAvailable = false;
674783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                        break;
675783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    }
676783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
677783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                final File file = new File(path);
678783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                if (file.exists()) {
679783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    numberIsAvailable = false;
680783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                    break;
681783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                }
682783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
683783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            if (numberIsAvailable) {
684783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa                return String.format("%s/%s.%s", destDirectory, body, mFileNameExtension);
685783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa            }
686783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        }
687783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa
688783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        Log.w(LOG_TAG, "Reached vCard number limit. Maybe there are too many vCard in the storage");
689783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        mErrorReason = getString(R.string.fail_reason_too_many_vcard);
690783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa        return null;
691ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa    }
692f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa}
693