1 /* 2 * Copyright (C) 2019 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 17 package com.android.car.dialer.notification; 18 19 import android.app.Notification; 20 import android.app.NotificationChannel; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.drawable.Icon; 26 import android.telecom.Call; 27 import android.text.TextUtils; 28 29 import androidx.annotation.StringRes; 30 31 import com.android.car.dialer.R; 32 import com.android.car.dialer.log.L; 33 import com.android.car.telephony.common.CallDetail; 34 import com.android.car.telephony.common.TelecomUtils; 35 36 import java.util.HashSet; 37 import java.util.Set; 38 import java.util.concurrent.CompletableFuture; 39 40 import javax.inject.Inject; 41 import javax.inject.Singleton; 42 43 import dagger.hilt.android.qualifiers.ApplicationContext; 44 45 /** Controller that manages the heads up notification for incoming calls. */ 46 @Singleton 47 public final class InCallNotificationController { 48 private static final String TAG = "CD.InCallNotificationController"; 49 private static final String CHANNEL_ID = "com.android.car.dialer.incoming"; 50 // A random number that is used for notification id. 51 private static final int NOTIFICATION_ID = 20181105; 52 53 private boolean mShowFullscreenIncallUi; 54 55 private final Context mContext; 56 private final NotificationManager mNotificationManager; 57 private final Notification.Builder mNotificationBuilder; 58 private final Set<String> mActiveInCallNotifications; 59 private CompletableFuture<Void> mNotificationFuture; 60 61 @Inject InCallNotificationController(@pplicationContext Context context)62 public InCallNotificationController(@ApplicationContext Context context) { 63 mContext = context; 64 65 mShowFullscreenIncallUi = mContext.getResources().getBoolean( 66 R.bool.config_show_hun_fullscreen_incall_ui); 67 mNotificationManager = 68 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 69 70 CharSequence name = mContext.getString(R.string.in_call_notification_channel_name); 71 NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, name, 72 NotificationManager.IMPORTANCE_HIGH); 73 mNotificationManager.createNotificationChannel(notificationChannel); 74 75 mNotificationBuilder = new Notification.Builder(mContext, CHANNEL_ID) 76 .setSmallIcon(R.drawable.ic_phone) 77 .setColor(mContext.getColor(R.color.notification_app_icon_color)) 78 .setCategory(Notification.CATEGORY_CALL) 79 .setOngoing(true) 80 .setAutoCancel(false); 81 82 mActiveInCallNotifications = new HashSet<>(); 83 } 84 85 86 /** Show a new incoming call notification or update the existing incoming call notification. */ showInCallNotification(Call call)87 public void showInCallNotification(Call call) { 88 L.d(TAG, "showInCallNotification"); 89 90 if (mNotificationFuture != null) { 91 mNotificationFuture.cancel(true); 92 } 93 94 CallDetail callDetail = CallDetail.fromTelecomCallDetail(call.getDetails()); 95 String callNumber = callDetail.getNumber(); 96 mActiveInCallNotifications.add(callNumber); 97 98 if (mShowFullscreenIncallUi) { 99 mNotificationBuilder.setFullScreenIntent( 100 getFullscreenIntent(call), /* highPriority= */true); 101 } 102 mNotificationBuilder 103 .setLargeIcon((Icon) null) 104 .setContentTitle(TelecomUtils.getBidiWrappedNumber(callNumber)) 105 .setContentText(mContext.getString(R.string.notification_incoming_call)) 106 .setActions( 107 getAction(call, R.string.answer_call, 108 NotificationService.ACTION_ANSWER_CALL), 109 getAction(call, R.string.decline_call, 110 NotificationService.ACTION_DECLINE_CALL)); 111 mNotificationManager.notify( 112 callNumber, 113 NOTIFICATION_ID, 114 mNotificationBuilder.build()); 115 116 mNotificationFuture = NotificationUtils.getDisplayNameAndRoundedAvatar(mContext, callNumber) 117 .thenAcceptAsync((pair) -> { 118 // Check that the notification hasn't already been dismissed 119 if (mActiveInCallNotifications.contains(callNumber)) { 120 mNotificationBuilder 121 .setLargeIcon(pair.second) 122 .setContentTitle(TelecomUtils.getBidiWrappedNumber(pair.first)); 123 124 mNotificationManager.notify( 125 callNumber, 126 NOTIFICATION_ID, 127 mNotificationBuilder.build()); 128 } 129 }, mContext.getMainExecutor()); 130 } 131 132 /** Cancel the incoming call notification for the given call. */ cancelInCallNotification(Call call)133 public void cancelInCallNotification(Call call) { 134 L.d(TAG, "cancelInCallNotification"); 135 if (call.getDetails() != null) { 136 String callNumber = CallDetail.fromTelecomCallDetail(call.getDetails()).getNumber(); 137 cancelInCallNotification(callNumber); 138 } 139 } 140 141 /** 142 * Cancel the incoming call notification for the given call id. Any action that dismisses the 143 * notification needs to call this explicitly. 144 */ cancelInCallNotification(String callId)145 void cancelInCallNotification(String callId) { 146 if (TextUtils.isEmpty(callId)) { 147 return; 148 } 149 mActiveInCallNotifications.remove(callId); 150 mNotificationManager.cancel(callId, NOTIFICATION_ID); 151 } 152 getFullscreenIntent(Call call)153 private PendingIntent getFullscreenIntent(Call call) { 154 Intent intent = getIntent(NotificationService.ACTION_SHOW_FULLSCREEN_UI, call); 155 return PendingIntent.getService(mContext, 0, intent, 156 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 157 } 158 getAction(Call call, @StringRes int actionText, String intentAction)159 private Notification.Action getAction(Call call, @StringRes int actionText, 160 String intentAction) { 161 CharSequence text = mContext.getString(actionText); 162 PendingIntent intent = PendingIntent.getService( 163 mContext, 164 0, 165 getIntent(intentAction, call), 166 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 167 return new Notification.Action.Builder(null, text, intent).build(); 168 } 169 getIntent(String action, Call call)170 private Intent getIntent(String action, Call call) { 171 Intent intent = new Intent(action, null, mContext, NotificationService.class); 172 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 173 intent.putExtra(NotificationService.EXTRA_PHONE_NUMBER, 174 CallDetail.fromTelecomCallDetail(call.getDetails()).getNumber()); 175 return intent; 176 } 177 } 178