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.annotation.TargetApi; 20 import android.app.Notification; 21 import android.app.NotificationChannel; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.graphics.drawable.Icon; 27 import android.text.TextUtils; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 import androidx.annotation.StringRes; 32 import androidx.core.util.Pair; 33 import androidx.lifecycle.Observer; 34 35 import com.android.car.dialer.Constants; 36 import com.android.car.dialer.R; 37 import com.android.car.dialer.livedata.UnreadMissedCallLiveData; 38 import com.android.car.dialer.log.L; 39 import com.android.car.dialer.ui.TelecomActivity; 40 import com.android.car.dialer.ui.TelecomPageTab; 41 import com.android.car.telephony.common.PhoneCallLog; 42 43 import java.util.ArrayList; 44 import java.util.Collections; 45 import java.util.List; 46 47 /** Controller that manages the missed call notifications. */ 48 public final class MissedCallNotificationController { 49 private static final String TAG = "CD.MissedCallNotification"; 50 private static final String CHANNEL_ID = "com.android.car.dialer.missedcall"; 51 // A random number that is used for notification id. 52 private static final int NOTIFICATION_ID = 20190520; 53 54 private static MissedCallNotificationController sMissedCallNotificationController; 55 56 /** 57 * Initialized a globally accessible {@link MissedCallNotificationController} which can be 58 * retrieved by {@link #get}. If this function is called a second time before calling {@link 59 * #tearDown()}, an {@link IllegalStateException} will be thrown. 60 * 61 * @param applicationContext Application context. 62 */ init(Context applicationContext)63 public static void init(Context applicationContext) { 64 if (sMissedCallNotificationController == null) { 65 sMissedCallNotificationController = new MissedCallNotificationController( 66 applicationContext); 67 } else { 68 throw new IllegalStateException( 69 "MissedCallNotificationController has been initialized."); 70 } 71 } 72 73 /** 74 * Gets the global {@link MissedCallNotificationController} instance. Make sure {@link 75 * #init(Context)} is called before calling this method. 76 */ get()77 public static MissedCallNotificationController get() { 78 if (sMissedCallNotificationController == null) { 79 throw new IllegalStateException( 80 "Call MissedCallNotificationController.init(Context) before calling this " 81 + "function"); 82 } 83 return sMissedCallNotificationController; 84 } 85 86 /** Tear down the global missed call notification controller. */ tearDown()87 public void tearDown() { 88 mUnreadMissedCallLiveData.removeObserver(mUnreadMissedCallObserver); 89 sMissedCallNotificationController = null; 90 } 91 92 private final Context mContext; 93 private final NotificationManager mNotificationManager; 94 private final UnreadMissedCallLiveData mUnreadMissedCallLiveData; 95 private final Observer<List<PhoneCallLog>> mUnreadMissedCallObserver; 96 private final List<PhoneCallLog> mCurrentPhoneCallLogList; 97 98 @TargetApi(26) MissedCallNotificationController(Context context)99 private MissedCallNotificationController(Context context) { 100 mContext = context; 101 mNotificationManager = 102 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 103 CharSequence name = mContext.getString(R.string.missed_call_notification_channel_name); 104 NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, name, 105 NotificationManager.IMPORTANCE_DEFAULT); 106 mNotificationManager.createNotificationChannel(notificationChannel); 107 108 mCurrentPhoneCallLogList = new ArrayList<>(); 109 mUnreadMissedCallLiveData = UnreadMissedCallLiveData.newInstance(context); 110 mUnreadMissedCallObserver = this::updateNotifications; 111 mUnreadMissedCallLiveData.observeForever(mUnreadMissedCallObserver); 112 } 113 114 /** 115 * The phone call log list might be null when switching users if permission gets denied and 116 * throws exception. 117 */ updateNotifications(@ullable List<PhoneCallLog> phoneCallLogs)118 private void updateNotifications(@Nullable List<PhoneCallLog> phoneCallLogs) { 119 List<PhoneCallLog> updatedPhoneCallLogs = 120 phoneCallLogs == null ? Collections.emptyList() : phoneCallLogs; 121 for (PhoneCallLog phoneCallLog : updatedPhoneCallLogs) { 122 showMissedCallNotification(phoneCallLog); 123 if (mCurrentPhoneCallLogList.contains(phoneCallLog)) { 124 mCurrentPhoneCallLogList.remove(phoneCallLog); 125 } 126 } 127 128 for (PhoneCallLog phoneCallLog : mCurrentPhoneCallLogList) { 129 cancelMissedCallNotification(phoneCallLog); 130 } 131 mCurrentPhoneCallLogList.clear(); 132 mCurrentPhoneCallLogList.addAll(updatedPhoneCallLogs); 133 } 134 showMissedCallNotification(PhoneCallLog phoneCallLog)135 private void showMissedCallNotification(PhoneCallLog phoneCallLog) { 136 L.d(TAG, "show missed call notification %s", phoneCallLog); 137 String phoneNumberString = phoneCallLog.getPhoneNumberString(); 138 Pair<String, Icon> displayNameAndRoundedAvatar = 139 NotificationUtils.getDisplayNameAndRoundedAvatar(mContext, phoneNumberString); 140 Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID) 141 .setSmallIcon(R.drawable.ic_phone) 142 .setLargeIcon(displayNameAndRoundedAvatar.second) 143 .setContentTitle( 144 mContext.getString(R.string.notification_missed_call) + String.format( 145 " (%d)", phoneCallLog.getAllCallRecords().size())) 146 .setContentText(displayNameAndRoundedAvatar.first) 147 .setContentIntent(getContentPendingIntent()) 148 .setDeleteIntent(getDeleteIntent()) 149 .setOnlyAlertOnce(true) 150 .setShowWhen(true) 151 .setWhen(phoneCallLog.getLastCallEndTimestamp()) 152 .setAutoCancel(false); 153 154 if (!TextUtils.isEmpty(phoneNumberString)) { 155 builder.addAction(getAction(phoneNumberString, R.string.call_back, 156 NotificationService.ACTION_CALL_BACK_MISSED)); 157 // TODO: add action button to send message 158 } 159 160 mNotificationManager.notify( 161 getTag(phoneCallLog), 162 NOTIFICATION_ID, 163 builder.build()); 164 } 165 cancelMissedCallNotification(PhoneCallLog phoneCallLog)166 private void cancelMissedCallNotification(PhoneCallLog phoneCallLog) { 167 L.d(TAG, "cancel missed call notification %s", phoneCallLog); 168 mNotificationManager.cancel(getTag(phoneCallLog), NOTIFICATION_ID); 169 } 170 getContentPendingIntent()171 private PendingIntent getContentPendingIntent() { 172 Intent intent = new Intent(mContext, TelecomActivity.class); 173 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 174 intent.setAction(Constants.Intents.ACTION_SHOW_PAGE); 175 intent.putExtra(Constants.Intents.EXTRA_SHOW_PAGE, TelecomPageTab.Page.CALL_HISTORY); 176 intent.putExtra(Constants.Intents.EXTRA_ACTION_READ_MISSED, true); 177 PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 178 PendingIntent.FLAG_UPDATE_CURRENT); 179 return pendingIntent; 180 } 181 getDeleteIntent()182 private PendingIntent getDeleteIntent() { 183 Intent intent = new Intent(NotificationService.ACTION_READ_ALL_MISSED, null, mContext, 184 NotificationReceiver.class); 185 PendingIntent pendingIntent = PendingIntent.getBroadcast( 186 mContext, 187 0, 188 intent, 189 PendingIntent.FLAG_UPDATE_CURRENT); 190 return pendingIntent; 191 } 192 getAction(String phoneNumberString, @StringRes int actionText, String intentAction)193 private Notification.Action getAction(String phoneNumberString, @StringRes int actionText, 194 String intentAction) { 195 CharSequence text = mContext.getString(actionText); 196 PendingIntent intent = PendingIntent.getBroadcast( 197 mContext, 198 0, 199 getIntent(intentAction, phoneNumberString), 200 PendingIntent.FLAG_UPDATE_CURRENT); 201 return new Notification.Action.Builder(null, text, intent).build(); 202 } 203 getIntent(String action, String phoneNumberString)204 private Intent getIntent(String action, String phoneNumberString) { 205 Intent intent = new Intent(action, null, mContext, NotificationReceiver.class); 206 intent.putExtra(NotificationService.EXTRA_CALL_ID, phoneNumberString); 207 return intent; 208 } 209 getTag(@onNull PhoneCallLog phoneCallLog)210 private String getTag(@NonNull PhoneCallLog phoneCallLog) { 211 return String.valueOf(phoneCallLog.hashCode()); 212 } 213 } 214