• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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