| /* |
| * Copyright (C) 2017 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.dialer.app.calllog; |
| |
| import android.app.Notification; |
| import android.app.PendingIntent; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.graphics.Bitmap; |
| import android.net.Uri; |
| import android.os.Build.VERSION; |
| import android.os.Build.VERSION_CODES; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.support.v4.app.NotificationCompat; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import com.android.contacts.common.util.ContactDisplayUtils; |
| import com.android.dialer.app.DialtactsActivity; |
| import com.android.dialer.app.R; |
| import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; |
| import com.android.dialer.app.contactinfo.ContactPhotoLoader; |
| import com.android.dialer.app.list.DialtactsPagerAdapter; |
| import com.android.dialer.common.LogUtil; |
| import com.android.dialer.compat.android.provider.VoicemailCompat; |
| import com.android.dialer.logging.DialerImpression; |
| import com.android.dialer.logging.Logger; |
| import com.android.dialer.notification.DialerNotificationManager; |
| import com.android.dialer.notification.NotificationChannelManager; |
| import com.android.dialer.notification.NotificationManagerUtils; |
| import com.android.dialer.phonenumbercache.ContactInfo; |
| import com.android.dialer.telecom.TelecomUtil; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** Shows a notification in the status bar for visual voicemail. */ |
| final class VisualVoicemailNotifier { |
| /** Prefix used to generate a unique tag for each voicemail notification. */ |
| static final String NOTIFICATION_TAG_PREFIX = "VisualVoicemail_"; |
| /** Common ID for all voicemail notifications. */ |
| static final int NOTIFICATION_ID = 1; |
| /** Tag for the group summary notification. */ |
| static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_VisualVoicemail"; |
| /** |
| * Key used to associate all voicemail notifications and the summary as belonging to a single |
| * group. |
| */ |
| private static final String GROUP_KEY = "VisualVoicemailGroup"; |
| |
| /** |
| * @param shouldAlert whether ringtone or vibration should be made when the notification is posted |
| * or updated. Should only be true when there is a real new voicemail. |
| */ |
| public static void showNotifications( |
| @NonNull Context context, |
| @NonNull List<NewCall> newCalls, |
| @NonNull Map<String, ContactInfo> contactInfos, |
| @Nullable String callers, |
| boolean shouldAlert) { |
| LogUtil.enterBlock("VisualVoicemailNotifier.showNotifications"); |
| PendingIntent deleteIntent = |
| CallLogNotificationsService.createMarkAllNewVoicemailsAsOldIntent(context); |
| String contentTitle = |
| context |
| .getResources() |
| .getQuantityString( |
| R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size()); |
| NotificationCompat.Builder groupSummary = |
| createNotificationBuilder(context) |
| .setContentTitle(contentTitle) |
| .setContentText(callers) |
| .setDeleteIntent(deleteIntent) |
| .setGroupSummary(true) |
| .setContentIntent(newVoicemailIntent(context, null)); |
| |
| if (VERSION.SDK_INT >= VERSION_CODES.O) { |
| if (shouldAlert) { |
| groupSummary.setOnlyAlertOnce(false); |
| // Group summary will alert when posted/updated |
| groupSummary.setGroupAlertBehavior(Notification.GROUP_ALERT_ALL); |
| } else { |
| // Only children will alert. but since all children are set to "only alert summary" it is |
| // effectively silenced. |
| groupSummary.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN); |
| } |
| PhoneAccountHandle handle = getAccountForCall(context, newCalls.get(0)); |
| groupSummary.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle)); |
| } |
| |
| DialerNotificationManager.notify( |
| context, GROUP_SUMMARY_NOTIFICATION_TAG, NOTIFICATION_ID, groupSummary.build()); |
| |
| for (NewCall voicemail : newCalls) { |
| DialerNotificationManager.notify( |
| context, |
| getNotificationTagForVoicemail(voicemail), |
| NOTIFICATION_ID, |
| createNotificationForVoicemail(context, voicemail, contactInfos)); |
| } |
| } |
| |
| public static void cancelAllVoicemailNotifications(@NonNull Context context) { |
| LogUtil.enterBlock("VisualVoicemailNotifier.cancelAllVoicemailNotifications"); |
| NotificationManagerUtils.cancelAllInGroup(context, GROUP_KEY); |
| } |
| |
| public static void cancelSingleVoicemailNotification( |
| @NonNull Context context, @Nullable Uri voicemailUri) { |
| LogUtil.enterBlock("VisualVoicemailNotifier.cancelSingleVoicemailNotification"); |
| if (voicemailUri == null) { |
| LogUtil.e("VisualVoicemailNotifier.cancelSingleVoicemailNotification", "uri is null"); |
| return; |
| } |
| // This will also dismiss the group summary if there are no more voicemail notifications. |
| DialerNotificationManager.cancel( |
| context, getNotificationTagForUri(voicemailUri), NOTIFICATION_ID); |
| } |
| |
| private static String getNotificationTagForVoicemail(@NonNull NewCall voicemail) { |
| return getNotificationTagForUri(voicemail.voicemailUri); |
| } |
| |
| private static String getNotificationTagForUri(@NonNull Uri voicemailUri) { |
| return NOTIFICATION_TAG_PREFIX + voicemailUri; |
| } |
| |
| private static NotificationCompat.Builder createNotificationBuilder(@NonNull Context context) { |
| return new NotificationCompat.Builder(context) |
| .setSmallIcon(android.R.drawable.stat_notify_voicemail) |
| .setColor(context.getColor(R.color.dialer_theme_color)) |
| .setGroup(GROUP_KEY) |
| .setOnlyAlertOnce(true) |
| .setAutoCancel(true); |
| } |
| |
| static Notification createNotificationForVoicemail( |
| @NonNull Context context, |
| @NonNull NewCall voicemail, |
| @NonNull Map<String, ContactInfo> contactInfos) { |
| PhoneAccountHandle handle = getAccountForCall(context, voicemail); |
| ContactInfo contactInfo = contactInfos.get(voicemail.number); |
| |
| NotificationCompat.Builder builder = |
| createNotificationBuilder(context) |
| .setContentTitle( |
| ContactDisplayUtils.getTtsSpannedPhoneNumber( |
| context.getResources(), |
| R.string.notification_new_voicemail_ticker, |
| contactInfo.name)) |
| .setWhen(voicemail.dateMs) |
| .setSound(getVoicemailRingtoneUri(context, handle)) |
| .setDefaults(getNotificationDefaultFlags(context, handle)); |
| |
| if (!TextUtils.isEmpty(voicemail.transcription)) { |
| Logger.get(context) |
| .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION); |
| builder |
| .setContentText(voicemail.transcription) |
| .setStyle(new NotificationCompat.BigTextStyle().bigText(voicemail.transcription)); |
| } else { |
| switch (voicemail.transcriptionState) { |
| case VoicemailCompat.TRANSCRIPTION_IN_PROGRESS: |
| Logger.get(context) |
| .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_IN_PROGRESS); |
| builder.setContentText(context.getString(R.string.voicemail_transcription_in_progress)); |
| break; |
| case VoicemailCompat.TRANSCRIPTION_FAILED_NO_SPEECH_DETECTED: |
| Logger.get(context) |
| .logImpression( |
| DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE); |
| builder.setContentText( |
| context.getString(R.string.voicemail_transcription_failed_no_speech)); |
| break; |
| case VoicemailCompat.TRANSCRIPTION_FAILED_LANGUAGE_NOT_SUPPORTED: |
| Logger.get(context) |
| .logImpression( |
| DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE); |
| builder.setContentText( |
| context.getString(R.string.voicemail_transcription_failed_language_not_supported)); |
| break; |
| case VoicemailCompat.TRANSCRIPTION_FAILED: |
| Logger.get(context) |
| .logImpression( |
| DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE); |
| builder.setContentText(context.getString(R.string.voicemail_transcription_failed)); |
| break; |
| default: |
| Logger.get(context) |
| .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_NO_TRANSCRIPTION); |
| break; |
| } |
| } |
| |
| if (voicemail.voicemailUri != null) { |
| builder.setDeleteIntent( |
| CallLogNotificationsService.createMarkSingleNewVoicemailAsOldIntent( |
| context, voicemail.voicemailUri)); |
| } |
| |
| if (VERSION.SDK_INT >= VERSION_CODES.O) { |
| builder.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle)); |
| builder.setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY); |
| } |
| |
| ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); |
| Bitmap photoIcon = loader.loadPhotoIcon(); |
| if (photoIcon != null) { |
| builder.setLargeIcon(photoIcon); |
| } |
| builder.setContentIntent(newVoicemailIntent(context, voicemail)); |
| Logger.get(context).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED); |
| return builder.build(); |
| } |
| |
| @Nullable |
| private static Uri getVoicemailRingtoneUri( |
| @NonNull Context context, @Nullable PhoneAccountHandle handle) { |
| if (VERSION.SDK_INT < VERSION_CODES.N) { |
| return null; |
| } |
| if (handle == null) { |
| LogUtil.i("VisualVoicemailNotifier.getVoicemailRingtoneUri", "null handle, getting fallback"); |
| handle = getFallbackAccount(context); |
| if (handle == null) { |
| LogUtil.i( |
| "VisualVoicemailNotifier.getVoicemailRingtoneUri", |
| "no fallback handle, using null (default) ringtone"); |
| return null; |
| } |
| } |
| return context.getSystemService(TelephonyManager.class).getVoicemailRingtoneUri(handle); |
| } |
| |
| private static int getNotificationDefaultFlags( |
| @NonNull Context context, @Nullable PhoneAccountHandle handle) { |
| if (VERSION.SDK_INT < VERSION_CODES.N) { |
| return Notification.DEFAULT_ALL; |
| } |
| if (handle == null) { |
| LogUtil.i( |
| "VisualVoicemailNotifier.getNotificationDefaultFlags", "null handle, getting fallback"); |
| handle = getFallbackAccount(context); |
| if (handle == null) { |
| LogUtil.i( |
| "VisualVoicemailNotifier.getNotificationDefaultFlags", |
| "no fallback handle, using default vibration"); |
| return Notification.DEFAULT_ALL; |
| } |
| } |
| if (context.getSystemService(TelephonyManager.class).isVoicemailVibrationEnabled(handle)) { |
| return Notification.DEFAULT_VIBRATE; |
| } |
| return 0; |
| } |
| |
| private static PendingIntent newVoicemailIntent( |
| @NonNull Context context, @Nullable NewCall voicemail) { |
| Intent intent = |
| DialtactsActivity.getShowTabIntent(context, DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL); |
| // TODO (a bug): scroll to this voicemail |
| if (voicemail != null) { |
| intent.setData(voicemail.voicemailUri); |
| } |
| intent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true); |
| return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); |
| } |
| |
| /** |
| * Gets a phone account for the given call entry. This could be null if SIM associated with the |
| * entry is no longer in the device or for other reasons (for example, modem reboot). |
| */ |
| @Nullable |
| public static PhoneAccountHandle getAccountForCall( |
| @NonNull Context context, @Nullable NewCall call) { |
| if (call == null || call.accountComponentName == null || call.accountId == null) { |
| return null; |
| } |
| return new PhoneAccountHandle( |
| ComponentName.unflattenFromString(call.accountComponentName), call.accountId); |
| } |
| |
| /** |
| * Gets any available phone account that can be used to get sound settings for voicemail. This is |
| * only called if the phone account for the voicemail entry can't be found. |
| */ |
| @Nullable |
| public static PhoneAccountHandle getFallbackAccount(@NonNull Context context) { |
| PhoneAccountHandle handle = |
| TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL); |
| if (handle == null) { |
| List<PhoneAccountHandle> handles = TelecomUtil.getCallCapablePhoneAccounts(context); |
| if (!handles.isEmpty()) { |
| handle = handles.get(0); |
| } |
| } |
| return handle; |
| } |
| |
| private VisualVoicemailNotifier() {} |
| } |