diff options
Diffstat (limited to 'java/com/android/incallui/spam/SpamCallListListener.java')
-rw-r--r-- | java/com/android/incallui/spam/SpamCallListListener.java | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java new file mode 100644 index 000000000..0897842de --- /dev/null +++ b/java/com/android/incallui/spam/SpamCallListListener.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui.spam; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.telecom.DisconnectCause; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; +import com.android.contacts.common.GeoUtil; +import com.android.contacts.common.compat.PhoneNumberUtilsCompat; +import com.android.dialer.blocking.FilteredNumberCompat; +import com.android.dialer.blocking.FilteredNumbersUtil; +import com.android.dialer.common.LogUtil; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.ContactLookupResult; +import com.android.dialer.logging.nano.DialerImpression; +import com.android.dialer.spam.Spam; +import com.android.incallui.R; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; +import com.android.incallui.call.DialerCall.CallHistoryStatus; +import com.android.incallui.call.DialerCall.SessionModificationState; +import java.util.Random; + +/** + * Creates notifications after a call ends if the call matched the criteria (incoming, accepted, + * etc). + */ +public class SpamCallListListener implements CallList.Listener { + + static final int NOTIFICATION_ID = 1; + private static final String TAG = "SpamCallListListener"; + private final Context context; + private final Random random; + + public SpamCallListListener(Context context) { + this.context = context; + this.random = new Random(); + } + + public SpamCallListListener(Context context, Random rand) { + this.context = context; + this.random = rand; + } + + private static String pii(String pii) { + return com.android.incallui.Log.pii(pii); + } + + @Override + public void onIncomingCall(final DialerCall call) { + String number = call.getNumber(); + if (TextUtils.isEmpty(number)) { + return; + } + NumberInCallHistoryTask.Listener listener = + new NumberInCallHistoryTask.Listener() { + @Override + public void onComplete(@CallHistoryStatus int callHistoryStatus) { + call.setCallHistoryStatus(callHistoryStatus); + } + }; + new NumberInCallHistoryTask(context, listener, number, GeoUtil.getCurrentCountryIso(context)) + .submitTask(); + } + + @Override + public void onUpgradeToVideo(DialerCall call) {} + + @Override + public void onSessionModificationStateChange(@SessionModificationState int newState) {} + + @Override + public void onCallListChange(CallList callList) {} + + @Override + public void onWiFiToLteHandover(DialerCall call) {} + + @Override + public void onHandoverToWifiFailed(DialerCall call) {} + + @Override + public void onDisconnect(DialerCall call) { + if (!shouldShowAfterCallNotification(call)) { + return; + } + String e164Number = + PhoneNumberUtils.formatNumberToE164( + call.getNumber(), GeoUtil.getCurrentCountryIso(context)); + if (!FilteredNumbersUtil.canBlockNumber(context, e164Number, call.getNumber()) + || !FilteredNumberCompat.canAttemptBlockOperations(context)) { + return; + } + if (e164Number == null) { + return; + } + showNotification(call); + } + + /** Posts the intent for displaying the after call spam notification to the user. */ + private void showNotification(DialerCall call) { + if (call.isSpam()) { + maybeShowSpamCallNotification(call); + } else { + LogUtil.d(TAG, "Showing not spam notification for number=" + pii(call.getNumber())); + maybeShowNonSpamCallNotification(call); + } + } + + /** Determines if the after call notification should be shown for the specified call. */ + private boolean shouldShowAfterCallNotification(DialerCall call) { + if (!Spam.get(context).isSpamNotificationEnabled()) { + return false; + } + + String number = call.getNumber(); + if (TextUtils.isEmpty(number)) { + return false; + } + + DialerCall.LogState logState = call.getLogState(); + if (!logState.isIncoming) { + return false; + } + + if (logState.duration <= 0) { + return false; + } + + if (logState.contactLookupResult != ContactLookupResult.Type.NOT_FOUND + && logState.contactLookupResult != ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE) { + return false; + } + + int callHistoryStatus = call.getCallHistoryStatus(); + if (callHistoryStatus == DialerCall.CALL_HISTORY_STATUS_PRESENT) { + return false; + } else if (callHistoryStatus == DialerCall.CALL_HISTORY_STATUS_UNKNOWN) { + LogUtil.i(TAG, "DialerCall history status is unknown, returning false"); + return false; + } + + // Check if call disconnected because of either user hanging up + int disconnectCause = call.getDisconnectCause().getCode(); + if (disconnectCause != DisconnectCause.LOCAL && disconnectCause != DisconnectCause.REMOTE) { + return false; + } + + LogUtil.i(TAG, "shouldShowAfterCallNotification, returning true"); + return true; + } + + /** + * Creates a notification builder with properties common among the two after call notifications. + */ + private Notification.Builder createAfterCallNotificationBuilder(DialerCall call) { + return new Notification.Builder(context) + .setContentIntent( + createActivityPendingIntent(call, SpamNotificationActivity.ACTION_SHOW_DIALOG)) + .setCategory(Notification.CATEGORY_STATUS) + .setPriority(Notification.PRIORITY_DEFAULT) + .setColor(context.getColor(R.color.dialer_theme_color)) + .setSmallIcon(R.drawable.ic_call_end_white_24dp); + } + + private CharSequence getDisplayNumber(DialerCall call) { + String formattedNumber = + PhoneNumberUtils.formatNumber(call.getNumber(), GeoUtil.getCurrentCountryIso(context)); + return PhoneNumberUtilsCompat.createTtsSpannable(formattedNumber); + } + + /** Display a notification with two actions: "add contact" and "report spam". */ + private void showNonSpamCallNotification(DialerCall call) { + Notification.Builder notificationBuilder = + createAfterCallNotificationBuilder(call) + .setLargeIcon(Icon.createWithResource(context, R.drawable.unknown_notification_icon)) + .setContentText( + context.getString(R.string.spam_notification_non_spam_call_collapsed_text)) + .setStyle( + new Notification.BigTextStyle() + .bigText( + context.getString(R.string.spam_notification_non_spam_call_expanded_text))) + // Add contact + .addAction( + new Notification.Action.Builder( + R.drawable.ic_person_add_grey600_24dp, + context.getString(R.string.spam_notification_add_contact_action_text), + createActivityPendingIntent( + call, SpamNotificationActivity.ACTION_ADD_TO_CONTACTS)) + .build()) + // Block/report spam + .addAction( + new Notification.Action.Builder( + R.drawable.ic_block_grey600_24dp, + context.getString(R.string.spam_notification_report_spam_action_text), + createBlockReportSpamPendingIntent(call)) + .build()) + .setContentTitle( + context.getString(R.string.non_spam_notification_title, getDisplayNumber(call))); + ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)) + .notify(call.getNumber(), NOTIFICATION_ID, notificationBuilder.build()); + } + + private boolean shouldThrottleSpamNotification() { + int randomNumber = random.nextInt(100); + int thresholdForShowing = Spam.get(context).percentOfSpamNotificationsToShow(); + if (thresholdForShowing == 0) { + LogUtil.d( + TAG, + "shouldThrottleSpamNotification, not showing - percentOfSpamNotificationsToShow is 0"); + return true; + } else if (randomNumber < thresholdForShowing) { + LogUtil.d( + TAG, + "shouldThrottleSpamNotification, showing " + randomNumber + " < " + thresholdForShowing); + return false; + } else { + LogUtil.d( + TAG, + "shouldThrottleSpamNotification, not showing " + + randomNumber + + " >= " + + thresholdForShowing); + return true; + } + } + + private boolean shouldThrottleNonSpamNotification() { + int randomNumber = random.nextInt(100); + int thresholdForShowing = Spam.get(context).percentOfNonSpamNotificationsToShow(); + if (thresholdForShowing == 0) { + LogUtil.d(TAG, "Not showing non spam notification: percentOfNonSpamNotificationsToShow is 0"); + return true; + } else if (randomNumber < thresholdForShowing) { + LogUtil.d( + TAG, "Showing non spam notification: " + randomNumber + " < " + thresholdForShowing); + return false; + } else { + LogUtil.d( + TAG, "Not showing non spam notification:" + randomNumber + " >= " + thresholdForShowing); + return true; + } + } + + private void maybeShowSpamCallNotification(DialerCall call) { + if (shouldThrottleSpamNotification()) { + Logger.get(context) + .logCallImpression( + DialerImpression.Type.SPAM_NOTIFICATION_NOT_SHOWN_AFTER_THROTTLE, + call.getUniqueCallId(), + call.getTimeAddedMs()); + } else { + Logger.get(context) + .logCallImpression( + DialerImpression.Type.SPAM_NOTIFICATION_SHOWN_AFTER_THROTTLE, + call.getUniqueCallId(), + call.getTimeAddedMs()); + showSpamCallNotification(call); + } + } + + private void maybeShowNonSpamCallNotification(DialerCall call) { + if (shouldThrottleNonSpamNotification()) { + Logger.get(context) + .logCallImpression( + DialerImpression.Type.NON_SPAM_NOTIFICATION_NOT_SHOWN_AFTER_THROTTLE, + call.getUniqueCallId(), + call.getTimeAddedMs()); + } else { + Logger.get(context) + .logCallImpression( + DialerImpression.Type.NON_SPAM_NOTIFICATION_SHOWN_AFTER_THROTTLE, + call.getUniqueCallId(), + call.getTimeAddedMs()); + showNonSpamCallNotification(call); + } + } + + /** Display a notification with the action "not spam". */ + private void showSpamCallNotification(DialerCall call) { + Notification.Builder notificationBuilder = + createAfterCallNotificationBuilder(call) + .setLargeIcon(Icon.createWithResource(context, R.drawable.spam_notification_icon)) + .setContentText(context.getString(R.string.spam_notification_spam_call_collapsed_text)) + .setStyle( + new Notification.BigTextStyle() + .bigText(context.getString(R.string.spam_notification_spam_call_expanded_text))) + // Not spam + .addAction( + new Notification.Action.Builder( + R.drawable.ic_close_grey600_24dp, + context.getString(R.string.spam_notification_not_spam_action_text), + createNotSpamPendingIntent(call)) + .build()) + // Block/report spam + .addAction( + new Notification.Action.Builder( + R.drawable.ic_block_grey600_24dp, + context.getString(R.string.spam_notification_block_spam_action_text), + createBlockReportSpamPendingIntent(call)) + .build()) + .setContentTitle( + context.getString(R.string.spam_notification_title, getDisplayNumber(call))); + ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)) + .notify(call.getNumber(), NOTIFICATION_ID, notificationBuilder.build()); + } + + /** + * Creates a pending intent for block/report spam action. If enabled, this intent is forwarded to + * the {@link SpamNotificationActivity}, otherwise to the {@link SpamNotificationService}. + */ + private PendingIntent createBlockReportSpamPendingIntent(DialerCall call) { + String action = SpamNotificationActivity.ACTION_MARK_NUMBER_AS_SPAM; + return Spam.get(context).isDialogEnabledForSpamNotification() + ? createActivityPendingIntent(call, action) + : createServicePendingIntent(call, action); + } + + /** + * Creates a pending intent for not spam action. If enabled, this intent is forwarded to the + * {@link SpamNotificationActivity}, otherwise to the {@link SpamNotificationService}. + */ + private PendingIntent createNotSpamPendingIntent(DialerCall call) { + String action = SpamNotificationActivity.ACTION_MARK_NUMBER_AS_NOT_SPAM; + return Spam.get(context).isDialogEnabledForSpamNotification() + ? createActivityPendingIntent(call, action) + : createServicePendingIntent(call, action); + } + + /** Creates a pending intent for {@link SpamNotificationService}. */ + private PendingIntent createServicePendingIntent(DialerCall call, String action) { + Intent intent = + SpamNotificationService.createServiceIntent(context, call, action, NOTIFICATION_ID); + return PendingIntent.getService( + context, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_ONE_SHOT); + } + + /** Creates a pending intent for {@link SpamNotificationActivity}. */ + private PendingIntent createActivityPendingIntent(DialerCall call, String action) { + Intent intent = + SpamNotificationActivity.createActivityIntent(context, call, action, NOTIFICATION_ID); + return PendingIntent.getActivity( + context, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_ONE_SHOT); + } +} |