| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.contacts.vcard; |
| |
| import com.android.contacts.R; |
| import com.android.contacts.activities.PeopleActivity; |
| import com.android.vcard.VCardComposer; |
| import com.android.vcard.VCardConfig; |
| |
| import android.app.Notification; |
| import android.app.NotificationManager; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.net.Uri; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.RawContactsEntity; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import java.io.BufferedWriter; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Writer; |
| |
| /** |
| * Class for processing one export request from a user. Dropped after exporting requested Uri(s). |
| * {@link VCardService} will create another object when there is another export request. |
| */ |
| public class ExportProcessor extends ProcessorBase { |
| private static final String LOG_TAG = "VCardExport"; |
| private static final boolean DEBUG = VCardService.DEBUG; |
| |
| private final VCardService mService; |
| private final ContentResolver mResolver; |
| private final NotificationManager mNotificationManager; |
| private final ExportRequest mExportRequest; |
| private final int mJobId; |
| |
| private volatile boolean mCanceled; |
| private volatile boolean mDone; |
| |
| public ExportProcessor(VCardService service, ExportRequest exportRequest, int jobId) { |
| mService = service; |
| mResolver = service.getContentResolver(); |
| mNotificationManager = |
| (NotificationManager)mService.getSystemService(Context.NOTIFICATION_SERVICE); |
| mExportRequest = exportRequest; |
| mJobId = jobId; |
| } |
| |
| @Override |
| public final int getType() { |
| return VCardService.TYPE_EXPORT; |
| } |
| |
| @Override |
| public void run() { |
| // ExecutorService ignores RuntimeException, so we need to show it here. |
| try { |
| runInternal(); |
| |
| if (isCancelled()) { |
| doCancelNotification(); |
| } |
| } catch (OutOfMemoryError e) { |
| Log.e(LOG_TAG, "OutOfMemoryError thrown during import", e); |
| throw e; |
| } catch (RuntimeException e) { |
| Log.e(LOG_TAG, "RuntimeException thrown during export", e); |
| throw e; |
| } finally { |
| synchronized (this) { |
| mDone = true; |
| } |
| } |
| } |
| |
| private void runInternal() { |
| if (DEBUG) Log.d(LOG_TAG, String.format("vCard export (id: %d) has started.", mJobId)); |
| final ExportRequest request = mExportRequest; |
| VCardComposer composer = null; |
| Writer writer = null; |
| boolean successful = false; |
| try { |
| if (isCancelled()) { |
| Log.i(LOG_TAG, "Export request is cancelled before handling the request"); |
| return; |
| } |
| final Uri uri = request.destUri; |
| final OutputStream outputStream; |
| try { |
| outputStream = mResolver.openOutputStream(uri); |
| } catch (FileNotFoundException e) { |
| Log.w(LOG_TAG, "FileNotFoundException thrown", e); |
| // Need concise title. |
| |
| final String errorReason = |
| mService.getString(R.string.fail_reason_could_not_open_file, |
| uri, e.getMessage()); |
| doFinishNotification(errorReason, null); |
| return; |
| } |
| |
| final String exportType = request.exportType; |
| final int vcardType; |
| if (TextUtils.isEmpty(exportType)) { |
| vcardType = VCardConfig.getVCardTypeFromString( |
| mService.getString(R.string.config_export_vcard_type)); |
| } else { |
| vcardType = VCardConfig.getVCardTypeFromString(exportType); |
| } |
| |
| composer = new VCardComposer(mService, vcardType, true); |
| |
| // for test |
| // int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC | |
| // VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES); |
| // composer = new VCardComposer(ExportVCardActivity.this, vcardType, true); |
| |
| writer = new BufferedWriter(new OutputStreamWriter(outputStream)); |
| final Uri contentUriForRawContactsEntity = RawContactsEntity.CONTENT_URI.buildUpon() |
| .appendQueryParameter(RawContactsEntity.FOR_EXPORT_ONLY, "1") |
| .build(); |
| // TODO: should provide better selection. |
| if (!composer.init(Contacts.CONTENT_URI, new String[] {Contacts._ID}, |
| null, null, |
| null, contentUriForRawContactsEntity)) { |
| final String errorReason = composer.getErrorReason(); |
| Log.e(LOG_TAG, "initialization of vCard composer failed: " + errorReason); |
| final String translatedErrorReason = |
| translateComposerError(errorReason); |
| final String title = |
| mService.getString(R.string.fail_reason_could_not_initialize_exporter, |
| translatedErrorReason); |
| doFinishNotification(title, null); |
| return; |
| } |
| |
| final int total = composer.getCount(); |
| if (total == 0) { |
| final String title = |
| mService.getString(R.string.fail_reason_no_exportable_contact); |
| doFinishNotification(title, null); |
| return; |
| } |
| |
| int current = 1; // 1-origin |
| while (!composer.isAfterLast()) { |
| if (isCancelled()) { |
| Log.i(LOG_TAG, "Export request is cancelled during composing vCard"); |
| return; |
| } |
| try { |
| writer.write(composer.createOneEntry()); |
| } catch (IOException e) { |
| final String errorReason = composer.getErrorReason(); |
| Log.e(LOG_TAG, "Failed to read a contact: " + errorReason); |
| final String translatedErrorReason = |
| translateComposerError(errorReason); |
| final String title = |
| mService.getString(R.string.fail_reason_error_occurred_during_export, |
| translatedErrorReason); |
| doFinishNotification(title, null); |
| return; |
| } |
| |
| // vCard export is quite fast (compared to import), and frequent notifications |
| // bother notification bar too much. |
| if (current % 100 == 1) { |
| doProgressNotification(uri, total, current); |
| } |
| current++; |
| } |
| Log.i(LOG_TAG, "Successfully finished exporting vCard " + request.destUri); |
| |
| if (DEBUG) { |
| Log.d(LOG_TAG, "Ask MediaScanner to scan the file: " + request.destUri.getPath()); |
| } |
| mService.updateMediaScanner(request.destUri.getPath()); |
| |
| successful = true; |
| final String filename = uri.getLastPathSegment(); |
| final String title = mService.getString(R.string.exporting_vcard_finished_title, |
| filename); |
| doFinishNotification(title, null); |
| } finally { |
| if (composer != null) { |
| composer.terminate(); |
| } |
| if (writer != null) { |
| try { |
| writer.close(); |
| } catch (IOException e) { |
| Log.w(LOG_TAG, "IOException is thrown during close(). Ignored. " + e); |
| } |
| } |
| mService.handleFinishExportNotification(mJobId, successful); |
| } |
| } |
| |
| private String translateComposerError(String errorMessage) { |
| final Resources resources = mService.getResources(); |
| if (VCardComposer.FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO.equals(errorMessage)) { |
| return resources.getString(R.string.composer_failed_to_get_database_infomation); |
| } else if (VCardComposer.FAILURE_REASON_NO_ENTRY.equals(errorMessage)) { |
| return resources.getString(R.string.composer_has_no_exportable_contact); |
| } else if (VCardComposer.FAILURE_REASON_NOT_INITIALIZED.equals(errorMessage)) { |
| return resources.getString(R.string.composer_not_initialized); |
| } else { |
| return errorMessage; |
| } |
| } |
| |
| private void doProgressNotification(Uri uri, int totalCount, int currentCount) { |
| final String displayName = uri.getLastPathSegment(); |
| final String description = |
| mService.getString(R.string.exporting_contact_list_message, displayName); |
| final String tickerText = |
| mService.getString(R.string.exporting_contact_list_title); |
| final Notification notification = |
| NotificationImportExportListener.constructProgressNotification(mService, |
| VCardService.TYPE_EXPORT, description, tickerText, mJobId, displayName, |
| totalCount, currentCount); |
| mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG, |
| mJobId, notification); |
| } |
| |
| private void doCancelNotification() { |
| if (DEBUG) Log.d(LOG_TAG, "send cancel notification"); |
| final String description = mService.getString(R.string.exporting_vcard_canceled_title, |
| mExportRequest.destUri.getLastPathSegment()); |
| final Notification notification = |
| NotificationImportExportListener.constructCancelNotification(mService, description); |
| mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG, |
| mJobId, notification); |
| } |
| |
| private void doFinishNotification(final String title, final String description) { |
| if (DEBUG) Log.d(LOG_TAG, "send finish notification: " + title + ", " + description); |
| final Intent intent = new Intent(mService, PeopleActivity.class); |
| final Notification notification = |
| NotificationImportExportListener.constructFinishNotification(mService, title, |
| description, intent); |
| mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG, |
| mJobId, notification); |
| } |
| |
| @Override |
| public synchronized boolean cancel(boolean mayInterruptIfRunning) { |
| if (DEBUG) Log.d(LOG_TAG, "received cancel request"); |
| if (mDone || mCanceled) { |
| return false; |
| } |
| mCanceled = true; |
| return true; |
| } |
| |
| @Override |
| public synchronized boolean isCancelled() { |
| return mCanceled; |
| } |
| |
| @Override |
| public synchronized boolean isDone() { |
| return mDone; |
| } |
| |
| public ExportRequest getRequest() { |
| return mExportRequest; |
| } |
| } |