• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.systemui.car.systembar;
17 
18 import android.annotation.Nullable;
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.widget.FrameLayout;
25 
26 import androidx.annotation.IdRes;
27 import androidx.annotation.VisibleForTesting;
28 
29 import com.android.car.ui.FocusParkingView;
30 import com.android.car.ui.utils.ViewUtils;
31 import com.android.systemui.Gefingerpoken;
32 import com.android.systemui.R;
33 import com.android.systemui.car.systembar.CarSystemBarController.SystemBarSide;
34 import com.android.systemui.car.systembar.element.CarSystemBarElementInitializer;
35 import com.android.systemui.car.window.OverlayPanelViewController;
36 import com.android.systemui.car.window.OverlayViewController;
37 import com.android.systemui.car.window.OverlayVisibilityMediator;
38 import com.android.systemui.settings.UserTracker;
39 import com.android.systemui.util.ViewController;
40 
41 import dagger.Lazy;
42 import dagger.assisted.Assisted;
43 import dagger.assisted.AssistedFactory;
44 import dagger.assisted.AssistedInject;
45 
46 import java.util.Set;
47 
48 /**
49  * A controller for initializing the system bar views.
50  */
51 public class CarSystemBarViewControllerImpl
52         extends ViewController<CarSystemBarViewControllerImpl.TouchInterceptingFrameLayout>
53         implements CarSystemBarViewController, Gefingerpoken {
54 
55     private static final String LAST_FOCUSED_VIEW_ID = "last_focused_view_id";
56 
57     protected final Context mContext;
58 
59     private final UserTracker mUserTracker;
60     private final CarSystemBarElementInitializer mCarSystemBarElementInitializer;
61     private final SystemBarConfigs mSystemBarConfigs;
62     private final ButtonRoleHolderController mButtonRoleHolderController;
63     private final Lazy<MicPrivacyChipViewController> mMicPrivacyChipViewControllerLazy;
64     private final Lazy<CameraPrivacyChipViewController> mCameraPrivacyChipViewControllerLazy;
65     private final @SystemBarSide int mSide;
66     private final OverlayVisibilityMediator mOverlayVisibilityMediator;
67 
68     private final boolean mConsumeTouchWhenPanelOpen;
69     private final boolean mButtonsDraggable;
70     private View mNavButtons;
71     private View mLockScreenButtons;
72     private View mOcclusionButtons;
73     // used to wire in open/close gestures for overlay panels
74     private Set<View.OnTouchListener> mSystemBarTouchListeners;
75 
76     @AssistedInject
CarSystemBarViewControllerImpl(Context context, UserTracker userTracker, CarSystemBarElementInitializer elementInitializer, SystemBarConfigs systemBarConfigs, ButtonRoleHolderController buttonRoleHolderController, Lazy<CameraPrivacyChipViewController> cameraPrivacyChipViewControllerLazy, Lazy<MicPrivacyChipViewController> micPrivacyChipViewControllerLazy, OverlayVisibilityMediator overlayVisibilityMediator, @Assisted @SystemBarSide int side, @Assisted ViewGroup systemBarView)77     public CarSystemBarViewControllerImpl(Context context,
78             UserTracker userTracker,
79             CarSystemBarElementInitializer elementInitializer,
80             SystemBarConfigs systemBarConfigs,
81             ButtonRoleHolderController buttonRoleHolderController,
82             Lazy<CameraPrivacyChipViewController> cameraPrivacyChipViewControllerLazy,
83             Lazy<MicPrivacyChipViewController> micPrivacyChipViewControllerLazy,
84             OverlayVisibilityMediator overlayVisibilityMediator,
85             @Assisted @SystemBarSide int side,
86             @Assisted ViewGroup systemBarView) {
87         super(new TouchInterceptingFrameLayout(context, systemBarView));
88 
89         mContext = context;
90         mUserTracker = userTracker;
91         mCarSystemBarElementInitializer = elementInitializer;
92         mSystemBarConfigs = systemBarConfigs;
93         mButtonRoleHolderController = buttonRoleHolderController;
94         mCameraPrivacyChipViewControllerLazy = cameraPrivacyChipViewControllerLazy;
95         mMicPrivacyChipViewControllerLazy = micPrivacyChipViewControllerLazy;
96         mSide = side;
97         mOverlayVisibilityMediator = overlayVisibilityMediator;
98 
99         mConsumeTouchWhenPanelOpen = getResources().getBoolean(
100                 R.bool.config_consumeSystemBarTouchWhenNotificationPanelOpen);
101         mButtonsDraggable = getResources().getBoolean(R.bool.config_systemBarButtonsDraggable);
102     }
103 
104     @Override
onInit()105     protected void onInit() {
106         // Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here
107         // when the user navigates to another window. This is also used to prevent wrap-around.
108         mView.addView(new FocusParkingView(mContext), 0);
109         mView.setTouchListener(this);
110 
111         setupSystemBarButtons(mView, mUserTracker);
112         mCarSystemBarElementInitializer.initializeCarSystemBarElements(mView);
113 
114         mNavButtons = mView.findViewById(R.id.nav_buttons);
115         mLockScreenButtons = mView.findViewById(R.id.lock_screen_nav_buttons);
116         mOcclusionButtons = mView.findViewById(R.id.occlusion_buttons);
117         // Needs to be clickable so that it will receive ACTION_MOVE events.
118         mView.setClickable(true);
119         // Needs to not be focusable so rotary won't highlight the entire nav bar.
120         mView.setFocusable(false);
121     }
122 
123     @Override
onSaveInstanceState(Bundle outState)124     public void onSaveInstanceState(Bundle outState) {
125         // The focused view will be destroyed during re-layout, causing the framework to adjust
126         // the focus unexpectedly. To avoid that, move focus to a view that won't be
127         // destroyed during re-layout and has no focus highlight (the FocusParkingView), then
128         // move focus back to the previously focused view after re-layout.
129         outState.putInt(LAST_FOCUSED_VIEW_ID, cacheAndHideFocus(mView));
130     }
131 
132     @Override
onRestoreInstanceState(Bundle savedInstanceState)133     public void onRestoreInstanceState(Bundle savedInstanceState) {
134         restoreFocus(mView, savedInstanceState.getInt(LAST_FOCUSED_VIEW_ID, View.NO_ID));
135     }
136 
137     @Override
getView()138     public ViewGroup getView() {
139         return mView;
140     }
141 
142     @Override
setSystemBarTouchListeners( Set<View.OnTouchListener> systemBarTouchListeners)143     public void setSystemBarTouchListeners(
144             Set<View.OnTouchListener> systemBarTouchListeners) {
145         mSystemBarTouchListeners = systemBarTouchListeners;
146     }
147 
148     @Override
showButtonsOfType(@uttonsType int buttonsType)149     public void showButtonsOfType(@ButtonsType int buttonsType) {
150         switch(buttonsType) {
151             case BUTTON_TYPE_NAVIGATION:
152                 setNavigationButtonsVisibility(View.VISIBLE);
153                 setKeyguardButtonsVisibility(View.GONE);
154                 setOcclusionButtonsVisibility(View.GONE);
155                 break;
156             case BUTTON_TYPE_KEYGUARD:
157                 setNavigationButtonsVisibility(View.GONE);
158                 setKeyguardButtonsVisibility(View.VISIBLE);
159                 setOcclusionButtonsVisibility(View.GONE);
160                 break;
161             case BUTTON_TYPE_OCCLUSION:
162                 setNavigationButtonsVisibility(View.GONE);
163                 setKeyguardButtonsVisibility(View.GONE);
164                 setOcclusionButtonsVisibility(View.VISIBLE);
165                 break;
166         }
167     }
168 
169     /**
170      * Used to forward touch events even if the touch was initiated from a child component
171      */
172     @Override
onInterceptTouchEvent(MotionEvent ev)173     public boolean onInterceptTouchEvent(MotionEvent ev) {
174         if (mSystemBarTouchListeners != null && !mSystemBarTouchListeners.isEmpty()) {
175             if (!mButtonsDraggable) {
176                 return false;
177             }
178 
179             OverlayViewController topOverlay =
180                     mOverlayVisibilityMediator.getHighestZOrderOverlayViewController();
181             boolean shouldConsumeEvent = topOverlay instanceof OverlayPanelViewController
182                     ? ((OverlayPanelViewController) topOverlay).shouldPanelConsumeSystemBarTouch()
183                     : false;
184 
185             // Forward touch events to the status bar window so it can drag
186             // windows if required (ex. Notification shade)
187             triggerAllTouchListeners(mView, ev);
188 
189             if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) {
190                 return true;
191             }
192         }
193         return false;
194     }
195 
196     /**
197      * Used for forwarding onTouch events on the systembar.
198      */
199     @Override
onTouchEvent(MotionEvent event)200     public boolean onTouchEvent(MotionEvent event) {
201         triggerAllTouchListeners(mView, event);
202         return false;
203     }
204 
205     @Override
onViewAttached()206     protected void onViewAttached() {
207         mSystemBarConfigs.insetSystemBar(mSide, mView);
208 
209         mButtonRoleHolderController.addAllButtonsWithRoleName(mView);
210         mMicPrivacyChipViewControllerLazy.get().addPrivacyChipView(mView);
211         mCameraPrivacyChipViewControllerLazy.get().addPrivacyChipView(mView);
212     }
213 
214     @Override
onViewDetached()215     protected void onViewDetached() {
216         mButtonRoleHolderController.removeAll();
217         mMicPrivacyChipViewControllerLazy.get().removeAll();
218         mCameraPrivacyChipViewControllerLazy.get().removeAll();
219     }
220 
221     @AssistedFactory
222     public interface Factory
223             extends CarSystemBarViewControllerFactory<CarSystemBarViewControllerImpl> {
224     }
225 
setupSystemBarButtons(View v, UserTracker userTracker)226     private void setupSystemBarButtons(View v, UserTracker userTracker) {
227         if (v instanceof CarSystemBarButton) {
228             ((CarSystemBarButton) v).setUserTracker(userTracker);
229         } else if (v instanceof ViewGroup) {
230             ViewGroup viewGroup = (ViewGroup) v;
231             for (int i = 0; i < viewGroup.getChildCount(); i++) {
232                 setupSystemBarButtons(viewGroup.getChildAt(i), userTracker);
233             }
234         }
235     }
236 
setNavigationButtonsVisibility(@iew.Visibility int visibility)237     private void setNavigationButtonsVisibility(@View.Visibility int visibility) {
238         if (mNavButtons != null) {
239             mNavButtons.setVisibility(visibility);
240         }
241     }
242 
setKeyguardButtonsVisibility(@iew.Visibility int visibility)243     private void setKeyguardButtonsVisibility(@View.Visibility int visibility) {
244         if (mLockScreenButtons != null) {
245             mLockScreenButtons.setVisibility(visibility);
246         }
247     }
248 
setOcclusionButtonsVisibility(@iew.Visibility int visibility)249     private void setOcclusionButtonsVisibility(@View.Visibility int visibility) {
250         if (mOcclusionButtons != null) {
251             mOcclusionButtons.setVisibility(visibility);
252         }
253     }
254 
triggerAllTouchListeners(View view, MotionEvent event)255     private void triggerAllTouchListeners(View view, MotionEvent event) {
256         if (mSystemBarTouchListeners == null) {
257             return;
258         }
259         for (View.OnTouchListener listener : mSystemBarTouchListeners) {
260             listener.onTouch(view, event);
261         }
262     }
263 
264     @VisibleForTesting
cacheAndHideFocus(@ullable View rootView)265     static int cacheAndHideFocus(@Nullable View rootView) {
266         if (rootView == null) return View.NO_ID;
267         View focusedView = rootView.findFocus();
268         if (focusedView == null || focusedView instanceof FocusParkingView) return View.NO_ID;
269         int focusedViewId = focusedView.getId();
270         ViewUtils.hideFocus(rootView);
271         return focusedViewId;
272     }
273 
restoreFocus(@ullable View rootView, @IdRes int viewToFocusId)274     private static boolean restoreFocus(@Nullable View rootView, @IdRes int viewToFocusId) {
275         if (rootView == null || viewToFocusId == View.NO_ID) return false;
276         View focusedView = rootView.findViewById(viewToFocusId);
277         if (focusedView == null) return false;
278         focusedView.requestFocus();
279         return true;
280     }
281 
282     static class TouchInterceptingFrameLayout extends FrameLayout {
283         @Nullable
284         private Gefingerpoken mTouchListener;
285 
TouchInterceptingFrameLayout(Context context, ViewGroup content)286         TouchInterceptingFrameLayout(Context context, ViewGroup content) {
287             super(context);
288             addView(content);
289         }
290 
setTouchListener(@ullable Gefingerpoken listener)291         void setTouchListener(@Nullable Gefingerpoken listener) {
292             mTouchListener = listener;
293         }
294 
295         /** Called when a touch is being intercepted in a ViewGroup. */
296         @Override
onInterceptTouchEvent(MotionEvent ev)297         public boolean onInterceptTouchEvent(MotionEvent ev) {
298             return (mTouchListener != null && mTouchListener
299                     .onInterceptTouchEvent(ev)) ? true : super.onInterceptTouchEvent(ev);
300         }
301 
302         /** Called when a touch is being handled by a view. */
303         @Override
onTouchEvent(MotionEvent ev)304         public boolean onTouchEvent(MotionEvent ev) {
305             return (mTouchListener != null && mTouchListener
306                     .onTouchEvent(ev)) ? true : super.onTouchEvent(ev);
307         }
308     }
309 }
310