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