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.dialer.calllog;
18 
19 import com.google.common.collect.Maps;
20 
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.content.ComponentName;
25 import android.content.ContentResolver;
26 import android.content.ContentUris;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.res.Resources;
30 import android.net.Uri;
31 import android.support.annotation.Nullable;
32 import android.support.v4.util.Pair;
33 import android.telecom.PhoneAccount;
34 import android.telecom.PhoneAccountHandle;
35 import android.telephony.TelephonyManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.android.contacts.common.ContactsUtils;
40 import com.android.contacts.common.compat.TelephonyManagerCompat;
41 import com.android.contacts.common.util.ContactDisplayUtils;
42 import com.android.dialer.DialtactsActivity;
43 import com.android.dialer.R;
44 import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall;
45 import com.android.dialer.filterednumber.FilteredNumbersUtil;
46 import com.android.dialer.list.ListsFragment;
47 import com.android.dialer.util.TelecomUtil;
48 
49 import java.util.Iterator;
50 import java.util.List;
51 import java.util.Map;
52 
53 /**
54  * Shows a voicemail notification in the status bar.
55  */
56 public class DefaultVoicemailNotifier {
57     public static final String TAG = "VoicemailNotifier";
58 
59     /** The tag used to identify notifications from this class. */
60     private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier";
61     /** The identifier of the notification of new voicemails. */
62     private static final int NOTIFICATION_ID = 1;
63 
64     /** The singleton instance of {@link DefaultVoicemailNotifier}. */
65     private static DefaultVoicemailNotifier sInstance;
66 
67     private final Context mContext;
68 
69     /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */
getInstance(Context context)70     public static DefaultVoicemailNotifier getInstance(Context context) {
71         if (sInstance == null) {
72             ContentResolver contentResolver = context.getContentResolver();
73             sInstance = new DefaultVoicemailNotifier(context);
74         }
75         return sInstance;
76     }
77 
DefaultVoicemailNotifier(Context context)78     private DefaultVoicemailNotifier(Context context) {
79         mContext = context;
80     }
81 
82     /**
83      * Updates the notification and notifies of the call with the given URI.
84      *
85      * Clears the notification if there are no new voicemails, and notifies if the given URI
86      * corresponds to a new voicemail.
87      *
88      * It is not safe to call this method from the main thread.
89      */
updateNotification(Uri newCallUri)90     public void updateNotification(Uri newCallUri) {
91         // Lookup the list of new voicemails to include in the notification.
92         // TODO: Move this into a service, to avoid holding the receiver up.
93         final List<NewCall> newCalls =
94                 CallLogNotificationsHelper.getInstance(mContext).getNewVoicemails();
95 
96         if (newCalls == null) {
97             // Query failed, just return.
98             return;
99         }
100 
101         if (newCalls.isEmpty()) {
102             // No voicemails to notify about: clear the notification.
103             getNotificationManager().cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
104             return;
105         }
106 
107         Resources resources = mContext.getResources();
108 
109         // This represents a list of names to include in the notification.
110         String callers = null;
111 
112         // Maps each number into a name: if a number is in the map, it has already left a more
113         // recent voicemail.
114         final Map<String, String> names = Maps.newHashMap();
115 
116         // Determine the call corresponding to the new voicemail we have to notify about.
117         NewCall callToNotify = null;
118 
119         // Iterate over the new voicemails to determine all the information above.
120         Iterator<NewCall> itr = newCalls.iterator();
121         while (itr.hasNext()) {
122             NewCall newCall = itr.next();
123 
124             // Skip notifying for numbers which are blocked.
125             if (FilteredNumbersUtil.shouldBlockVoicemail(
126                     mContext, newCall.number, newCall.countryIso, newCall.dateMs)) {
127                 itr.remove();
128 
129                 // Delete the voicemail.
130                 mContext.getContentResolver().delete(newCall.voicemailUri, null, null);
131                 continue;
132             }
133 
134             // Check if we already know the name associated with this number.
135             String name = names.get(newCall.number);
136             if (name == null) {
137                 name = CallLogNotificationsHelper.getInstance(mContext).getName(newCall.number,
138                         newCall.numberPresentation, newCall.countryIso);
139                 names.put(newCall.number, name);
140                 // This is a new caller. Add it to the back of the list of callers.
141                 if (TextUtils.isEmpty(callers)) {
142                     callers = name;
143                 } else {
144                     callers = resources.getString(
145                             R.string.notification_voicemail_callers_list, callers, name);
146                 }
147             }
148             // Check if this is the new call we need to notify about.
149             if (newCallUri != null && newCall.voicemailUri != null &&
150                     ContentUris.parseId(newCallUri) == ContentUris.parseId(newCall.voicemailUri)) {
151                 callToNotify = newCall;
152             }
153         }
154 
155         // All the potential new voicemails have been removed, e.g. if they were spam.
156         if (newCalls.isEmpty()) {
157             return;
158         }
159 
160         // If there is only one voicemail, set its transcription as the "long text".
161         String transcription = null;
162         if (newCalls.size() == 1) {
163             transcription = newCalls.get(0).transcription;
164         }
165 
166         if (newCallUri != null && callToNotify == null) {
167             Log.e(TAG, "The new call could not be found in the call log: " + newCallUri);
168         }
169 
170         // Determine the title of the notification and the icon for it.
171         final String title = resources.getQuantityString(
172                 R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size());
173         // TODO: Use the photo of contact if all calls are from the same person.
174         final int icon = android.R.drawable.stat_notify_voicemail;
175 
176         Pair<Uri, Integer> info = getNotificationInfo(callToNotify);
177 
178         Notification.Builder notificationBuilder = new Notification.Builder(mContext)
179                 .setSmallIcon(icon)
180                 .setContentTitle(title)
181                 .setContentText(callers)
182                 .setStyle(new Notification.BigTextStyle().bigText(transcription))
183                 .setColor(resources.getColor(R.color.dialer_theme_color))
184                 .setSound(info.first)
185                 .setDefaults(info.second)
186                 .setDeleteIntent(createMarkNewVoicemailsAsOldIntent())
187                 .setAutoCancel(true);
188 
189         // Determine the intent to fire when the notification is clicked on.
190         final Intent contentIntent;
191         // Open the call log.
192         contentIntent = new Intent(mContext, DialtactsActivity.class);
193         contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_VOICEMAIL);
194         notificationBuilder.setContentIntent(PendingIntent.getActivity(
195                 mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT));
196 
197         // The text to show in the ticker, describing the new event.
198         if (callToNotify != null) {
199             CharSequence msg = ContactDisplayUtils.getTtsSpannedPhoneNumber(
200                     resources,
201                     R.string.notification_new_voicemail_ticker,
202                     names.get(callToNotify.number));
203             notificationBuilder.setTicker(msg);
204         }
205         Log.i(TAG, "Creating voicemail notification");
206         getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID,
207                 notificationBuilder.build());
208     }
209 
210     /**
211      * Determines which ringtone Uri and Notification defaults to use when updating the notification
212      * for the given call.
213      */
getNotificationInfo(@ullable NewCall callToNotify)214     private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) {
215         Log.v(TAG, "getNotificationInfo");
216         if (callToNotify == null) {
217             Log.i(TAG, "callToNotify == null");
218             return new Pair<>(null, 0);
219         }
220         PhoneAccountHandle accountHandle = null;
221         if (callToNotify.accountComponentName == null || callToNotify.accountId == null) {
222             Log.v(TAG, "accountComponentName == null || callToNotify.accountId == null");
223             accountHandle = TelecomUtil
224                 .getDefaultOutgoingPhoneAccount(mContext, PhoneAccount.SCHEME_TEL);
225             if (accountHandle == null) {
226                 Log.i(TAG, "No default phone account found, using default notification ringtone");
227                 return new Pair<>(null, Notification.DEFAULT_ALL);
228             }
229 
230         } else {
231             accountHandle = new PhoneAccountHandle(
232                 ComponentName.unflattenFromString(callToNotify.accountComponentName),
233                 callToNotify.accountId);
234         }
235         if (accountHandle.getComponentName() != null) {
236             Log.v(TAG, "PhoneAccountHandle.ComponentInfo:" + accountHandle.getComponentName());
237         } else {
238             Log.i(TAG, "PhoneAccountHandle.ComponentInfo: null");
239         }
240         return new Pair<>(
241                 TelephonyManagerCompat.getVoicemailRingtoneUri(
242                         getTelephonyManager(), accountHandle),
243                 getNotificationDefaults(accountHandle));
244     }
245 
getNotificationDefaults(PhoneAccountHandle accountHandle)246     private int getNotificationDefaults(PhoneAccountHandle accountHandle) {
247         if (ContactsUtils.FLAG_N_FEATURE) {
248             return TelephonyManagerCompat.isVoicemailVibrationEnabled(getTelephonyManager(),
249                     accountHandle) ? Notification.DEFAULT_VIBRATE : 0;
250         }
251         return Notification.DEFAULT_ALL;
252     }
253 
254     /** Creates a pending intent that marks all new voicemails as old. */
createMarkNewVoicemailsAsOldIntent()255     private PendingIntent createMarkNewVoicemailsAsOldIntent() {
256         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
257         intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
258         return PendingIntent.getService(mContext, 0, intent, 0);
259     }
260 
getNotificationManager()261     private NotificationManager getNotificationManager() {
262         return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
263     }
264 
getTelephonyManager()265     private TelephonyManager getTelephonyManager() {
266         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
267     }
268 
269 }
270