blob: beabe26bce54b615293e45ecc0ad0e0eceb5b613 [file] [log] [blame]
/*
* Copyright (C) 2011 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 android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;
import com.android.contacts.R;
import com.android.contacts.util.ContactsNotificationChannelsUtil;
import com.android.vcard.VCardEntry;
import java.text.NumberFormat;
public class NotificationImportExportListener implements VCardImportExportListener,
Handler.Callback {
/** The tag used by vCard-related notifications. */
/* package */ static final String DEFAULT_NOTIFICATION_TAG = "VCardServiceProgress";
/**
* The tag used by vCard-related failure notifications.
* <p>
* Use a different tag from {@link #DEFAULT_NOTIFICATION_TAG} so that failures do not get
* replaced by other notifications and vice-versa.
*/
/* package */ static final String FAILURE_NOTIFICATION_TAG = "VCardServiceFailure";
private final NotificationManager mNotificationManager;
private final Activity mContext;
private final Handler mHandler;
public NotificationImportExportListener(Activity activity) {
mContext = activity;
mNotificationManager = (NotificationManager) activity.getSystemService(
Context.NOTIFICATION_SERVICE);
mHandler = new Handler(this);
}
@Override
public boolean handleMessage(Message msg) {
String text = (String) msg.obj;
Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
return true;
}
@Override
public Notification onImportProcessed(ImportRequest request, int jobId, int sequence) {
// Show a notification about the status
final String displayName;
final String message;
if (request.displayName != null) {
displayName = request.displayName;
message = mContext.getString(R.string.vcard_import_will_start_message, displayName);
} else {
displayName = mContext.getString(R.string.vcard_unknown_filename);
message = mContext.getString(
R.string.vcard_import_will_start_message_with_default_name);
}
// We just want to show notification for the first vCard.
if (sequence == 0) {
// TODO: Ideally we should detect the current status of import/export and
// show "started" when we can import right now and show "will start" when
// we cannot.
mHandler.obtainMessage(0, message).sendToTarget();
}
ContactsNotificationChannelsUtil.createDefaultChannel(mContext);
return constructProgressNotification(mContext, VCardService.TYPE_IMPORT, message, message,
jobId, displayName, -1, 0);
}
@Override
public Notification onImportParsed(ImportRequest request, int jobId, VCardEntry entry, int currentCount,
int totalCount) {
if (entry.isIgnorable()) {
return null;
}
final String totalCountString = String.valueOf(totalCount);
final String tickerText =
mContext.getString(R.string.progress_notifier_message,
String.valueOf(currentCount),
totalCountString,
entry.getDisplayName());
final String description = mContext.getString(R.string.importing_vcard_description,
entry.getDisplayName());
return constructProgressNotification(mContext.getApplicationContext(),
VCardService.TYPE_IMPORT, description, tickerText, jobId, request.displayName,
totalCount, currentCount);
}
@Override
public void onImportFinished(ImportRequest request, int jobId, Uri createdUri) {
final String description = mContext.getString(R.string.importing_vcard_finished_title,
request.displayName);
final Intent intent;
if (createdUri != null) {
final long rawContactId = ContentUris.parseId(createdUri);
final Uri contactUri = RawContacts.getContactLookupUri(
mContext.getContentResolver(), ContentUris.withAppendedId(
RawContacts.CONTENT_URI, rawContactId));
intent = new Intent(Intent.ACTION_VIEW, contactUri);
} else {
intent = new Intent(Intent.ACTION_VIEW);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
}
intent.setPackage(mContext.getPackageName());
final Notification notification =
NotificationImportExportListener.constructFinishNotification(mContext,
description, null, intent);
mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG,
jobId, notification);
}
@Override
public void onImportFailed(ImportRequest request) {
// TODO: a little unkind to show Toast in this case, which is shown just a moment.
// Ideally we should show some persistent something users can notice more easily.
mHandler.obtainMessage(0,
mContext.getString(R.string.vcard_import_request_rejected_message)).sendToTarget();
}
@Override
public void onImportCanceled(ImportRequest request, int jobId) {
final String description = mContext.getString(R.string.importing_vcard_canceled_title,
request.displayName);
final Notification notification =
NotificationImportExportListener.constructCancelNotification(mContext, description);
mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG,
jobId, notification);
}
@Override
public Notification onExportProcessed(ExportRequest request, int jobId) {
final String displayName = ExportVCardActivity.getOpenableUriDisplayName(mContext,
request.destUri);
final String message = mContext.getString(R.string.contacts_export_will_start_message);
mHandler.obtainMessage(0, message).sendToTarget();
ContactsNotificationChannelsUtil.createDefaultChannel(mContext);
return constructProgressNotification(mContext, VCardService.TYPE_EXPORT, message, message,
jobId, displayName, -1, 0);
}
@Override
public void onExportFailed(ExportRequest request) {
mHandler.obtainMessage(0,
mContext.getString(R.string.vcard_export_request_rejected_message)).sendToTarget();
}
@Override
public void onCancelRequest(CancelRequest request, int type) {
final String description = type == VCardService.TYPE_IMPORT ?
mContext.getString(R.string.importing_vcard_canceled_title, request.displayName) :
mContext.getString(R.string.exporting_vcard_canceled_title, request.displayName);
final Notification notification = constructCancelNotification(mContext, description);
mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, request.jobId, notification);
}
/**
* Constructs a {@link Notification} showing the current status of import/export.
* Users can cancel the process with the Notification.
*
* @param context
* @param type import/export
* @param description Content of the Notification.
* @param tickerText
* @param jobId
* @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX").
* Typycally a file name.
* @param totalCount The number of vCard entries to be imported. Used to show progress bar.
* -1 lets the system show the progress bar with "indeterminate" state.
* @param currentCount The index of current vCard. Used to show progress bar.
*/
/* package */ static Notification constructProgressNotification(
Context context, int type, String description, String tickerText,
int jobId, String displayName, int totalCount, int currentCount) {
// Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't
// preserve them across multiple Notifications. PendingIntent preserves the first extras
// (when flag is not set), or update them when PendingIntent#getActivity() is called
// (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we
// expect (for each vCard import/export request).
//
// We use query parameter in Uri instead.
// Scheme and Authority is arbitorary, assuming CancelActivity never refers them.
final Intent intent = new Intent(context, CancelActivity.class);
final Uri uri = (new Uri.Builder())
.scheme("invalidscheme")
.authority("invalidauthority")
.appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId))
.appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName)
.appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build();
intent.setData(uri);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setOngoing(true)
.setChannelId(ContactsNotificationChannelsUtil.DEFAULT_CHANNEL)
.setOnlyAlertOnce(true)
.setProgress(totalCount, currentCount, totalCount == - 1)
.setTicker(tickerText)
.setContentTitle(description)
.setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setSmallIcon(type == VCardService.TYPE_IMPORT
? android.R.drawable.stat_sys_download
: android.R.drawable.stat_sys_upload)
.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
if (totalCount > 0) {
String percentage =
NumberFormat.getPercentInstance().format((double) currentCount / totalCount);
builder.setContentText(percentage);
}
return builder.build();
}
/**
* Constructs a Notification telling users the process is canceled.
*
* @param context
* @param description Content of the Notification
*/
/* package */ static Notification constructCancelNotification(
Context context, String description) {
ContactsNotificationChannelsUtil.createDefaultChannel(context);
return new NotificationCompat.Builder(context,
ContactsNotificationChannelsUtil.DEFAULT_CHANNEL)
.setAutoCancel(true)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setContentTitle(description)
.setContentText(description)
// Launch an intent that won't resolve to anything. Restrict the intent to this
// app to make sure that no other app can steal this pending-intent b/19296918.
.setContentIntent(PendingIntent
.getActivity(context, 0, new Intent(context.getPackageName(), null), 0))
.build();
}
/**
* Constructs a Notification telling users the process is finished.
*
* @param context
* @param description Content of the Notification
* @param intent Intent to be launched when the Notification is clicked. Can be null.
*/
/* package */ static Notification constructFinishNotification(
Context context, String title, String description, Intent intent) {
return constructFinishNotificationWithFlags(context, title, description, intent, 0);
}
/**
* @param flags use FLAG_ACTIVITY_NEW_TASK to set it as new task, to get rid of cached files.
*/
/* package */ static Notification constructFinishNotificationWithFlags(
Context context, String title, String description, Intent intent, int flags) {
ContactsNotificationChannelsUtil.createDefaultChannel(context);
return new NotificationCompat.Builder(context,
ContactsNotificationChannelsUtil.DEFAULT_CHANNEL)
.setAutoCancel(true)
.setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setSmallIcon(R.drawable.quantum_ic_done_vd_theme_24)
.setContentTitle(title)
.setContentText(description)
// If no intent provided, include an intent that won't resolve to anything.
// Restrict the intent to this app to make sure that no other app can steal this
// pending-intent b/19296918.
.setContentIntent(PendingIntent.getActivity(context, 0,
(intent != null ? intent : new Intent(context.getPackageName(), null)),
flags))
.build();
}
/**
* Constructs a Notification telling the vCard import has failed.
*
* @param context
* @param reason The reason why the import has failed. Shown in description field.
*/
/* package */ static Notification constructImportFailureNotification(
Context context, String reason) {
ContactsNotificationChannelsUtil.createDefaultChannel(context);
return new NotificationCompat.Builder(context,
ContactsNotificationChannelsUtil.DEFAULT_CHANNEL)
.setAutoCancel(true)
.setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle(context.getString(R.string.vcard_import_failed))
.setContentText(reason)
// Launch an intent that won't resolve to anything. Restrict the intent to this
// app to make sure that no other app can steal this pending-intent b/19296918.
.setContentIntent(PendingIntent
.getActivity(context, 0, new Intent(context.getPackageName(), null), 0))
.build();
}
}