1 /*
2  * Copyright (C) 2016 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 package com.android.dialer.calllog;
17 
18 import android.app.Notification;
19 import android.app.NotificationManager;
20 import android.app.PendingIntent;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.Bitmap;
25 import android.os.AsyncTask;
26 import android.provider.CallLog.Calls;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import com.android.contacts.common.ContactsUtils;
31 import com.android.contacts.common.util.PhoneNumberHelper;
32 import com.android.dialer.DialtactsActivity;
33 import com.android.dialer.R;
34 import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall;
35 import com.android.dialer.contactinfo.ContactPhotoLoader;
36 import com.android.dialer.compat.UserManagerCompat;
37 import com.android.dialer.list.ListsFragment;
38 import com.android.dialer.util.DialerUtils;
39 import com.android.dialer.util.IntentUtil;
40 import com.android.dialer.util.IntentUtil.CallIntentBuilder;
41 
42 import java.util.List;
43 
44 /**
45  * Creates a notification for calls that the user missed (neither answered nor rejected).
46  *
47  */
48 public class MissedCallNotifier {
49     public static final String TAG = "MissedCallNotifier";
50 
51     /** The tag used to identify notifications from this class. */
52     private static final String NOTIFICATION_TAG = "MissedCallNotifier";
53     /** The identifier of the notification of new missed calls. */
54     private static final int NOTIFICATION_ID = 1;
55     /** Preference file key for number of missed calls. */
56     private static final String MISSED_CALL_COUNT = "missed_call_count";
57 
58     private static MissedCallNotifier sInstance;
59     private Context mContext;
60 
61     /** Returns the singleton instance of the {@link MissedCallNotifier}. */
getInstance(Context context)62     public static MissedCallNotifier getInstance(Context context) {
63         if (sInstance == null) {
64             sInstance = new MissedCallNotifier(context);
65         }
66         return sInstance;
67     }
68 
MissedCallNotifier(Context context)69     private MissedCallNotifier(Context context) {
70         mContext = context;
71     }
72 
updateMissedCallNotification(int count, String number)73     public void updateMissedCallNotification(int count, String number) {
74         final int titleResId;
75         final String expandedText;  // The text in the notification's line 1 and 2.
76 
77         final List<NewCall> newCalls =
78                 CallLogNotificationsHelper.getInstance(mContext).getNewMissedCalls();
79 
80         if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) {
81             if (newCalls == null) {
82                 // If the intent did not contain a count, and we are unable to get a count from the
83                 // call log, then no notification can be shown.
84                 return;
85             }
86             count = newCalls.size();
87         }
88 
89         if (count == 0) {
90             // No voicemails to notify about: clear the notification.
91             clearMissedCalls();
92             return;
93         }
94 
95         // The call log has been updated, use that information preferentially.
96         boolean useCallLog = newCalls != null && newCalls.size() == count;
97         NewCall newestCall = useCallLog ? newCalls.get(0) : null;
98         long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis();
99 
100         Notification.Builder builder = new Notification.Builder(mContext);
101         // Display the first line of the notification:
102         // 1 missed call: <caller name || handle>
103         // More than 1 missed call: <number of calls> + "missed calls"
104         if (count == 1) {
105             //TODO: look up caller ID that is not in contacts.
106             ContactInfo contactInfo = CallLogNotificationsHelper.getInstance(mContext)
107                     .getContactInfo(useCallLog ? newestCall.number : number,
108                             useCallLog ? newestCall.numberPresentation
109                                     : Calls.PRESENTATION_ALLOWED,
110                             useCallLog ? newestCall.countryIso : null);
111 
112             titleResId = contactInfo.userType == ContactsUtils.USER_TYPE_WORK
113                     ? R.string.notification_missedWorkCallTitle
114                     : R.string.notification_missedCallTitle;
115 
116             expandedText = contactInfo.name;
117             ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo);
118             Bitmap photoIcon = loader.loadPhotoIcon();
119             if (photoIcon != null) {
120                 builder.setLargeIcon(photoIcon);
121             }
122         } else {
123             titleResId = R.string.notification_missedCallsTitle;
124             expandedText =
125                     mContext.getString(R.string.notification_missedCallsMsg, count);
126         }
127 
128         // Create a public viewable version of the notification, suitable for display when sensitive
129         // notification content is hidden.
130         Notification.Builder publicBuilder = new Notification.Builder(mContext);
131         publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
132                 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
133                 // Show "Phone" for notification title.
134                 .setContentTitle(mContext.getText(R.string.userCallActivityLabel))
135                 // Notification details shows that there are missed call(s), but does not reveal
136                 // the missed caller information.
137                 .setContentText(mContext.getText(titleResId))
138                 .setContentIntent(createCallLogPendingIntent())
139                 .setAutoCancel(true)
140                 .setWhen(timeMs)
141                 .setDeleteIntent(createClearMissedCallsPendingIntent());
142 
143         // Create the notification suitable for display when sensitive information is showing.
144         builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
145                 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
146                 .setContentTitle(mContext.getText(titleResId))
147                 .setContentText(expandedText)
148                 .setContentIntent(createCallLogPendingIntent())
149                 .setAutoCancel(true)
150                 .setWhen(timeMs)
151                 .setDeleteIntent(createClearMissedCallsPendingIntent())
152                 // Include a public version of the notification to be shown when the missed call
153                 // notification is shown on the user's lock screen and they have chosen to hide
154                 // sensitive notification information.
155                 .setPublicVersion(publicBuilder.build());
156 
157         // Add additional actions when there is only 1 missed call and the user isn't locked
158         if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) {
159             if (!TextUtils.isEmpty(number)
160                     && !TextUtils.equals(
161                     number, mContext.getString(R.string.handle_restricted))) {
162                 builder.addAction(R.drawable.ic_phone_24dp,
163                         mContext.getString(R.string.notification_missedCall_call_back),
164                         createCallBackPendingIntent(number));
165 
166                 if (!PhoneNumberHelper.isUriNumber(number)) {
167                     builder.addAction(R.drawable.ic_message_24dp,
168                             mContext.getString(R.string.notification_missedCall_message),
169                             createSendSmsFromNotificationPendingIntent(number));
170                 }
171             }
172         }
173 
174         Notification notification = builder.build();
175         configureLedOnNotification(notification);
176 
177         Log.i(TAG, "Adding missed call notification.");
178         getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
179     }
180 
clearMissedCalls()181     private void clearMissedCalls() {
182         AsyncTask.execute(new Runnable() {
183             @Override
184             public void run() {
185                 // Call log is only accessible when unlocked. If that's the case, clear the list of
186                 // new missed calls from the call log.
187                 if (UserManagerCompat.isUserUnlocked(mContext)) {
188                     ContentValues values = new ContentValues();
189                     values.put(Calls.NEW, 0);
190                     values.put(Calls.IS_READ, 1);
191                     StringBuilder where = new StringBuilder();
192                     where.append(Calls.NEW);
193                     where.append(" = 1 AND ");
194                     where.append(Calls.TYPE);
195                     where.append(" = ?");
196                     try {
197                         mContext.getContentResolver().update(Calls.CONTENT_URI, values,
198                                 where.toString(), new String[]{Integer.toString(Calls.
199                                         MISSED_TYPE)});
200                     } catch (IllegalArgumentException e) {
201                         Log.w(TAG, "ContactsProvider update command failed", e);
202                     }
203                 }
204                 getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
205             }
206         });
207     }
208 
209     /**
210      * Trigger an intent to make a call from a missed call number.
211      */
callBackFromMissedCall(String number)212     public void callBackFromMissedCall(String number) {
213         closeSystemDialogs(mContext);
214         CallLogNotificationsHelper.removeMissedCallNotifications(mContext);
215         DialerUtils.startActivityWithErrorToast(
216                 mContext,
217                 new CallIntentBuilder(number)
218                         .build()
219                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
220     }
221 
222     /**
223      * Trigger an intent to send an sms from a missed call number.
224      */
sendSmsFromMissedCall(String number)225     public void sendSmsFromMissedCall(String number) {
226         closeSystemDialogs(mContext);
227         CallLogNotificationsHelper.removeMissedCallNotifications(mContext);
228         DialerUtils.startActivityWithErrorToast(
229                 mContext,
230                 IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
231     }
232 
233     /**
234      * Creates a new pending intent that sends the user to the call log.
235      *
236      * @return The pending intent.
237      */
createCallLogPendingIntent()238     private PendingIntent createCallLogPendingIntent() {
239         Intent contentIntent = new Intent(mContext, DialtactsActivity.class);
240         contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_HISTORY);
241         return PendingIntent.getActivity(
242                 mContext, 0, contentIntent,PendingIntent.FLAG_UPDATE_CURRENT);
243     }
244 
245     /** Creates a pending intent that marks all new missed calls as old. */
createClearMissedCallsPendingIntent()246     private PendingIntent createClearMissedCallsPendingIntent() {
247         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
248         intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD);
249         return PendingIntent.getService(mContext, 0, intent, 0);
250     }
251 
createCallBackPendingIntent(String number)252     private PendingIntent createCallBackPendingIntent(String number) {
253         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
254         intent.setAction(
255                 CallLogNotificationsService.ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION);
256         intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number);
257         // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new
258         // extra.
259         return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
260     }
261 
createSendSmsFromNotificationPendingIntent(String number)262     private PendingIntent createSendSmsFromNotificationPendingIntent(String number) {
263         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
264         intent.setAction(
265                 CallLogNotificationsService.ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION);
266         intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number);
267         // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new
268         // extra.
269         return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
270     }
271 
272     /**
273      * Configures a notification to emit the blinky notification light.
274      */
configureLedOnNotification(Notification notification)275     private void configureLedOnNotification(Notification notification) {
276         notification.flags |= Notification.FLAG_SHOW_LIGHTS;
277         notification.defaults |= Notification.DEFAULT_LIGHTS;
278     }
279 
280     /**
281      * Closes open system dialogs and the notification shade.
282      */
closeSystemDialogs(Context context)283     private void closeSystemDialogs(Context context) {
284         context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
285     }
286 
getNotificationMgr()287     private NotificationManager getNotificationMgr() {
288         return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
289     }
290 }
291