blob: b52839bb82a8426297bb0027b2f17beeca82c27f [file] [log] [blame]
/*
* 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;
}
}