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.IdRes;
20 import android.content.Context;
21 import android.util.ArrayMap;
22 import android.util.Log;
23 import android.view.View;
24 import android.view.ViewGroup;
25 
26 import androidx.annotation.LayoutRes;
27 
28 import com.android.car.dockutil.Flags;
29 import com.android.car.ui.FocusParkingView;
30 import com.android.systemui.R;
31 import com.android.systemui.car.systembar.element.CarSystemBarElementController;
32 import com.android.systemui.car.systembar.element.CarSystemBarElementInitializer;
33 import com.android.systemui.dagger.SysUISingleton;
34 import com.android.systemui.flags.FeatureFlags;
35 import com.android.systemui.settings.UserTracker;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 import javax.inject.Inject;
41 
42 /** A factory that creates and caches views for navigation bars. */
43 @SysUISingleton
44 public class CarSystemBarViewFactory {
45 
46     private static final String TAG = CarSystemBarViewFactory.class.getSimpleName();
47     private static final ArrayMap<Type, Integer> sLayoutMap = setupLayoutMapping();
48 
setupLayoutMapping()49     private static ArrayMap<Type, Integer> setupLayoutMapping() {
50         ArrayMap<Type, Integer> map = new ArrayMap<>();
51         map.put(Type.TOP, R.layout.car_top_system_bar);
52         map.put(Type.TOP_WITH_DOCK, R.layout.car_top_system_bar_dock);
53         map.put(Type.TOP_UNPROVISIONED, R.layout.car_top_system_bar_unprovisioned);
54         map.put(Type.BOTTOM, R.layout.car_bottom_system_bar);
55         map.put(Type.BOTTOM_WITH_DOCK, R.layout.car_bottom_system_bar_dock);
56         map.put(Type.BOTTOM_UNPROVISIONED, R.layout.car_bottom_system_bar_unprovisioned);
57         map.put(Type.LEFT, R.layout.car_left_system_bar);
58         map.put(Type.LEFT_UNPROVISIONED, R.layout.car_left_system_bar_unprovisioned);
59         map.put(Type.RIGHT, R.layout.car_right_system_bar);
60         map.put(Type.RIGHT_UNPROVISIONED, R.layout.car_right_system_bar_unprovisioned);
61         return map;
62     }
63 
64     private final Context mContext;
65     private final ArrayMap<Type, CarSystemBarView> mCachedViewMap = new ArrayMap<>(
66             Type.values().length);
67     private final ArrayMap<Type, ViewGroup> mCachedContainerMap = new ArrayMap<>();
68     private final FeatureFlags mFeatureFlags;
69     private final UserTracker mUserTracker;
70     private final CarSystemBarElementInitializer mCarSystemBarElementInitializer;
71     private final List<CarSystemBarElementController> mCarSystemBarElementControllers =
72             new ArrayList<>();
73 
74     /** Type of navigation bar to be created. */
75     private enum Type {
76         TOP,
77         TOP_WITH_DOCK,
78         TOP_UNPROVISIONED,
79         BOTTOM,
80         BOTTOM_WITH_DOCK,
81         BOTTOM_UNPROVISIONED,
82         LEFT,
83         LEFT_UNPROVISIONED,
84         RIGHT,
85         RIGHT_UNPROVISIONED
86     }
87 
88     @Inject
CarSystemBarViewFactory( Context context, FeatureFlags featureFlags, UserTracker userTracker, CarSystemBarElementInitializer elementInitializer )89     public CarSystemBarViewFactory(
90             Context context,
91             FeatureFlags featureFlags,
92             UserTracker userTracker,
93             CarSystemBarElementInitializer elementInitializer
94     ) {
95         mContext = context;
96         mFeatureFlags = featureFlags;
97         mUserTracker = userTracker;
98         mCarSystemBarElementInitializer = elementInitializer;
99     }
100 
101     /** Gets the top window. */
getTopWindow()102     public ViewGroup getTopWindow() {
103         return getWindowCached(Type.TOP);
104     }
105 
106     /** Gets the bottom window. */
getBottomWindow()107     public ViewGroup getBottomWindow() {
108         return getWindowCached(Type.BOTTOM);
109     }
110 
111     /** Gets the left window. */
getLeftWindow()112     public ViewGroup getLeftWindow() {
113         return getWindowCached(Type.LEFT);
114     }
115 
116     /** Gets the right window. */
getRightWindow()117     public ViewGroup getRightWindow() {
118         return getWindowCached(Type.RIGHT);
119     }
120 
121     /** Gets the top bar. */
getTopBar(boolean isSetUp)122     public CarSystemBarView getTopBar(boolean isSetUp) {
123         if (Flags.dockFeature()) {
124             return getBar(isSetUp, Type.TOP_WITH_DOCK, Type.TOP_UNPROVISIONED);
125         }
126         return getBar(isSetUp, Type.TOP, Type.TOP_UNPROVISIONED);
127     }
128 
129     /** Gets the bottom bar. */
getBottomBar(boolean isSetUp)130     public CarSystemBarView getBottomBar(boolean isSetUp) {
131         if (Flags.dockFeature()) {
132             return getBar(isSetUp, Type.BOTTOM_WITH_DOCK, Type.BOTTOM_UNPROVISIONED);
133         }
134         return getBar(isSetUp, Type.BOTTOM, Type.BOTTOM_UNPROVISIONED);
135     }
136 
137     /** Gets the left bar. */
getLeftBar(boolean isSetUp)138     public CarSystemBarView getLeftBar(boolean isSetUp) {
139         return getBar(isSetUp, Type.LEFT, Type.LEFT_UNPROVISIONED);
140     }
141 
142     /** Gets the right bar. */
getRightBar(boolean isSetUp)143     public CarSystemBarView getRightBar(boolean isSetUp) {
144         return getBar(isSetUp, Type.RIGHT, Type.RIGHT_UNPROVISIONED);
145     }
146 
getWindowCached(Type type)147     private ViewGroup getWindowCached(Type type) {
148         if (mCachedContainerMap.containsKey(type)) {
149             return mCachedContainerMap.get(type);
150         }
151 
152         ViewGroup window = (ViewGroup) View.inflate(mContext,
153                 R.layout.navigation_bar_window, /* root= */ null);
154         window.setId(getWindowId(type));
155         mCachedContainerMap.put(type, window);
156         return mCachedContainerMap.get(type);
157     }
158 
159     @IdRes
getWindowId(Type type)160     private int getWindowId(Type type) {
161         return switch (type) {
162             case TOP -> R.id.car_top_bar_window;
163             case BOTTOM -> R.id.car_bottom_bar_window;
164             case LEFT -> R.id.car_left_bar_window;
165             case RIGHT -> R.id.car_right_bar_window;
166             default -> throw new IllegalArgumentException("unknown system bar window type " + type);
167         };
168     }
169 
getBar(boolean isSetUp, Type provisioned, Type unprovisioned)170     private CarSystemBarView getBar(boolean isSetUp, Type provisioned, Type unprovisioned) {
171         CarSystemBarView view = getBarCached(isSetUp, provisioned, unprovisioned);
172 
173         if (view == null) {
174             String name = isSetUp ? provisioned.name() : unprovisioned.name();
175             Log.e(TAG, "CarStatusBar failed inflate for " + name);
176             throw new RuntimeException(
177                     "Unable to build " + name + " nav bar due to missing layout");
178         }
179         return view;
180     }
181 
getBarCached(boolean isSetUp, Type provisioned, Type unprovisioned)182     private CarSystemBarView getBarCached(boolean isSetUp, Type provisioned, Type unprovisioned) {
183         Type type = isSetUp ? provisioned : unprovisioned;
184         if (mCachedViewMap.containsKey(type)) {
185             return mCachedViewMap.get(type);
186         }
187 
188         Integer barLayoutInteger = sLayoutMap.get(type);
189         if (barLayoutInteger == null) {
190             return null;
191         }
192         @LayoutRes int barLayout = barLayoutInteger;
193         CarSystemBarView view = (CarSystemBarView) View.inflate(mContext, barLayout,
194                 /* root= */ null);
195 
196         view.setupHvacButton();
197         view.setupSystemBarButtons(mUserTracker);
198         mCarSystemBarElementControllers.addAll(
199                 mCarSystemBarElementInitializer.initializeCarSystemBarElements(view));
200 
201         // Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here
202         // when the user navigates to another window. This is also used to prevent wrap-around.
203         view.addView(new FocusParkingView(mContext), 0);
204 
205         mCachedViewMap.put(type, view);
206         return mCachedViewMap.get(type);
207     }
208 
209     /** Resets the cached system bar views. */
resetSystemBarViewCache()210     protected void resetSystemBarViewCache() {
211         mCachedViewMap.clear();
212     }
213 
214     /** Resets the cached system bar windows and system bar views. */
resetSystemBarWindowCache()215     protected void resetSystemBarWindowCache() {
216         resetSystemBarViewCache();
217         mCachedContainerMap.clear();
218     }
219 }
220