1 /* 2 * Copyright (C) 2011 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 */ 16 17 package com.android.contacts.vcard; 18 19 import com.android.contacts.R; 20 import com.android.vcard.VCardEntry; 21 22 import android.app.Activity; 23 import android.app.Notification; 24 import android.app.NotificationManager; 25 import android.app.PendingIntent; 26 import android.content.ContentUris; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.net.Uri; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.provider.ContactsContract.RawContacts; 33 import android.widget.RemoteViews; 34 import android.widget.Toast; 35 36 public class NotificationImportExportListener implements VCardImportExportListener, 37 Handler.Callback { 38 /** The tag used by vCard-related notifications. */ 39 /* package */ static final String DEFAULT_NOTIFICATION_TAG = "VCardServiceProgress"; 40 /** 41 * The tag used by vCard-related failure notifications. 42 * <p> 43 * Use a different tag from {@link #DEFAULT_NOTIFICATION_TAG} so that failures do not get 44 * replaced by other notifications and vice-versa. 45 */ 46 /* package */ static final String FAILURE_NOTIFICATION_TAG = "VCardServiceFailure"; 47 48 private final NotificationManager mNotificationManager; 49 private final Activity mContext; 50 private final Handler mHandler; 51 52 public NotificationImportExportListener(Activity activity) { 53 mContext = activity; 54 mNotificationManager = (NotificationManager) activity.getSystemService( 55 Context.NOTIFICATION_SERVICE); 56 mHandler = new Handler(this); 57 } 58 59 @Override 60 public boolean handleMessage(Message msg) { 61 String text = (String) msg.obj; 62 Toast.makeText(mContext, text, Toast.LENGTH_LONG).show(); 63 return true; 64 } 65 66 @Override 67 public void onImportProcessed(ImportRequest request, int jobId, int sequence) { 68 // Show a notification about the status 69 final String displayName; 70 final String message; 71 if (request.displayName != null) { 72 displayName = request.displayName; 73 message = mContext.getString(R.string.vcard_import_will_start_message, displayName); 74 } else { 75 displayName = mContext.getString(R.string.vcard_unknown_filename); 76 message = mContext.getString( 77 R.string.vcard_import_will_start_message_with_default_name); 78 } 79 80 // We just want to show notification for the first vCard. 81 if (sequence == 0) { 82 // TODO: Ideally we should detect the current status of import/export and 83 // show "started" when we can import right now and show "will start" when 84 // we cannot. 85 mHandler.obtainMessage(0, message).sendToTarget(); 86 } 87 88 final Notification notification = constructProgressNotification(mContext, 89 VCardService.TYPE_IMPORT, message, message, jobId, displayName, -1, 0); 90 mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, jobId, notification); 91 } 92 93 @Override 94 public void onImportParsed(ImportRequest request, int jobId, VCardEntry entry, int currentCount, 95 int totalCount) { 96 if (entry.isIgnorable()) { 97 return; 98 } 99 100 final String totalCountString = String.valueOf(totalCount); 101 final String tickerText = 102 mContext.getString(R.string.progress_notifier_message, 103 String.valueOf(currentCount), 104 totalCountString, 105 entry.getDisplayName()); 106 final String description = mContext.getString(R.string.importing_vcard_description, 107 entry.getDisplayName()); 108 109 final Notification notification = constructProgressNotification( 110 mContext.getApplicationContext(), VCardService.TYPE_IMPORT, description, tickerText, 111 jobId, request.displayName, totalCount, currentCount); 112 mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, jobId, notification); 113 } 114 115 @Override 116 public void onImportFinished(ImportRequest request, int jobId, Uri createdUri) { 117 final String description = mContext.getString(R.string.importing_vcard_finished_title, 118 request.displayName); 119 final Intent intent; 120 if (createdUri != null) { 121 final long rawContactId = ContentUris.parseId(createdUri); 122 final Uri contactUri = RawContacts.getContactLookupUri( 123 mContext.getContentResolver(), ContentUris.withAppendedId( 124 RawContacts.CONTENT_URI, rawContactId)); 125 intent = new Intent(Intent.ACTION_VIEW, contactUri); 126 } else { 127 intent = null; 128 } 129 final Notification notification = 130 NotificationImportExportListener.constructFinishNotification(mContext, 131 description, null, intent); 132 mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG, 133 jobId, notification); 134 } 135 136 @Override 137 public void onImportFailed(ImportRequest request) { 138 // TODO: a little unkind to show Toast in this case, which is shown just a moment. 139 // Ideally we should show some persistent something users can notice more easily. 140 mHandler.obtainMessage(0, 141 mContext.getString(R.string.vcard_import_request_rejected_message)).sendToTarget(); 142 } 143 144 @Override 145 public void onImportCanceled(ImportRequest request, int jobId) { 146 final String description = mContext.getString(R.string.importing_vcard_canceled_title, 147 request.displayName); 148 final Notification notification = 149 NotificationImportExportListener.constructCancelNotification(mContext, description); 150 mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG, 151 jobId, notification); 152 } 153 154 @Override 155 public void onExportProcessed(ExportRequest request, int jobId) { 156 final String displayName = request.destUri.getLastPathSegment(); 157 final String message = mContext.getString(R.string.vcard_export_will_start_message, 158 displayName); 159 160 mHandler.obtainMessage(0, message).sendToTarget(); 161 final Notification notification = 162 NotificationImportExportListener.constructProgressNotification(mContext, 163 VCardService.TYPE_EXPORT, message, message, jobId, displayName, -1, 0); 164 mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, jobId, notification); 165 } 166 167 @Override 168 public void onExportFailed(ExportRequest request) { 169 mHandler.obtainMessage(0, 170 mContext.getString(R.string.vcard_export_request_rejected_message)).sendToTarget(); 171 } 172 173 @Override 174 public void onCancelRequest(CancelRequest request, int type) { 175 final String description = type == VCardService.TYPE_IMPORT ? 176 mContext.getString(R.string.importing_vcard_canceled_title, request.displayName) : 177 mContext.getString(R.string.exporting_vcard_canceled_title, request.displayName); 178 final Notification notification = constructCancelNotification(mContext, description); 179 mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, request.jobId, notification); 180 } 181 182 /** 183 * Constructs a {@link Notification} showing the current status of import/export. 184 * Users can cancel the process with the Notification. 185 * 186 * @param context 187 * @param type import/export 188 * @param description Content of the Notification. 189 * @param tickerText 190 * @param jobId 191 * @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX"). 192 * Typycally a file name. 193 * @param totalCount The number of vCard entries to be imported. Used to show progress bar. 194 * -1 lets the system show the progress bar with "indeterminate" state. 195 * @param currentCount The index of current vCard. Used to show progress bar. 196 */ 197 /* package */ static Notification constructProgressNotification( 198 Context context, int type, String description, String tickerText, 199 int jobId, String displayName, int totalCount, int currentCount) { 200 final RemoteViews remoteViews = 201 new RemoteViews(context.getPackageName(), 202 R.layout.status_bar_ongoing_event_progress_bar); 203 remoteViews.setTextViewText(R.id.status_description, description); 204 remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount, 205 totalCount == -1); 206 final String percentage; 207 if (totalCount > 0) { 208 percentage = context.getString(R.string.percentage, 209 String.valueOf(currentCount * 100/totalCount)); 210 } else { 211 percentage = ""; 212 } 213 remoteViews.setTextViewText(R.id.status_progress_text, percentage); 214 final int icon = (type == VCardService.TYPE_IMPORT ? android.R.drawable.stat_sys_download : 215 android.R.drawable.stat_sys_upload); 216 remoteViews.setImageViewResource(R.id.status_icon, icon); 217 218 final Notification notification = new Notification(); 219 notification.icon = icon; 220 notification.tickerText = tickerText; 221 notification.contentView = remoteViews; 222 notification.flags |= Notification.FLAG_ONGOING_EVENT; 223 224 // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't 225 // preserve them across multiple Notifications. PendingIntent preserves the first extras 226 // (when flag is not set), or update them when PendingIntent#getActivity() is called 227 // (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we 228 // expect (for each vCard import/export request). 229 // 230 // We use query parameter in Uri instead. 231 // Scheme and Authority is arbitorary, assuming CancelActivity never refers them. 232 final Intent intent = new Intent(context, CancelActivity.class); 233 final Uri uri = (new Uri.Builder()) 234 .scheme("invalidscheme") 235 .authority("invalidauthority") 236 .appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId)) 237 .appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName) 238 .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build(); 239 intent.setData(uri); 240 241 notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0); 242 return notification; 243 } 244 245 /** 246 * Constructs a Notification telling users the process is canceled. 247 * 248 * @param context 249 * @param description Content of the Notification 250 */ 251 /* package */ static Notification constructCancelNotification( 252 Context context, String description) { 253 return new Notification.Builder(context) 254 .setAutoCancel(true) 255 .setSmallIcon(android.R.drawable.stat_notify_error) 256 .setContentTitle(description) 257 .setContentText(description) 258 .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0)) 259 .getNotification(); 260 } 261 262 /** 263 * Constructs a Notification telling users the process is finished. 264 * 265 * @param context 266 * @param description Content of the Notification 267 * @param intent Intent to be launched when the Notification is clicked. Can be null. 268 */ 269 /* package */ static Notification constructFinishNotification( 270 Context context, String title, String description, Intent intent) { 271 return new Notification.Builder(context) 272 .setAutoCancel(true) 273 .setSmallIcon(android.R.drawable.stat_sys_download_done) 274 .setContentTitle(title) 275 .setContentText(description) 276 .setContentIntent(PendingIntent.getActivity(context, 0, 277 (intent != null ? intent : new Intent()), 0)) 278 .getNotification(); 279 } 280 281 /** 282 * Constructs a Notification telling the vCard import has failed. 283 * 284 * @param context 285 * @param reason The reason why the import has failed. Shown in description field. 286 */ 287 /* package */ static Notification constructImportFailureNotification( 288 Context context, String reason) { 289 return new Notification.Builder(context) 290 .setAutoCancel(true) 291 .setSmallIcon(android.R.drawable.stat_notify_error) 292 .setContentTitle(context.getString(R.string.vcard_import_failed)) 293 .setContentText(reason) 294 .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0)) 295 .getNotification(); 296 } 297 298 @Override 299 public void onComplete() { 300 mContext.finish(); 301 } 302 } 303