1 /* 2 * Copyright (C) 2018 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 package com.android.car.notification.template; 17 18 import android.app.Notification; 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.graphics.PorterDuff; 22 import android.graphics.PorterDuffColorFilter; 23 import android.graphics.drawable.Drawable; 24 import android.util.AttributeSet; 25 import android.view.View; 26 import android.widget.Button; 27 import android.widget.RelativeLayout; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.car.assist.client.CarAssistUtils; 34 import com.android.car.notification.AlertEntry; 35 import com.android.car.notification.NotificationClickHandlerFactory; 36 import com.android.car.notification.NotificationDataManager; 37 import com.android.car.notification.PreprocessingManager; 38 import com.android.car.notification.R; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 43 /** 44 * Notification actions view that contains the buttons that fire actions. 45 */ 46 public class CarNotificationActionsView extends RelativeLayout implements 47 PreprocessingManager.CallStateListener { 48 49 // Maximum 3 actions 50 // https://developer.android.com/reference/android/app/Notification.Builder.html#addAction 51 @VisibleForTesting 52 static final int MAX_NUM_ACTIONS = 3; 53 @VisibleForTesting 54 static final int FIRST_MESSAGE_ACTION_BUTTON_INDEX = 0; 55 @VisibleForTesting 56 static final int SECOND_MESSAGE_ACTION_BUTTON_INDEX = 1; 57 58 private final List<Button> mActionButtons = new ArrayList<>(); 59 private final Context mContext; 60 private final CarAssistUtils mCarAssistUtils; 61 62 private boolean mIsCategoryCall; 63 private boolean mIsInCall; 64 CarNotificationActionsView(Context context)65 public CarNotificationActionsView(Context context) { 66 this(context, /* attrs= */ null); 67 } 68 CarNotificationActionsView(Context context, AttributeSet attrs)69 public CarNotificationActionsView(Context context, AttributeSet attrs) { 70 this(context, attrs, /* defStyleAttr= */ 0); 71 } 72 CarNotificationActionsView(Context context, AttributeSet attrs, int defStyleAttr)73 public CarNotificationActionsView(Context context, AttributeSet attrs, int defStyleAttr) { 74 this(context, attrs, defStyleAttr, /* defStyleRes= */ 0); 75 } 76 CarNotificationActionsView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)77 public CarNotificationActionsView( 78 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 79 this(context, attrs, defStyleAttr, defStyleRes, new CarAssistUtils(context)); 80 } 81 82 @VisibleForTesting CarNotificationActionsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, @NonNull CarAssistUtils carAssistUtils)83 CarNotificationActionsView(Context context, AttributeSet attrs, int defStyleAttr, 84 int defStyleRes, @NonNull CarAssistUtils carAssistUtils) { 85 super(context, attrs, defStyleAttr, defStyleRes); 86 87 mContext = context; 88 mCarAssistUtils = carAssistUtils; 89 init(attrs); 90 } 91 init(@ullable AttributeSet attrs)92 private void init(@Nullable AttributeSet attrs) { 93 if (attrs != null) { 94 TypedArray attributes = 95 mContext.obtainStyledAttributes(attrs, R.styleable.CarNotificationActionsView); 96 mIsCategoryCall = 97 attributes.getBoolean(R.styleable.CarNotificationActionsView_categoryCall, 98 /* defaultValue= */ false); 99 attributes.recycle(); 100 } 101 102 PreprocessingManager.getInstance(mContext).addCallStateListener(this); 103 104 inflate(mContext, R.layout.car_notification_actions_view, /* root= */ this); 105 } 106 107 /** 108 * Binds the notification action buttons. 109 * 110 * @param clickHandlerFactory factory class used to generate {@link OnClickListener}s. 111 * @param alertEntry the notification that contains the actions. 112 */ bind(NotificationClickHandlerFactory clickHandlerFactory, AlertEntry alertEntry)113 public void bind(NotificationClickHandlerFactory clickHandlerFactory, AlertEntry alertEntry) { 114 Notification notification = alertEntry.getNotification(); 115 Notification.Action[] actions = notification.actions; 116 if (actions == null || actions.length == 0) { 117 return; 118 } 119 120 if (CarAssistUtils.isCarCompatibleMessagingNotification( 121 alertEntry.getStatusBarNotification())) { 122 boolean canPlayMessage = mCarAssistUtils.hasActiveAssistant() 123 || mCarAssistUtils.isFallbackAssistantEnabled(); 124 if (canPlayMessage) { 125 createPlayButton(clickHandlerFactory, alertEntry); 126 } 127 createMuteButton(clickHandlerFactory, alertEntry); 128 return; 129 } 130 131 int length = Math.min(actions.length, MAX_NUM_ACTIONS); 132 for (int i = 0; i < length; i++) { 133 Notification.Action action = actions[i]; 134 Button button = mActionButtons.get(i); 135 button.setVisibility(View.VISIBLE); 136 // clear spannables and only use the text 137 button.setText(action.title.toString()); 138 139 if (action.actionIntent != null) { 140 button.setOnClickListener(clickHandlerFactory.getActionClickHandler(alertEntry, i)); 141 } 142 } 143 144 if (mIsCategoryCall) { 145 Drawable acceptButton = mContext.getResources().getDrawable( 146 R.drawable.call_action_button_background); 147 acceptButton.setColorFilter( 148 new PorterDuffColorFilter(mContext.getColor(R.color.call_accept_button), 149 PorterDuff.Mode.SRC_IN)); 150 mActionButtons.get(0).setBackground(acceptButton); 151 152 Drawable declineButton = mContext.getResources().getDrawable( 153 R.drawable.call_action_button_background); 154 declineButton.setColorFilter( 155 new PorterDuffColorFilter(mContext.getColor(R.color.call_decline_button), 156 PorterDuff.Mode.SRC_IN)); 157 mActionButtons.get(1).setBackground(declineButton); 158 } 159 } 160 161 /** 162 * Resets the notification actions empty for recycling. 163 */ reset()164 public void reset() { 165 for (Button button : mActionButtons) { 166 button.setVisibility(View.GONE); 167 button.setText(null); 168 button.setOnClickListener(null); 169 } 170 PreprocessingManager.getInstance(getContext()).removeCallStateListener(this); 171 } 172 173 @Override onFinishInflate()174 protected void onFinishInflate() { 175 super.onFinishInflate(); 176 mActionButtons.add(findViewById(R.id.action_1)); 177 mActionButtons.add(findViewById(R.id.action_2)); 178 mActionButtons.add(findViewById(R.id.action_3)); 179 } 180 181 @VisibleForTesting getActionButtons()182 List<Button> getActionButtons() { 183 return mActionButtons; 184 } 185 186 @VisibleForTesting setCategoryIsCall(boolean isCall)187 void setCategoryIsCall(boolean isCall) { 188 mIsCategoryCall = isCall; 189 } 190 191 /** 192 * The Play button triggers the assistant to read the message aloud, optionally prompting the 193 * user to reply to the message afterwards. 194 */ createPlayButton(NotificationClickHandlerFactory clickHandlerFactory, AlertEntry alertEntry)195 private void createPlayButton(NotificationClickHandlerFactory clickHandlerFactory, 196 AlertEntry alertEntry) { 197 if (mIsInCall) return; 198 199 Button button = mActionButtons.get(FIRST_MESSAGE_ACTION_BUTTON_INDEX); 200 button.setText(mContext.getString(R.string.assist_action_play_label)); 201 button.setVisibility(View.VISIBLE); 202 button.setOnClickListener( 203 clickHandlerFactory.getPlayClickHandler(alertEntry)); 204 } 205 206 /** 207 * The Mute button allows users to toggle whether or not incoming notification with the same 208 * statusBarNotification key will be shown with a HUN and trigger a notification sound. 209 */ createMuteButton(NotificationClickHandlerFactory clickHandlerFactory, AlertEntry alertEntry)210 private void createMuteButton(NotificationClickHandlerFactory clickHandlerFactory, 211 AlertEntry alertEntry) { 212 int index = SECOND_MESSAGE_ACTION_BUTTON_INDEX; 213 if (mIsInCall) index = FIRST_MESSAGE_ACTION_BUTTON_INDEX; 214 215 Button button = mActionButtons.get(index); 216 NotificationDataManager manager = clickHandlerFactory.getNotificationDataManager(); 217 button.setText((manager != null && manager.isMessageNotificationMuted(alertEntry)) 218 ? mContext.getString(R.string.action_unmute_long) 219 : mContext.getString(R.string.action_mute_long)); 220 button.setVisibility(View.VISIBLE); 221 button.setOnClickListener(clickHandlerFactory.getMuteClickHandler(button, alertEntry)); 222 } 223 224 /** Implementation of {@link PreprocessingManager.CallStateListener} **/ 225 @Override onCallStateChanged(boolean isInCall)226 public void onCallStateChanged(boolean isInCall) { 227 mIsInCall = isInCall; 228 } 229 } 230