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.app.IntentService;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.support.annotation.NonNull;
25 import android.support.annotation.Nullable;
26 import android.support.annotation.VisibleForTesting;
27 import android.support.annotation.WorkerThread;
28 import android.telecom.PhoneAccountHandle;
29 import com.android.dialer.app.voicemail.LegacyVoicemailNotificationReceiver;
30 import com.android.dialer.common.Assert;
31 import com.android.dialer.common.LogUtil;
32 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
33 import com.android.dialer.common.concurrent.DialerExecutorComponent;
34 import com.android.dialer.notification.missedcalls.MissedCallNotificationCanceller;
35 import com.android.dialer.telecom.TelecomUtil;
36 import com.android.dialer.util.PermissionsUtil;
37 
38 /**
39  * Provides operations for managing call-related notifications.
40  *
41  * <p>It handles the following actions:
42  *
43  * <ul>
44  *   <li>Updating voicemail notifications
45  *   <li>Marking new voicemails as old
46  *   <li>Updating missed call notifications
47  *   <li>Marking new missed calls as old
48  *   <li>Calling back from a missed call
49  *   <li>Sending an SMS from a missed call
50  * </ul>
51  */
52 public class CallLogNotificationsService extends IntentService {
53 
54   @VisibleForTesting
55   static final String ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD =
56       "com.android.dialer.calllog.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD";
57 
58   private static final String ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD =
59       "com.android.dialer.calllog.ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD ";
60 
61   @VisibleForTesting
62   static final String ACTION_CANCEL_ALL_MISSED_CALLS =
63       "com.android.dialer.calllog.ACTION_CANCEL_ALL_MISSED_CALLS";
64 
65   private static final String ACTION_CANCEL_SINGLE_MISSED_CALL =
66       "com.android.dialer.calllog.ACTION_CANCEL_SINGLE_MISSED_CALL";
67 
68   /** Action to call back a missed call. */
69   public static final String ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION =
70       "com.android.dialer.calllog.CALL_BACK_FROM_MISSED_CALL_NOTIFICATION";
71 
72   /** Action mark legacy voicemail as dismissed. */
73   public static final String ACTION_LEGACY_VOICEMAIL_DISMISSED =
74       "com.android.dialer.calllog.ACTION_LEGACY_VOICEMAIL_DISMISSED";
75 
76   private static final String EXTRA_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE";
77 
78   public static final int UNKNOWN_MISSED_CALL_COUNT = -1;
79 
CallLogNotificationsService()80   public CallLogNotificationsService() {
81     super("CallLogNotificationsService");
82   }
83 
markAllNewVoicemailsAsOld(Context context)84   public static void markAllNewVoicemailsAsOld(Context context) {
85     LogUtil.enterBlock("CallLogNotificationsService.markAllNewVoicemailsAsOld");
86     Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
87     serviceIntent.setAction(CallLogNotificationsService.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD);
88     context.startService(serviceIntent);
89   }
90 
cancelAllMissedCalls(Context context)91   public static void cancelAllMissedCalls(Context context) {
92     LogUtil.enterBlock("CallLogNotificationsService.cancelAllMissedCalls");
93     DialerExecutorComponent.get(context)
94         .dialerExecutorFactory()
95         .createNonUiTaskBuilder(new CancelAllMissedCallsWorker())
96         .build()
97         .executeSerial(context);
98   }
99 
createMarkAllNewVoicemailsAsOldIntent(@onNull Context context)100   public static PendingIntent createMarkAllNewVoicemailsAsOldIntent(@NonNull Context context) {
101     Intent intent = new Intent(context, CallLogNotificationsService.class);
102     intent.setAction(CallLogNotificationsService.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD);
103     return PendingIntent.getService(context, 0, intent, 0);
104   }
105 
createMarkSingleNewVoicemailAsOldIntent( @onNull Context context, @Nullable Uri voicemailUri)106   public static PendingIntent createMarkSingleNewVoicemailAsOldIntent(
107       @NonNull Context context, @Nullable Uri voicemailUri) {
108     Intent intent = new Intent(context, CallLogNotificationsService.class);
109     intent.setAction(CallLogNotificationsService.ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD);
110     intent.setData(voicemailUri);
111     return PendingIntent.getService(context, 0, intent, 0);
112   }
113 
createCancelAllMissedCallsPendingIntent(@onNull Context context)114   public static PendingIntent createCancelAllMissedCallsPendingIntent(@NonNull Context context) {
115     Intent intent = new Intent(context, CallLogNotificationsService.class);
116     intent.setAction(ACTION_CANCEL_ALL_MISSED_CALLS);
117     return PendingIntent.getService(context, 0, intent, 0);
118   }
119 
createCancelSingleMissedCallPendingIntent( @onNull Context context, @Nullable Uri callUri)120   public static PendingIntent createCancelSingleMissedCallPendingIntent(
121       @NonNull Context context, @Nullable Uri callUri) {
122     Intent intent = new Intent(context, CallLogNotificationsService.class);
123     intent.setAction(ACTION_CANCEL_SINGLE_MISSED_CALL);
124     intent.setData(callUri);
125     return PendingIntent.getService(context, 0, intent, 0);
126   }
127 
createLegacyVoicemailDismissedPendingIntent( @onNull Context context, PhoneAccountHandle phoneAccountHandle)128   public static PendingIntent createLegacyVoicemailDismissedPendingIntent(
129       @NonNull Context context, PhoneAccountHandle phoneAccountHandle) {
130     Intent intent = new Intent(context, CallLogNotificationsService.class);
131     intent.setAction(ACTION_LEGACY_VOICEMAIL_DISMISSED);
132     intent.putExtra(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
133     return PendingIntent.getService(context, 0, intent, 0);
134   }
135 
136   @Override
onHandleIntent(Intent intent)137   protected void onHandleIntent(Intent intent) {
138     if (intent == null) {
139       LogUtil.e("CallLogNotificationsService.onHandleIntent", "could not handle null intent");
140       return;
141     }
142 
143     if (!PermissionsUtil.hasPermission(this, android.Manifest.permission.READ_CALL_LOG)
144         || !PermissionsUtil.hasPermission(this, android.Manifest.permission.WRITE_CALL_LOG)) {
145       LogUtil.e("CallLogNotificationsService.onHandleIntent", "no READ_CALL_LOG permission");
146       return;
147     }
148 
149     String action = intent.getAction();
150     LogUtil.i("CallLogNotificationsService.onHandleIntent", "action: " + action);
151     switch (action) {
152       case ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD:
153         VoicemailQueryHandler.markAllNewVoicemailsAsOld(this);
154         VisualVoicemailNotifier.cancelAllVoicemailNotifications(this);
155         break;
156       case ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD:
157         Uri voicemailUri = intent.getData();
158         VoicemailQueryHandler.markSingleNewVoicemailAsOld(this, voicemailUri);
159         VisualVoicemailNotifier.cancelSingleVoicemailNotification(this, voicemailUri);
160         break;
161       case ACTION_LEGACY_VOICEMAIL_DISMISSED:
162         LegacyVoicemailNotificationReceiver.setDismissed(
163             this, intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE), true);
164         break;
165       case ACTION_CANCEL_ALL_MISSED_CALLS:
166         cancelAllMissedCalls(this);
167         break;
168       case ACTION_CANCEL_SINGLE_MISSED_CALL:
169         Uri callUri = intent.getData();
170         CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead(this, callUri);
171         MissedCallNotificationCanceller.cancelSingle(this, callUri);
172         TelecomUtil.cancelMissedCallsNotification(this);
173         break;
174       case ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION:
175         MissedCallNotifier.getInstance(this)
176             .callBackFromMissedCall(
177                 intent.getStringExtra(
178                     MissedCallNotificationReceiver.EXTRA_NOTIFICATION_PHONE_NUMBER),
179                 intent.getData());
180         break;
181       default:
182         LogUtil.e("CallLogNotificationsService.onHandleIntent", "no handler for action: " + action);
183         break;
184     }
185   }
186 
187   @WorkerThread
cancelAllMissedCallsBackground(Context context)188   private static void cancelAllMissedCallsBackground(Context context) {
189     LogUtil.enterBlock("CallLogNotificationsService.cancelAllMissedCallsBackground");
190     Assert.isWorkerThread();
191     CallLogNotificationsQueryHelper.markAllMissedCallsInCallLogAsRead(context);
192     MissedCallNotificationCanceller.cancelAll(context);
193     TelecomUtil.cancelMissedCallsNotification(context);
194   }
195 
196   /** Worker that cancels all missed call notifications and updates call log entries. */
197   private static class CancelAllMissedCallsWorker implements Worker<Context, Void> {
198 
199     @Nullable
200     @Override
doInBackground(@ullable Context context)201     public Void doInBackground(@Nullable Context context) throws Throwable {
202       if (context != null) {
203         cancelAllMissedCallsBackground(context);
204       }
205       return null;
206     }
207   }
208 }
209