1 /*
2  * Copyright (C) 2015 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.statusbar.car;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.util.AttributeSet;
22 import android.view.MotionEvent;
23 import android.view.View;
24 import android.widget.LinearLayout;
25 
26 import com.android.systemui.Dependency;
27 import com.android.systemui.R;
28 import com.android.systemui.statusbar.phone.StatusBarIconController;
29 
30 /**
31  * A custom navigation bar for the automotive use case.
32  * <p>
33  * The navigation bar in the automotive use case is more like a list of shortcuts, rendered
34  * in a linear layout.
35  */
36 class CarNavigationBarView extends LinearLayout {
37     private View mNavButtons;
38     private CarNavigationButton mNotificationsButton;
39     private CarStatusBar mCarStatusBar;
40     private Context mContext;
41     private View mLockScreenButtons;
42     // used to wire in open/close gestures for notifications
43     private OnTouchListener mStatusBarWindowTouchListener;
44 
45 
CarNavigationBarView(Context context, AttributeSet attrs)46     public CarNavigationBarView(Context context, AttributeSet attrs) {
47         super(context, attrs);
48         mContext = context;
49     }
50 
51     @Override
onFinishInflate()52     public void onFinishInflate() {
53         mNavButtons = findViewById(R.id.nav_buttons);
54         mLockScreenButtons = findViewById(R.id.lock_screen_nav_buttons);
55 
56         mNotificationsButton = findViewById(R.id.notifications);
57         if (mNotificationsButton != null) {
58             mNotificationsButton.setOnClickListener(this::onNotificationsClick);
59         }
60         View mStatusIcons = findViewById(R.id.statusIcons);
61         if (mStatusIcons != null) {
62             // Attach the controllers for Status icons such as wifi and bluetooth if the standard
63             // container is in the view.
64             StatusBarIconController.DarkIconManager mDarkIconManager =
65                     new StatusBarIconController.DarkIconManager(
66                             mStatusIcons.findViewById(R.id.statusIcons));
67             mDarkIconManager.setShouldLog(true);
68             Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
69         }
70         // needs to be clickable so that it will receive ACTION_MOVE events
71         setClickable(true);
72     }
73 
74     // Used to forward touch events even if the touch was initiated from a child component
75     @Override
onInterceptTouchEvent(MotionEvent ev)76     public boolean onInterceptTouchEvent(MotionEvent ev) {
77         if (mStatusBarWindowTouchListener != null) {
78             boolean shouldConsumeEvent = shouldConsumeNotificationButtonEvent(ev);
79             // Forward touch events to the status bar window so it can drag
80             // windows if required (Notification shade)
81             mStatusBarWindowTouchListener.onTouch(this, ev);
82             // return true if child views should not receive this event.
83             if (shouldConsumeEvent) {
84                 return true;
85             }
86         }
87         return super.onInterceptTouchEvent(ev);
88     }
89 
90     /**
91      * If the motion event is over top of the notification button while the notification
92      * panel is open, we need the statusbar touch listeners handle the event instead of the button.
93      * Since the statusbar listener will trigger a close of the notification panel before the
94      * any button click events are fired this will prevent reopening the panel.
95      *
96      * Note: we can't use requestDisallowInterceptTouchEvent because the gesture detector will
97      * always receive the ACTION_DOWN and thus think a longpress happened if no other events are
98      * received
99      *
100      * @return true if the notification button should not receive the event
101      */
shouldConsumeNotificationButtonEvent(MotionEvent ev)102     private boolean shouldConsumeNotificationButtonEvent(MotionEvent ev) {
103         if (mNotificationsButton == null || !mCarStatusBar.isNotificationPanelOpen()) {
104             return false;
105         }
106         Rect notificationButtonLocation = new Rect();
107         mNotificationsButton.getHitRect(notificationButtonLocation);
108         return notificationButtonLocation.contains((int) ev.getX(), (int) ev.getY());
109     }
110 
111 
setStatusBar(CarStatusBar carStatusBar)112     void setStatusBar(CarStatusBar carStatusBar) {
113         mCarStatusBar = carStatusBar;
114     }
115 
116     /**
117      * Set a touch listener that will be called from onInterceptTouchEvent and onTouchEvent
118      *
119      * @param statusBarWindowTouchListener The listener to call from touch and intercept touch
120      */
setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener)121     void setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener) {
122         mStatusBarWindowTouchListener = statusBarWindowTouchListener;
123     }
124 
125     @Override
onTouchEvent(MotionEvent event)126     public boolean onTouchEvent(MotionEvent event) {
127         if (mStatusBarWindowTouchListener != null) {
128             mStatusBarWindowTouchListener.onTouch(this, event);
129         }
130         return super.onTouchEvent(event);
131     }
132 
onNotificationsClick(View v)133     protected void onNotificationsClick(View v) {
134         mCarStatusBar.togglePanel();
135     }
136 
137     /**
138      * If there are buttons declared in the layout they will be shown and the normal
139      * Nav buttons will be hidden.
140      */
showKeyguardButtons()141     public void showKeyguardButtons() {
142         if (mLockScreenButtons == null) {
143             return;
144         }
145         mLockScreenButtons.setVisibility(View.VISIBLE);
146         mNavButtons.setVisibility(View.GONE);
147     }
148 
149     /**
150      * If there are buttons declared in the layout they will be hidden and the normal
151      * Nav buttons will be shown.
152      */
hideKeyguardButtons()153     public void hideKeyguardButtons() {
154         if (mLockScreenButtons == null) return;
155 
156         mNavButtons.setVisibility(View.VISIBLE);
157         mLockScreenButtons.setVisibility(View.GONE);
158     }
159 
160     /**
161      * Toggles the notification unseen indicator on/off.
162      *
163      * @param hasUnseen true if the unseen notification count is great than 0.
164      */
toggleNotificationUnseenIndicator(Boolean hasUnseen)165     void toggleNotificationUnseenIndicator(Boolean hasUnseen) {
166         if (mNotificationsButton == null) return;
167 
168         mNotificationsButton.setUnseen(hasUnseen);
169     }
170 }
171