VCardService.java revision ab59660a17e896593f2a07c2e1191c2c23e3e353
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; 26ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.net.Uri; 27476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Handler; 28f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.os.IBinder; 29476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Message; 30476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawaimport android.os.Messenger; 31f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.util.Log; 32ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawaimport android.widget.RemoteViews; 33f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawaimport android.widget.Toast; 34f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 357c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.HashMap; 367c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.Map; 377c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.ExecutorService; 387c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.Executors; 397c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawaimport java.util.concurrent.RejectedExecutionException; 401b918e58f4a3ae8d32af83f6f69bbf2de57a94f9Daisuke Miyakawa 41f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa/** 427c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * The class responsible for handling vCard import/export requests. 437c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * 447c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * This Service creates one ImportRequest/ExportRequest object (as Runnable) per request and push 457c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * it to {@link ExecutorService} with single thread executor. The executor handles each request 467c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * one by one, and notifies users when needed. 47f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa */ 487c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// TODO: Using IntentService looks simpler than using Service + ServiceConnection though this 497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa// works fine enough. Investigate the feasibility. 50d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawapublic class VCardService extends Service { 517c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private final static String LOG_TAG = "VCardService"; 52f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 53ef41f8866e8e7d52e04907f7282adcf5f4749f25Daisuke Miyakawa /* package */ static final int MSG_IMPORT_REQUEST = 1; 54d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa /* package */ static final int MSG_EXPORT_REQUEST = 2; 55ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static final int MSG_CANCEL_REQUEST = 3; 56f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 57ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 58ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Specifies the type of operation. Used when constructing a {@link Notification}, canceling 59ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * some operation, etc. 60ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 61ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static final int TYPE_IMPORT = 1; 62ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static final int TYPE_EXPORT = 2; 63f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 64aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa /* package */ static final String CACHE_FILE_PREFIX = "import_tmp_"; 65f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 66910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private final Messenger mMessenger = new Messenger(new Handler() { 67476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa @Override 68476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa public void handleMessage(Message msg) { 69476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa switch (msg.what) { 70ef41f8866e8e7d52e04907f7282adcf5f4749f25Daisuke Miyakawa case MSG_IMPORT_REQUEST: { 717c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa handleImportRequest((ImportRequest)msg.obj); 72476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa break; 73915723665339c73c9bdebc7bf8ef56c414602c2cDaisuke Miyakawa } 74d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa case MSG_EXPORT_REQUEST: { 757c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa handleExportRequest((ExportRequest)msg.obj); 76d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa break; 77d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa } 78ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa case MSG_CANCEL_REQUEST: { 79ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa handleCancelRequest((CancelRequest)msg.obj); 8018b5190d6ed37be04d153a5d6f205076b38ac479Daisuke Miyakawa break; 8118b5190d6ed37be04d153a5d6f205076b38ac479Daisuke Miyakawa } 827c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa // TODO: add cancel capability for export.. 83d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa default: { 84aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa Log.w(LOG_TAG, "Received unknown request, ignoring it."); 85476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa super.hasMessages(msg.what); 86d8fb81a0024d30c027ea6ebf57d29d3ff10453fbDaisuke Miyakawa } 87476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa } 88f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa } 89910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa }); 90f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 91ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa private NotificationManager mNotificationManager; 92ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 937c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa // Should be single thread, as we don't want to simultaneously handle import and export 947c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa // requests. 95910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 967c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 977c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private int mCurrentJobId; 98910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa 99910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa // Stores all unfinished import/export jobs which will be executed by mExecutorService. 100910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa // Key is jobId. 101910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private final Map<Integer, ProcessorBase> mRunningJobMap = 102910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa new HashMap<Integer, ProcessorBase>(); 103476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa 104476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa @Override 105ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa public void onCreate() { 106ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa super.onCreate(); 107ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); 108ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 109ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 110ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa @Override 111476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa public int onStartCommand(Intent intent, int flags, int id) { 112476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa return START_STICKY; 113f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa } 114f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa 115f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa @Override 116f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa public IBinder onBind(Intent intent) { 117476004be0c4907b462b3d699671d9e1cff1a7bd7Daisuke Miyakawa return mMessenger.getBinder(); 118f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa } 119aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa 120aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa @Override 121aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa public void onDestroy() { 122910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa Log.i(LOG_TAG, "VCardService is being destroyed."); 123910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa cancelAllRequestsAndShutdown(); 124aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa clearCache(); 125aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa super.onDestroy(); 126aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 127aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa 1287c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private synchronized void handleImportRequest(ImportRequest request) { 129ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (tryExecute(new ImportProcessor(this, request, mCurrentJobId))) { 130ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String displayName = request.originalUri.getLastPathSegment(); 131ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String message = getString(R.string.vcard_import_will_start_message, 132ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa displayName); 133ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // TODO: Ideally we should detect the current status of import/export and show 134ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // "started" when we can import right now and show "will start" when we cannot. 135ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, message, Toast.LENGTH_LONG).show(); 136ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 137ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = 138ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa constructProgressNotification( 139ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa this, TYPE_IMPORT, message, message, mCurrentJobId, 140ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa displayName, -1, 0); 141ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager.notify(mCurrentJobId, notification); 142ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mCurrentJobId++; 143ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 144ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // TODO: a little unkind to show Toast in this case, which is shown just a moment. 145ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // Ideally we should show some persistent something users can notice more easily. 146ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, getString(R.string.vcard_import_request_rejected_message), 147ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.LENGTH_LONG).show(); 148ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 1497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 1507c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 1517c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa private synchronized void handleExportRequest(ExportRequest request) { 152ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (tryExecute(new ExportProcessor(this, request, mCurrentJobId))) { 153ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String displayName = request.destUri.getLastPathSegment(); 154ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String message = getString(R.string.vcard_export_will_start_message, 155ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa displayName); 156ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, message, Toast.LENGTH_LONG).show(); 157ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = 158ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa constructProgressNotification(this, TYPE_EXPORT, message, message, 159ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mCurrentJobId, displayName, -1, 0); 160ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager.notify(mCurrentJobId, notification); 161ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mCurrentJobId++; 162ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 163ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message), 164ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Toast.LENGTH_LONG).show(); 165ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 166910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa } 167910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa 168910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa /** 169ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor. 170ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @return true when successful. 171910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa */ 172ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa private synchronized boolean tryExecute(ProcessorBase processor) { 1737c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa try { 174910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mExecutorService.execute(processor); 175910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mRunningJobMap.put(mCurrentJobId, processor); 176ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return true; 1777c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } catch (RejectedExecutionException e) { 178910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa Log.w(LOG_TAG, "Failed to excetute a job.", e); 179ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return false; 1807c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 1817c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 1827c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 183ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa private void handleCancelRequest(CancelRequest request) { 184ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final int jobId = request.jobId; 185ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Log.i(LOG_TAG, String.format("Received cancel request. (id: %d)", jobId)); 186ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final ProcessorBase processor = mRunningJobMap.remove(jobId); 1877c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 188ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (processor != null) { 189ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa processor.cancel(true); 190ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String description = processor.getType() == TYPE_IMPORT ? 191ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa getString(R.string.importing_vcard_canceled_title, request.displayName) : 192ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa getString(R.string.exporting_vcard_canceled_title, request.displayName); 193ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = constructCancelNotification(this, description); 194ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa mNotificationManager.notify(jobId, notification); 195ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 196ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 1977c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 198f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopServiceWhenNoJob(); 199f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 200f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa 201f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa /** 202f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * Checks job list and call {@link #stopSelf()} when there's no job now. 203f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * A new job cannot be submitted any more after this call. 204f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa */ 205f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa private synchronized void stopServiceWhenNoJob() { 206910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa if (mRunningJobMap.size() > 0) { 207910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) { 208f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa final int jobId = entry.getKey(); 209910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa final ProcessorBase processor = entry.getValue(); 210910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa if (processor.isDone()) { 211910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mRunningJobMap.remove(jobId); 212f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } else { 213f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa Log.i(LOG_TAG, String.format("Found unfinished job (id: %d)", jobId)); 214f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa return; 215f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 216f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 217f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa } 218f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa 219f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa Log.i(LOG_TAG, "No unfinished job. Stop this service."); 220f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa mExecutorService.shutdown(); 221f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopSelf(); 2227c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 2237c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 2247c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /* package */ synchronized void handleFinishImportNotification( 2257c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa int jobId, boolean successful) { 226910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa Log.v(LOG_TAG, String.format("Received vCard import finish notification (id: %d). " 2277c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa + "Result: %b", jobId, (successful ? "success" : "failure"))); 228910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa if (mRunningJobMap.remove(jobId) == null) { 2297c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 2307c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 231f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopServiceWhenNoJob(); 2327c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 2337c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 2347c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /* package */ synchronized void handleFinishExportNotification( 2357c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa int jobId, boolean successful) { 236910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa Log.v(LOG_TAG, String.format("Received vCard export finish notification (id: %d). " 2377c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa + "Result: %b", jobId, (successful ? "success" : "failure"))); 238910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa if (mRunningJobMap.remove(jobId) == null) { 2397c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 2407c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 241f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa stopServiceWhenNoJob(); 2427c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 2437c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 2447c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /** 245910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa * Cancels all the import/export requests and calls {@link ExecutorService#shutdown()}, which 246f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * means this Service becomes no longer ready for import/export requests. 247f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * 248f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa * Mainly called from onDestroy(). 2497c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa */ 250910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa private synchronized void cancelAllRequestsAndShutdown() { 251910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) { 252910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa entry.getValue().cancel(true); 2537c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 254910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa mRunningJobMap.clear(); 255f219f6ee48c503bb2b628c3f2aeff53b15c5a947Daisuke Miyakawa mExecutorService.shutdown(); 2567c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa } 2577c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa 2587c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa /** 2597c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa * Removes import caches stored locally. 2607c819a1a434e02c54f6d216aa3b1a0d08cc93f50Daisuke Miyakawa */ 261aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa private void clearCache() { 262910d3e7854e657d20ab8c3a5a330b2a3188b1c74Daisuke Miyakawa for (final String fileName : fileList()) { 263aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa if (fileName.startsWith(CACHE_FILE_PREFIX)) { 264aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa // We don't want to keep all the caches so we remove cache files old enough. 265aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa Log.i(LOG_TAG, "Remove a temporary file: " + fileName); 266aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa deleteFile(fileName); 267aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 268aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 269aba4353bcf77ba91463cfd079feec3b6c6f59c5cDaisuke Miyakawa } 270ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 271ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 272ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Constructs a {@link Notification} showing the current status of import/export. 273ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Users can cancel the process with the Notification. 274ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * 275ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param context 276ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param type import/export 277ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param description Content of the Notification. 278ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param tickerText 279ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param jobId 280ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX"). 281ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Typycally a file name. 282ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param totalCount The number of vCard entries to be imported. Used to show progress bar. 283ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * -1 lets the system show the progress bar with "indeterminate" state. 284ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param currentCount The index of current vCard. Used to show progress bar. 285ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 286ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static Notification constructProgressNotification( 287ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Context context, int type, String description, String tickerText, 288ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa int jobId, String displayName, int totalCount, int currentCount) { 289ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final RemoteViews remoteViews = 290ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa new RemoteViews(context.getPackageName(), 291ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa R.layout.status_bar_ongoing_event_progress_bar); 292ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setTextViewText(R.id.status_description, description); 293ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount, 294ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa totalCount == -1); 295ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final String percentage; 296ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa if (totalCount > 0) { 297ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa percentage = context.getString(R.string.percentage, 298ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa String.valueOf(currentCount * 100/totalCount)); 299ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } else { 300ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa percentage = ""; 301ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 302ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setTextViewText(R.id.status_progress_text, percentage); 303ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final int icon = (type == TYPE_IMPORT ? android.R.drawable.stat_sys_download : 304ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa android.R.drawable.stat_sys_upload); 305ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa remoteViews.setImageViewResource(R.id.status_icon, icon); 306ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 307ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = new Notification(); 308ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.icon = icon; 309ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.tickerText = tickerText; 310ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.contentView = remoteViews; 311ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.flags |= Notification.FLAG_ONGOING_EVENT; 312ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 313ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't 314ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // preserve them across multiple Notifications. PendingIntent preserves the first extras 315ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // (when flag is not set), or update them when PendingIntent#getActivity() is called 316ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we 317ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // expect (for each vCard import/export request). 318ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // 319ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // We use query parameter in Uri instead. 320ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa // Scheme and Authority is arbitorary, assuming CancelActivity never refers them. 321ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Intent intent = new Intent(context, CancelActivity.class); 322ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Uri uri = (new Uri.Builder()) 323ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .scheme("invalidscheme") 324ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .authority("invalidauthority") 325ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId)) 326ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName) 327ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build(); 328ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa intent.setData(uri); 329ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 330ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0); 331ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return notification; 332ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 333ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 334ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 335ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Constructs a Notification telling users the process is canceled. 336ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * 337ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param context 338ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param description Content of the Notification 339ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 340ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static Notification constructCancelNotification( 341ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Context context, String description) { 342ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = new Notification(); 343ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.flags |= Notification.FLAG_AUTO_CANCEL; 344ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.icon = android.R.drawable.stat_notify_error; 345ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.setLatestEventInfo(context, description, description, 346ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa PendingIntent.getActivity(context, 0, null, 0)); 347ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return notification; 348ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 349ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa 350ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /** 351ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * Constructs a Notification telling users the process is finished. 352ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * 353ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param context 354ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param description Content of the Notification 355ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa * @param intent Intent to be launched when the Notification is clicked. Can be null. 356ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa */ 357ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa /* package */ static Notification constructFinishNotification( 358ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa Context context, String description, Intent intent) { 359ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa final Notification notification = new Notification(); 360ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.flags |= Notification.FLAG_AUTO_CANCEL; 361ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.icon = android.R.drawable.stat_sys_download_done; 362ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa notification.setLatestEventInfo(context, description, description, 363ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa PendingIntent.getActivity(context, 0, intent, 0)); 364ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa return notification; 365ab59660a17e896593f2a07c2e1191c2c23e3e353Daisuke Miyakawa } 366f21bf27c13dacec9b4ed74cba9046a64948e97fbDaisuke Miyakawa} 367