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