diff options
Diffstat (limited to 'src/com/android/dialer/calllog/DefaultVoicemailNotifier.java')
| -rw-r--r-- | src/com/android/dialer/calllog/DefaultVoicemailNotifier.java | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java new file mode 100644 index 000000000..0f6fe3b08 --- /dev/null +++ b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.calllog; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.database.Cursor; +import android.net.Uri; +import android.provider.CallLog.Calls; +import android.provider.ContactsContract.PhoneLookup; +import android.text.TextUtils; +import android.util.Log; + +import com.android.common.io.MoreCloseables; +import com.android.dialer.CallDetailActivity; +import com.android.contacts.R; +import com.google.common.collect.Maps; + +import java.util.Map; + +/** + * Implementation of {@link VoicemailNotifier} that shows a notification in the + * status bar. + */ +public class DefaultVoicemailNotifier implements VoicemailNotifier { + public static final String TAG = "DefaultVoicemailNotifier"; + + /** The tag used to identify notifications from this class. */ + private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; + /** The identifier of the notification of new voicemails. */ + private static final int NOTIFICATION_ID = 1; + + /** The singleton instance of {@link DefaultVoicemailNotifier}. */ + private static DefaultVoicemailNotifier sInstance; + + private final Context mContext; + private final NotificationManager mNotificationManager; + private final NewCallsQuery mNewCallsQuery; + private final NameLookupQuery mNameLookupQuery; + private final PhoneNumberHelper mPhoneNumberHelper; + + /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */ + public static synchronized DefaultVoicemailNotifier getInstance(Context context) { + if (sInstance == null) { + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + ContentResolver contentResolver = context.getContentResolver(); + sInstance = new DefaultVoicemailNotifier(context, notificationManager, + createNewCallsQuery(contentResolver), + createNameLookupQuery(contentResolver), + createPhoneNumberHelper(context)); + } + return sInstance; + } + + private DefaultVoicemailNotifier(Context context, + NotificationManager notificationManager, NewCallsQuery newCallsQuery, + NameLookupQuery nameLookupQuery, PhoneNumberHelper phoneNumberHelper) { + mContext = context; + mNotificationManager = notificationManager; + mNewCallsQuery = newCallsQuery; + mNameLookupQuery = nameLookupQuery; + mPhoneNumberHelper = phoneNumberHelper; + } + + /** Updates the notification and notifies of the call with the given URI. */ + @Override + public void updateNotification(Uri newCallUri) { + // Lookup the list of new voicemails to include in the notification. + // TODO: Move this into a service, to avoid holding the receiver up. + final NewCall[] newCalls = mNewCallsQuery.query(); + + if (newCalls == null) { + // Query failed, just return. + return; + } + + if (newCalls.length == 0) { + // No voicemails to notify about: clear the notification. + clearNotification(); + return; + } + + Resources resources = mContext.getResources(); + + // This represents a list of names to include in the notification. + String callers = null; + + // Maps each number into a name: if a number is in the map, it has already left a more + // recent voicemail. + final Map<String, String> names = Maps.newHashMap(); + + // Determine the call corresponding to the new voicemail we have to notify about. + NewCall callToNotify = null; + + // Iterate over the new voicemails to determine all the information above. + for (NewCall newCall : newCalls) { + // Check if we already know the name associated with this number. + String name = names.get(newCall.number); + if (name == null) { + // Look it up in the database. + name = mNameLookupQuery.query(newCall.number); + // If we cannot lookup the contact, use the number instead. + if (name == null) { + name = mPhoneNumberHelper.getDisplayNumber(newCall.number, "").toString(); + if (TextUtils.isEmpty(name)) { + name = newCall.number; + } + } + names.put(newCall.number, name); + // This is a new caller. Add it to the back of the list of callers. + if (TextUtils.isEmpty(callers)) { + callers = name; + } else { + callers = resources.getString( + R.string.notification_voicemail_callers_list, callers, name); + } + } + // Check if this is the new call we need to notify about. + if (newCallUri != null && newCallUri.equals(newCall.voicemailUri)) { + callToNotify = newCall; + } + } + + if (newCallUri != null && callToNotify == null) { + Log.e(TAG, "The new call could not be found in the call log: " + newCallUri); + } + + // Determine the title of the notification and the icon for it. + final String title = resources.getQuantityString( + R.plurals.notification_voicemail_title, newCalls.length, newCalls.length); + // TODO: Use the photo of contact if all calls are from the same person. + final int icon = android.R.drawable.stat_notify_voicemail; + + Notification.Builder notificationBuilder = new Notification.Builder(mContext) + .setSmallIcon(icon) + .setContentTitle(title) + .setContentText(callers) + .setDefaults(callToNotify != null ? Notification.DEFAULT_ALL : 0) + .setDeleteIntent(createMarkNewVoicemailsAsOldIntent()) + .setAutoCancel(true); + + // Determine the intent to fire when the notification is clicked on. + final Intent contentIntent; + if (newCalls.length == 1) { + // Open the voicemail directly. + contentIntent = new Intent(mContext, CallDetailActivity.class); + contentIntent.setData(newCalls[0].callsUri); + contentIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, + newCalls[0].voicemailUri); + Intent playIntent = new Intent(mContext, CallDetailActivity.class); + playIntent.setData(newCalls[0].callsUri); + playIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, + newCalls[0].voicemailUri); + playIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, true); + playIntent.putExtra(CallDetailActivity.EXTRA_FROM_NOTIFICATION, true); + notificationBuilder.addAction(R.drawable.ic_play_holo_dark, + resources.getString(R.string.notification_action_voicemail_play), + PendingIntent.getActivity(mContext, 0, playIntent, 0)); + } else { + // Open the call log. + contentIntent = new Intent(Intent.ACTION_VIEW, Calls.CONTENT_URI); + } + notificationBuilder.setContentIntent( + PendingIntent.getActivity(mContext, 0, contentIntent, 0)); + + // The text to show in the ticker, describing the new event. + if (callToNotify != null) { + notificationBuilder.setTicker(resources.getString( + R.string.notification_new_voicemail_ticker, names.get(callToNotify.number))); + } + + mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notificationBuilder.build()); + } + + /** Creates a pending intent that marks all new voicemails as old. */ + private PendingIntent createMarkNewVoicemailsAsOldIntent() { + Intent intent = new Intent(mContext, CallLogNotificationsService.class); + intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); + return PendingIntent.getService(mContext, 0, intent, 0); + } + + @Override + public void clearNotification() { + mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); + } + + /** Information about a new voicemail. */ + private static final class NewCall { + public final Uri callsUri; + public final Uri voicemailUri; + public final String number; + + public NewCall(Uri callsUri, Uri voicemailUri, String number) { + this.callsUri = callsUri; + this.voicemailUri = voicemailUri; + this.number = number; + } + } + + /** Allows determining the new calls for which a notification should be generated. */ + public interface NewCallsQuery { + /** + * Returns the new calls for which a notification should be generated. + */ + public NewCall[] query(); + } + + /** Create a new instance of {@link NewCallsQuery}. */ + public static NewCallsQuery createNewCallsQuery(ContentResolver contentResolver) { + return new DefaultNewCallsQuery(contentResolver); + } + + /** + * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to + * notify about in the call log. + */ + private static final class DefaultNewCallsQuery implements NewCallsQuery { + private static final String[] PROJECTION = { + Calls._ID, Calls.NUMBER, Calls.VOICEMAIL_URI + }; + private static final int ID_COLUMN_INDEX = 0; + private static final int NUMBER_COLUMN_INDEX = 1; + private static final int VOICEMAIL_URI_COLUMN_INDEX = 2; + + private final ContentResolver mContentResolver; + + private DefaultNewCallsQuery(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + + @Override + public NewCall[] query() { + final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE); + final String[] selectionArgs = new String[]{ Integer.toString(Calls.VOICEMAIL_TYPE) }; + Cursor cursor = null; + try { + cursor = mContentResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, PROJECTION, + selection, selectionArgs, Calls.DEFAULT_SORT_ORDER); + if (cursor == null) { + return null; + } + NewCall[] newCalls = new NewCall[cursor.getCount()]; + while (cursor.moveToNext()) { + newCalls[cursor.getPosition()] = createNewCallsFromCursor(cursor); + } + return newCalls; + } finally { + MoreCloseables.closeQuietly(cursor); + } + } + + /** Returns an instance of {@link NewCall} created by using the values of the cursor. */ + private NewCall createNewCallsFromCursor(Cursor cursor) { + String voicemailUriString = cursor.getString(VOICEMAIL_URI_COLUMN_INDEX); + Uri callsUri = ContentUris.withAppendedId( + Calls.CONTENT_URI_WITH_VOICEMAIL, cursor.getLong(ID_COLUMN_INDEX)); + Uri voicemailUri = voicemailUriString == null ? null : Uri.parse(voicemailUriString); + return new NewCall(callsUri, voicemailUri, cursor.getString(NUMBER_COLUMN_INDEX)); + } + } + + /** Allows determining the name associated with a given phone number. */ + public interface NameLookupQuery { + /** + * Returns the name associated with the given number in the contacts database, or null if + * the number does not correspond to any of the contacts. + * <p> + * If there are multiple contacts with the same phone number, it will return the name of one + * of the matching contacts. + */ + public String query(String number); + } + + /** Create a new instance of {@link NameLookupQuery}. */ + public static NameLookupQuery createNameLookupQuery(ContentResolver contentResolver) { + return new DefaultNameLookupQuery(contentResolver); + } + + /** + * Default implementation of {@link NameLookupQuery} that looks up the name of a contact in the + * contacts database. + */ + private static final class DefaultNameLookupQuery implements NameLookupQuery { + private static final String[] PROJECTION = { PhoneLookup.DISPLAY_NAME }; + private static final int DISPLAY_NAME_COLUMN_INDEX = 0; + + private final ContentResolver mContentResolver; + + private DefaultNameLookupQuery(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + + @Override + public String query(String number) { + Cursor cursor = null; + try { + cursor = mContentResolver.query( + Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)), + PROJECTION, null, null, null); + if (cursor == null || !cursor.moveToFirst()) return null; + return cursor.getString(DISPLAY_NAME_COLUMN_INDEX); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + + /** + * Create a new PhoneNumberHelper. + * <p> + * This will cause some Disk I/O, at least the first time it is created, so it should not be + * called from the main thread. + */ + public static PhoneNumberHelper createPhoneNumberHelper(Context context) { + return new PhoneNumberHelper(context.getResources()); + } +} |
