VCardService.java revision 783a09a8770f4322a45cee456adefbbc71218ece
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; 27ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.net.Uri; 28476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Handler; 29f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.os.IBinder; 30476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Message; 31476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Messenger; 32783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.os.RemoteException; 33783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport android.text.TextUtils; 34f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.util.Log; 35ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.widget.RemoteViews; 36f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.widget.Toast; 37f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 38783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.io.File; 397c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.HashMap; 40783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.util.HashSet; 417c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.Map; 42783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawaimport java.util.Set; 437c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.ExecutorService; 447c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.Executors; 457c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.RejectedExecutionException; 461b918e58f4a3ae8d32af83f6f69bbf2de57a94f9Daisuke Miyakawa 47f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa/** 487c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * The class responsible for handling vCard import/export requests. 497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * 507c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * This Service creates one ImportRequest/ExportRequest object (as Runnable) per request and push 517c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * it to {@link ExecutorService} with single thread executor. The executor handles each request 527c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * one by one, and notifies users when needed. 53f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa */ 547c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// TODO: Using IntentService looks simpler than using Service + ServiceConnection though this 557c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// works fine enough. Investigate the feasibility. 56d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawapublic class VCardService extends Service { 577c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private final static String LOG_TAG = "VCardService"; 58783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa /* package */ final static boolean DEBUG = true; 59f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 60ef41f8866e8e7d52e04907f7282adcf5f4749f25Daisuke Miyakawa /* package */ static final int MSG_IMPORT_REQUEST = 1; 61d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa /* package */ static final int MSG_EXPORT_REQUEST = 2; 62ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static final int MSG_CANCEL_REQUEST = 3; 63783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa /* package */ static final int MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION = 4; 64783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa /* package */ static final int MSG_SET_AVAILABLE_EXPORT_DESTINATION = 5; 65f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 66ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 67ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Specifies the type of operation. Used when constructing a {@link Notification}, canceling 68ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * some operation, etc. 69ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 70ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static final int TYPE_IMPORT = 1; 71ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static final int TYPE_EXPORT = 2; 72f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 73aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa /* package */ static final String CACHE_FILE_PREFIX = "import_tmp_"; 74f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 75910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private final Messenger mMessenger = new Messenger(new Handler() { 76476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa @Override 77476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa public void handleMessage(Message msg) { 78476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa switch (msg.what) { 79ef41f8866e8e7d52e04907f7282adcf5f4749f25Daisuke Miyakawa case MSG_IMPORT_REQUEST: { 807c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa handleImportRequest((ImportRequest)msg.obj); 81476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa break; 82915723665339c73c9bdebc7bf8ef56c414602c2cDaisuke Miyakawa } 83d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa case MSG_EXPORT_REQUEST: { 847c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa handleExportRequest((ExportRequest)msg.obj); 85d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa break; 86d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa } 87ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa case MSG_CANCEL_REQUEST: { 88ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa handleCancelRequest((CancelRequest)msg.obj); 8918b5190d6ed37be04d153a5d6f205076b38ac479Daisuke Miyakawa break; 9018b5190d6ed37be04d153a5d6f205076b38ac479Daisuke Miyakawa } 91783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa case MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION: { 92783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa handleRequestAvailableExportDestination(msg); 93783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa break; 94783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 957c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa // TODO: add cancel capability for export.. 96d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa default: { 97aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa Log.w(LOG_TAG, "Received unknown request, ignoring it."); 98476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa super.hasMessages(msg.what); 99d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa } 100476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa } 101f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa } 102910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa }); 103f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 104ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa private NotificationManager mNotificationManager; 105ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 1067c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa // Should be single thread, as we don't want to simultaneously handle import and export 1077c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa // requests. 108910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 1097c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 1107c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private int mCurrentJobId; 111910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa 112910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa // Stores all unfinished import/export jobs which will be executed by mExecutorService. 113910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa // Key is jobId. 114910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private final Map<Integer, ProcessorBase> mRunningJobMap = 115910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa new HashMap<Integer, ProcessorBase>(); 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; 120783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private String mTargetDirectory; 121783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private String mFileNamePrefix; 122783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private String mFileNameSuffix; 123783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private int mFileIndexMinimum; 124783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private int mFileIndexMaximum; 125783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private String mFileNameExtension; 126783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private Set<String> mExtensionsToConsider; 127783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private String mErrorReason; 128783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 129783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa // File names currently reserved by some export job. 130783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private final Set<String> mReservedDestination = new HashSet<String>(); 131783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa /* ** end of vCard exporter params ** */ 132783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 133476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa @Override 134ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa public void onCreate() { 135ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa super.onCreate(); 136783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) Log.d(LOG_TAG, "vCard Service is being created."); 137ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); 138783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa initExporterParams(); 139783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 140783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 141783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private void initExporterParams() { 142783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mTargetDirectory = getString(R.string.config_export_dir); 143783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mFileNamePrefix = getString(R.string.config_export_file_prefix); 144783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mFileNameSuffix = getString(R.string.config_export_file_suffix); 145783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mFileNameExtension = getString(R.string.config_export_file_extension); 146783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 147783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mExtensionsToConsider = new HashSet<String>(); 148783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mExtensionsToConsider.add(mFileNameExtension); 149783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 150783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String additionalExtensions = 151783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa getString(R.string.config_export_extensions_to_consider); 152783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (!TextUtils.isEmpty(additionalExtensions)) { 153783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa for (String extension : additionalExtensions.split(",")) { 154783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String trimed = extension.trim(); 155783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (trimed.length() > 0) { 156783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mExtensionsToConsider.add(trimed); 157783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 158783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 159783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 160783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 161783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final Resources resources = getResources(); 162783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mFileIndexMinimum = resources.getInteger(R.integer.config_export_file_min_index); 163783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mFileIndexMaximum = resources.getInteger(R.integer.config_export_file_max_index); 164ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 165ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 166ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa @Override 167476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa public int onStartCommand(Intent intent, int flags, int id) { 168476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa return START_STICKY; 169f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa } 170f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 171f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa @Override 172f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa public IBinder onBind(Intent intent) { 173476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa return mMessenger.getBinder(); 174f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa } 175aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa 176aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa @Override 177aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa public void onDestroy() { 178783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) Log.d(LOG_TAG, "VCardService is being destroyed."); 179910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa cancelAllRequestsAndShutdown(); 180aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa clearCache(); 181aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa super.onDestroy(); 182aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 183aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa 1847c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private synchronized void handleImportRequest(ImportRequest request) { 185783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) { 186783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.d(LOG_TAG, 187783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String.format("received import request (uri: %s, originalUri: %s)", 188783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa request.uri, request.originalUri)); 189783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 190ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (tryExecute(new ImportProcessor(this, request, mCurrentJobId))) { 191ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String displayName = request.originalUri.getLastPathSegment(); 192ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String message = getString(R.string.vcard_import_will_start_message, 193ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa displayName); 194ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // TODO: Ideally we should detect the current status of import/export and show 195ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // "started" when we can import right now and show "will start" when we cannot. 196ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, message, Toast.LENGTH_LONG).show(); 197ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 198ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = 199ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa constructProgressNotification( 200ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa this, TYPE_IMPORT, message, message, mCurrentJobId, 201ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa displayName, -1, 0); 202ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager.notify(mCurrentJobId, notification); 203ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mCurrentJobId++; 204ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 205ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // TODO: a little unkind to show Toast in this case, which is shown just a moment. 206ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // Ideally we should show some persistent something users can notice more easily. 207ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, getString(R.string.vcard_import_request_rejected_message), 208ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.LENGTH_LONG).show(); 209ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 2107c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 2117c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 2127c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private synchronized void handleExportRequest(ExportRequest request) { 213ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (tryExecute(new ExportProcessor(this, request, mCurrentJobId))) { 214ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String displayName = request.destUri.getLastPathSegment(); 215ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String message = getString(R.string.vcard_export_will_start_message, 216ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa displayName); 217783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 218783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String path = request.destUri.getEncodedPath(); 219783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) Log.d(LOG_TAG, "Reserve the path " + path); 220783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (!mReservedDestination.add(path)) { 221783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.w(LOG_TAG, 222783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String.format("The path %s is already reserved. Reject export request", 223783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa path)); 224783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message), 225783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Toast.LENGTH_LONG).show(); 226783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa return; 227783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 228783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 229ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, message, Toast.LENGTH_LONG).show(); 230ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = 231ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa constructProgressNotification(this, TYPE_EXPORT, message, message, 232ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mCurrentJobId, displayName, -1, 0); 233ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager.notify(mCurrentJobId, notification); 234ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mCurrentJobId++; 235ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 236ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message), 237ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.LENGTH_LONG).show(); 238ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 239910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa } 240910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa 241910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa /** 242ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor. 243ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @return true when successful. 244910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa */ 245ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa private synchronized boolean tryExecute(ProcessorBase processor) { 2467c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa try { 247910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mExecutorService.execute(processor); 248910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mRunningJobMap.put(mCurrentJobId, processor); 249ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return true; 2507c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } catch (RejectedExecutionException e) { 251910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa Log.w(LOG_TAG, "Failed to excetute a job.", e); 252ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return false; 2537c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 2547c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 2557c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 256783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private synchronized void handleCancelRequest(CancelRequest request) { 257ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final int jobId = request.jobId; 258783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) Log.d(LOG_TAG, String.format("Received cancel request. (id: %d)", jobId)); 259ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final ProcessorBase processor = mRunningJobMap.remove(jobId); 2607c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 261ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (processor != null) { 262ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa processor.cancel(true); 263ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String description = processor.getType() == TYPE_IMPORT ? 264ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa getString(R.string.importing_vcard_canceled_title, request.displayName) : 265ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa getString(R.string.exporting_vcard_canceled_title, request.displayName); 266ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = constructCancelNotification(this, description); 267ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager.notify(jobId, notification); 268783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (processor.getType() == TYPE_EXPORT) { 269783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String path = 270783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa ((ExportProcessor)processor).getRequest().destUri.getEncodedPath(); 271783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.i(LOG_TAG, 272783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String.format("Cancel reservation for the path %s if appropriate", path)); 273783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (!mReservedDestination.remove(path)) { 274783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.w(LOG_TAG, "Not reserved."); 275783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 276783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 277ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 278ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 2797c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 280f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopServiceWhenNoJob(); 281f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 282f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa 283783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private synchronized void handleRequestAvailableExportDestination(Message msg) { 284783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) Log.d(LOG_TAG, "Received available export destination request."); 285783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final Messenger messenger = msg.replyTo; 286783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String path = getAppropriateDestination(mTargetDirectory); 287783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final Message message; 288783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (path != null) { 289783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa message = Message.obtain(null, 290783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa VCardService.MSG_SET_AVAILABLE_EXPORT_DESTINATION, 0, 0, path); 291783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } else { 292783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa message = Message.obtain(null, 293783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa VCardService.MSG_SET_AVAILABLE_EXPORT_DESTINATION, 294783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa R.id.dialog_fail_to_export_with_reason, 0, mErrorReason); 295783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 296783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa try { 297783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa messenger.send(message); 298783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } catch (RemoteException e) { 299783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.w(LOG_TAG, "Failed to send reply for available export destination request.", e); 300783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 301783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 302783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 303f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa /** 304f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * Checks job list and call {@link #stopSelf()} when there's no job now. 305f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * A new job cannot be submitted any more after this call. 306f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa */ 307f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa private synchronized void stopServiceWhenNoJob() { 308910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa if (mRunningJobMap.size() > 0) { 309910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) { 310f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa final int jobId = entry.getKey(); 311910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa final ProcessorBase processor = entry.getValue(); 312910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa if (processor.isDone()) { 313910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mRunningJobMap.remove(jobId); 314f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } else { 315f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa Log.i(LOG_TAG, String.format("Found unfinished job (id: %d)", jobId)); 316f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa return; 317f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 318f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 319f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 320f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa 321f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa Log.i(LOG_TAG, "No unfinished job. Stop this service."); 322f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa mExecutorService.shutdown(); 323f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopSelf(); 3247c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 3257c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 3267c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /* package */ synchronized void handleFinishImportNotification( 3277c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa int jobId, boolean successful) { 328783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) { 329783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.d(LOG_TAG, String.format("Received vCard import finish notification (id: %d). " 330783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa + "Result: %b", jobId, (successful ? "success" : "failure"))); 331783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 332910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa if (mRunningJobMap.remove(jobId) == null) { 3337c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 3347c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 335f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopServiceWhenNoJob(); 3367c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 3377c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 3387c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /* package */ synchronized void handleFinishExportNotification( 3397c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa int jobId, boolean successful) { 340783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) { 341783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.d(LOG_TAG, String.format("Received vCard export finish notification (id: %d). " 342783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa + "Result: %b", jobId, (successful ? "success" : "failure"))); 343783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 344783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final ProcessorBase job = mRunningJobMap.remove(jobId); 345783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (job == null) { 3467c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 347783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } else if (!(job instanceof ExportProcessor)) { 348783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.w(LOG_TAG, 349783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String.format("Removed job (id: %s) isn't ExportProcessor", jobId)); 350783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } else { 351783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String path = ((ExportProcessor)job).getRequest().destUri.getEncodedPath(); 352783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) Log.d(LOG_TAG, "Remove reserved path " + path); 353783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mReservedDestination.remove(path); 3547c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 355783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 356f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopServiceWhenNoJob(); 3577c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 3587c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 3597c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /** 360910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa * Cancels all the import/export requests and calls {@link ExecutorService#shutdown()}, which 361f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * means this Service becomes no longer ready for import/export requests. 362f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * 363f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * Mainly called from onDestroy(). 3647c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa */ 365910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private synchronized void cancelAllRequestsAndShutdown() { 366910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) { 367910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa entry.getValue().cancel(true); 3687c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 369910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mRunningJobMap.clear(); 370f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa mExecutorService.shutdown(); 3717c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 3727c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 3737c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /** 3747c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * Removes import caches stored locally. 3757c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa */ 376aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa private void clearCache() { 377910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa for (final String fileName : fileList()) { 378aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa if (fileName.startsWith(CACHE_FILE_PREFIX)) { 379aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa // We don't want to keep all the caches so we remove cache files old enough. 380aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa Log.i(LOG_TAG, "Remove a temporary file: " + fileName); 381aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa deleteFile(fileName); 382aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 383aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 384aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 385ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 386ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 387ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Constructs a {@link Notification} showing the current status of import/export. 388ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Users can cancel the process with the Notification. 389ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * 390ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param context 391ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param type import/export 392ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param description Content of the Notification. 393ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param tickerText 394ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param jobId 395ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX"). 396ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Typycally a file name. 397ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param totalCount The number of vCard entries to be imported. Used to show progress bar. 398ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * -1 lets the system show the progress bar with "indeterminate" state. 399ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param currentCount The index of current vCard. Used to show progress bar. 400ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 401ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static Notification constructProgressNotification( 402ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Context context, int type, String description, String tickerText, 403ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa int jobId, String displayName, int totalCount, int currentCount) { 404ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final RemoteViews remoteViews = 405ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa new RemoteViews(context.getPackageName(), 406ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa R.layout.status_bar_ongoing_event_progress_bar); 407ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setTextViewText(R.id.status_description, description); 408ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount, 409ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa totalCount == -1); 410ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String percentage; 411ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (totalCount > 0) { 412ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa percentage = context.getString(R.string.percentage, 413ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa String.valueOf(currentCount * 100/totalCount)); 414ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 415ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa percentage = ""; 416ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 417ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setTextViewText(R.id.status_progress_text, percentage); 418ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final int icon = (type == TYPE_IMPORT ? android.R.drawable.stat_sys_download : 419ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa android.R.drawable.stat_sys_upload); 420ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setImageViewResource(R.id.status_icon, icon); 421ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 422ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = new Notification(); 423ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.icon = icon; 424ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.tickerText = tickerText; 425ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.contentView = remoteViews; 426ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.flags |= Notification.FLAG_ONGOING_EVENT; 427ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 428ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't 429ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // preserve them across multiple Notifications. PendingIntent preserves the first extras 430ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // (when flag is not set), or update them when PendingIntent#getActivity() is called 431ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we 432ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // expect (for each vCard import/export request). 433ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // 434ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // We use query parameter in Uri instead. 435ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // Scheme and Authority is arbitorary, assuming CancelActivity never refers them. 436ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Intent intent = new Intent(context, CancelActivity.class); 437ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Uri uri = (new Uri.Builder()) 438ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .scheme("invalidscheme") 439ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .authority("invalidauthority") 440ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId)) 441ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName) 442ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build(); 443ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa intent.setData(uri); 444ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 445ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0); 446ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return notification; 447ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 448ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 449ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 450ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Constructs a Notification telling users the process is canceled. 451ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * 452ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param context 453ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param description Content of the Notification 454ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 455ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static Notification constructCancelNotification( 456ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Context context, String description) { 457783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa return new Notification.Builder(context) 458783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setAutoCancel(true) 459783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setSmallIcon(android.R.drawable.stat_notify_error) 460783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setContentTitle(description) 461783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setContentText(description) 462783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0)) 463783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .getNotification(); 464ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 465ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 466ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 467ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Constructs a Notification telling users the process is finished. 468ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * 469ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param context 470ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param description Content of the Notification 471ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param intent Intent to be launched when the Notification is clicked. Can be null. 472ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 473ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static Notification constructFinishNotification( 474783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Context context, String title, String description, Intent intent) { 475783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa return new Notification.Builder(context) 476783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setAutoCancel(true) 477783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setSmallIcon(android.R.drawable.stat_sys_download_done) 478783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setContentTitle(title) 479783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setContentText(description) 480783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0)) 481783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa .getNotification(); 482783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 483783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 484783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa /** 485783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * Returns an appropriate file name for vCard export. Returns null when impossible. 486783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * 487783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * @return destination path for a vCard file to be exported. null on error and mErrorReason 488783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * is correctly set. 489783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa */ 490783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa private String getAppropriateDestination(final String destDirectory) { 491783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa /* 492783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * Here, file names have 5 parts: directory, prefix, index, suffix, and extension. 493783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * e.g. "/mnt/sdcard/prfx00001sfx.vcf" -> "/mnt/sdcard", "prfx", "00001", "sfx", and ".vcf" 494783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * (In default, prefix and suffix is empty, so usually the destination would be 495783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * /mnt/sdcard/00001.vcf.) 496783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * 497783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * This method increments "index" part from 1 to maximum, and checks whether any file name 498783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * following naming rule is available. If there's no file named /mnt/sdcard/00001.vcf, the 499783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * name will be returned to a caller. If there are 00001.vcf 00002.vcf, 00003.vcf is 500783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * returned. 501783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * 502783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * There may not be any appropriate file name. If there are 99999 vCard files in the 503783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * storage, for example, there's no appropriate name, so this method returns 504783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa * null. 505783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa */ 506783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 507783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa // Count the number of digits of mFileIndexMaximum 508783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa // e.g. When mFileIndexMaximum is 99999, fileIndexDigit becomes 5, as we will count the 509783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa int fileIndexDigit = 0; 510783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa { 511783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa // Calling Math.Log10() is costly. 512783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa int tmp; 513783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa for (fileIndexDigit = 0, tmp = mFileIndexMaximum; tmp > 0; 514783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa fileIndexDigit++, tmp /= 10) { 515783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 516783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 517783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 518783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa // %s05d%s (e.g. "p00001s") 519783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String bodyFormat = "%s%0" + fileIndexDigit + "d%s"; 520783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 521783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (!ALLOW_LONG_FILE_NAME) { 522783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String possibleBody = 523783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String.format(bodyFormat, mFileNamePrefix, 1, mFileNameSuffix); 524783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) { 525783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.e(LOG_TAG, "This code does not allow any long file name."); 526783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mErrorReason = getString(R.string.fail_reason_too_long_filename, 527783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String.format("%s.%s", possibleBody, mFileNameExtension)); 528783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.w(LOG_TAG, "File name becomes too long."); 529783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa return null; 530783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 531783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 532783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 533783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) { 534783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa boolean numberIsAvailable = true; 535783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String body = null; 536783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa for (String possibleExtension : mExtensionsToConsider) { 537783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix); 538783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final String path = 539783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa String.format("%s/%s.%s", destDirectory, body, possibleExtension); 540783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa synchronized (this) { 541783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (mReservedDestination.contains(path)) { 542783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (DEBUG) { 543783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.d(LOG_TAG, String.format("The path %s is reserved.", path)); 544783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 545783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa numberIsAvailable = false; 546783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa break; 547783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 548783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 549783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa final File file = new File(path); 550783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (file.exists()) { 551783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa numberIsAvailable = false; 552783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa break; 553783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 554783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 555783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa if (numberIsAvailable) { 556783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa return String.format("%s/%s.%s", destDirectory, body, mFileNameExtension); 557783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 558783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa } 559783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa 560783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa Log.w(LOG_TAG, "Reached vCard number limit. Maybe there are too many vCard in the storage"); 561783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa mErrorReason = getString(R.string.fail_reason_too_many_vcard); 562783a09a8770f4322a45cee456adefbbc71218eceDaisuke Miyakawa return null; 563ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 564f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa} 565