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