Add notifications for new voicemails.

New voicemails trigger a broadcast: receive the broadcast and show the
notification in the status bar for it.

Lookup the number and contact name (if any) and include those in the
notification.

Bug: 4968670

Change-Id: I7c68458696199c47fe49b37a732fe10ce24e3fe9
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f4c6227..56bf4e5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -612,6 +612,16 @@
                     android:resource="@xml/social_widget_info" />
         </receiver>
 
+        <receiver android:name=".calllog.NewVoicemailReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.NEW_VOICEMAIL" />
+                <data
+                    android:scheme="content"
+                    android:host="com.android.voicemail"
+                />
+            </intent-filter>
+        </receiver>
+
         <activity
             android:name=".socialwidget.SocialWidgetConfigureActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar" >
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0064a8b..a2baaa2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1021,7 +1021,7 @@
     <!-- Button caption that allows the user to clear the defaults  (like primary email) of one
     contact. [CHAR LIMIT=15] -->
     <string name="quickcontact_clear_defaults_button">Clear</string>
-    
+
     <!-- The menu item to open the list of accounts -->
     <string name="menu_accounts">Accounts</string>
 
@@ -1427,7 +1427,7 @@
         contact aggregation suggestion in Contact editor. [CHAR LIMIT=128]-->
     <string name="aggregation_suggestion_edit_dialog_title">Edit selected contacts</string>
 
-    <!-- The message in a confirmation dialog shown when the user selects a 
+    <!-- The message in a confirmation dialog shown when the user selects a
         contact aggregation suggestion in Contact editor. [CHAR LIMIT=512]-->
     <string name="aggregation_suggestion_edit_dialog_message">Switch to editing
         the selected contact? Information you entered so far will be copied.</string>
@@ -1542,7 +1542,7 @@
     </plurals>
 
     <!-- Confirmation message of the dialog that allows deletion of a contact group  [CHAR LIMIT=256] -->
-    <string name="delete_group_dialog_message">Are you sure you want to delete the group 
+    <string name="delete_group_dialog_message">Are you sure you want to delete the group
       \'<xliff:g id="group_label" example="Friends">%1$s</xliff:g>\'?
       (Contacts themselves will not be deleted.)
     </string>
@@ -1558,7 +1558,7 @@
 
     <!-- Toast shown when text is copied to the clipboard [CHAR LIMIT=64] -->
     <string name="toast_text_copied">Text copied</string>
-    
+
     <!-- Title of the alert dialog when the user hits the Cancel button in the editor [CHAR LIMIT=64] -->
     <string name="cancel_confirmation_dialog_title">Discard changes</string>
 
@@ -1585,4 +1585,7 @@
 
     <!-- The title of the activity that edits an existing group [CHAR LIMIT=NONE] -->
     <string name="editGroup_title_edit">Edit group</string>
+
+    <!-- Title of the notification of new voicemail. -->
+    <string name="notification_voicemail_title">New voicemail</string>
 </resources>
diff --git a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
new file mode 100644
index 0000000..b2ee5ce
--- /dev/null
+++ b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
@@ -0,0 +1,198 @@
+/*
+ * 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 com.android.contacts.R;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.VoicemailContract;
+
+/**
+ * Implementation of {@link VoicemailNotifier} that shows a notification in the status bar.
+ */
+public class DefaultVoicemailNotifier implements VoicemailNotifier {
+    /** 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;
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private final VoicemailNumberQuery mVoicemailNumberQuery;
+    private final NameLookupQuery mNameLookupQuery;
+
+    public DefaultVoicemailNotifier(Context context, NotificationManager notificationManager,
+            VoicemailNumberQuery voicemailNumberQuery, NameLookupQuery nameLookupQuery) {
+        mContext = context;
+        mNotificationManager = notificationManager;
+        mVoicemailNumberQuery = voicemailNumberQuery;
+        mNameLookupQuery = nameLookupQuery;
+    }
+
+    @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;
+        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)
+                .setAutoCancel(true)
+                .getNotification();
+
+        // Open the voicemail when clicking on the notification.
+        notification.contentIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW, uri), 0);
+
+        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+    }
+
+    @Override
+    public void clearNewVoicemailNotification() {
+        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+    }
+
+    /** Allows determining the number associated with a given voicemail. */
+    public interface VoicemailNumberQuery {
+        /**
+         * 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.
+         */
+        public String query(Uri uri);
+    }
+
+    /** Create a new instance of {@link VoicemailNumberQuery}. */
+    public static VoicemailNumberQuery createVoicemailNumberQuery(ContentResolver contentResolver) {
+        return new DefaultVoicemailNumberQuery(contentResolver);
+    }
+
+    /**
+     * Default implementation of {@link VoicemailNumberQuery} that looks up the number in the
+     * voicemail content provider.
+     */
+    private static final class DefaultVoicemailNumberQuery implements VoicemailNumberQuery {
+        private static final String[] PROJECTION = { VoicemailContract.Voicemails.NUMBER };
+        private static final int NUMBER_COLUMN_INDEX = 0;
+
+        private final ContentResolver mContentResolver;
+
+        private DefaultVoicemailNumberQuery(ContentResolver contentResolver) {
+            mContentResolver = contentResolver;
+        }
+
+        @Override
+        public String query(Uri uri) {
+            validateVoicemailUri(uri);
+            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();
+                }
+            }
+        }
+
+        /**
+         * 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");
+            }
+        }
+    }
+
+    /** 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);
+    }
+
+    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.moveToFirst()) return null;
+                return cursor.getString(DISPLAY_NAME_COLUMN_INDEX);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/contacts/calllog/NewVoicemailReceiver.java b/src/com/android/contacts/calllog/NewVoicemailReceiver.java
new file mode 100644
index 0000000..0b9f2fa
--- /dev/null
+++ b/src/com/android/contacts/calllog/NewVoicemailReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Receiver for new voicemail notifications.
+ * <p>
+ * Delegates to a {@link VoicemailNotifier}.
+ */
+public class NewVoicemailReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        getVoicemailNotifier(context).notifyNewVoicemail(intent.getData());
+    }
+
+    private VoicemailNotifier getVoicemailNotifier(Context context) {
+        NotificationManager notificationManager =
+                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        return new DefaultVoicemailNotifier(context, notificationManager,
+                DefaultVoicemailNotifier.createVoicemailNumberQuery(context.getContentResolver()),
+                DefaultVoicemailNotifier.createNameLookupQuery(context.getContentResolver()));
+    }
+}
diff --git a/src/com/android/contacts/calllog/VoicemailNotifier.java b/src/com/android/contacts/calllog/VoicemailNotifier.java
new file mode 100644
index 0000000..ba82f21
--- /dev/null
+++ b/src/com/android/contacts/calllog/VoicemailNotifier.java
@@ -0,0 +1,35 @@
+/*
+ * 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.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);
+
+      /** Clears the new voicemail notification. */
+      public void clearNewVoicemailNotification();
+}