• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.calllog;
18 
19 import android.content.Context;
20 import android.net.Uri;
21 import android.service.notification.StatusBarNotification;
22 import android.support.annotation.NonNull;
23 import android.support.annotation.Nullable;
24 import android.support.annotation.WorkerThread;
25 import android.text.TextUtils;
26 import android.util.ArrayMap;
27 import com.android.dialer.app.R;
28 import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall;
29 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
30 import com.android.dialer.blocking.FilteredNumbersUtil;
31 import com.android.dialer.common.Assert;
32 import com.android.dialer.common.LogUtil;
33 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
34 import com.android.dialer.common.concurrent.DialerExecutorComponent;
35 import com.android.dialer.notification.DialerNotificationManager;
36 import com.android.dialer.phonenumbercache.ContactInfo;
37 import com.android.dialer.telecom.TelecomUtil;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Map;
41 
42 /** Updates voicemail notifications in the background. */
43 class VisualVoicemailUpdateTask implements Worker<VisualVoicemailUpdateTask.Input, Void> {
44   @Nullable
45   @Override
doInBackground(@onNull Input input)46   public Void doInBackground(@NonNull Input input) throws Throwable {
47     updateNotification(input.context, input.queryHelper, input.queryHandler);
48     return null;
49   }
50 
51   /**
52    * Updates the notification and notifies of the call with the given URI.
53    *
54    * <p>Clears the notification if there are no new voicemails, and notifies if the given URI
55    * corresponds to a new voicemail.
56    */
57   @WorkerThread
updateNotification( Context context, CallLogNotificationsQueryHelper queryHelper, FilteredNumberAsyncQueryHandler queryHandler)58   private static void updateNotification(
59       Context context,
60       CallLogNotificationsQueryHelper queryHelper,
61       FilteredNumberAsyncQueryHandler queryHandler) {
62     Assert.isWorkerThread();
63     LogUtil.enterBlock("VisualVoicemailUpdateTask.updateNotification");
64 
65     List<NewCall> voicemailsToNotify = queryHelper.getNewVoicemails();
66     if (voicemailsToNotify == null) {
67       // Query failed, just return
68       return;
69     }
70     voicemailsToNotify = filterBlockedNumbers(context, queryHandler, voicemailsToNotify);
71     boolean shouldAlert =
72         !voicemailsToNotify.isEmpty()
73             && voicemailsToNotify.size() > getExistingNotificationCount(context);
74     voicemailsToNotify.addAll(getAndUpdateVoicemailsWithExistingNotification(context, queryHelper));
75     if (voicemailsToNotify.isEmpty()) {
76       LogUtil.i("VisualVoicemailUpdateTask.updateNotification", "no voicemails to notify about");
77       VisualVoicemailNotifier.cancelAllVoicemailNotifications(context);
78       VoicemailNotificationJobService.cancelJob(context);
79       return;
80     }
81 
82     // This represents a list of names to include in the notification.
83     String callers = null;
84 
85     // Maps each number into a name: if a number is in the map, it has already left a more
86     // recent voicemail.
87     Map<String, ContactInfo> contactInfos = new ArrayMap<>();
88     for (NewCall newCall : voicemailsToNotify) {
89       if (!contactInfos.containsKey(newCall.number)) {
90         ContactInfo contactInfo =
91             queryHelper.getContactInfo(
92                 newCall.number, newCall.numberPresentation, newCall.countryIso);
93         contactInfos.put(newCall.number, contactInfo);
94 
95         // This is a new caller. Add it to the back of the list of callers.
96         if (TextUtils.isEmpty(callers)) {
97           callers = contactInfo.name;
98         } else {
99           callers =
100               context.getString(
101                   R.string.notification_voicemail_callers_list, callers, contactInfo.name);
102         }
103       }
104     }
105     VisualVoicemailNotifier.showNotifications(
106         context, voicemailsToNotify, contactInfos, callers, shouldAlert);
107 
108     // Set trigger to update notifications when database changes.
109     VoicemailNotificationJobService.scheduleJob(context);
110   }
111 
112   @WorkerThread
113   @NonNull
getExistingNotificationCount(Context context)114   private static int getExistingNotificationCount(Context context) {
115     Assert.isWorkerThread();
116     int result = 0;
117     for (StatusBarNotification notification :
118         DialerNotificationManager.getActiveNotifications(context)) {
119       if (notification.getId() != VisualVoicemailNotifier.NOTIFICATION_ID) {
120         continue;
121       }
122       if (TextUtils.isEmpty(notification.getTag())
123           || !notification.getTag().startsWith(VisualVoicemailNotifier.NOTIFICATION_TAG_PREFIX)) {
124         continue;
125       }
126       result++;
127     }
128     return result;
129   }
130 
131   /**
132    * Cancel notification for voicemail that is already deleted. Returns a list of voicemails that
133    * already has notifications posted and should be updated.
134    */
135   @WorkerThread
136   @NonNull
getAndUpdateVoicemailsWithExistingNotification( Context context, CallLogNotificationsQueryHelper queryHelper)137   private static List<NewCall> getAndUpdateVoicemailsWithExistingNotification(
138       Context context, CallLogNotificationsQueryHelper queryHelper) {
139     Assert.isWorkerThread();
140     List<NewCall> result = new ArrayList<>();
141     for (StatusBarNotification notification :
142         DialerNotificationManager.getActiveNotifications(context)) {
143       if (notification.getId() != VisualVoicemailNotifier.NOTIFICATION_ID) {
144         continue;
145       }
146       if (TextUtils.isEmpty(notification.getTag())
147           || !notification.getTag().startsWith(VisualVoicemailNotifier.NOTIFICATION_TAG_PREFIX)) {
148         continue;
149       }
150       String uri =
151           notification.getTag().replace(VisualVoicemailNotifier.NOTIFICATION_TAG_PREFIX, "");
152       NewCall existingCall = queryHelper.getNewCallsQuery().query(Uri.parse(uri));
153       if (existingCall != null) {
154         result.add(existingCall);
155       } else {
156         LogUtil.i(
157             "VisualVoicemailUpdateTask.getVoicemailsWithExistingNotification",
158             "voicemail deleted, removing notification");
159         DialerNotificationManager.cancel(context, notification.getTag(), notification.getId());
160       }
161     }
162     return result;
163   }
164 
165   @WorkerThread
filterBlockedNumbers( Context context, FilteredNumberAsyncQueryHandler queryHandler, List<NewCall> newCalls)166   private static List<NewCall> filterBlockedNumbers(
167       Context context, FilteredNumberAsyncQueryHandler queryHandler, List<NewCall> newCalls) {
168     Assert.isWorkerThread();
169     if (FilteredNumbersUtil.hasRecentEmergencyCall(context)) {
170       LogUtil.i(
171           "VisualVoicemailUpdateTask.filterBlockedNumbers",
172           "not filtering due to recent emergency call");
173       return newCalls;
174     }
175 
176     List<NewCall> result = new ArrayList<>();
177     for (NewCall newCall : newCalls) {
178       if (queryHandler.getBlockedIdSynchronous(newCall.number, newCall.countryIso) != null) {
179         LogUtil.i(
180             "VisualVoicemailUpdateTask.filterBlockedNumbers",
181             "found voicemail from blocked number, deleting");
182         if (newCall.voicemailUri != null) {
183           // Delete the voicemail.
184           CallLogAsyncTaskUtil.deleteVoicemailSynchronous(context, newCall.voicemailUri);
185         }
186       } else {
187         result.add(newCall);
188       }
189     }
190     return result;
191   }
192 
193   /** Updates the voicemail notifications displayed. */
scheduleTask(@onNull Context context, @NonNull Runnable callback)194   static void scheduleTask(@NonNull Context context, @NonNull Runnable callback) {
195     Assert.isNotNull(context);
196     Assert.isNotNull(callback);
197     if (!TelecomUtil.isDefaultDialer(context)) {
198       LogUtil.i("VisualVoicemailUpdateTask.scheduleTask", "not default dialer, not running");
199       callback.run();
200       return;
201     }
202 
203     Input input =
204         new Input(
205             context,
206             CallLogNotificationsQueryHelper.getInstance(context),
207             new FilteredNumberAsyncQueryHandler(context));
208     DialerExecutorComponent.get(context)
209         .dialerExecutorFactory()
210         .createNonUiTaskBuilder(new VisualVoicemailUpdateTask())
211         .onSuccess(
212             output -> {
213               LogUtil.i("VisualVoicemailUpdateTask.scheduleTask", "update successful");
214               callback.run();
215             })
216         .onFailure(
217             throwable -> {
218               LogUtil.i("VisualVoicemailUpdateTask.scheduleTask", "update failed: " + throwable);
219               callback.run();
220             })
221         .build()
222         .executeParallel(input);
223   }
224 
225   static class Input {
226     @NonNull final Context context;
227     @NonNull final CallLogNotificationsQueryHelper queryHelper;
228     @NonNull final FilteredNumberAsyncQueryHandler queryHandler;
229 
Input( Context context, CallLogNotificationsQueryHelper queryHelper, FilteredNumberAsyncQueryHandler queryHandler)230     Input(
231         Context context,
232         CallLogNotificationsQueryHelper queryHelper,
233         FilteredNumberAsyncQueryHandler queryHandler) {
234       this.context = context;
235       this.queryHelper = queryHelper;
236       this.queryHandler = queryHandler;
237     }
238   }
239 }
240