summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/app/calllog/MissedCallNotifier.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/app/calllog/MissedCallNotifier.java')
-rw-r--r--java/com/android/dialer/app/calllog/MissedCallNotifier.java405
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);
}
}