diff options
Diffstat (limited to 'java/com/android/dialer/app/calllog/MissedCallNotifier.java')
-rw-r--r-- | java/com/android/dialer/app/calllog/MissedCallNotifier.java | 405 |
1 files changed, 247 insertions, 158 deletions
diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java index 2fa3dae65..5b5661615 100644 --- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java +++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java @@ -16,16 +16,20 @@ package com.android.dialer.app.calllog; import android.app.Notification; +import android.app.Notification.Builder; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.os.AsyncTask; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.provider.CallLog.Calls; +import android.service.notification.StatusBarNotification; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; import android.support.v4.os.UserManagerCompat; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; @@ -34,109 +38,117 @@ import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; -import com.android.dialer.app.calllog.CallLogNotificationsHelper.NewCall; +import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; import com.android.dialer.app.contactinfo.ContactPhotoLoader; import com.android.dialer.app.list.ListsFragment; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallInitiationType; -import com.android.dialer.common.ConfigProviderBindings; import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.NotificationChannelManager; +import com.android.dialer.notification.NotificationChannelManager.Channel; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumberutil.PhoneNumberHelper; +import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** Creates a notification for calls that the user missed (neither answered nor rejected). */ public class MissedCallNotifier { /** The tag used to identify notifications from this class. */ - private static final String NOTIFICATION_TAG = "MissedCallNotifier"; + static final String NOTIFICATION_TAG = "MissedCallNotifier"; /** The identifier of the notification of new missed calls. */ - private static final int NOTIFICATION_ID = 1; + private static final int NOTIFICATION_ID = R.id.notification_missed_call; - private static MissedCallNotifier sInstance; - private Context mContext; - private CallLogNotificationsHelper mCalllogNotificationsHelper; + private final Context context; + private final CallLogNotificationsQueryHelper callLogNotificationsQueryHelper; @VisibleForTesting - MissedCallNotifier(Context context, CallLogNotificationsHelper callLogNotificationsHelper) { - mContext = context; - mCalllogNotificationsHelper = callLogNotificationsHelper; + MissedCallNotifier( + Context context, CallLogNotificationsQueryHelper callLogNotificationsQueryHelper) { + this.context = context; + this.callLogNotificationsQueryHelper = callLogNotificationsQueryHelper; } - /** Returns the singleton instance of the {@link MissedCallNotifier}. */ + /** Returns an instance of {@link MissedCallNotifier}. */ public static MissedCallNotifier getInstance(Context context) { - if (sInstance == null) { - CallLogNotificationsHelper callLogNotificationsHelper = - CallLogNotificationsHelper.getInstance(context); - sInstance = new MissedCallNotifier(context, callLogNotificationsHelper); - } - return sInstance; + CallLogNotificationsQueryHelper callLogNotificationsQueryHelper = + CallLogNotificationsQueryHelper.getInstance(context); + return new MissedCallNotifier(context, callLogNotificationsQueryHelper); } /** - * Creates a missed call notification with a post call message if there are no existing missed - * calls. + * Update missed call notifications from the call log. Accepts default information in case call + * log cannot be accessed. + * + * @param count the number of missed calls to display if call log cannot be accessed. May be + * {@link CallLogNotificationsService#UNKNOWN_MISSED_CALL_COUNT} if unknown. + * @param number the phone number of the most recent call to display if the call log cannot be + * accessed. May be null if unknown. */ - public void createPostCallMessageNotification(String number, String message) { - int count = CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT; - if (ConfigProviderBindings.get(mContext).getBoolean("enable_call_compose", false)) { - updateMissedCallNotification(count, number, message); - } else { - updateMissedCallNotification(count, number, null); - } - } - - /** Creates a missed call notification. */ - public void updateMissedCallNotification(int count, String number) { - updateMissedCallNotification(count, number, null); - } - - private void updateMissedCallNotification( - int count, String number, @Nullable String postCallMessage) { + @WorkerThread + public void updateMissedCallNotification(int count, @Nullable String number) { final int titleResId; CharSequence expandedText; // The text in the notification's line 1 and 2. - final List<NewCall> newCalls = mCalllogNotificationsHelper.getNewMissedCalls(); + List<NewCall> newCalls = callLogNotificationsQueryHelper.getNewMissedCalls(); - if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { - if (newCalls == null) { - // If the intent did not contain a count, and we are unable to get a count from the - // call log, then no notification can be shown. - return; + if ((newCalls != null && newCalls.isEmpty()) || count == 0) { + // No calls to notify about: clear the notification. + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, null); + return; + } + + if (newCalls != null) { + if (count != CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT + && count != newCalls.size()) { + LogUtil.w( + "MissedCallNotifier.updateMissedCallNotification", + "Call count does not match call log count." + + " count: " + + count + + " newCalls.size(): " + + newCalls.size()); } count = newCalls.size(); } - if (count == 0) { - // No voicemails to notify about: clear the notification. - clearMissedCalls(); + if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { + // If the intent did not contain a count, and we are unable to get a count from the + // call log, then no notification can be shown. return; } - // The call log has been updated, use that information preferentially. - boolean useCallLog = newCalls != null && newCalls.size() == count; - NewCall newestCall = useCallLog ? newCalls.get(0) : null; - long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis(); - String missedNumber = useCallLog ? newestCall.number : number; + Notification.Builder groupSummary = createNotificationBuilder(); + boolean useCallList = newCalls != null; - Notification.Builder builder = new Notification.Builder(mContext); - // Display the first line of the notification: - // 1 missed call: <caller name || handle> - // More than 1 missed call: <number of calls> + "missed calls" if (count == 1) { + NewCall call = + useCallList + ? newCalls.get(0) + : new NewCall( + null, + null, + number, + Calls.PRESENTATION_ALLOWED, + null, + null, + null, + null, + System.currentTimeMillis()); + //TODO: look up caller ID that is not in contacts. ContactInfo contactInfo = - mCalllogNotificationsHelper.getContactInfo( - missedNumber, - useCallLog ? newestCall.numberPresentation : Calls.PRESENTATION_ALLOWED, - useCallLog ? newestCall.countryIso : null); - + callLogNotificationsQueryHelper.getContactInfo( + call.number, call.numberPresentation, call.countryIso); titleResId = contactInfo.userType == ContactsUtils.USER_TYPE_WORK ? R.string.notification_missedWorkCallTitle : R.string.notification_missedCallTitle; + if (TextUtils.equals(contactInfo.name, contactInfo.formattedNumber) || TextUtils.equals(contactInfo.name, contactInfo.number)) { expandedText = @@ -147,134 +159,195 @@ public class MissedCallNotifier { expandedText = contactInfo.name; } - if (!TextUtils.isEmpty(postCallMessage)) { - // Ex. "John Doe: Hey dude" - expandedText = - mContext.getString( - R.string.post_call_notification_message, expandedText, postCallMessage); - } - ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo); + ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); Bitmap photoIcon = loader.loadPhotoIcon(); if (photoIcon != null) { - builder.setLargeIcon(photoIcon); + groupSummary.setLargeIcon(photoIcon); } } else { titleResId = R.string.notification_missedCallsTitle; - expandedText = mContext.getString(R.string.notification_missedCallsMsg, count); + expandedText = context.getString(R.string.notification_missedCallsMsg, count); } // Create a public viewable version of the notification, suitable for display when sensitive // notification content is hidden. - Notification.Builder publicBuilder = new Notification.Builder(mContext); - publicBuilder - .setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - // Show "Phone" for notification title. - .setContentTitle(mContext.getText(R.string.userCallActivityLabel)) - // Notification details shows that there are missed call(s), but does not reveal - // the missed caller information. - .setContentText(mContext.getText(titleResId)) + Notification.Builder publicSummaryBuilder = createNotificationBuilder(); + publicSummaryBuilder + .setContentTitle(context.getText(titleResId)) .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setShowWhen(true) - .setDeleteIntent(createClearMissedCallsPendingIntent()); + .setDeleteIntent(createClearMissedCallsPendingIntent(null)); + // Create the notification summary suitable for display when sensitive information is showing. + groupSummary + .setContentTitle(context.getText(titleResId)) + .setContentText(expandedText) + .setContentIntent(createCallLogPendingIntent()) + .setDeleteIntent(createClearMissedCallsPendingIntent(null)) + .setGroupSummary(useCallList) + .setOnlyAlertOnce(useCallList) + .setPublicVersion(publicSummaryBuilder.build()); + + NotificationChannelManager.applyChannel( + groupSummary, + context, + Channel.MISSED_CALL, + PhoneAccountHandles.getAccount(context, useCallList ? newCalls.get(0) : null)); + + Notification notification = groupSummary.build(); + configureLedOnNotification(notification); + + LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); + getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + + if (useCallList) { + // Do not repost active notifications to prevent erasing post call notes. + NotificationManager manager = getNotificationMgr(); + Set<String> activeTags = new HashSet<>(); + for (StatusBarNotification activeNotification : manager.getActiveNotifications()) { + activeTags.add(activeNotification.getTag()); + } + + for (NewCall call : newCalls) { + String callTag = call.callsUri.toString(); + if (!activeTags.contains(callTag)) { + manager.notify(callTag, NOTIFICATION_ID, getNotificationForCall(call, null)); + } + } + } + } + + public void insertPostCallNotification(@NonNull String number, @NonNull String note) { + List<NewCall> newCalls = callLogNotificationsQueryHelper.getNewMissedCalls(); + if (newCalls != null && !newCalls.isEmpty()) { + for (NewCall call : newCalls) { + if (call.number.equals(number.replace("tel:", ""))) { + // Update the first notification that matches our post call note sender. + getNotificationMgr() + .notify( + call.callsUri.toString(), NOTIFICATION_ID, getNotificationForCall(call, note)); + break; + } + } + } + } + + private Notification getNotificationForCall( + @NonNull NewCall call, @Nullable String postCallMessage) { + ContactInfo contactInfo = + callLogNotificationsQueryHelper.getContactInfo( + call.number, call.numberPresentation, call.countryIso); + + // Create a public viewable version of the notification, suitable for display when sensitive + // notification content is hidden. + int titleResId = + contactInfo.userType == ContactsUtils.USER_TYPE_WORK + ? R.string.notification_missedWorkCallTitle + : R.string.notification_missedCallTitle; + Notification.Builder publicBuilder = + createNotificationBuilder(call).setContentTitle(context.getText(titleResId)); + + Notification.Builder builder = createNotificationBuilder(call); + CharSequence expandedText; + if (TextUtils.equals(contactInfo.name, contactInfo.formattedNumber) + || TextUtils.equals(contactInfo.name, contactInfo.number)) { + expandedText = + PhoneNumberUtilsCompat.createTtsSpannable( + BidiFormatter.getInstance() + .unicodeWrap(contactInfo.name, TextDirectionHeuristics.LTR)); + } else { + expandedText = contactInfo.name; + } + + if (postCallMessage != null) { + expandedText = + context.getString(R.string.post_call_notification_message, expandedText, postCallMessage); + } + + ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); + Bitmap photoIcon = loader.loadPhotoIcon(); + if (photoIcon != null) { + builder.setLargeIcon(photoIcon); + } // Create the notification suitable for display when sensitive information is showing. builder - .setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - .setContentTitle(mContext.getText(titleResId)) + .setContentTitle(context.getText(titleResId)) .setContentText(expandedText) - .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setShowWhen(true) - .setDefaults(Notification.DEFAULT_VIBRATE) - .setDeleteIntent(createClearMissedCallsPendingIntent()) // Include a public version of the notification to be shown when the missed call // notification is shown on the user's lock screen and they have chosen to hide // sensitive notification information. .setPublicVersion(publicBuilder.build()); - // Add additional actions when there is only 1 missed call and the user isn't locked - if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) { - if (!TextUtils.isEmpty(missedNumber) - && !TextUtils.equals(missedNumber, mContext.getString(R.string.handle_restricted))) { + // Add additional actions when the user isn't locked + if (UserManagerCompat.isUserUnlocked(context)) { + if (!TextUtils.isEmpty(call.number) + && !TextUtils.equals(call.number, context.getString(R.string.handle_restricted))) { builder.addAction( - R.drawable.ic_phone_24dp, - mContext.getString(R.string.notification_missedCall_call_back), - createCallBackPendingIntent(missedNumber)); + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_phone_24dp), + context.getString(R.string.notification_missedCall_call_back), + createCallBackPendingIntent(call.number, call.callsUri)) + .build()); - if (!PhoneNumberHelper.isUriNumber(missedNumber)) { + if (!PhoneNumberHelper.isUriNumber(call.number)) { builder.addAction( - R.drawable.ic_message_24dp, - mContext.getString(R.string.notification_missedCall_message), - createSendSmsFromNotificationPendingIntent(missedNumber)); + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_message_24dp), + context.getString(R.string.notification_missedCall_message), + createSendSmsFromNotificationPendingIntent(call.number, call.callsUri)) + .build()); } } } Notification notification = builder.build(); configureLedOnNotification(notification); + return notification; + } - LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); - getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + private Notification.Builder createNotificationBuilder() { + return new Notification.Builder(context) + .setGroup(NOTIFICATION_TAG) + .setSmallIcon(android.R.drawable.stat_notify_missed_call) + .setColor(context.getResources().getColor(R.color.dialer_theme_color, null)) + .setAutoCancel(true) + .setOnlyAlertOnce(true) + .setShowWhen(true) + .setDefaults(Notification.DEFAULT_VIBRATE); } - private void clearMissedCalls() { - AsyncTask.execute( - new Runnable() { - @Override - public void run() { - // Call log is only accessible when unlocked. If that's the case, clear the list of - // new missed calls from the call log. - if (UserManagerCompat.isUserUnlocked(mContext)) { - ContentValues values = new ContentValues(); - values.put(Calls.NEW, 0); - values.put(Calls.IS_READ, 1); - StringBuilder where = new StringBuilder(); - where.append(Calls.NEW); - where.append(" = 1 AND "); - where.append(Calls.TYPE); - where.append(" = ?"); - try { - mContext - .getContentResolver() - .update( - Calls.CONTENT_URI, - values, - where.toString(), - new String[] {Integer.toString(Calls.MISSED_TYPE)}); - } catch (IllegalArgumentException e) { - LogUtil.e( - "MissedCallNotifier.clearMissedCalls", - "contacts provider update command failed", - e); - } - } - getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - } - }); + private Notification.Builder createNotificationBuilder(@NonNull NewCall call) { + Builder builder = + createNotificationBuilder() + .setWhen(call.dateMs) + .setDeleteIntent(createClearMissedCallsPendingIntent(call.callsUri)) + .setContentIntent(createCallLogPendingIntent(call.callsUri)); + + NotificationChannelManager.applyChannel( + builder, context, Channel.MISSED_CALL, PhoneAccountHandles.getAccount(context, call)); + return builder; } /** Trigger an intent to make a call from a missed call number. */ - public void callBackFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); + @WorkerThread + public void callBackFromMissedCall(String number, Uri callUri) { + closeSystemDialogs(context); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri); + TelecomUtil.cancelMissedCallsNotification(context); DialerUtils.startActivityWithErrorToast( - mContext, + context, new CallIntentBuilder(number, CallInitiationType.Type.MISSED_CALL_NOTIFICATION) .build() .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } /** Trigger an intent to send an sms from a missed call number. */ - public void sendSmsFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); + @WorkerThread + public void sendSmsFromMissedCall(String number, Uri callUri) { + closeSystemDialogs(context); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri); + TelecomUtil.cancelMissedCallsNotification(context); DialerUtils.startActivityWithErrorToast( - mContext, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + context, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } /** @@ -283,34 +356,50 @@ public class MissedCallNotifier { * @return The pending intent. */ private PendingIntent createCallLogPendingIntent() { + return createCallLogPendingIntent(null); + } + + /** + * Creates a new pending intent that sends the user to the call log. + * + * @return The pending intent. + * @param callUri Uri of the call to jump to. May be null + */ + private PendingIntent createCallLogPendingIntent(@Nullable Uri callUri) { Intent contentIntent = - DialtactsActivity.getShowTabIntent(mContext, ListsFragment.TAB_INDEX_HISTORY); - return PendingIntent.getActivity(mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); + DialtactsActivity.getShowTabIntent(context, ListsFragment.TAB_INDEX_HISTORY); + // TODO (b/35486204): scroll to call + contentIntent.setData(callUri); + return PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); } /** Creates a pending intent that marks all new missed calls as old. */ - private PendingIntent createClearMissedCallsPendingIntent() { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createClearMissedCallsPendingIntent(@Nullable Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD); - return PendingIntent.getService(mContext, 0, intent, 0); + intent.setData(callUri); + return PendingIntent.getService(context, 0, intent, 0); } - private PendingIntent createCallBackPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createCallBackPendingIntent(String number, @NonNull Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION); intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); + intent.setData(callUri); // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - private PendingIntent createSendSmsFromNotificationPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createSendSmsFromNotificationPendingIntent( + String number, @NonNull Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION); intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); + intent.setData(callUri); // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } /** Configures a notification to emit the blinky notification light. */ @@ -325,6 +414,6 @@ public class MissedCallNotifier { } private NotificationManager getNotificationMgr() { - return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } } |