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 android.annotation.IntDef;
20 import android.content.Context;
21 import android.util.AttributeSet;
22 import android.util.Log;
23 import android.view.MotionEvent;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.LinearLayout;
27 
28 import com.android.systemui.R;
29 import com.android.systemui.car.hvac.HvacPanelOverlayViewController;
30 import com.android.systemui.car.statusicon.ui.QuickControlsEntryPointsController;
31 import com.android.systemui.car.statusicon.ui.ReadOnlyIconsController;
32 import com.android.systemui.car.systembar.CarSystemBarController.HvacPanelController;
33 import com.android.systemui.car.systembar.CarSystemBarController.NotificationsShadeController;
34 import com.android.systemui.flags.FeatureFlags;
35 import com.android.systemui.statusbar.phone.StatusBarIconController;
36 
37 import java.lang.annotation.ElementType;
38 import java.lang.annotation.Target;
39 
40 /**
41  * A custom system bar for the automotive use case.
42  * <p>
43  * The system bar in the automotive use case is more like a list of shortcuts, rendered
44  * in a linear layout.
45  */
46 public class CarSystemBarView extends LinearLayout {
47 
48     @IntDef(value = {BUTTON_TYPE_NAVIGATION, BUTTON_TYPE_KEYGUARD, BUTTON_TYPE_OCCLUSION})
49     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
50     private @interface ButtonsType {
51     }
52 
53     private static final String TAG = CarSystemBarView.class.getSimpleName();
54 
55     public static final int BUTTON_TYPE_NAVIGATION = 0;
56     public static final int BUTTON_TYPE_KEYGUARD = 1;
57     public static final int BUTTON_TYPE_OCCLUSION = 2;
58 
59     private final boolean mConsumeTouchWhenPanelOpen;
60     private final boolean mButtonsDraggable;
61 
62     private View mNavButtons;
63     private CarSystemBarButton mNotificationsButton;
64     private HvacButton mHvacButton;
65     private NotificationsShadeController mNotificationsShadeController;
66     private HvacPanelController mHvacPanelController;
67     private View mLockScreenButtons;
68     private View mOcclusionButtons;
69     private ViewGroup mQcEntryPointsContainer;
70     private ViewGroup mReadOnlyIconsContainer;
71     // used to wire in open/close gestures for notifications
72     private OnTouchListener mStatusBarWindowTouchListener;
73     private HvacPanelOverlayViewController mHvacPanelOverlayViewController;
74 
CarSystemBarView(Context context, AttributeSet attrs)75     public CarSystemBarView(Context context, AttributeSet attrs) {
76         super(context, attrs);
77         mConsumeTouchWhenPanelOpen = getResources().getBoolean(
78                 R.bool.config_consumeSystemBarTouchWhenNotificationPanelOpen);
79         mButtonsDraggable = getResources().getBoolean(R.bool.config_systemBarButtonsDraggable);
80     }
81 
82     @Override
onFinishInflate()83     public void onFinishInflate() {
84         mNavButtons = findViewById(R.id.nav_buttons);
85         mLockScreenButtons = findViewById(R.id.lock_screen_nav_buttons);
86         mOcclusionButtons = findViewById(R.id.occlusion_buttons);
87         mNotificationsButton = findViewById(R.id.notifications);
88         mHvacButton = findViewById(R.id.hvac);
89         mQcEntryPointsContainer = findViewById(R.id.qc_entry_points_container);
90         mReadOnlyIconsContainer = findViewById(R.id.read_only_icons_container);
91         if (mNotificationsButton != null) {
92             mNotificationsButton.setOnClickListener(this::onNotificationsClick);
93         }
94         // Needs to be clickable so that it will receive ACTION_MOVE events.
95         setClickable(true);
96         // Needs to not be focusable so rotary won't highlight the entire nav bar.
97         setFocusable(false);
98     }
99 
setupHvacButton()100     void setupHvacButton() {
101         if (mHvacButton != null) {
102             mHvacButton.setOnClickListener(this::onHvacClick);
103         }
104     }
105 
setupQuickControlsEntryPoints( QuickControlsEntryPointsController quickControlsEntryPointsController, boolean isSetUp)106     void setupQuickControlsEntryPoints(
107             QuickControlsEntryPointsController quickControlsEntryPointsController,
108             boolean isSetUp) {
109         if (mQcEntryPointsContainer != null) {
110             quickControlsEntryPointsController.addIconViews(mQcEntryPointsContainer, isSetUp);
111         }
112     }
113 
setupReadOnlyIcons(ReadOnlyIconsController readOnlyIconsController)114     void setupReadOnlyIcons(ReadOnlyIconsController readOnlyIconsController) {
115         if (mReadOnlyIconsContainer != null) {
116             readOnlyIconsController.addIconViews(mReadOnlyIconsContainer);
117         }
118     }
119 
setupIconController(FeatureFlags featureFlags, StatusBarIconController iconController)120     void setupIconController(FeatureFlags featureFlags, StatusBarIconController iconController) {
121         View mStatusIcons = findViewById(R.id.statusIcons);
122         if (mStatusIcons != null) {
123             // Attach the controllers for Status icons such as wifi and bluetooth if the standard
124             // container is in the view.
125             StatusBarIconController.DarkIconManager mDarkIconManager =
126                     new StatusBarIconController.DarkIconManager(
127                             mStatusIcons.findViewById(R.id.statusIcons), featureFlags);
128             mDarkIconManager.setShouldLog(true);
129             iconController.addIconGroup(mDarkIconManager);
130         }
131     }
132 
133     // Used to forward touch events even if the touch was initiated from a child component
134     @Override
onInterceptTouchEvent(MotionEvent ev)135     public boolean onInterceptTouchEvent(MotionEvent ev) {
136         if (mStatusBarWindowTouchListener != null) {
137             if (!mButtonsDraggable) {
138                 return false;
139             }
140             boolean shouldConsumeEvent = mNotificationsShadeController == null ? false
141                     : mNotificationsShadeController.isNotificationPanelOpen();
142 
143             // Forward touch events to the status bar window so it can drag
144             // windows if required (Notification shade)
145             mStatusBarWindowTouchListener.onTouch(this, ev);
146 
147             if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) {
148                 return true;
149             }
150         }
151         return super.onInterceptTouchEvent(ev);
152     }
153 
154     /** Sets the notifications panel controller. */
setNotificationsPanelController(NotificationsShadeController controller)155     public void setNotificationsPanelController(NotificationsShadeController controller) {
156         mNotificationsShadeController = controller;
157     }
158 
159     /** Sets the HVAC panel controller. */
setHvacPanelController(HvacPanelController controller)160     public void setHvacPanelController(HvacPanelController controller) {
161         mHvacPanelController = controller;
162     }
163 
164     /** Gets the notifications panel controller. */
getNotificationsPanelController()165     public NotificationsShadeController getNotificationsPanelController() {
166         return mNotificationsShadeController;
167     }
168 
169     /** Gets the HVAC panel controller. */
getHvacPanelController()170     public HvacPanelController getHvacPanelController() {
171         return mHvacPanelController;
172     }
173 
174     /**
175      * Sets a touch listener that will be called from onInterceptTouchEvent and onTouchEvent
176      *
177      * @param statusBarWindowTouchListener The listener to call from touch and intercept touch
178      */
setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener)179     public void setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener) {
180         mStatusBarWindowTouchListener = statusBarWindowTouchListener;
181     }
182 
183     /** Gets the touch listener that will be called from onInterceptTouchEvent and onTouchEvent. */
getStatusBarWindowTouchListener()184     public OnTouchListener getStatusBarWindowTouchListener() {
185         return mStatusBarWindowTouchListener;
186     }
187 
188     @Override
onTouchEvent(MotionEvent event)189     public boolean onTouchEvent(MotionEvent event) {
190         if (mStatusBarWindowTouchListener != null) {
191             mStatusBarWindowTouchListener.onTouch(this, event);
192         }
193         return super.onTouchEvent(event);
194     }
195 
onNotificationsClick(View v)196     protected void onNotificationsClick(View v) {
197         if (mNotificationsButton != null
198                 && mNotificationsButton.getDisabled()) {
199             mNotificationsButton.runOnClickWhileDisabled();
200             return;
201         }
202         if (mNotificationsShadeController != null) {
203             mNotificationsShadeController.togglePanel();
204         }
205     }
206 
onHvacClick(View v)207     protected void onHvacClick(View v) {
208         if (mHvacPanelController != null) {
209             mHvacPanelController.togglePanel();
210         }
211     }
212 
213     /**
214      * Shows buttons of the specified {@link ButtonsType}.
215      *
216      * NOTE: Only one type of buttons can be shown at a time, so showing buttons of one type will
217      * hide all buttons of other types.
218      *
219      * @param buttonsType
220      */
showButtonsOfType(@uttonsType int buttonsType)221     public void showButtonsOfType(@ButtonsType int buttonsType) {
222         switch(buttonsType) {
223             case BUTTON_TYPE_NAVIGATION:
224                 setNavigationButtonsVisibility(View.VISIBLE);
225                 setKeyguardButtonsVisibility(View.GONE);
226                 setOcclusionButtonsVisibility(View.GONE);
227                 break;
228             case BUTTON_TYPE_KEYGUARD:
229                 setNavigationButtonsVisibility(View.GONE);
230                 setKeyguardButtonsVisibility(View.VISIBLE);
231                 setOcclusionButtonsVisibility(View.GONE);
232                 break;
233             case BUTTON_TYPE_OCCLUSION:
234                 setNavigationButtonsVisibility(View.GONE);
235                 setKeyguardButtonsVisibility(View.GONE);
236                 setOcclusionButtonsVisibility(View.VISIBLE);
237                 break;
238         }
239     }
240 
241     /**
242      * Sets the system bar view's disabled state and runnable when disabled.
243      */
setLockTaskDisabledButton(int viewId, boolean disabled, Runnable runnable)244     public void setLockTaskDisabledButton(int viewId, boolean disabled, Runnable runnable) {
245         CarSystemBarButton button = findViewById(viewId);
246         if (button != null) {
247             button.setDisabled(disabled, runnable);
248         }
249     }
250 
251     /**
252      * Sets the system bar ViewGroup container's visibility
253      */
setLockTaskDisabledContainer(int viewId, @View.Visibility int visibility)254     public void setLockTaskDisabledContainer(int viewId, @View.Visibility int visibility) {
255         View v = findViewById(viewId);
256         if (v == null) {
257             Log.e(TAG, "setLockTaskViewVisibility for: " + viewId + " not found");
258             return;
259         }
260         if (v instanceof ViewGroup) {
261             ViewGroup group = (ViewGroup) v;
262             for (int i = 0; i < group.getChildCount(); i++) {
263                 group.getChildAt(i).setVisibility(visibility);
264             }
265             return;
266         }
267         v.setVisibility(visibility);
268     }
269 
270     /**
271      * Sets the HvacPanelOverlayViewController and adds HVAC button listeners
272      */
registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller)273     public void registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller) {
274         mHvacPanelOverlayViewController = controller;
275         if (mHvacPanelOverlayViewController != null && mHvacButton != null) {
276             mHvacPanelOverlayViewController.registerViewStateListener(mHvacButton);
277         }
278     }
279 
setNavigationButtonsVisibility(@iew.Visibility int visibility)280     private void setNavigationButtonsVisibility(@View.Visibility int visibility) {
281         if (mNavButtons != null) {
282             mNavButtons.setVisibility(visibility);
283         }
284     }
285 
setKeyguardButtonsVisibility(@iew.Visibility int visibility)286     private void setKeyguardButtonsVisibility(@View.Visibility int visibility) {
287         if (mLockScreenButtons != null) {
288             mLockScreenButtons.setVisibility(visibility);
289         }
290     }
291 
setOcclusionButtonsVisibility(@iew.Visibility int visibility)292     private void setOcclusionButtonsVisibility(@View.Visibility int visibility) {
293         if (mOcclusionButtons != null) {
294             mOcclusionButtons.setVisibility(visibility);
295         }
296     }
297 
298     /**
299      * Toggles the notification unseen indicator on/off.
300      *
301      * @param hasUnseen true if the unseen notification count is great than 0.
302      */
toggleNotificationUnseenIndicator(Boolean hasUnseen)303     public void toggleNotificationUnseenIndicator(Boolean hasUnseen) {
304         if (mNotificationsButton == null) return;
305 
306         mNotificationsButton.setUnseen(hasUnseen);
307     }
308 }
309