2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
15 package com.android.systemui.statusbar.phone;
17 import android.annotation.Nullable;
18 import android.content.Context;
19 import android.content.res.Configuration;
20 import android.graphics.drawable.Icon;
21 import android.util.AttributeSet;
22 import android.util.Log;
23 import android.util.SparseArray;
24 import android.view.Display;
25 import android.view.Display.Mode;
26 import android.view.Gravity;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.WindowManager;
31 import android.widget.FrameLayout;
32 import android.widget.LinearLayout;
33 import android.widget.Space;
35 import com.android.systemui.Dependency;
36 import com.android.systemui.R;
37 import com.android.systemui.plugins.PluginListener;
38 import com.android.systemui.plugins.PluginManager;
39 import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider;
40 import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseFrameLayout;
41 import com.android.systemui.statusbar.policy.KeyButtonView;
42 import com.android.systemui.tuner.TunerService;
43 import com.android.systemui.tuner.TunerService.Tunable;
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.Objects;
49 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
51 public class NavigationBarInflaterView extends FrameLayout
52 implements Tunable, PluginListener<NavBarButtonProvider> {
54 private static final String TAG = "NavBarInflater";
56 public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
57 public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
58 public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
60 public static final String MENU_IME = "menu_ime";
61 public static final String BACK = "back";
62 public static final String HOME = "home";
63 public static final String RECENT = "recent";
64 public static final String NAVSPACE = "space";
65 public static final String CLIPBOARD = "clipboard";
66 public static final String KEY = "key";
67 public static final String LEFT = "left";
68 public static final String RIGHT = "right";
70 public static final String GRAVITY_SEPARATOR = ";";
71 public static final String BUTTON_SEPARATOR = ",";
73 public static final String SIZE_MOD_START = "[";
74 public static final String SIZE_MOD_END = "]";
76 public static final String KEY_CODE_START = "(";
77 public static final String KEY_IMAGE_DELIM = ":";
78 public static final String KEY_CODE_END = ")";
79 private static final String WEIGHT_SUFFIX = "W";
80 private static final String WEIGHT_CENTERED_SUFFIX = "WC";
82 private final List<NavBarButtonProvider> mPlugins = new ArrayList<>();
84 protected LayoutInflater mLayoutInflater;
85 protected LayoutInflater mLandscapeInflater;
87 protected FrameLayout mRot0;
88 protected FrameLayout mRot90;
89 private boolean isRot0Landscape;
91 private SparseArray<ButtonDispatcher> mButtonDispatchers;
92 private String mCurrentLayout;
94 private View mLastPortrait;
95 private View mLastLandscape;
97 private boolean mAlternativeOrder;
99 public NavigationBarInflaterView(Context context, AttributeSet attrs) {
100 super(context, attrs);
102 Display display = ((WindowManager)
103 context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
104 Mode displayMode = display.getMode();
105 isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
108 private void createInflaters() {
109 mLayoutInflater = LayoutInflater.from(mContext);
110 Configuration landscape = new Configuration();
111 landscape.setTo(mContext.getResources().getConfiguration());
112 landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
113 mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
117 protected void onFinishInflate() {
118 super.onFinishInflate();
121 inflateLayout(getDefaultLayout());
124 private void inflateChildren() {
126 mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);
127 mRot0.setId(R.id.rot0);
129 mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this,
131 mRot90.setId(R.id.rot90);
133 updateAlternativeOrder();
134 if (getParent() instanceof NavigationBarView) {
135 ((NavigationBarView) getParent()).updateRotatedViews();
139 protected String getDefaultLayout() {
140 return mContext.getString(R.string.config_navBarLayout);
144 protected void onAttachedToWindow() {
145 super.onAttachedToWindow();
146 Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
148 Dependency.get(PluginManager.class).addPluginListener(this,
149 NavBarButtonProvider.class, true /* Allow multiple */);
153 protected void onDetachedFromWindow() {
154 Dependency.get(TunerService.class).removeTunable(this);
155 Dependency.get(PluginManager.class).removePluginListener(this);
156 super.onDetachedFromWindow();
160 public void onTuningChanged(String key, String newValue) {
161 if (NAV_BAR_VIEWS.equals(key)) {
162 if (!Objects.equals(mCurrentLayout, newValue)) {
164 inflateLayout(newValue);
166 } else if (NAV_BAR_LEFT.equals(key) || NAV_BAR_RIGHT.equals(key)) {
168 inflateLayout(mCurrentLayout);
172 public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDisatchers) {
173 mButtonDispatchers = buttonDisatchers;
174 for (int i = 0; i < buttonDisatchers.size(); i++) {
175 initiallyFill(buttonDisatchers.valueAt(i));
179 public void setAlternativeOrder(boolean alternativeOrder) {
180 if (alternativeOrder != mAlternativeOrder) {
181 mAlternativeOrder = alternativeOrder;
182 updateAlternativeOrder();
186 private void updateAlternativeOrder() {
187 updateAlternativeOrder(mRot0.findViewById(R.id.ends_group));
188 updateAlternativeOrder(mRot0.findViewById(R.id.center_group));
189 updateAlternativeOrder(mRot90.findViewById(R.id.ends_group));
190 updateAlternativeOrder(mRot90.findViewById(R.id.center_group));
193 private void updateAlternativeOrder(View v) {
194 if (v instanceof ReverseLinearLayout) {
195 ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder);
199 private void initiallyFill(ButtonDispatcher buttonDispatcher) {
200 addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.ends_group));
201 addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.center_group));
202 addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.ends_group));
203 addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.center_group));
206 private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
207 for (int i = 0; i < parent.getChildCount(); i++) {
208 // Need to manually search for each id, just in case each group has more than one
209 // of a single id. It probably mostly a waste of time, but shouldn't take long
210 // and will only happen once.
211 if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
212 buttonDispatcher.addView(parent.getChildAt(i));
213 } else if (parent.getChildAt(i) instanceof ViewGroup) {
214 addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
219 protected void inflateLayout(String newLayout) {
220 mCurrentLayout = newLayout;
221 if (newLayout == null) {
222 newLayout = getDefaultLayout();
224 String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
225 String[] start = sets[0].split(BUTTON_SEPARATOR);
226 String[] center = sets[1].split(BUTTON_SEPARATOR);
227 String[] end = sets[2].split(BUTTON_SEPARATOR);
228 // Inflate these in start to end order or accessibility traversal will be messed up.
229 inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true);
230 inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);
232 inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
233 inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);
235 addGravitySpacer(mRot0.findViewById(R.id.ends_group));
236 addGravitySpacer(mRot90.findViewById(R.id.ends_group));
238 inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
239 inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
242 private void addGravitySpacer(LinearLayout layout) {
243 layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
246 private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
248 for (int i = 0; i < buttons.length; i++) {
249 inflateButton(buttons[i], parent, landscape, start);
253 private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
254 if (layoutParams instanceof LinearLayout.LayoutParams) {
255 return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
256 ((LinearLayout.LayoutParams) layoutParams).weight);
258 return new LayoutParams(layoutParams.width, layoutParams.height);
262 protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
264 LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
265 View v = createView(buttonSpec, parent, inflater);
266 if (v == null) return null;
268 v = applySize(v, buttonSpec, landscape, start);
271 View lastView = landscape ? mLastLandscape : mLastPortrait;
272 View accessibilityView = v;
273 if (v instanceof ReverseFrameLayout) {
274 accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
276 if (lastView != null) {
277 accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
280 mLastLandscape = accessibilityView;
282 mLastPortrait = accessibilityView;
287 private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
288 String sizeStr = extractSize(buttonSpec);
289 if (sizeStr == null) return v;
291 if (sizeStr.contains(WEIGHT_SUFFIX)) {
292 float weight = Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
293 FrameLayout frame = new ReverseFrameLayout(mContext);
294 LayoutParams childParams = new LayoutParams(v.getLayoutParams());
295 if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
296 childParams.gravity = Gravity.CENTER;
298 childParams.gravity = landscape ? (start ? Gravity.BOTTOM : Gravity.TOP)
299 : (start ? Gravity.START : Gravity.END);
301 frame.addView(v, childParams);
302 frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
303 frame.setClipChildren(false);
304 frame.setClipToPadding(false);
307 float size = Float.parseFloat(sizeStr);
308 ViewGroup.LayoutParams params = v.getLayoutParams();
309 params.width = (int) (params.width * size);
313 private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
315 String button = extractButton(buttonSpec);
316 if (LEFT.equals(button)) {
317 String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
318 button = extractButton(s);
319 } else if (RIGHT.equals(button)) {
320 String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);
321 button = extractButton(s);
323 // Let plugins go first so they can override a standard view if they want.
324 for (NavBarButtonProvider provider : mPlugins) {
325 v = provider.createView(buttonSpec, parent);
326 if (v != null) return v;
328 if (HOME.equals(button)) {
329 v = inflater.inflate(R.layout.home, parent, false);
330 } else if (BACK.equals(button)) {
331 v = inflater.inflate(R.layout.back, parent, false);
332 } else if (RECENT.equals(button)) {
333 v = inflater.inflate(R.layout.recent_apps, parent, false);
334 } else if (MENU_IME.equals(button)) {
335 v = inflater.inflate(R.layout.menu_ime, parent, false);
336 } else if (NAVSPACE.equals(button)) {
337 v = inflater.inflate(R.layout.nav_key_space, parent, false);
338 } else if (CLIPBOARD.equals(button)) {
339 v = inflater.inflate(R.layout.clipboard, parent, false);
340 } else if (button.startsWith(KEY)) {
341 String uri = extractImage(button);
342 int code = extractKeycode(button);
343 v = inflater.inflate(R.layout.custom_key, parent, false);
344 ((KeyButtonView) v).setCode(code);
346 if (uri.contains(":")) {
347 ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
348 } else if (uri.contains("/")) {
349 int index = uri.indexOf('/');
350 String pkg = uri.substring(0, index);
351 int id = Integer.parseInt(uri.substring(index + 1));
352 ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
359 public static String extractImage(String buttonSpec) {
360 if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
363 final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
364 String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
368 public static int extractKeycode(String buttonSpec) {
369 if (!buttonSpec.contains(KEY_CODE_START)) {
372 final int start = buttonSpec.indexOf(KEY_CODE_START);
373 String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
374 return Integer.parseInt(subStr);
377 public static String extractSize(String buttonSpec) {
378 if (!buttonSpec.contains(SIZE_MOD_START)) {
381 final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
382 return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
385 public static String extractButton(String buttonSpec) {
386 if (!buttonSpec.contains(SIZE_MOD_START)) {
389 return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
392 private void addToDispatchers(View v) {
393 if (mButtonDispatchers != null) {
394 final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
395 if (indexOfKey >= 0) {
396 mButtonDispatchers.valueAt(indexOfKey).addView(v);
397 } else if (v instanceof ViewGroup) {
398 final ViewGroup viewGroup = (ViewGroup)v;
399 final int N = viewGroup.getChildCount();
400 for (int i = 0; i < N; i++) {
401 addToDispatchers(viewGroup.getChildAt(i));
409 private void clearViews() {
410 if (mButtonDispatchers != null) {
411 for (int i = 0; i < mButtonDispatchers.size(); i++) {
412 mButtonDispatchers.valueAt(i).clear();
415 clearAllChildren(mRot0.findViewById(R.id.nav_buttons));
416 clearAllChildren(mRot90.findViewById(R.id.nav_buttons));
419 private void clearAllChildren(ViewGroup group) {
420 for (int i = 0; i < group.getChildCount(); i++) {
421 ((ViewGroup) group.getChildAt(i)).removeAllViews();
426 public void onPluginConnected(NavBarButtonProvider plugin, Context context) {
427 mPlugins.add(plugin);
429 inflateLayout(mCurrentLayout);
433 public void onPluginDisconnected(NavBarButtonProvider plugin) {
434 mPlugins.remove(plugin);
436 inflateLayout(mCurrentLayout);