diff options
author | Flavio Lerda <flerda@google.com> | 2011-07-26 13:38:44 +0100 |
---|---|---|
committer | Flavio Lerda <flerda@google.com> | 2011-07-26 20:21:54 +0100 |
commit | b78b7096618cd9c3c8db8e4a8e0ed684fe8b1b11 (patch) | |
tree | 0d7833523bc7890ba3fd0bb7969e35852738ae9d | |
parent | 458627bdb10df6d7b0e3fb13e72cf97f96b6358a (diff) | |
download | packages_apps_Contacts-b78b7096618cd9c3c8db8e4a8e0ed684fe8b1b11.tar.gz packages_apps_Contacts-b78b7096618cd9c3c8db8e4a8e0ed684fe8b1b11.tar.bz2 packages_apps_Contacts-b78b7096618cd9c3c8db8e4a8e0ed684fe8b1b11.zip |
Fixes for voicemail notifications.
- Handle multiple notifications.
- Clear notifications correctly.
- Show un-cleared notifications on reboot.
Bug: 4968721
Change-Id: I1bd1eda4d75371fb7ba92063d74a61de232b61d6
-rw-r--r-- | AndroidManifest.xml | 10 | ||||
-rw-r--r-- | res/values/strings.xml | 22 | ||||
-rw-r--r-- | src/com/android/contacts/calllog/CallLogNotificationsService.java | 53 | ||||
-rw-r--r-- | src/com/android/contacts/calllog/CallLogReceiver.java (renamed from src/com/android/contacts/calllog/NewVoicemailReceiver.java) | 33 | ||||
-rw-r--r-- | src/com/android/contacts/calllog/DefaultVoicemailNotifier.java | 265 | ||||
-rw-r--r-- | src/com/android/contacts/calllog/VoicemailNotifier.java | 23 |
6 files changed, 316 insertions, 90 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ff7c0890f..6856452aa 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -647,7 +647,7 @@ android:resource="@xml/social_widget_info" /> </receiver> - <receiver android:name=".calllog.NewVoicemailReceiver"> + <receiver android:name=".calllog.CallLogReceiver"> <intent-filter> <action android:name="android.intent.action.NEW_VOICEMAIL" /> <data @@ -655,6 +655,9 @@ android:host="com.android.voicemail" /> </intent-filter> + <intent-filter android:priority="100"> + <action android:name="android.intent.action.BOOT_COMPLETED"/> + </intent-filter> </receiver> <activity @@ -664,5 +667,10 @@ <action android:name="android.intent.action.APPWIDGET_PICK" /> </intent-filter> </activity> + + <service + android:name=".calllog.CallLogNotificationsService" + android:exported="false" + /> </application> </manifest> diff --git a/res/values/strings.xml b/res/values/strings.xml index 9962f0f5e..ea1d348d9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1602,13 +1602,29 @@ <!-- Button to view the updates from the current group on the group detail page [CHAR LIMIT=20] --> <string name="view_updates_from_group">View updates</string> - <!-- Title of the notification of new voicemail. --> - <string name="notification_voicemail_title">New voicemail</string> + <!-- Title of the notification of new voicemails. [CHAR LIMIT=30] --> + <plurals name="notification_voicemail_title"> + <item quantity="one">Voicemail</item> + <item quantity="other"><xliff:g id="count">%1$d</xliff:g> Voicemails</item> + </plurals> + + <!-- Used to build a list of names or phone numbers, to indicate the callers who left + voicemails. + The first argument may be one or more callers, the most recent ones. + The second argument is an additional callers. + This string is used to build a list of callers. + + [CHAR LIMIT=10] + --> + <string name="notification_voicemail_callers_list"><xliff:g id="newer_callers">%1$s</xliff:g>, <xliff:g id="older_caller">%2$s</xliff:g></string> + + <!-- Text used in the ticker to notify the user of the latest voicemail. [CHAR LIMIT=30] --> + <string name="notification_new_voicemail_ticker">New voicemail from <xliff:g id="caller">%1$s</xliff:g></string> <!-- Initial display for position of current playback, do not translate. --> <string name="voicemail_initial_time">00:05</string> - <!-- Message to show when there is an error playing back the voicemail. --> + <!-- Message to show when there is an error playing back the voicemail. [CHAR LIMIT=40] --> <string name="voicemail_playback_error">Could not play voicemail</string> <!-- The separator between the call type text and the date in the call log [CHAR LIMIT=3] --> diff --git a/src/com/android/contacts/calllog/CallLogNotificationsService.java b/src/com/android/contacts/calllog/CallLogNotificationsService.java new file mode 100644 index 000000000..eda11d6c9 --- /dev/null +++ b/src/com/android/contacts/calllog/CallLogNotificationsService.java @@ -0,0 +1,53 @@ +/* + * 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.contacts.calllog; + +import android.app.IntentService; +import android.content.Intent; +import android.util.Log; + +/** + * Provides operations for managing notifications. + * <p> + * At the moment, it only handle {@link #ACTION_MARK_NEW_CALLS_AS_OLD}, which marks all the new + * items in the call log as old; this is called when a notification is dismissed. + */ +public class CallLogNotificationsService extends IntentService { + private static final String TAG = "CallLogNotificationsService"; + + // Action to mark all the new calls as old. Invoked when the notifications need to be cleared. + public static final String ACTION_MARK_NEW_CALLS_AS_OLD = + "com.android.contacts.ACTION_MARK_NEW_CALLS_AS_OLD"; + + private CallLogQueryHandler mCallLogQueryHandler; + + public CallLogNotificationsService() { + super("CallLogNotificationsService"); + mCallLogQueryHandler = new CallLogQueryHandler(getContentResolver(), null /*listener*/); + } + + @Override + protected void onHandleIntent(Intent intent) { + if (ACTION_MARK_NEW_CALLS_AS_OLD.equals(intent.getAction())) { + mCallLogQueryHandler.markNewCallsAsOld(); + return; + } else { + Log.d(TAG, "onHandleIntent: could not handle: " + intent); + } + } + +} diff --git a/src/com/android/contacts/calllog/NewVoicemailReceiver.java b/src/com/android/contacts/calllog/CallLogReceiver.java index 0b9f2fa1e..a3ff1f2dd 100644 --- a/src/com/android/contacts/calllog/NewVoicemailReceiver.java +++ b/src/com/android/contacts/calllog/CallLogReceiver.java @@ -18,25 +18,44 @@ package com.android.contacts.calllog; import android.app.NotificationManager; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.provider.VoicemailContract; +import android.util.Log; /** - * Receiver for new voicemail notifications. + * Receiver for call log events. * <p> - * Delegates to a {@link VoicemailNotifier}. + * It is currently used to handle {@link VoicemailContract#ACTION_NEW_VOICEMAIL} and + * {@link Intent#ACTION_BOOT_COMPLETED}. */ -public class NewVoicemailReceiver extends BroadcastReceiver { +public class CallLogReceiver extends BroadcastReceiver { + private static final String TAG = "CallLogReceiver"; + + private VoicemailNotifier mNotifier; + @Override public void onReceive(Context context, Intent intent) { - getVoicemailNotifier(context).notifyNewVoicemail(intent.getData()); + if (mNotifier == null) { + mNotifier = getNotifier(context); + } + if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) { + mNotifier.notifyNewVoicemail(intent.getData()); + } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + mNotifier.updateNotification(); + } else { + Log.d(TAG, "onReceive: could not handle: " + intent); + } } - private VoicemailNotifier getVoicemailNotifier(Context context) { + private VoicemailNotifier getNotifier(Context context) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + ContentResolver contentResolver = context.getContentResolver(); return new DefaultVoicemailNotifier(context, notificationManager, - DefaultVoicemailNotifier.createVoicemailNumberQuery(context.getContentResolver()), - DefaultVoicemailNotifier.createNameLookupQuery(context.getContentResolver())); + DefaultVoicemailNotifier.createNewCallsQuery(contentResolver), + DefaultVoicemailNotifier.createNameLookupQuery(contentResolver), + DefaultVoicemailNotifier.createPhoneNumberHelper(context)); } } diff --git a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java index b2ee5ce74..bb0b0f3c1 100644 --- a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java +++ b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java @@ -16,23 +16,36 @@ package com.android.contacts.calllog; +import com.android.common.io.MoreCloseables; +import com.android.contacts.CallDetailActivity; import com.android.contacts.R; +import com.google.common.collect.Maps; 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.provider.VoicemailContract; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Map; /** - * Implementation of {@link VoicemailNotifier} that shows a notification in the status bar. + * 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. */ @@ -40,115 +53,211 @@ public class DefaultVoicemailNotifier implements VoicemailNotifier { private final Context mContext; private final NotificationManager mNotificationManager; - private final VoicemailNumberQuery mVoicemailNumberQuery; + private final NewCallsQuery mNewCallsQuery; private final NameLookupQuery mNameLookupQuery; + private final PhoneNumberHelper mPhoneNumberHelper; - public DefaultVoicemailNotifier(Context context, NotificationManager notificationManager, - VoicemailNumberQuery voicemailNumberQuery, NameLookupQuery nameLookupQuery) { + public DefaultVoicemailNotifier(Context context, + NotificationManager notificationManager, NewCallsQuery newCallsQuery, + NameLookupQuery nameLookupQuery, PhoneNumberHelper phoneNumberHelper) { mContext = context; mNotificationManager = notificationManager; - mVoicemailNumberQuery = voicemailNumberQuery; + mNewCallsQuery = newCallsQuery; mNameLookupQuery = nameLookupQuery; + mPhoneNumberHelper = phoneNumberHelper; } @Override - public void notifyNewVoicemail(Uri uri) { - // Lookup the number that left the voicemail. - String number = mVoicemailNumberQuery.query(uri); - // Lookup the name of the contact associated with this number. - String name = mNameLookupQuery.query(number); - // Show the name of the contact if available, falling back to using the number if not. - String displayName = name == null ? number : name; + public void notifyNewVoicemail(Uri newVoicemailUri) { + Log.d(TAG, "notifyNewVoicemail: " + newVoicemailUri); + updateNotification(newVoicemailUri); + } + + @Override + public void updateNotification() { + Log.d(TAG, "updateNotification"); + updateNotification(null); + } + + /** Updates the notification and notifies of the call with the given URI. */ + private 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.length == 0) { + Log.e(TAG, "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 notification = new Notification.Builder(mContext) - .setSmallIcon(android.R.drawable.stat_notify_voicemail) - .setContentTitle(mContext.getString(R.string.notification_voicemail_title)) - .setContentText(displayName) - .setDefaults(Notification.DEFAULT_ALL) + .setSmallIcon(icon) + .setContentTitle(title) + .setContentText(callers) + .setDefaults(callToNotify != null ? Notification.DEFAULT_ALL : 0) + .setDeleteIntent(createMarkNewCallsAsOld()) .setAutoCancel(true) .getNotification(); - // Open the voicemail when clicking on the notification. - notification.contentIntent = - PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW, uri), 0); + // Determine the intent to fire when the notification is clicked on. + final Intent contentIntent; + if (newCalls.length == 1) { + // Open the voicemail directly. + Log.d(TAG, "Opening voicemail directly on select"); + contentIntent = new Intent(mContext, CallDetailActivity.class); + contentIntent.setData(newCalls[0].callsUri); + contentIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, + newCalls[0].voicemailUri); + } else { + // Open the call log. + Log.d(TAG, "Opening call log on select"); + contentIntent = new Intent(Intent.ACTION_VIEW, Calls.CONTENT_URI); + } + notification.contentIntent = PendingIntent.getActivity(mContext, 0, contentIntent, 0); + + // The text to show in the ticker, describing the new event. + if (callToNotify != null) { + notification.tickerText = resources.getString( + R.string.notification_new_voicemail_ticker, names.get(callToNotify.number)); + } mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); } + /** Creates a pending intent that marks all new calls as old. */ + private PendingIntent createMarkNewCallsAsOld() { + Intent intent = new Intent(mContext, CallLogNotificationsService.class); + intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_CALLS_AS_OLD); + return PendingIntent.getService(mContext, 0, intent, 0); + } + @Override - public void clearNewVoicemailNotification() { + public void clearNotification() { mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); } - /** Allows determining the number associated with a given voicemail. */ - public interface VoicemailNumberQuery { + /** 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 number associated with a voicemail URI, or null if the URI does not actually - * correspond to a voicemail. - * - * @throws IllegalArgumentException if the given {@code uri} is not a voicemail URI. + * Returns the new calls for which a notification should be generated. */ - public String query(Uri uri); + public NewCall[] query(); } - /** Create a new instance of {@link VoicemailNumberQuery}. */ - public static VoicemailNumberQuery createVoicemailNumberQuery(ContentResolver contentResolver) { - return new DefaultVoicemailNumberQuery(contentResolver); + /** Create a new instance of {@link NewCallsQuery}. */ + public static NewCallsQuery createNewCallsQuery(ContentResolver contentResolver) { + return new DefaultNewCallsQuery(contentResolver); } /** - * Default implementation of {@link VoicemailNumberQuery} that looks up the number in the - * voicemail content provider. + * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to + * notify about in the call log. */ - private static final class DefaultVoicemailNumberQuery implements VoicemailNumberQuery { - private static final String[] PROJECTION = { VoicemailContract.Voicemails.NUMBER }; - private static final int NUMBER_COLUMN_INDEX = 0; + 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 DefaultVoicemailNumberQuery(ContentResolver contentResolver) { + private DefaultNewCallsQuery(ContentResolver contentResolver) { mContentResolver = contentResolver; } @Override - public String query(Uri uri) { - validateVoicemailUri(uri); + 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(uri, PROJECTION, null, null, null); - if (cursor.getCount() != 1) return null; - if (!cursor.moveToFirst()) return null; - return cursor.getString(NUMBER_COLUMN_INDEX); - } finally { - if (cursor != null) { - cursor.close(); + cursor = mContentResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, PROJECTION, + selection, selectionArgs, Calls.DEFAULT_SORT_ORDER); + NewCall[] newCalls = new NewCall[cursor.getCount()]; + while (cursor.moveToNext()) { + newCalls[cursor.getPosition()] = createNewCallsFromCursor(cursor); } + Log.d(TAG, "DefaultNewCallsQuery: " + newCalls.length + " new calls"); + return newCalls; + } finally { + MoreCloseables.closeQuietly(cursor); } } - /** - * Makes sure that the given URI is a valid voicemail URI. - * - * @throws IllegalArgumentException if the URI is not valid - */ - private void validateVoicemailUri(Uri uri) { - // Cannot be null. - if (uri == null) throw new IllegalArgumentException("invalid voicemail URI"); - // Must have the right schema. - if (!VoicemailContract.Voicemails.CONTENT_URI.getScheme().equals(uri.getScheme())) { - throw new IllegalArgumentException("invalid voicemail URI"); - } - // Must have the right authority. - if (!VoicemailContract.AUTHORITY.equals(uri.getAuthority())) { - throw new IllegalArgumentException("invalid voicemail URI"); - } - // Must have a valid path. - if (uri.getPath() == null) { - throw new IllegalArgumentException("invalid voicemail URI"); - } - // Must be a path within the voicemails table. - if (!uri.getPath().startsWith(VoicemailContract.Voicemails.CONTENT_URI.getPath())) { - throw new IllegalArgumentException("invalid voicemail URI"); - } + /** 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)); } } @@ -169,6 +278,10 @@ public class DefaultVoicemailNotifier implements VoicemailNotifier { 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; @@ -195,4 +308,16 @@ public class DefaultVoicemailNotifier implements VoicemailNotifier { } } } + + /** + * 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) { + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + return new PhoneNumberHelper(context.getResources(), telephonyManager.getVoiceMailNumber()); + } } diff --git a/src/com/android/contacts/calllog/VoicemailNotifier.java b/src/com/android/contacts/calllog/VoicemailNotifier.java index ba82f2148..1ac949ddf 100644 --- a/src/com/android/contacts/calllog/VoicemailNotifier.java +++ b/src/com/android/contacts/calllog/VoicemailNotifier.java @@ -22,14 +22,19 @@ import android.net.Uri; * Handles notifications for voicemails. */ public interface VoicemailNotifier { - /** - * Notifies the user of a new voicemail. - * - * @param newVoicemailUri URI of the new voicemail record just inserted - * @throws IllegalArgumentException if the URI does not correspond to a voicemail - */ - public void notifyNewVoicemail(Uri newVoicemailUri); + /** + * Notifies the user of a new voicemail. + * + * @param newVoicemailUri URI of the new voicemail record just inserted + */ + public void notifyNewVoicemail(Uri newVoicemailUri); - /** Clears the new voicemail notification. */ - public void clearNewVoicemailNotification(); + /** + * Updates the notification and clears it if there are no new voicemails. Called when the phone + * just rebooted to put back notifications for anything the user has not acknowledged. + */ + public void updateNotification(); + + /** Clears the new voicemail notification. */ + public void clearNotification(); } |