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