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