DefaultVoicemailNotifier.java revision d8046e520a866b9948ee9ba47cf642b441ca8e23
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.dialer.app.calllog;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.net.Uri;
27import android.os.Build.VERSION;
28import android.os.Build.VERSION_CODES;
29import android.support.annotation.NonNull;
30import android.support.annotation.Nullable;
31import android.support.annotation.VisibleForTesting;
32import android.support.v4.util.Pair;
33import android.telecom.PhoneAccountHandle;
34import android.telephony.TelephonyManager;
35import android.text.TextUtils;
36import android.util.ArrayMap;
37import com.android.contacts.common.compat.TelephonyManagerCompat;
38import com.android.contacts.common.util.ContactDisplayUtils;
39import com.android.dialer.app.DialtactsActivity;
40import com.android.dialer.app.R;
41import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall;
42import com.android.dialer.app.contactinfo.ContactPhotoLoader;
43import com.android.dialer.app.list.DialtactsPagerAdapter;
44import com.android.dialer.blocking.FilteredNumbersUtil;
45import com.android.dialer.common.LogUtil;
46import com.android.dialer.logging.Logger;
47import com.android.dialer.logging.nano.DialerImpression;
48import com.android.dialer.notification.NotificationChannelManager;
49import com.android.dialer.notification.NotificationChannelManager.Channel;
50import com.android.dialer.phonenumbercache.ContactInfo;
51import java.util.Iterator;
52import java.util.List;
53import java.util.Map;
54
55/** Shows a voicemail notification in the status bar. */
56public class DefaultVoicemailNotifier {
57
58  public static final String TAG = "VoicemailNotifier";
59
60  /** The tag used to identify notifications from this class. */
61  static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier";
62  /** The identifier of the notification of new voicemails. */
63  private static final int NOTIFICATION_ID = R.id.notification_voicemail;
64
65  private final Context context;
66  private final CallLogNotificationsQueryHelper queryHelper;
67
68  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
69  DefaultVoicemailNotifier(Context context, CallLogNotificationsQueryHelper queryHelper) {
70    this.context = context;
71    this.queryHelper = queryHelper;
72  }
73
74  /** Returns an instance of {@link DefaultVoicemailNotifier}. */
75  public static DefaultVoicemailNotifier getInstance(Context context) {
76    return new DefaultVoicemailNotifier(
77        context, CallLogNotificationsQueryHelper.getInstance(context));
78  }
79
80  /**
81   * Updates the notification and notifies of the call with the given URI.
82   *
83   * <p>Clears the notification if there are no new voicemails, and notifies if the given URI
84   * corresponds to a new voicemail.
85   *
86   * <p>It is not safe to call this method from the main thread.
87   */
88  public void updateNotification() {
89    // Lookup the list of new voicemails to include in the notification.
90    final List<NewCall> newCalls = queryHelper.getNewVoicemails();
91
92    if (newCalls == null) {
93      // Query failed, just return.
94      return;
95    }
96
97    Resources resources = context.getResources();
98
99    // This represents a list of names to include in the notification.
100    String callers = null;
101
102    // Maps each number into a name: if a number is in the map, it has already left a more
103    // recent voicemail.
104    final Map<String, ContactInfo> contactInfos = new ArrayMap<>();
105
106    // Iterate over the new voicemails to determine all the information above.
107    Iterator<NewCall> itr = newCalls.iterator();
108    while (itr.hasNext()) {
109      NewCall newCall = itr.next();
110
111      // Skip notifying for numbers which are blocked.
112      if (FilteredNumbersUtil.shouldBlockVoicemail(
113          context, newCall.number, newCall.countryIso, newCall.dateMs)) {
114        itr.remove();
115
116        if (newCall.voicemailUri != null) {
117          // Delete the voicemail.
118          context.getContentResolver().delete(newCall.voicemailUri, null, null);
119        }
120        continue;
121      }
122
123      // Check if we already know the name associated with this number.
124      ContactInfo contactInfo = contactInfos.get(newCall.number);
125      if (contactInfo == null) {
126        contactInfo =
127            queryHelper.getContactInfo(
128                newCall.number, newCall.numberPresentation, newCall.countryIso);
129        contactInfos.put(newCall.number, contactInfo);
130        // This is a new caller. Add it to the back of the list of callers.
131        if (TextUtils.isEmpty(callers)) {
132          callers = contactInfo.name;
133        } else {
134          callers =
135              resources.getString(
136                  R.string.notification_voicemail_callers_list, callers, contactInfo.name);
137        }
138      }
139    }
140
141    if (newCalls.isEmpty()) {
142      // No voicemails to notify about
143      return;
144    }
145
146    Notification.Builder groupSummary =
147        createNotificationBuilder()
148            .setContentTitle(
149                resources.getQuantityString(
150                    R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size()))
151            .setContentText(callers)
152            .setDeleteIntent(createMarkNewVoicemailsAsOldIntent(null))
153            .setGroupSummary(true)
154            .setContentIntent(newVoicemailIntent(null));
155
156    NotificationChannelManager.applyChannel(
157        groupSummary,
158        context,
159        Channel.VOICEMAIL,
160        PhoneAccountHandles.getAccount(context, newCalls.get(0)));
161
162    LogUtil.i(TAG, "Creating voicemail notification");
163    getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, groupSummary.build());
164
165    for (NewCall voicemail : newCalls) {
166      getNotificationManager()
167          .notify(
168              voicemail.callsUri.toString(),
169              NOTIFICATION_ID,
170              createNotificationForVoicemail(voicemail, contactInfos));
171    }
172  }
173
174  /**
175   * Determines which ringtone Uri and Notification defaults to use when updating the notification
176   * for the given call.
177   */
178  private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) {
179    LogUtil.v(TAG, "getNotificationInfo");
180    if (callToNotify == null) {
181      LogUtil.i(TAG, "callToNotify == null");
182      return new Pair<>(null, 0);
183    }
184    PhoneAccountHandle accountHandle = PhoneAccountHandles.getAccount(context, callToNotify);
185    if (accountHandle == null) {
186      LogUtil.i(TAG, "No default phone account found, using default notification ringtone");
187      return new Pair<>(null, Notification.DEFAULT_ALL);
188    }
189    return new Pair<>(
190        TelephonyManagerCompat.getVoicemailRingtoneUri(getTelephonyManager(), accountHandle),
191        getNotificationDefaults(accountHandle));
192  }
193
194  private int getNotificationDefaults(PhoneAccountHandle accountHandle) {
195    if (VERSION.SDK_INT >= VERSION_CODES.N) {
196      return TelephonyManagerCompat.isVoicemailVibrationEnabled(
197              getTelephonyManager(), accountHandle)
198          ? Notification.DEFAULT_VIBRATE
199          : 0;
200    }
201    return Notification.DEFAULT_ALL;
202  }
203
204  /** Creates a pending intent that marks all new voicemails as old. */
205  private PendingIntent createMarkNewVoicemailsAsOldIntent(@Nullable Uri voicemailUri) {
206    Intent intent = new Intent(context, CallLogNotificationsService.class);
207    intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
208    intent.setData(voicemailUri);
209    return PendingIntent.getService(context, 0, intent, 0);
210  }
211
212  private NotificationManager getNotificationManager() {
213    return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
214  }
215
216  private TelephonyManager getTelephonyManager() {
217    return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
218  }
219
220  private Notification createNotificationForVoicemail(
221      @NonNull NewCall voicemail, @NonNull Map<String, ContactInfo> contactInfos) {
222    Pair<Uri, Integer> notificationInfo = getNotificationInfo(voicemail);
223    ContactInfo contactInfo = contactInfos.get(voicemail.number);
224
225    Notification.Builder notificationBuilder =
226        createNotificationBuilder()
227            .setContentTitle(
228                context
229                    .getResources()
230                    .getQuantityString(R.plurals.notification_voicemail_title, 1, 1))
231            .setContentText(
232                ContactDisplayUtils.getTtsSpannedPhoneNumber(
233                    context.getResources(),
234                    R.string.notification_new_voicemail_ticker,
235                    contactInfo.name))
236            .setWhen(voicemail.dateMs)
237            .setSound(notificationInfo.first)
238            .setDefaults(notificationInfo.second);
239
240    if (voicemail.voicemailUri != null) {
241      notificationBuilder.setDeleteIntent(
242          createMarkNewVoicemailsAsOldIntent(voicemail.voicemailUri));
243    }
244
245    NotificationChannelManager.applyChannel(
246        notificationBuilder,
247        context,
248        Channel.VOICEMAIL,
249        PhoneAccountHandles.getAccount(context, voicemail));
250
251    ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo);
252    Bitmap photoIcon = loader.loadPhotoIcon();
253    if (photoIcon != null) {
254      notificationBuilder.setLargeIcon(photoIcon);
255    }
256    if (!TextUtils.isEmpty(voicemail.transcription)) {
257      Logger.get(context)
258          .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION);
259      notificationBuilder.setStyle(
260          new Notification.BigTextStyle().bigText(voicemail.transcription));
261    }
262    notificationBuilder.setContentIntent(newVoicemailIntent(voicemail));
263    Logger.get(context).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED);
264    return notificationBuilder.build();
265  }
266
267  private Notification.Builder createNotificationBuilder() {
268    return new Notification.Builder(context)
269        .setSmallIcon(android.R.drawable.stat_notify_voicemail)
270        .setColor(context.getColor(R.color.dialer_theme_color))
271        .setGroup(NOTIFICATION_TAG)
272        .setOnlyAlertOnce(true)
273        .setAutoCancel(true);
274  }
275
276  private PendingIntent newVoicemailIntent(@Nullable NewCall voicemail) {
277    Intent intent =
278        DialtactsActivity.getShowTabIntent(context, DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
279    // TODO (b/35486204): scroll to this voicemail
280    if (voicemail != null) {
281      intent.setData(voicemail.voicemailUri);
282    }
283    intent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true);
284    return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
285  }
286}
287