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.IntentService;
20import android.app.PendingIntent;
21import android.content.Context;
22import android.content.Intent;
23import android.net.Uri;
24import android.support.annotation.NonNull;
25import android.support.annotation.Nullable;
26import android.support.annotation.VisibleForTesting;
27import android.support.annotation.WorkerThread;
28import android.telecom.PhoneAccountHandle;
29import com.android.dialer.app.voicemail.LegacyVoicemailNotificationReceiver;
30import com.android.dialer.common.Assert;
31import com.android.dialer.common.LogUtil;
32import com.android.dialer.common.concurrent.DialerExecutor.Worker;
33import com.android.dialer.common.concurrent.DialerExecutorComponent;
34import com.android.dialer.notification.missedcalls.MissedCallNotificationCanceller;
35import com.android.dialer.telecom.TelecomUtil;
36import 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 */
52public 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
80  public CallLogNotificationsService() {
81    super("CallLogNotificationsService");
82  }
83
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
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
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
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
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
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
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
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.markAllNewVoicemailsAsRead(this);
154        VisualVoicemailNotifier.cancelAllVoicemailNotifications(this);
155        break;
156      case ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD:
157        Uri voicemailUri = intent.getData();
158        VoicemailQueryHandler.markSingleNewVoicemailAsRead(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
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
201    public Void doInBackground(@Nullable Context context) throws Throwable {
202      if (context != null) {
203        cancelAllMissedCallsBackground(context);
204      }
205      return null;
206    }
207  }
208}
209