VCardService.java revision 7c819a1a434e02c54f6d216aa3b1a0d08cc93f50
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.contacts.vcard; 17 18import com.android.contacts.R; 19 20import android.app.Service; 21import android.content.Intent; 22import android.os.Handler; 23import android.os.IBinder; 24import android.os.Message; 25import android.os.Messenger; 26import android.util.Log; 27import android.widget.Toast; 28 29import java.io.File; 30import java.util.Date; 31import java.util.HashMap; 32import java.util.Map; 33import java.util.concurrent.ExecutorService; 34import java.util.concurrent.Executors; 35import java.util.concurrent.RejectedExecutionException; 36 37/** 38 * The class responsible for handling vCard import/export requests. 39 * 40 * This Service creates one ImportRequest/ExportRequest object (as Runnable) per request and push 41 * it to {@link ExecutorService} with single thread executor. The executor handles each request 42 * one by one, and notifies users when needed. 43 */ 44// TODO: Using IntentService looks simpler than using Service + ServiceConnection though this 45// works fine enough. Investigate the feasibility. 46public class VCardService extends Service { 47 private final static String LOG_TAG = "VCardService"; 48 49 /* package */ static final int MSG_IMPORT_REQUEST = 1; 50 /* package */ static final int MSG_EXPORT_REQUEST = 2; 51 /* package */ static final int MSG_CANCEL_IMPORT_REQUEST = 3; 52 53 /* package */ static final int IMPORT_NOTIFICATION_ID = 1000; 54 /* package */ static final int EXPORT_NOTIFICATION_ID = 1001; 55 56 /* package */ static final String CACHE_FILE_PREFIX = "import_tmp_"; 57 58 public class RequestHandler extends Handler { 59 @Override 60 public void handleMessage(Message msg) { 61 switch (msg.what) { 62 case MSG_IMPORT_REQUEST: { 63 handleImportRequest((ImportRequest)msg.obj); 64 break; 65 } 66 case MSG_EXPORT_REQUEST: { 67 handleExportRequest((ExportRequest)msg.obj); 68 break; 69 } 70 case MSG_CANCEL_IMPORT_REQUEST: { 71 handleCancelAllImportRequest(); 72 break; 73 } 74 // TODO: add cancel capability for export.. 75 default: { 76 Log.w(LOG_TAG, "Received unknown request, ignoring it."); 77 super.hasMessages(msg.what); 78 } 79 } 80 } 81 } 82 83 private final Handler mHandler = new RequestHandler(); 84 private final Messenger mMessenger = new Messenger(mHandler); 85 // Should be single thread, as we don't want to simultaneously handle import and export 86 // requests. 87 private ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 88 89 private int mCurrentJobId; 90 private final Map<Integer, ImportProcessor> mRunningJobMapForImport = 91 new HashMap<Integer, ImportProcessor>(); 92 private final Map<Integer, ExportProcessor> mRunningJobMapForExport = 93 new HashMap<Integer, ExportProcessor>(); 94 95 @Override 96 public int onStartCommand(Intent intent, int flags, int id) { 97 return START_STICKY; 98 } 99 100 @Override 101 public IBinder onBind(Intent intent) { 102 return mMessenger.getBinder(); 103 } 104 105 @Override 106 public void onDestroy() { 107 Log.i(LOG_TAG, "VCardService is finishing()"); 108 cancelRequestsAndshutdown(); 109 clearCache(); 110 super.onDestroy(); 111 } 112 113 private synchronized void handleImportRequest(ImportRequest request) { 114 Log.i(LOG_TAG, String.format("Received vCard import request. id: %d", mCurrentJobId)); 115 final ImportProcessor importProcessor = 116 new ImportProcessor(this, request, mCurrentJobId); 117 try { 118 mExecutorService.submit(importProcessor); 119 } catch (RejectedExecutionException e) { 120 Log.w(LOG_TAG, "vCard import request is rejected.", e); 121 // TODO: a little unkind to show Toast in this case, which is shown just a moment. 122 // Ideally we should show some persistent something users can notice more easily. 123 Toast.makeText(this, getString(R.string.vcard_import_request_rejected_message), 124 Toast.LENGTH_LONG).show(); 125 return; 126 } 127 mRunningJobMapForImport.put(mCurrentJobId, importProcessor); 128 mCurrentJobId++; 129 // TODO: Ideally we should detect the current status of import/export and show "started" 130 // when we can import right now and show "will start" when we cannot. 131 Toast.makeText(this, getString(R.string.vcard_import_will_start_message), 132 Toast.LENGTH_LONG).show(); 133 } 134 135 private synchronized void handleExportRequest(ExportRequest request) { 136 Log.i(LOG_TAG, String.format("Received vCard export request. id: %d", mCurrentJobId)); 137 final ExportProcessor exportProcessor = 138 new ExportProcessor(this, request, mCurrentJobId); 139 try { 140 mExecutorService.submit(exportProcessor); 141 } catch (RejectedExecutionException e) { 142 Log.w(LOG_TAG, "vCard export request is rejected.", e); 143 Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message), 144 Toast.LENGTH_LONG).show(); 145 return; 146 } 147 mRunningJobMapForExport.put(mCurrentJobId, exportProcessor); 148 mCurrentJobId++; 149 // See the comment in handleImportRequest() 150 Toast.makeText(this, getString(R.string.vcard_export_will_start_message), 151 Toast.LENGTH_LONG).show(); 152 } 153 154 private synchronized void handleCancelAllImportRequest() { 155 Log.i(LOG_TAG, "Received cancel import request."); 156 cancelAllImportRequest(); 157 mRunningJobMapForImport.clear(); 158 } 159 160 private void cancelAllImportRequest() { 161 for (final Map.Entry<Integer, ImportProcessor> entry : 162 mRunningJobMapForImport.entrySet()) { 163 final int jobId = entry.getKey(); 164 final ImportProcessor importProcessor = entry.getValue(); 165 importProcessor.cancel(); 166 Log.i(LOG_TAG, String.format("Canceling job %d", jobId)); 167 } 168 } 169 170 private void cancelAllExportRequest() { 171 for (final Map.Entry<Integer, ExportProcessor> entry : 172 mRunningJobMapForExport.entrySet()) { 173 final int jobId = entry.getKey(); 174 final ExportProcessor exportProcessor = entry.getValue(); 175 exportProcessor.cancel(); 176 Log.i(LOG_TAG, String.format("Canceling job %d", jobId)); 177 } 178 } 179 180 /* package */ synchronized void handleFinishImportNotification( 181 int jobId, boolean successful) { 182 Log.i(LOG_TAG, String.format("Received vCard import finish notification (id: %d). " 183 + "Result: %b", jobId, (successful ? "success" : "failure"))); 184 if (mRunningJobMapForImport.remove(jobId) == null) { 185 Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 186 } 187 } 188 189 /* package */ synchronized void handleFinishExportNotification( 190 int jobId, boolean successful) { 191 Log.i(LOG_TAG, String.format("Received vCard export finish notification (id: %d). " 192 + "Result: %b", jobId, (successful ? "success" : "failure"))); 193 if (mRunningJobMapForExport.remove(jobId) == null) { 194 Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId)); 195 } 196 } 197 198 /** 199 * Cancels all the import/export requests and call {@link ExecutorService#shutdown()}, which 200 * means this Service becomes no longer ready for import/export requests. Mainly used in 201 * onDestroy(). 202 */ 203 private synchronized void cancelRequestsAndshutdown() { 204 synchronized (this) { 205 if (mRunningJobMapForImport.size() > 0) { 206 Log.i(LOG_TAG, 207 String.format("Cancel existing all import requests (remains: ", 208 mRunningJobMapForImport.size())); 209 cancelAllImportRequest(); 210 } 211 if (mRunningJobMapForExport.size() > 0) { 212 Log.i(LOG_TAG, 213 String.format("Cancel existing all import requests (remains: ", 214 mRunningJobMapForExport.size())); 215 cancelAllExportRequest(); 216 } 217 mExecutorService.shutdown(); 218 } 219 } 220 221 /** 222 * Removes import caches stored locally. 223 */ 224 private void clearCache() { 225 Log.i(LOG_TAG, "start removing cache files if exist."); 226 final String[] fileLists = fileList(); 227 for (String fileName : fileLists) { 228 if (fileName.startsWith(CACHE_FILE_PREFIX)) { 229 // We don't want to keep all the caches so we remove cache files old enough. 230 // TODO: Ideally we should ask VCardService whether the file is being used or 231 // going to be used. 232 final Date now = new Date(); 233 final File file = getFileStreamPath(fileName); 234 Log.i(LOG_TAG, "Remove a temporary file: " + fileName); 235 deleteFile(fileName); 236 } 237 } 238 } 239} 240