1 /*
2  * Copyright (C) 2020 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.systemui.car.systembar;
18 
19 import static com.android.systemui.car.systembar.CarSystemBar.DEBUG;
20 
21 import android.annotation.IntDef;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.util.AttributeSet;
25 import android.util.Log;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.LinearLayout;
30 
31 import com.android.car.dockutil.Flags;
32 import com.android.systemui.R;
33 import com.android.systemui.car.hvac.HvacPanelOverlayViewController;
34 import com.android.systemui.car.hvac.HvacView;
35 import com.android.systemui.car.hvac.TemperatureControlView;
36 import com.android.systemui.car.notification.NotificationPanelViewController;
37 import com.android.systemui.car.systembar.CarSystemBarController.HvacPanelController;
38 import com.android.systemui.car.systembar.CarSystemBarController.NotificationsShadeController;
39 import com.android.systemui.settings.UserTracker;
40 
41 import java.lang.annotation.ElementType;
42 import java.lang.annotation.Target;
43 import java.util.Set;
44 
45 /**
46  * A custom system bar for the automotive use case.
47  * <p>
48  * The system bar in the automotive use case is more like a list of shortcuts, rendered
49  * in a linear layout.
50  */
51 public class CarSystemBarView extends LinearLayout {
52 
53     @IntDef(value = {BUTTON_TYPE_NAVIGATION, BUTTON_TYPE_KEYGUARD, BUTTON_TYPE_OCCLUSION})
54     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
55     private @interface ButtonsType {
56     }
57 
58     private static final String TAG = CarSystemBarView.class.getSimpleName();
59 
60     public static final int BUTTON_TYPE_NAVIGATION = 0;
61     public static final int BUTTON_TYPE_KEYGUARD = 1;
62     public static final int BUTTON_TYPE_OCCLUSION = 2;
63 
64     private final boolean mConsumeTouchWhenPanelOpen;
65     private final boolean mButtonsDraggable;
66     private CarSystemBarButton mHomeButton;
67     private CarSystemBarButton mPassengerHomeButton;
68     private View mNavButtons;
69     private CarSystemBarButton mNotificationsButton;
70     private CarSystemBarButton mHvacButton;
71     private HvacView mDriverHvacView;
72     private HvacView mPassengerHvacView;
73     private NotificationsShadeController mNotificationsShadeController;
74     private HvacPanelController mHvacPanelController;
75     private View mLockScreenButtons;
76     private View mOcclusionButtons;
77     // used to wire in open/close gestures for overlay panels
78     private Set<OnTouchListener> mStatusBarWindowTouchListeners;
79     private HvacPanelOverlayViewController mHvacPanelOverlayViewController;
80     private NotificationPanelViewController mNotificationPanelViewController;
81     private CarSystemBarButton mControlCenterButton;
82 
CarSystemBarView(Context context, AttributeSet attrs)83     public CarSystemBarView(Context context, AttributeSet attrs) {
84         super(context, attrs);
85         mConsumeTouchWhenPanelOpen = getResources().getBoolean(
86                 R.bool.config_consumeSystemBarTouchWhenNotificationPanelOpen);
87         mButtonsDraggable = getResources().getBoolean(R.bool.config_systemBarButtonsDraggable);
88     }
89 
90     @Override
onFinishInflate()91     public void onFinishInflate() {
92         mHomeButton = findViewById(R.id.home);
93         mPassengerHomeButton = findViewById(R.id.passenger_home);
94         mNavButtons = findViewById(R.id.nav_buttons);
95         mLockScreenButtons = findViewById(R.id.lock_screen_nav_buttons);
96         mOcclusionButtons = findViewById(R.id.occlusion_buttons);
97         mNotificationsButton = findViewById(R.id.notifications);
98         mHvacButton = findViewById(R.id.hvac);
99         mDriverHvacView = findViewById(R.id.driver_hvac);
100         mPassengerHvacView = findViewById(R.id.passenger_hvac);
101         mControlCenterButton = findViewById(R.id.control_center_nav);
102         if (mNotificationsButton != null) {
103             mNotificationsButton.setOnClickListener(this::onNotificationsClick);
104         }
105         setupHvacButton();
106         // Needs to be clickable so that it will receive ACTION_MOVE events.
107         setClickable(true);
108         // Needs to not be focusable so rotary won't highlight the entire nav bar.
109         setFocusable(false);
110     }
111 
updateHomeButtonVisibility(boolean isPassenger)112     void updateHomeButtonVisibility(boolean isPassenger) {
113         if (!isPassenger) {
114             return;
115         }
116         if (mPassengerHomeButton != null) {
117             if (mHomeButton != null) {
118                 mHomeButton.setVisibility(GONE);
119             }
120             mPassengerHomeButton.setVisibility(VISIBLE);
121         }
122     }
123 
setupHvacButton()124     void setupHvacButton() {
125         if (mHvacButton != null) {
126             mHvacButton.setOnClickListener(this::onHvacClick);
127         }
128 
129         if (Flags.dockFeature()) {
130             if (mDriverHvacView instanceof TemperatureControlView) {
131                 ((TemperatureControlView) mDriverHvacView).setTemperatureTextClickListener(
132                         this::onHvacClick);
133             }
134             if (mPassengerHvacView instanceof TemperatureControlView) {
135                 ((TemperatureControlView) mPassengerHvacView).setTemperatureTextClickListener(
136                         this::onHvacClick);
137             }
138         }
139     }
140 
setupSystemBarButtons(UserTracker userTracker)141     void setupSystemBarButtons(UserTracker userTracker) {
142         setupSystemBarButtons(this, userTracker);
143     }
144 
setupSystemBarButtons(View v, UserTracker userTracker)145     private void setupSystemBarButtons(View v, UserTracker userTracker) {
146         if (v instanceof CarSystemBarButton) {
147             ((CarSystemBarButton) v).setUserTracker(userTracker);
148         } else if (v instanceof ViewGroup) {
149             ViewGroup viewGroup = (ViewGroup) v;
150             for (int i = 0; i < viewGroup.getChildCount(); i++) {
151                 setupSystemBarButtons(viewGroup.getChildAt(i), userTracker);
152             }
153         }
154     }
155 
updateControlCenterButtonVisibility(boolean isMumd)156     void updateControlCenterButtonVisibility(boolean isMumd) {
157         if (mControlCenterButton != null) {
158             mControlCenterButton.setVisibility(isMumd ? VISIBLE : GONE);
159         }
160     }
161 
162     // Used to forward touch events even if the touch was initiated from a child component
163     @Override
onInterceptTouchEvent(MotionEvent ev)164     public boolean onInterceptTouchEvent(MotionEvent ev) {
165         if (mStatusBarWindowTouchListeners != null && !mStatusBarWindowTouchListeners.isEmpty()) {
166             if (!mButtonsDraggable) {
167                 return false;
168             }
169             boolean shouldConsumeEvent = mNotificationsShadeController == null ? false
170                     : mNotificationsShadeController.isNotificationPanelOpen();
171 
172             // Forward touch events to the status bar window so it can drag
173             // windows if required (ex. Notification shade)
174             triggerAllTouchListeners(this, ev);
175 
176             if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) {
177                 return true;
178             }
179         }
180         return super.onInterceptTouchEvent(ev);
181     }
182 
183     /** Sets the notifications panel controller. */
setNotificationsPanelController(NotificationsShadeController controller)184     public void setNotificationsPanelController(NotificationsShadeController controller) {
185         mNotificationsShadeController = controller;
186     }
187 
188     /** Sets the HVAC panel controller. */
setHvacPanelController(HvacPanelController controller)189     public void setHvacPanelController(HvacPanelController controller) {
190         mHvacPanelController = controller;
191     }
192 
193     /** Gets the notifications panel controller. */
getNotificationsPanelController()194     public NotificationsShadeController getNotificationsPanelController() {
195         return mNotificationsShadeController;
196     }
197 
198     /** Gets the HVAC panel controller. */
getHvacPanelController()199     public HvacPanelController getHvacPanelController() {
200         return mHvacPanelController;
201     }
202 
203     /**
204      * Sets the touch listeners that will be called from onInterceptTouchEvent and onTouchEvent
205      *
206      * @param statusBarWindowTouchListeners List of listeners to call from touch and intercept touch
207      */
setStatusBarWindowTouchListeners( Set<OnTouchListener> statusBarWindowTouchListeners)208     public void setStatusBarWindowTouchListeners(
209             Set<OnTouchListener> statusBarWindowTouchListeners) {
210         mStatusBarWindowTouchListeners = statusBarWindowTouchListeners;
211     }
212 
213     /** Gets the touch listeners that will be called from onInterceptTouchEvent and onTouchEvent. */
getStatusBarWindowTouchListeners()214     public Set<OnTouchListener> getStatusBarWindowTouchListeners() {
215         return mStatusBarWindowTouchListeners;
216     }
217 
218     @Override
onTouchEvent(MotionEvent event)219     public boolean onTouchEvent(MotionEvent event) {
220         triggerAllTouchListeners(this, event);
221         return super.onTouchEvent(event);
222     }
223 
onNotificationsClick(View v)224     protected void onNotificationsClick(View v) {
225         if (mNotificationsButton != null
226                 && mNotificationsButton.getDisabled()) {
227             mNotificationsButton.runOnClickWhileDisabled();
228             return;
229         }
230         if (mNotificationsShadeController != null) {
231             // If the notification shade is about to open, close the hvac panel
232             if (!mNotificationsShadeController.isNotificationPanelOpen()
233                     && mHvacPanelController != null
234                     && mHvacPanelController.isHvacPanelOpen()) {
235                 mHvacPanelController.togglePanel();
236             }
237             mNotificationsShadeController.togglePanel();
238         }
239     }
240 
onHvacClick(View v)241     protected void onHvacClick(View v) {
242         if (mHvacPanelController != null) {
243             // If the hvac panel is about to open, close the notification shade
244             if (!mHvacPanelController.isHvacPanelOpen()
245                     && mNotificationsShadeController != null
246                     && mNotificationsShadeController.isNotificationPanelOpen()) {
247                 mNotificationsShadeController.togglePanel();
248             }
249             mHvacPanelController.togglePanel();
250         }
251     }
252 
253     /**
254      * Shows buttons of the specified {@link ButtonsType}.
255      *
256      * NOTE: Only one type of buttons can be shown at a time, so showing buttons of one type will
257      * hide all buttons of other types.
258      *
259      * @param buttonsType
260      */
showButtonsOfType(@uttonsType int buttonsType)261     public void showButtonsOfType(@ButtonsType int buttonsType) {
262         switch(buttonsType) {
263             case BUTTON_TYPE_NAVIGATION:
264                 setNavigationButtonsVisibility(View.VISIBLE);
265                 setKeyguardButtonsVisibility(View.GONE);
266                 setOcclusionButtonsVisibility(View.GONE);
267                 break;
268             case BUTTON_TYPE_KEYGUARD:
269                 setNavigationButtonsVisibility(View.GONE);
270                 setKeyguardButtonsVisibility(View.VISIBLE);
271                 setOcclusionButtonsVisibility(View.GONE);
272                 break;
273             case BUTTON_TYPE_OCCLUSION:
274                 setNavigationButtonsVisibility(View.GONE);
275                 setKeyguardButtonsVisibility(View.GONE);
276                 setOcclusionButtonsVisibility(View.VISIBLE);
277                 break;
278         }
279     }
280 
281     /**
282      * Sets the system bar view's disabled state and runnable when disabled.
283      */
setDisabledSystemBarButton(int viewId, boolean disabled, Runnable runnable, @Nullable String buttonName)284     public void setDisabledSystemBarButton(int viewId, boolean disabled, Runnable runnable,
285                 @Nullable String buttonName) {
286         CarSystemBarButton button = findViewById(viewId);
287         if (button != null) {
288             if (DEBUG) {
289                 Log.d(TAG, "setDisabledSystemBarButton for: " + buttonName + " to: " + disabled);
290             }
291             button.setDisabled(disabled, runnable);
292         }
293     }
294 
295     /**
296      * Sets the system bar specific View container's visibility. ViewName is used just for
297      * debugging.
298      */
setVisibilityByViewId(int viewId, @Nullable String viewName, @View.Visibility int visibility)299     public void setVisibilityByViewId(int viewId, @Nullable String viewName,
300                 @View.Visibility int visibility) {
301         View v = findViewById(viewId);
302         if (v != null) {
303             if (DEBUG) Log.d(TAG, "setVisibilityByViewId for: " + viewName + " to: " + visibility);
304             v.setVisibility(visibility);
305         }
306     }
307 
308     /**
309      * Sets the HvacPanelOverlayViewController and adds HVAC button listeners
310      */
registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller)311     public void registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller) {
312         mHvacPanelOverlayViewController = controller;
313         if (mHvacPanelOverlayViewController != null && mHvacButton != null) {
314             mHvacPanelOverlayViewController.registerViewStateListener(mHvacButton);
315         }
316     }
317 
318     /**
319      * Sets the NotificationPanelViewController and adds button listeners
320      */
registerNotificationPanelViewController( NotificationPanelViewController controller)321     public void registerNotificationPanelViewController(
322             NotificationPanelViewController controller) {
323         mNotificationPanelViewController = controller;
324         if (mNotificationPanelViewController != null && mNotificationsButton != null) {
325             mNotificationPanelViewController.registerViewStateListener(mNotificationsButton);
326         }
327     }
328 
setNavigationButtonsVisibility(@iew.Visibility int visibility)329     private void setNavigationButtonsVisibility(@View.Visibility int visibility) {
330         if (mNavButtons != null) {
331             mNavButtons.setVisibility(visibility);
332         }
333     }
334 
setKeyguardButtonsVisibility(@iew.Visibility int visibility)335     private void setKeyguardButtonsVisibility(@View.Visibility int visibility) {
336         if (mLockScreenButtons != null) {
337             mLockScreenButtons.setVisibility(visibility);
338         }
339     }
340 
setOcclusionButtonsVisibility(@iew.Visibility int visibility)341     private void setOcclusionButtonsVisibility(@View.Visibility int visibility) {
342         if (mOcclusionButtons != null) {
343             mOcclusionButtons.setVisibility(visibility);
344         }
345     }
346 
triggerAllTouchListeners(View view, MotionEvent event)347     private void triggerAllTouchListeners(View view, MotionEvent event) {
348         if (mStatusBarWindowTouchListeners == null) {
349             return;
350         }
351         for (OnTouchListener listener : mStatusBarWindowTouchListeners) {
352             listener.onTouch(view, event);
353         }
354     }
355 
356     /**
357      * Toggles the notification unseen indicator on/off.
358      *
359      * @param hasUnseen true if the unseen notification count is great than 0.
360      */
toggleNotificationUnseenIndicator(Boolean hasUnseen)361     public void toggleNotificationUnseenIndicator(Boolean hasUnseen) {
362         if (mNotificationsButton == null) return;
363 
364         mNotificationsButton.setUnseen(hasUnseen);
365     }
366 }
367