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
17package com.android.contacts.vcard;
18
19import android.app.Activity;
20import android.app.Notification;
21import android.app.NotificationManager;
22import android.app.PendingIntent;
23import android.content.ContentUris;
24import android.content.Context;
25import android.content.Intent;
26import android.net.Uri;
27import android.os.Handler;
28import android.os.Message;
29import android.provider.ContactsContract.RawContacts;
30import android.widget.Toast;
31
32import com.android.contacts.R;
33import com.android.vcard.VCardEntry;
34
35public 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