blob: af80f6cf293251058c0342a39b126987c2d37cf2 [file] [log] [blame]
Ihab Awad8de76912015-02-17 12:25:52 -08001/*
2 * Copyright 2014, 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.server.telecom.ui;
18
Sailesh Nepal39245e72016-01-21 19:03:00 -080019import static android.Manifest.permission.READ_PHONE_STATE;
20
Bryce Lee41045b42015-08-05 13:10:39 -070021import android.content.ComponentName;
Tony Maka9930942016-01-15 10:57:14 +000022import android.content.ContentProvider;
23import android.content.pm.PackageManager.NameNotFoundException;
Tony Maka9930942016-01-15 10:57:14 +000024import android.telecom.PhoneAccountHandle;
Bryce Lee41045b42015-08-05 13:10:39 -070025import android.telecom.TelecomManager;
Hall Liua33c44b2015-10-19 18:10:26 -070026
Ihab Awad8de76912015-02-17 12:25:52 -080027import com.android.server.telecom.Call;
Santos Cordon92694512015-04-23 14:47:28 -070028import com.android.server.telecom.CallState;
Ihab Awadabcbce42015-04-07 14:04:01 -070029import com.android.server.telecom.CallerInfoAsyncQueryFactory;
Ihab Awad8de76912015-02-17 12:25:52 -080030import com.android.server.telecom.CallsManager;
31import com.android.server.telecom.CallsManagerListenerBase;
32import com.android.server.telecom.Constants;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070033import com.android.server.telecom.ContactsAsyncHelper;
Ihab Awad8de76912015-02-17 12:25:52 -080034import com.android.server.telecom.Log;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070035import com.android.server.telecom.MissedCallNotifier;
Tony Maka9930942016-01-15 10:57:14 +000036import com.android.server.telecom.PhoneAccountRegistrar;
Brad Ebinger6ab66c32016-07-15 19:27:51 -070037import com.android.server.telecom.PhoneNumberUtilsAdapter;
Ihab Awad8de76912015-02-17 12:25:52 -080038import com.android.server.telecom.R;
Brad Ebingere62e9e82016-02-01 18:26:40 -080039import com.android.server.telecom.Runnable;
Ihab Awad8de76912015-02-17 12:25:52 -080040import com.android.server.telecom.TelecomBroadcastIntentProcessor;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070041import com.android.server.telecom.TelecomSystem;
Yorke Lee226ae562015-03-27 14:00:13 -070042import com.android.server.telecom.components.TelecomBroadcastReceiver;
Ihab Awad8de76912015-02-17 12:25:52 -080043
44import android.app.Notification;
45import android.app.NotificationManager;
46import android.app.PendingIntent;
47import android.app.TaskStackBuilder;
48import android.content.AsyncQueryHandler;
49import android.content.ContentValues;
50import android.content.Context;
51import android.content.Intent;
Sailesh Nepal39245e72016-01-21 19:03:00 -080052import android.content.pm.ResolveInfo;
Ihab Awad8de76912015-02-17 12:25:52 -080053import android.database.Cursor;
54import android.graphics.Bitmap;
55import android.graphics.drawable.BitmapDrawable;
56import android.graphics.drawable.Drawable;
57import android.net.Uri;
58import android.os.AsyncTask;
Anthony Lee761a63f2015-03-26 11:28:52 -070059import android.os.Binder;
Ihab Awad8de76912015-02-17 12:25:52 -080060import android.os.UserHandle;
61import android.provider.CallLog.Calls;
Sailesh Nepal39245e72016-01-21 19:03:00 -080062import android.telecom.DefaultDialerManager;
Ihab Awad8de76912015-02-17 12:25:52 -080063import android.telecom.DisconnectCause;
64import android.telecom.PhoneAccount;
65import android.telephony.PhoneNumberUtils;
Tyler Gunnc8e528b2015-09-21 13:13:52 -070066import android.telephony.TelephonyManager;
Ihab Awad8de76912015-02-17 12:25:52 -080067import android.text.BidiFormatter;
68import android.text.TextDirectionHeuristics;
69import android.text.TextUtils;
70
Victor Chang9e7f98a2016-01-04 16:03:27 +000071import com.android.internal.telephony.CallerInfo;
72
Bryce Lee41045b42015-08-05 13:10:39 -070073import java.lang.Override;
74import java.lang.String;
Sailesh Nepal39245e72016-01-21 19:03:00 -080075import java.util.List;
Tyler Gunnc8e528b2015-09-21 13:13:52 -070076import java.util.Locale;
Tony Maka9930942016-01-15 10:57:14 +000077import java.util.concurrent.ConcurrentHashMap;
78import java.util.concurrent.ConcurrentMap;
79import java.util.concurrent.atomic.AtomicInteger;
Bryce Lee41045b42015-08-05 13:10:39 -070080
Ihab Awad8de76912015-02-17 12:25:52 -080081// TODO: Needed for move to system service: import com.android.internal.R;
82
83/**
84 * Creates a notification for calls that the user missed (neither answered nor rejected).
85 *
86 * TODO: Make TelephonyManager.clearMissedCalls call into this class.
87 *
88 * TODO: Reduce dependencies in this implementation; remove the need to create a new Call
89 * simply to look up caller metadata, and if possible, make it unnecessary to get a
90 * direct reference to the CallsManager. Try to make this class simply handle the UI
91 * and Android-framework entanglements of missed call notification.
92 */
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070093public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier {
Ihab Awad8de76912015-02-17 12:25:52 -080094
Tony Maka9930942016-01-15 10:57:14 +000095 public interface MissedCallNotifierImplFactory {
96 MissedCallNotifier makeMissedCallNotifierImpl(Context context,
Brad Ebinger6ab66c32016-07-15 19:27:51 -070097 PhoneAccountRegistrar phoneAccountRegistrar,
98 PhoneNumberUtilsAdapter phoneNumberUtilsAdapter);
Tony Maka9930942016-01-15 10:57:14 +000099 }
100
Hall Liua33c44b2015-10-19 18:10:26 -0700101 public interface NotificationBuilderFactory {
102 Notification.Builder getBuilder(Context context);
103 }
104
105 private static class DefaultNotificationBuilderFactory implements NotificationBuilderFactory {
106 public DefaultNotificationBuilderFactory() {}
107
108 @Override
109 public Notification.Builder getBuilder(Context context) {
110 return new Notification.Builder(context);
111 }
112 }
113
Ihab Awad8de76912015-02-17 12:25:52 -0800114 private static final String[] CALL_LOG_PROJECTION = new String[] {
115 Calls._ID,
116 Calls.NUMBER,
117 Calls.NUMBER_PRESENTATION,
118 Calls.DATE,
119 Calls.DURATION,
120 Calls.TYPE,
121 };
122
123 private static final int CALL_LOG_COLUMN_ID = 0;
124 private static final int CALL_LOG_COLUMN_NUMBER = 1;
125 private static final int CALL_LOG_COLUMN_NUMBER_PRESENTATION = 2;
126 private static final int CALL_LOG_COLUMN_DATE = 3;
127 private static final int CALL_LOG_COLUMN_DURATION = 4;
128 private static final int CALL_LOG_COLUMN_TYPE = 5;
129
130 private static final int MISSED_CALL_NOTIFICATION_ID = 1;
131
132 private final Context mContext;
Tony Maka9930942016-01-15 10:57:14 +0000133 private final PhoneAccountRegistrar mPhoneAccountRegistrar;
Ihab Awad8de76912015-02-17 12:25:52 -0800134 private final NotificationManager mNotificationManager;
Hall Liua33c44b2015-10-19 18:10:26 -0700135 private final NotificationBuilderFactory mNotificationBuilderFactory;
Brad Ebinger6ab66c32016-07-15 19:27:51 -0700136 private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
Tony Maka9930942016-01-15 10:57:14 +0000137 private UserHandle mCurrentUserHandle;
Bryce Lee41045b42015-08-05 13:10:39 -0700138
Ihab Awad8de76912015-02-17 12:25:52 -0800139 // Used to track the number of missed calls.
Tony Maka9930942016-01-15 10:57:14 +0000140 private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts;
Ihab Awad8de76912015-02-17 12:25:52 -0800141
Brad Ebinger6ab66c32016-07-15 19:27:51 -0700142 public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
143 PhoneNumberUtilsAdapter phoneNumberUtilsAdapter) {
144 this(context, phoneAccountRegistrar, phoneNumberUtilsAdapter,
145 new DefaultNotificationBuilderFactory());
Hall Liua33c44b2015-10-19 18:10:26 -0700146 }
147
148 public MissedCallNotifierImpl(Context context,
Tony Maka9930942016-01-15 10:57:14 +0000149 PhoneAccountRegistrar phoneAccountRegistrar,
Brad Ebinger6ab66c32016-07-15 19:27:51 -0700150 PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Hall Liua33c44b2015-10-19 18:10:26 -0700151 NotificationBuilderFactory notificationBuilderFactory) {
Ihab Awad8de76912015-02-17 12:25:52 -0800152 mContext = context;
Tony Maka9930942016-01-15 10:57:14 +0000153 mPhoneAccountRegistrar = phoneAccountRegistrar;
Brad Ebinger6ab66c32016-07-15 19:27:51 -0700154 mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
Ihab Awad8de76912015-02-17 12:25:52 -0800155 mNotificationManager =
156 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
Bryce Lee41045b42015-08-05 13:10:39 -0700157
Hall Liua33c44b2015-10-19 18:10:26 -0700158 mNotificationBuilderFactory = notificationBuilderFactory;
Tony Maka9930942016-01-15 10:57:14 +0000159 mMissedCallCounts = new ConcurrentHashMap<>();
Ihab Awad8de76912015-02-17 12:25:52 -0800160 }
161
Ihab Awad8de76912015-02-17 12:25:52 -0800162 /** Clears missed call notification and marks the call log's missed calls as read. */
Yorke Lee226ae562015-03-27 14:00:13 -0700163 @Override
Sailesh Nepal39245e72016-01-21 19:03:00 -0800164 public void clearMissedCalls(UserHandle userHandle) {
165 // If the default dialer is showing the missed call notification then it will modify the
166 // call log and we don't have to do anything here.
167 if (!shouldManageNotificationThroughDefaultDialer(userHandle)) {
168 markMissedCallsAsRead(userHandle);
169 }
170 cancelMissedCallNotification(userHandle);
171 }
172
173 private void markMissedCallsAsRead(final UserHandle userHandle) {
Brad Ebingerf5e06662016-08-25 16:16:27 -0700174 AsyncTask.execute(new Runnable("MCNI.mMCAR", null /*lock*/) {
Ihab Awad8de76912015-02-17 12:25:52 -0800175 @Override
Brad Ebingere62e9e82016-02-01 18:26:40 -0800176 public void loggedRun() {
Ihab Awad8de76912015-02-17 12:25:52 -0800177 // Clear the list of new missed calls from the call log.
178 ContentValues values = new ContentValues();
179 values.put(Calls.NEW, 0);
180 values.put(Calls.IS_READ, 1);
181 StringBuilder where = new StringBuilder();
182 where.append(Calls.NEW);
183 where.append(" = 1 AND ");
184 where.append(Calls.TYPE);
185 where.append(" = ?");
Shriram Ganesh0f39c552015-06-24 15:56:51 -0700186 try {
Tony Maka9930942016-01-15 10:57:14 +0000187 Uri callsUri = ContentProvider
188 .maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier());
189 mContext.getContentResolver().update(callsUri, values,
Shriram Ganesh0f39c552015-06-24 15:56:51 -0700190 where.toString(), new String[]{ Integer.toString(Calls.
191 MISSED_TYPE) });
192 } catch (IllegalArgumentException e) {
193 Log.w(this, "ContactsProvider update command failed", e);
194 }
Ihab Awad8de76912015-02-17 12:25:52 -0800195 }
Brad Ebingere62e9e82016-02-01 18:26:40 -0800196 }.prepare());
Ihab Awad8de76912015-02-17 12:25:52 -0800197 }
198
199 /**
Sailesh Nepal39245e72016-01-21 19:03:00 -0800200 * Returns the missed-call notificatino intent to send to the default dialer for the given user. * Note, the passed in userHandle is always the non-managed user for SIM calls (multi-user
201 * calls). In this case we return the default dialer for the logged in user. This is never the
202 * managed (work profile) dialer.
203 *
204 * For non-multi-user calls (3rd party phone accounts), the passed in userHandle is the user
205 * handle of the phone account. This could be a managed user. In that case we return the default
206 * dialer for the given user which could be a managed (work profile) dialer.
207 */
208 private Intent getShowMissedCallIntentForDefaultDialer(UserHandle userHandle) {
209 String dialerPackage = DefaultDialerManager
210 .getDefaultDialerApplication(mContext, userHandle.getIdentifier());
211 if (TextUtils.isEmpty(dialerPackage)) {
212 return null;
213 }
214 return new Intent(TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION)
215 .setPackage(dialerPackage);
216 }
217
218 private boolean shouldManageNotificationThroughDefaultDialer(UserHandle userHandle) {
219 Intent intent = getShowMissedCallIntentForDefaultDialer(userHandle);
220 if (intent == null) {
221 return false;
222 }
223
Sailesh Nepal4d34c5b2016-01-23 13:47:49 -0800224 List<ResolveInfo> receivers = mContext.getPackageManager()
225 .queryBroadcastReceiversAsUser(intent, 0, userHandle.getIdentifier());
Sailesh Nepal39245e72016-01-21 19:03:00 -0800226 return receivers.size() > 0;
227 }
228
229 private void sendNotificationThroughDefaultDialer(Call call, UserHandle userHandle) {
230 int count = mMissedCallCounts.get(userHandle).get();
231 Intent intent = getShowMissedCallIntentForDefaultDialer(userHandle)
232 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
Wei Liu61914862016-11-01 18:40:41 -0700233 .putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT,
234 createClearMissedCallsPendingIntent(userHandle))
Sailesh Nepal39245e72016-01-21 19:03:00 -0800235 .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count)
236 .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
237 call != null ? call.getPhoneNumber() : null);
238
Wei Liu61914862016-11-01 18:40:41 -0700239 if (count == 1 && call != null) {
240 final Uri handleUri = call.getHandle();
241 String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
242
243 if (!TextUtils.isEmpty(handle) && !TextUtils.equals(handle,
244 mContext.getString(R.string.handle_restricted))) {
245 intent.putExtra(TelecomManager.EXTRA_CALL_BACK_INTENT,
246 createCallBackPendingIntent(handleUri, userHandle));
247 }
248 }
249
250
Sailesh Nepal39245e72016-01-21 19:03:00 -0800251 Log.w(this, "Showing missed calls through default dialer.");
252 mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE);
253 }
254
255 /**
Ihab Awad8de76912015-02-17 12:25:52 -0800256 * Create a system notification for the missed call.
257 *
258 * @param call The missed call.
259 */
Yorke Lee226ae562015-03-27 14:00:13 -0700260 @Override
Ihab Awad8de76912015-02-17 12:25:52 -0800261 public void showMissedCallNotification(Call call) {
Tony Maka9930942016-01-15 10:57:14 +0000262 final PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
263 final PhoneAccount phoneAccount =
264 mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle);
265 UserHandle userHandle;
266 if (phoneAccount != null &&
267 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
268 userHandle = mCurrentUserHandle;
269 } else {
270 userHandle = phoneAccountHandle.getUserHandle();
271 }
272 showMissedCallNotification(call, userHandle);
273 }
Ihab Awad8de76912015-02-17 12:25:52 -0800274
Tony Maka9930942016-01-15 10:57:14 +0000275 private void showMissedCallNotification(Call call, UserHandle userHandle) {
Tony Mak3cf8d222016-01-15 16:25:35 +0000276 mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
277 int missCallCounts = mMissedCallCounts.get(userHandle).incrementAndGet();
Tony Maka9930942016-01-15 10:57:14 +0000278
Sailesh Nepal39245e72016-01-21 19:03:00 -0800279 if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
280 sendNotificationThroughDefaultDialer(call, userHandle);
281 return;
282 }
283
Ihab Awad8de76912015-02-17 12:25:52 -0800284 final int titleResId;
285 final String expandedText; // The text in the notification's line 1 and 2.
286
287 // Display the first line of the notification:
288 // 1 missed call: <caller name || handle>
289 // More than 1 missed call: <number of calls> + "missed calls"
Tony Maka9930942016-01-15 10:57:14 +0000290 if (missCallCounts == 1) {
Ihab Awad8de76912015-02-17 12:25:52 -0800291 expandedText = getNameForCall(call);
Victor Chang9e7f98a2016-01-04 16:03:27 +0000292
293 CallerInfo ci = call.getCallerInfo();
294 if (ci != null && ci.userType == CallerInfo.USER_TYPE_WORK) {
295 titleResId = R.string.notification_missedWorkCallTitle;
296 } else {
297 titleResId = R.string.notification_missedCallTitle;
298 }
Ihab Awad8de76912015-02-17 12:25:52 -0800299 } else {
300 titleResId = R.string.notification_missedCallsTitle;
301 expandedText =
Tony Maka9930942016-01-15 10:57:14 +0000302 mContext.getString(R.string.notification_missedCallsMsg, missCallCounts);
Ihab Awad8de76912015-02-17 12:25:52 -0800303 }
304
Tyler Gunn8fd6d312015-10-06 09:17:38 -0700305 // Create a public viewable version of the notification, suitable for display when sensitive
306 // notification content is hidden.
Tony Maka9930942016-01-15 10:57:14 +0000307 // We use user's context here to make sure notification is badged if it is a managed user.
308 Context contextForUser = getContextForUser(userHandle);
309 Notification.Builder publicBuilder = mNotificationBuilderFactory.getBuilder(contextForUser);
Tyler Gunn8fd6d312015-10-06 09:17:38 -0700310 publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
311 .setColor(mContext.getResources().getColor(R.color.theme_color))
312 .setWhen(call.getCreationTimeMillis())
313 // Show "Phone" for notification title.
314 .setContentTitle(mContext.getText(R.string.userCallActivityLabel))
315 // Notification details shows that there are missed call(s), but does not reveal
316 // the missed caller information.
317 .setContentText(mContext.getText(titleResId))
Tony Maka9930942016-01-15 10:57:14 +0000318 .setContentIntent(createCallLogPendingIntent(userHandle))
Tyler Gunn8fd6d312015-10-06 09:17:38 -0700319 .setAutoCancel(true)
Tony Maka9930942016-01-15 10:57:14 +0000320 .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle));
Tyler Gunn8fd6d312015-10-06 09:17:38 -0700321
322 // Create the notification suitable for display when sensitive information is showing.
Tony Maka9930942016-01-15 10:57:14 +0000323 Notification.Builder builder = mNotificationBuilderFactory.getBuilder(contextForUser);
Ihab Awad8de76912015-02-17 12:25:52 -0800324 builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
325 .setColor(mContext.getResources().getColor(R.color.theme_color))
326 .setWhen(call.getCreationTimeMillis())
327 .setContentTitle(mContext.getText(titleResId))
328 .setContentText(expandedText)
Tony Maka9930942016-01-15 10:57:14 +0000329 .setContentIntent(createCallLogPendingIntent(userHandle))
Ihab Awad8de76912015-02-17 12:25:52 -0800330 .setAutoCancel(true)
Tony Maka9930942016-01-15 10:57:14 +0000331 .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle))
Tyler Gunn8fd6d312015-10-06 09:17:38 -0700332 // Include a public version of the notification to be shown when the missed call
333 // notification is shown on the user's lock screen and they have chosen to hide
334 // sensitive notification information.
335 .setPublicVersion(publicBuilder.build());
Ihab Awad8de76912015-02-17 12:25:52 -0800336
337 Uri handleUri = call.getHandle();
338 String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
339
340 // Add additional actions when there is only 1 missed call, like call-back and SMS.
Tony Maka9930942016-01-15 10:57:14 +0000341 if (missCallCounts == 1) {
Ihab Awad8de76912015-02-17 12:25:52 -0800342 Log.d(this, "Add actions with number %s.", Log.piiHandle(handle));
343
344 if (!TextUtils.isEmpty(handle)
345 && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))) {
Andrew Leebbdd44c2015-05-27 12:34:14 -0700346 builder.addAction(R.drawable.ic_phone_24dp,
Ihab Awad8de76912015-02-17 12:25:52 -0800347 mContext.getString(R.string.notification_missedCall_call_back),
Tony Maka9930942016-01-15 10:57:14 +0000348 createCallBackPendingIntent(handleUri, userHandle));
Ihab Awad8de76912015-02-17 12:25:52 -0800349
Santos Cordon35ed7972015-09-16 16:33:44 -0700350 if (canRespondViaSms(call)) {
351 builder.addAction(R.drawable.ic_message_24dp,
352 mContext.getString(R.string.notification_missedCall_message),
Tony Maka9930942016-01-15 10:57:14 +0000353 createSendSmsFromNotificationPendingIntent(handleUri, userHandle));
Santos Cordon35ed7972015-09-16 16:33:44 -0700354 }
Ihab Awad8de76912015-02-17 12:25:52 -0800355 }
356
357 Bitmap photoIcon = call.getPhotoIcon();
358 if (photoIcon != null) {
359 builder.setLargeIcon(photoIcon);
360 } else {
361 Drawable photo = call.getPhoto();
362 if (photo != null && photo instanceof BitmapDrawable) {
363 builder.setLargeIcon(((BitmapDrawable) photo).getBitmap());
364 }
365 }
366 } else {
367 Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle),
Tony Maka9930942016-01-15 10:57:14 +0000368 missCallCounts);
Ihab Awad8de76912015-02-17 12:25:52 -0800369 }
370
371 Notification notification = builder.build();
372 configureLedOnNotification(notification);
373
374 Log.i(this, "Adding missed call notification for %s.", call);
Anthony Lee761a63f2015-03-26 11:28:52 -0700375 long token = Binder.clearCallingIdentity();
376 try {
377 mNotificationManager.notifyAsUser(
Tony Maka9930942016-01-15 10:57:14 +0000378 null /* tag */, MISSED_CALL_NOTIFICATION_ID, notification, userHandle);
Anthony Lee761a63f2015-03-26 11:28:52 -0700379 } finally {
380 Binder.restoreCallingIdentity(token);
381 }
Ihab Awad8de76912015-02-17 12:25:52 -0800382 }
383
Tony Maka9930942016-01-15 10:57:14 +0000384
Ihab Awad8de76912015-02-17 12:25:52 -0800385 /** Cancels the "missed call" notification. */
Tony Maka9930942016-01-15 10:57:14 +0000386 private void cancelMissedCallNotification(UserHandle userHandle) {
Ihab Awad8de76912015-02-17 12:25:52 -0800387 // Reset the number of missed calls to 0.
Tony Mak3cf8d222016-01-15 16:25:35 +0000388 mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
389 mMissedCallCounts.get(userHandle).set(0);
Bryce Lee41045b42015-08-05 13:10:39 -0700390
Sailesh Nepal39245e72016-01-21 19:03:00 -0800391 if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
392 sendNotificationThroughDefaultDialer(null, userHandle);
393 return;
394 }
395
Anthony Lee761a63f2015-03-26 11:28:52 -0700396 long token = Binder.clearCallingIdentity();
397 try {
Tony Maka9930942016-01-15 10:57:14 +0000398 mNotificationManager.cancelAsUser(null, MISSED_CALL_NOTIFICATION_ID, userHandle);
Anthony Lee761a63f2015-03-26 11:28:52 -0700399 } finally {
400 Binder.restoreCallingIdentity(token);
401 }
Ihab Awad8de76912015-02-17 12:25:52 -0800402 }
403
404 /**
405 * Returns the name to use in the missed call notification.
406 */
407 private String getNameForCall(Call call) {
408 String handle = call.getHandle() == null ? null : call.getHandle().getSchemeSpecificPart();
409 String name = call.getName();
410
Tyler Gunnc8e528b2015-09-21 13:13:52 -0700411 if (!TextUtils.isEmpty(handle)) {
412 String formattedNumber = PhoneNumberUtils.formatNumber(handle,
413 getCurrentCountryIso(mContext));
414
415 // The formatted number will be null if there was a problem formatting it, but we can
416 // default to using the unformatted number instead (e.g. a SIP URI may not be able to
417 // be formatted.
418 if (!TextUtils.isEmpty(formattedNumber)) {
419 handle = formattedNumber;
420 }
421 }
422
Ihab Awad8de76912015-02-17 12:25:52 -0800423 if (!TextUtils.isEmpty(name) && TextUtils.isGraphic(name)) {
424 return name;
425 } else if (!TextUtils.isEmpty(handle)) {
426 // A handle should always be displayed LTR using {@link BidiFormatter} regardless of the
427 // content of the rest of the notification.
428 // TODO: Does this apply to SIP addresses?
429 BidiFormatter bidiFormatter = BidiFormatter.getInstance();
430 return bidiFormatter.unicodeWrap(handle, TextDirectionHeuristics.LTR);
431 } else {
432 // Use "unknown" if the call is unidentifiable.
433 return mContext.getString(R.string.unknown);
434 }
435 }
436
437 /**
Tyler Gunnc8e528b2015-09-21 13:13:52 -0700438 * @return The ISO 3166-1 two letters country code of the country the user is in based on the
439 * network location. If the network location does not exist, fall back to the locale
440 * setting.
441 */
442 private String getCurrentCountryIso(Context context) {
443 // Without framework function calls, this seems to be the most accurate location service
444 // we can rely on.
445 final TelephonyManager telephonyManager =
446 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
447 String countryIso = telephonyManager.getNetworkCountryIso().toUpperCase();
448
449 if (countryIso == null) {
450 countryIso = Locale.getDefault().getCountry();
451 Log.w(this, "No CountryDetector; falling back to countryIso based on locale: "
452 + countryIso);
453 }
454 return countryIso;
455 }
456
457 /**
Ihab Awad8de76912015-02-17 12:25:52 -0800458 * Creates a new pending intent that sends the user to the call log.
459 *
460 * @return The pending intent.
461 */
Tony Maka9930942016-01-15 10:57:14 +0000462 private PendingIntent createCallLogPendingIntent(UserHandle userHandle) {
Ihab Awad8de76912015-02-17 12:25:52 -0800463 Intent intent = new Intent(Intent.ACTION_VIEW, null);
464 intent.setType(Calls.CONTENT_TYPE);
465
466 TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
467 taskStackBuilder.addNextIntent(intent);
468
Tony Maka9930942016-01-15 10:57:14 +0000469 return taskStackBuilder.getPendingIntent(0, 0, null, userHandle);
Ihab Awad8de76912015-02-17 12:25:52 -0800470 }
471
472 /**
473 * Creates an intent to be invoked when the missed call notification is cleared.
474 */
Tony Maka9930942016-01-15 10:57:14 +0000475 private PendingIntent createClearMissedCallsPendingIntent(UserHandle userHandle) {
Ihab Awad8de76912015-02-17 12:25:52 -0800476 return createTelecomPendingIntent(
Tony Maka9930942016-01-15 10:57:14 +0000477 TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null, userHandle);
Ihab Awad8de76912015-02-17 12:25:52 -0800478 }
479
480 /**
481 * Creates an intent to be invoked when the user opts to "call back" from the missed call
482 * notification.
483 *
484 * @param handle The handle to call back.
485 */
Tony Maka9930942016-01-15 10:57:14 +0000486 private PendingIntent createCallBackPendingIntent(Uri handle, UserHandle userHandle) {
Ihab Awad8de76912015-02-17 12:25:52 -0800487 return createTelecomPendingIntent(
Tony Maka9930942016-01-15 10:57:14 +0000488 TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle,
489 userHandle);
Ihab Awad8de76912015-02-17 12:25:52 -0800490 }
491
492 /**
493 * Creates an intent to be invoked when the user opts to "send sms" from the missed call
494 * notification.
495 */
Tony Maka9930942016-01-15 10:57:14 +0000496 private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle,
497 UserHandle userHandle) {
Ihab Awad8de76912015-02-17 12:25:52 -0800498 return createTelecomPendingIntent(
499 TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION,
Tony Maka9930942016-01-15 10:57:14 +0000500 Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null),
501 userHandle);
Ihab Awad8de76912015-02-17 12:25:52 -0800502 }
503
504 /**
505 * Creates generic pending intent from the specified parameters to be received by
506 * {@link TelecomBroadcastIntentProcessor}.
507 *
508 * @param action The intent action.
509 * @param data The intent data.
510 */
Tony Maka9930942016-01-15 10:57:14 +0000511 private PendingIntent createTelecomPendingIntent(String action, Uri data,
512 UserHandle userHandle) {
Yorke Lee226ae562015-03-27 14:00:13 -0700513 Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
Tony Maka9930942016-01-15 10:57:14 +0000514 intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
Ihab Awad8de76912015-02-17 12:25:52 -0800515 return PendingIntent.getBroadcast(mContext, 0, intent, 0);
516 }
517
518 /**
519 * Configures a notification to emit the blinky notification light.
520 */
521 private void configureLedOnNotification(Notification notification) {
522 notification.flags |= Notification.FLAG_SHOW_LIGHTS;
523 notification.defaults |= Notification.DEFAULT_LIGHTS;
524 }
525
Santos Cordon35ed7972015-09-16 16:33:44 -0700526 private boolean canRespondViaSms(Call call) {
527 // Only allow respond-via-sms for "tel:" calls.
528 return call.getHandle() != null &&
529 PhoneAccount.SCHEME_TEL.equals(call.getHandle().getScheme());
530 }
531
Ihab Awad8de76912015-02-17 12:25:52 -0800532 /**
533 * Adds the missed call notification on startup if there are unread missed calls.
534 */
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700535 @Override
Tony Maka9930942016-01-15 10:57:14 +0000536 public void reloadFromDatabase(
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700537 final TelecomSystem.SyncRoot lock,
538 final CallsManager callsManager,
Ihab Awadabcbce42015-04-07 14:04:01 -0700539 final ContactsAsyncHelper contactsAsyncHelper,
Tony Maka9930942016-01-15 10:57:14 +0000540 final CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
541 final UserHandle userHandle) {
542 Log.d(this, "reloadFromDatabase()...");
Ihab Awad8de76912015-02-17 12:25:52 -0800543
544 // instantiate query handler
545 AsyncQueryHandler queryHandler = new AsyncQueryHandler(mContext.getContentResolver()) {
546 @Override
547 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
548 Log.d(MissedCallNotifierImpl.this, "onQueryComplete()...");
549 if (cursor != null) {
550 try {
Tony Maka9930942016-01-15 10:57:14 +0000551 mMissedCallCounts.remove(userHandle);
Ihab Awad8de76912015-02-17 12:25:52 -0800552 while (cursor.moveToNext()) {
553 // Get data about the missed call from the cursor
554 final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
555 final int presentation =
556 cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION);
557 final long date = cursor.getLong(CALL_LOG_COLUMN_DATE);
558
559 final Uri handle;
560 if (presentation != Calls.PRESENTATION_ALLOWED
561 || TextUtils.isEmpty(handleString)) {
562 handle = null;
563 } else {
564 handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(handleString) ?
565 PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
566 handleString, null);
567 }
568
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700569 synchronized (lock) {
Ihab Awad8de76912015-02-17 12:25:52 -0800570
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700571 // Convert the data to a call object
Tyler Gunn8452be02015-09-17 09:57:02 -0700572 Call call = new Call(Call.CALL_ID_UNKNOWN, mContext, callsManager,
573 lock, null, contactsAsyncHelper,
Brad Ebinger6ab66c32016-07-15 19:27:51 -0700574 callerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter, null,
575 null, null, null, Call.CALL_DIRECTION_INCOMING, false,
576 false);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700577 call.setDisconnectCause(
578 new DisconnectCause(DisconnectCause.MISSED));
Santos Cordon5fa4e4f2015-06-10 14:56:01 -0700579 call.setState(CallState.DISCONNECTED, "throw away call");
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700580 call.setCreationTimeMillis(date);
581
582 // Listen for the update to the caller information before posting
583 // the notification so that we have the contact info and photo.
584 call.addListener(new Call.ListenerBase() {
585 @Override
586 public void onCallerInfoChanged(Call call) {
587 call.removeListener(
588 this); // No longer need to listen to call
589 // changes after the contact info
590 // is retrieved.
Tony Maka9930942016-01-15 10:57:14 +0000591 showMissedCallNotification(call, userHandle);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700592 }
593 });
594 // Set the handle here because that is what triggers the contact
595 // info query.
596 call.setHandle(handle, presentation);
597 }
Ihab Awad8de76912015-02-17 12:25:52 -0800598 }
599 } finally {
600 cursor.close();
601 }
602 }
603 }
604 };
605
606 // setup query spec, look for all Missed calls that are new.
Pooja Jain68385f52016-08-04 16:48:36 +0530607 StringBuilder where = new StringBuilder("(type=");
Ihab Awad8de76912015-02-17 12:25:52 -0800608 where.append(Calls.MISSED_TYPE);
Pooja Jain68385f52016-08-04 16:48:36 +0530609 where.append(" OR type=");
Pooja Jainf5063882016-11-14 18:36:08 +0530610 where.append(Calls.MISSED_IMS_TYPE);
Pooja Jain68385f52016-08-04 16:48:36 +0530611 where.append(" OR type=");
612 where.append(Calls.MISSED_WIFI_TYPE+")");
Ihab Awad8de76912015-02-17 12:25:52 -0800613 where.append(" AND new=1");
Roshan Piusec0a1462015-08-04 15:01:20 -0700614 where.append(" AND is_read=0");
Ihab Awad8de76912015-02-17 12:25:52 -0800615
Tony Maka9930942016-01-15 10:57:14 +0000616 Uri callsUri =
617 ContentProvider.maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier());
Ihab Awad8de76912015-02-17 12:25:52 -0800618 // start the query
Tony Maka9930942016-01-15 10:57:14 +0000619 queryHandler.startQuery(0, null, callsUri, CALL_LOG_PROJECTION,
Ihab Awad8de76912015-02-17 12:25:52 -0800620 where.toString(), null, Calls.DEFAULT_SORT_ORDER);
621 }
Tony Maka9930942016-01-15 10:57:14 +0000622
623 @Override
624 public void setCurrentUserHandle(UserHandle currentUserHandle) {
625 mCurrentUserHandle = currentUserHandle;
626 }
627
628 private Context getContextForUser(UserHandle user) {
629 try {
630 return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
631 } catch (NameNotFoundException e) {
632 // Default to mContext, not finding the package system is running as is unlikely.
633 return mContext;
634 }
635 }
Ihab Awad8de76912015-02-17 12:25:52 -0800636}