OSDN Git Service

Fix nav accessibility order
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / statusbar / phone / NavigationBarInflaterView.java
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
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
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
13  */
14
15 package com.android.systemui.statusbar.phone;
16
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;
34
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;
44
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.Objects;
48
49 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
50
51 public class NavigationBarInflaterView extends FrameLayout
52         implements Tunable, PluginListener<NavBarButtonProvider> {
53
54     private static final String TAG = "NavBarInflater";
55
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";
59
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";
69
70     public static final String GRAVITY_SEPARATOR = ";";
71     public static final String BUTTON_SEPARATOR = ",";
72
73     public static final String SIZE_MOD_START = "[";
74     public static final String SIZE_MOD_END = "]";
75
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";
81
82     private final List<NavBarButtonProvider> mPlugins = new ArrayList<>();
83
84     protected LayoutInflater mLayoutInflater;
85     protected LayoutInflater mLandscapeInflater;
86
87     protected FrameLayout mRot0;
88     protected FrameLayout mRot90;
89     private boolean isRot0Landscape;
90
91     private SparseArray<ButtonDispatcher> mButtonDispatchers;
92     private String mCurrentLayout;
93
94     private View mLastPortrait;
95     private View mLastLandscape;
96
97     private boolean mAlternativeOrder;
98
99     public NavigationBarInflaterView(Context context, AttributeSet attrs) {
100         super(context, attrs);
101         createInflaters();
102         Display display = ((WindowManager)
103                 context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
104         Mode displayMode = display.getMode();
105         isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
106     }
107
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));
114     }
115
116     @Override
117     protected void onFinishInflate() {
118         super.onFinishInflate();
119         inflateChildren();
120         clearViews();
121         inflateLayout(getDefaultLayout());
122     }
123
124     private void inflateChildren() {
125         removeAllViews();
126         mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);
127         mRot0.setId(R.id.rot0);
128         addView(mRot0);
129         mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this,
130                 false);
131         mRot90.setId(R.id.rot90);
132         addView(mRot90);
133         updateAlternativeOrder();
134         if (getParent() instanceof NavigationBarView) {
135             ((NavigationBarView) getParent()).updateRotatedViews();
136         }
137     }
138
139     protected String getDefaultLayout() {
140         return mContext.getString(R.string.config_navBarLayout);
141     }
142
143     @Override
144     protected void onAttachedToWindow() {
145         super.onAttachedToWindow();
146         Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
147                 NAV_BAR_RIGHT);
148         Dependency.get(PluginManager.class).addPluginListener(this,
149                 NavBarButtonProvider.class, true /* Allow multiple */);
150     }
151
152     @Override
153     protected void onDetachedFromWindow() {
154         Dependency.get(TunerService.class).removeTunable(this);
155         Dependency.get(PluginManager.class).removePluginListener(this);
156         super.onDetachedFromWindow();
157     }
158
159     @Override
160     public void onTuningChanged(String key, String newValue) {
161         if (NAV_BAR_VIEWS.equals(key)) {
162             if (!Objects.equals(mCurrentLayout, newValue)) {
163                 clearViews();
164                 inflateLayout(newValue);
165             }
166         } else if (NAV_BAR_LEFT.equals(key) || NAV_BAR_RIGHT.equals(key)) {
167             clearViews();
168             inflateLayout(mCurrentLayout);
169         }
170     }
171
172     public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDisatchers) {
173         mButtonDispatchers = buttonDisatchers;
174         for (int i = 0; i < buttonDisatchers.size(); i++) {
175             initiallyFill(buttonDisatchers.valueAt(i));
176         }
177     }
178
179     public void setAlternativeOrder(boolean alternativeOrder) {
180         if (alternativeOrder != mAlternativeOrder) {
181             mAlternativeOrder = alternativeOrder;
182             updateAlternativeOrder();
183         }
184     }
185
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));
191     }
192
193     private void updateAlternativeOrder(View v) {
194         if (v instanceof ReverseLinearLayout) {
195             ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder);
196         }
197     }
198
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));
204     }
205
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));
215             }
216         }
217     }
218
219     protected void inflateLayout(String newLayout) {
220         mCurrentLayout = newLayout;
221         if (newLayout == null) {
222             newLayout = getDefaultLayout();
223         }
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);
231
232         inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
233         inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);
234
235         addGravitySpacer(mRot0.findViewById(R.id.ends_group));
236         addGravitySpacer(mRot90.findViewById(R.id.ends_group));
237
238         inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
239         inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
240     }
241
242     private void addGravitySpacer(LinearLayout layout) {
243         layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
244     }
245
246     private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
247             boolean start) {
248         for (int i = 0; i < buttons.length; i++) {
249             inflateButton(buttons[i], parent, landscape, start);
250         }
251     }
252
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);
257         }
258         return new LayoutParams(layoutParams.width, layoutParams.height);
259     }
260
261     @Nullable
262     protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
263             boolean start) {
264         LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
265         View v = createView(buttonSpec, parent, inflater);
266         if (v == null) return null;
267
268         v = applySize(v, buttonSpec, landscape, start);
269         parent.addView(v);
270         addToDispatchers(v);
271         View lastView = landscape ? mLastLandscape : mLastPortrait;
272         View accessibilityView = v;
273         if (v instanceof ReverseFrameLayout) {
274             accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
275         }
276         if (lastView != null) {
277             accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
278         }
279         if (landscape) {
280             mLastLandscape = accessibilityView;
281         } else {
282             mLastPortrait = accessibilityView;
283         }
284         return v;
285     }
286
287     private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
288         String sizeStr = extractSize(buttonSpec);
289         if (sizeStr == null) return v;
290
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;
297             } else {
298                 childParams.gravity = landscape ? (start ? Gravity.BOTTOM : Gravity.TOP)
299                         : (start ? Gravity.START : Gravity.END);
300             }
301             frame.addView(v, childParams);
302             frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
303             frame.setClipChildren(false);
304             frame.setClipToPadding(false);
305             return frame;
306         }
307         float size = Float.parseFloat(sizeStr);
308         ViewGroup.LayoutParams params = v.getLayoutParams();
309         params.width = (int) (params.width * size);
310         return v;
311     }
312
313     private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
314         View v = null;
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);
322         }
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;
327         }
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);
345             if (uri != null) {
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));
353                 }
354             }
355         }
356         return v;
357     }
358
359     public static String extractImage(String buttonSpec) {
360         if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
361             return null;
362         }
363         final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
364         String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
365         return subStr;
366     }
367
368     public static int extractKeycode(String buttonSpec) {
369         if (!buttonSpec.contains(KEY_CODE_START)) {
370             return 1;
371         }
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);
375     }
376
377     public static String extractSize(String buttonSpec) {
378         if (!buttonSpec.contains(SIZE_MOD_START)) {
379             return null;
380         }
381         final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
382         return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
383     }
384
385     public static String extractButton(String buttonSpec) {
386         if (!buttonSpec.contains(SIZE_MOD_START)) {
387             return buttonSpec;
388         }
389         return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
390     }
391
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));
402                 }
403             }
404         }
405     }
406
407
408
409     private void clearViews() {
410         if (mButtonDispatchers != null) {
411             for (int i = 0; i < mButtonDispatchers.size(); i++) {
412                 mButtonDispatchers.valueAt(i).clear();
413             }
414         }
415         clearAllChildren(mRot0.findViewById(R.id.nav_buttons));
416         clearAllChildren(mRot90.findViewById(R.id.nav_buttons));
417     }
418
419     private void clearAllChildren(ViewGroup group) {
420         for (int i = 0; i < group.getChildCount(); i++) {
421             ((ViewGroup) group.getChildAt(i)).removeAllViews();
422         }
423     }
424
425     @Override
426     public void onPluginConnected(NavBarButtonProvider plugin, Context context) {
427         mPlugins.add(plugin);
428         clearViews();
429         inflateLayout(mCurrentLayout);
430     }
431
432     @Override
433     public void onPluginDisconnected(NavBarButtonProvider plugin) {
434         mPlugins.remove(plugin);
435         clearViews();
436         inflateLayout(mCurrentLayout);
437     }
438 }