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