blob: 36836c661760bb9439f68ce324e99e3f3bd2e8c2 [file] [log] [blame]
Chiao Chengd80c4342012-12-03 17:15:58 -08001/*
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.common.vcard;
17
18import android.accounts.Account;
19import android.content.ContentResolver;
20import android.net.Uri;
21import android.util.Log;
22
Walter Jang3a0b4832016-10-12 11:02:54 -070023import com.android.contactsbind.FeedbackHelper;
Chiao Chengd80c4342012-12-03 17:15:58 -080024import com.android.vcard.VCardEntry;
25import com.android.vcard.VCardEntryCommitter;
26import com.android.vcard.VCardEntryConstructor;
27import com.android.vcard.VCardEntryHandler;
28import com.android.vcard.VCardInterpreter;
29import com.android.vcard.VCardParser;
30import com.android.vcard.VCardParser_V21;
31import com.android.vcard.VCardParser_V30;
32import com.android.vcard.exception.VCardException;
Chiao Chengd80c4342012-12-03 17:15:58 -080033import com.android.vcard.exception.VCardNotSupportedException;
34import com.android.vcard.exception.VCardVersionException;
35
36import java.io.ByteArrayInputStream;
37import java.io.IOException;
38import java.io.InputStream;
39import java.util.ArrayList;
40import java.util.List;
41
42/**
43 * Class for processing one import request from a user. Dropped after importing requested Uri(s).
44 * {@link VCardService} will create another object when there is another import request.
45 */
46public class ImportProcessor extends ProcessorBase implements VCardEntryHandler {
47 private static final String LOG_TAG = "VCardImport";
48 private static final boolean DEBUG = VCardService.DEBUG;
49
50 private final VCardService mService;
51 private final ContentResolver mResolver;
52 private final ImportRequest mImportRequest;
53 private final int mJobId;
54 private final VCardImportExportListener mListener;
55
56 // TODO: remove and show appropriate message instead.
57 private final List<Uri> mFailedUris = new ArrayList<Uri>();
58
59 private VCardParser mVCardParser;
60
61 private volatile boolean mCanceled;
62 private volatile boolean mDone;
63
64 private int mCurrentCount = 0;
65 private int mTotalCount = 0;
66
67 public ImportProcessor(final VCardService service, final VCardImportExportListener listener,
68 final ImportRequest request, final int jobId) {
69 mService = service;
70 mResolver = mService.getContentResolver();
71 mListener = listener;
72
73 mImportRequest = request;
74 mJobId = jobId;
75 }
76
77 @Override
78 public void onStart() {
79 // do nothing
80 }
81
82 @Override
83 public void onEnd() {
84 // do nothing
85 }
86
87 @Override
88 public void onEntryCreated(VCardEntry entry) {
89 mCurrentCount++;
90 if (mListener != null) {
91 mListener.onImportParsed(mImportRequest, mJobId, entry, mCurrentCount, mTotalCount);
92 }
93 }
94
95 @Override
96 public final int getType() {
97 return VCardService.TYPE_IMPORT;
98 }
99
100 @Override
101 public void run() {
102 // ExecutorService ignores RuntimeException, so we need to show it here.
103 try {
104 runInternal();
105
106 if (isCancelled() && mListener != null) {
107 mListener.onImportCanceled(mImportRequest, mJobId);
108 }
Walter Jang3a0b4832016-10-12 11:02:54 -0700109 } catch (OutOfMemoryError|RuntimeException e) {
110 FeedbackHelper.sendFeedback(mService, LOG_TAG, "Vcard import failed", e);
Chiao Chengd80c4342012-12-03 17:15:58 -0800111 } finally {
112 synchronized (this) {
113 mDone = true;
114 }
115 }
116 }
117
118 private void runInternal() {
119 Log.i(LOG_TAG, String.format("vCard import (id: %d) has started.", mJobId));
120 final ImportRequest request = mImportRequest;
121 if (isCancelled()) {
122 Log.i(LOG_TAG, "Canceled before actually handling parameter (" + request.uri + ")");
123 return;
124 }
125 final int[] possibleVCardVersions;
126 if (request.vcardVersion == ImportVCardActivity.VCARD_VERSION_AUTO_DETECT) {
127 /**
128 * Note: this code assumes that a given Uri is able to be opened more than once,
129 * which may not be true in certain conditions.
130 */
131 possibleVCardVersions = new int[] {
132 ImportVCardActivity.VCARD_VERSION_V21,
133 ImportVCardActivity.VCARD_VERSION_V30
134 };
135 } else {
136 possibleVCardVersions = new int[] {
137 request.vcardVersion
138 };
139 }
140
141 final Uri uri = request.uri;
142 final Account account = request.account;
143 final int estimatedVCardType = request.estimatedVCardType;
144 final String estimatedCharset = request.estimatedCharset;
145 final int entryCount = request.entryCount;
146 mTotalCount += entryCount;
147
148 final VCardEntryConstructor constructor =
149 new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
150 final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
151 constructor.addEntryHandler(committer);
152 constructor.addEntryHandler(this);
153
154 InputStream is = null;
155 boolean successful = false;
156 try {
157 if (uri != null) {
158 Log.i(LOG_TAG, "start importing one vCard (Uri: " + uri + ")");
159 is = mResolver.openInputStream(uri);
160 } else if (request.data != null){
161 Log.i(LOG_TAG, "start importing one vCard (byte[])");
162 is = new ByteArrayInputStream(request.data);
163 }
164
165 if (is != null) {
166 successful = readOneVCard(is, estimatedVCardType, estimatedCharset, constructor,
167 possibleVCardVersions);
168 }
169 } catch (IOException e) {
170 successful = false;
171 } finally {
172 if (is != null) {
173 try {
174 is.close();
175 } catch (Exception e) {
176 // ignore
177 }
178 }
179 }
180
181 mService.handleFinishImportNotification(mJobId, successful);
182
183 if (successful) {
184 // TODO: successful becomes true even when cancelled. Should return more appropriate
185 // value
186 if (isCancelled()) {
187 Log.i(LOG_TAG, "vCard import has been canceled (uri: " + uri + ")");
188 // Cancel notification will be done outside this method.
189 } else {
190 Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
191 List<Uri> uris = committer.getCreatedUris();
192 if (mListener != null) {
Brian Attwell144bec22014-10-16 22:26:23 -0700193 if (uris != null && uris.size() == 1) {
Chiao Chengd80c4342012-12-03 17:15:58 -0800194 mListener.onImportFinished(mImportRequest, mJobId, uris.get(0));
195 } else {
Brian Attwell144bec22014-10-16 22:26:23 -0700196 if (uris == null || uris.size() == 0) {
197 // Not critical, but suspicious.
198 Log.w(LOG_TAG, "Created Uris is null or 0 length " +
199 "though the creation itself is successful.");
200 }
Chiao Chengd80c4342012-12-03 17:15:58 -0800201 mListener.onImportFinished(mImportRequest, mJobId, null);
202 }
203 }
204 }
205 } else {
206 Log.w(LOG_TAG, "Failed to read one vCard file: " + uri);
207 mFailedUris.add(uri);
208 }
209 }
210
211 private boolean readOneVCard(InputStream is, int vcardType, String charset,
212 final VCardInterpreter interpreter,
213 final int[] possibleVCardVersions) {
214 boolean successful = false;
215 final int length = possibleVCardVersions.length;
216 for (int i = 0; i < length; i++) {
217 final int vcardVersion = possibleVCardVersions[i];
218 try {
219 if (i > 0 && (interpreter instanceof VCardEntryConstructor)) {
220 // Let the object clean up internal temporary objects,
221 ((VCardEntryConstructor) interpreter).clear();
222 }
223
224 // We need synchronized block here,
225 // since we need to handle mCanceled and mVCardParser at once.
226 // In the worst case, a user may call cancel() just before creating
227 // mVCardParser.
228 synchronized (this) {
229 mVCardParser = (vcardVersion == ImportVCardActivity.VCARD_VERSION_V30 ?
230 new VCardParser_V30(vcardType) :
231 new VCardParser_V21(vcardType));
232 if (isCancelled()) {
233 Log.i(LOG_TAG, "ImportProcessor already recieves cancel request, so " +
234 "send cancel request to vCard parser too.");
235 mVCardParser.cancel();
236 }
237 }
238 mVCardParser.parse(is, interpreter);
239
240 successful = true;
241 break;
Walter Jang3a0b4832016-10-12 11:02:54 -0700242 } catch (IOException|VCardNotSupportedException e) {
243 // VCardNestedException (a subclass of VCardNotSupportedException) should
244 // not be thrown here. We should instead handle it
Chiao Chengd80c4342012-12-03 17:15:58 -0800245 // in the preprocessing session in ImportVCardActivity, as we don't try
246 // to detect the type of given vCard here.
247 //
248 // TODO: Handle this case appropriately, which should mean we have to have
249 // code trying to auto-detect the type of given vCard twice (both in
250 // ImportVCardActivity and ImportVCardService).
Walter Jang3a0b4832016-10-12 11:02:54 -0700251 FeedbackHelper.sendFeedback(mService, LOG_TAG, "Failed to read vcard", e);
Chiao Chengd80c4342012-12-03 17:15:58 -0800252 } catch (VCardVersionException e) {
253 if (i == length - 1) {
254 Log.e(LOG_TAG, "Appropriate version for this vCard is not found.");
255 } else {
256 // We'll try the other (v30) version.
257 }
258 } catch (VCardException e) {
259 Log.e(LOG_TAG, e.toString());
260 } finally {
261 if (is != null) {
262 try {
263 is.close();
264 } catch (IOException e) {
265 }
266 }
267 }
268 }
269
270 return successful;
271 }
272
273 @Override
274 public synchronized boolean cancel(boolean mayInterruptIfRunning) {
275 if (DEBUG) Log.d(LOG_TAG, "ImportProcessor received cancel request");
276 if (mDone || mCanceled) {
277 return false;
278 }
279 mCanceled = true;
280 synchronized (this) {
281 if (mVCardParser != null) {
282 mVCardParser.cancel();
283 }
284 }
285 return true;
286 }
287
288 @Override
289 public synchronized boolean isCancelled() {
290 return mCanceled;
291 }
292
293
294 @Override
295 public synchronized boolean isDone() {
296 return mDone;
297 }
298}