Home | History | Annotate | Download | only in phone
      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.content.res.Resources;
     21 import android.util.AttributeSet;
     22 import android.util.SparseArray;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.widget.FrameLayout;
     27 import android.widget.LinearLayout;
     28 import android.widget.Space;
     29 
     30 import com.android.systemui.R;
     31 import com.android.systemui.statusbar.policy.KeyButtonView;
     32 import com.android.systemui.tuner.TunerService;
     33 
     34 import java.util.Objects;
     35 
     36 public class NavigationBarInflaterView extends FrameLayout implements TunerService.Tunable {
     37 
     38     private static final String TAG = "NavBarInflater";
     39 
     40     public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
     41 
     42     public static final String MENU_IME = "menu_ime";
     43     public static final String BACK = "back";
     44     public static final String HOME = "home";
     45     public static final String RECENT = "recent";
     46     public static final String NAVSPACE = "space";
     47     public static final String CLIPBOARD = "clipboard";
     48     public static final String KEY = "key";
     49 
     50     public static final String GRAVITY_SEPARATOR = ";";
     51     public static final String BUTTON_SEPARATOR = ",";
     52 
     53     public static final String SIZE_MOD_START = "[";
     54     public static final String SIZE_MOD_END = "]";
     55 
     56     public static final String KEY_CODE_START = "(";
     57     public static final String KEY_IMAGE_DELIM = ":";
     58     public static final String KEY_CODE_END = ")";
     59 
     60     protected LayoutInflater mLayoutInflater;
     61     protected LayoutInflater mLandscapeInflater;
     62     private int mDensity;
     63 
     64     protected FrameLayout mRot0;
     65     protected FrameLayout mRot90;
     66 
     67     private SparseArray<ButtonDispatcher> mButtonDispatchers;
     68     private String mCurrentLayout;
     69 
     70     private View mLastRot0;
     71     private View mLastRot90;
     72 
     73     private boolean mAlternativeOrder;
     74 
     75     public NavigationBarInflaterView(Context context, AttributeSet attrs) {
     76         super(context, attrs);
     77         mDensity = context.getResources().getConfiguration().densityDpi;
     78         createInflaters();
     79     }
     80 
     81     private void createInflaters() {
     82         mLayoutInflater = LayoutInflater.from(mContext);
     83         Configuration landscape = new Configuration();
     84         landscape.setTo(mContext.getResources().getConfiguration());
     85         landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
     86         mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
     87     }
     88 
     89     @Override
     90     protected void onConfigurationChanged(Configuration newConfig) {
     91         super.onConfigurationChanged(newConfig);
     92         if (mDensity != newConfig.densityDpi) {
     93             mDensity = newConfig.densityDpi;
     94             createInflaters();
     95             inflateChildren();
     96             clearViews();
     97             inflateLayout(mCurrentLayout);
     98         }
     99     }
    100 
    101     @Override
    102     protected void onFinishInflate() {
    103         super.onFinishInflate();
    104         inflateChildren();
    105         clearViews();
    106         inflateLayout(getDefaultLayout());
    107     }
    108 
    109     private void inflateChildren() {
    110         removeAllViews();
    111         mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);
    112         mRot0.setId(R.id.rot0);
    113         addView(mRot0);
    114         mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this,
    115                 false);
    116         mRot90.setId(R.id.rot90);
    117         addView(mRot90);
    118         updateAlternativeOrder();
    119         if (getParent() instanceof NavigationBarView) {
    120             ((NavigationBarView) getParent()).updateRotatedViews();
    121         }
    122     }
    123 
    124     protected String getDefaultLayout() {
    125         return mContext.getString(R.string.config_navBarLayout);
    126     }
    127 
    128     @Override
    129     protected void onAttachedToWindow() {
    130         super.onAttachedToWindow();
    131         TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
    132     }
    133 
    134     @Override
    135     protected void onDetachedFromWindow() {
    136         TunerService.get(getContext()).removeTunable(this);
    137         super.onDetachedFromWindow();
    138     }
    139 
    140     @Override
    141     public void onTuningChanged(String key, String newValue) {
    142         if (NAV_BAR_VIEWS.equals(key)) {
    143             if (!Objects.equals(mCurrentLayout, newValue)) {
    144                 clearViews();
    145                 inflateLayout(newValue);
    146             }
    147         }
    148     }
    149 
    150     public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDisatchers) {
    151         mButtonDispatchers = buttonDisatchers;
    152         for (int i = 0; i < buttonDisatchers.size(); i++) {
    153             initiallyFill(buttonDisatchers.valueAt(i));
    154         }
    155     }
    156 
    157     public void setAlternativeOrder(boolean alternativeOrder) {
    158         if (alternativeOrder != mAlternativeOrder) {
    159             mAlternativeOrder = alternativeOrder;
    160             updateAlternativeOrder();
    161         }
    162     }
    163 
    164     private void updateAlternativeOrder() {
    165         updateAlternativeOrder(mRot0.findViewById(R.id.ends_group));
    166         updateAlternativeOrder(mRot0.findViewById(R.id.center_group));
    167         updateAlternativeOrder(mRot90.findViewById(R.id.ends_group));
    168         updateAlternativeOrder(mRot90.findViewById(R.id.center_group));
    169     }
    170 
    171     private void updateAlternativeOrder(View v) {
    172         if (v instanceof ReverseLinearLayout) {
    173             ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder);
    174         }
    175     }
    176 
    177     private void initiallyFill(ButtonDispatcher buttonDispatcher) {
    178         addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.ends_group));
    179         addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.center_group));
    180         addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.ends_group));
    181         addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.center_group));
    182     }
    183 
    184     private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
    185         for (int i = 0; i < parent.getChildCount(); i++) {
    186             // Need to manually search for each id, just in case each group has more than one
    187             // of a single id.  It probably mostly a waste of time, but shouldn't take long
    188             // and will only happen once.
    189             if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
    190                 buttonDispatcher.addView(parent.getChildAt(i));
    191             } else if (parent.getChildAt(i) instanceof ViewGroup) {
    192                 addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
    193             }
    194         }
    195     }
    196 
    197     protected void inflateLayout(String newLayout) {
    198         mCurrentLayout = newLayout;
    199         if (newLayout == null) {
    200             newLayout = getDefaultLayout();
    201         }
    202         String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
    203         String[] start = sets[0].split(BUTTON_SEPARATOR);
    204         String[] center = sets[1].split(BUTTON_SEPARATOR);
    205         String[] end = sets[2].split(BUTTON_SEPARATOR);
    206         // Inflate these in start to end order or accessibility traversal will be messed up.
    207         inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
    208         inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
    209 
    210         inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group), false);
    211         inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group), true);
    212 
    213         addGravitySpacer((LinearLayout) mRot0.findViewById(R.id.ends_group));
    214         addGravitySpacer((LinearLayout) mRot90.findViewById(R.id.ends_group));
    215 
    216         inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
    217         inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
    218     }
    219 
    220     private void addGravitySpacer(LinearLayout layout) {
    221         layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
    222     }
    223 
    224     private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape) {
    225         for (int i = 0; i < buttons.length; i++) {
    226             inflateButton(buttons[i], parent, landscape, i);
    227         }
    228     }
    229 
    230     private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
    231         if (layoutParams instanceof LinearLayout.LayoutParams) {
    232             return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
    233                     ((LinearLayout.LayoutParams) layoutParams).weight);
    234         }
    235         return new LayoutParams(layoutParams.width, layoutParams.height);
    236     }
    237 
    238     @Nullable
    239     protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
    240             int indexInParent) {
    241         LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
    242         float size = extractSize(buttonSpec);
    243         String button = extractButton(buttonSpec);
    244         View v = null;
    245         if (HOME.equals(button)) {
    246             v = inflater.inflate(R.layout.home, parent, false);
    247             if (landscape && isSw600Dp()) {
    248                 setupLandButton(v);
    249             }
    250         } else if (BACK.equals(button)) {
    251             v = inflater.inflate(R.layout.back, parent, false);
    252             if (landscape && isSw600Dp()) {
    253                 setupLandButton(v);
    254             }
    255         } else if (RECENT.equals(button)) {
    256             v = inflater.inflate(R.layout.recent_apps, parent, false);
    257             if (landscape && isSw600Dp()) {
    258                 setupLandButton(v);
    259             }
    260         } else if (MENU_IME.equals(button)) {
    261             v = inflater.inflate(R.layout.menu_ime, parent, false);
    262         } else if (NAVSPACE.equals(button)) {
    263             v = inflater.inflate(R.layout.nav_key_space, parent, false);
    264         } else if (CLIPBOARD.equals(button)) {
    265             v = inflater.inflate(R.layout.clipboard, parent, false);
    266         } else if (button.startsWith(KEY)) {
    267             String uri = extractImage(button);
    268             int code = extractKeycode(button);
    269             v = inflater.inflate(R.layout.custom_key, parent, false);
    270             ((KeyButtonView) v).setCode(code);
    271             if (uri != null) {
    272                 ((KeyButtonView) v).loadAsync(uri);
    273             }
    274         } else {
    275             return null;
    276         }
    277 
    278         if (size != 0) {
    279             ViewGroup.LayoutParams params = v.getLayoutParams();
    280             params.width = (int) (params.width * size);
    281         }
    282         parent.addView(v);
    283         addToDispatchers(v, landscape);
    284         View lastView = landscape ? mLastRot90 : mLastRot0;
    285         if (lastView != null) {
    286             v.setAccessibilityTraversalAfter(lastView.getId());
    287         }
    288         if (landscape) {
    289             mLastRot90 = v;
    290         } else {
    291             mLastRot0 = v;
    292         }
    293         return v;
    294     }
    295 
    296     public static String extractImage(String buttonSpec) {
    297         if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
    298             return null;
    299         }
    300         final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
    301         String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
    302         return subStr;
    303     }
    304 
    305     public static int extractKeycode(String buttonSpec) {
    306         if (!buttonSpec.contains(KEY_CODE_START)) {
    307             return 1;
    308         }
    309         final int start = buttonSpec.indexOf(KEY_CODE_START);
    310         String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
    311         return Integer.parseInt(subStr);
    312     }
    313 
    314     public static float extractSize(String buttonSpec) {
    315         if (!buttonSpec.contains(SIZE_MOD_START)) {
    316             return 1;
    317         }
    318         final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
    319         String sizeStr = buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
    320         return Float.parseFloat(sizeStr);
    321     }
    322 
    323     public static String extractButton(String buttonSpec) {
    324         if (!buttonSpec.contains(SIZE_MOD_START)) {
    325             return buttonSpec;
    326         }
    327         return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
    328     }
    329 
    330     private void addToDispatchers(View v, boolean landscape) {
    331         if (mButtonDispatchers != null) {
    332             final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
    333             if (indexOfKey >= 0) {
    334                 mButtonDispatchers.valueAt(indexOfKey).addView(v, landscape);
    335             } else if (v instanceof ViewGroup) {
    336                 final ViewGroup viewGroup = (ViewGroup)v;
    337                 final int N = viewGroup.getChildCount();
    338                 for (int i = 0; i < N; i++) {
    339                     addToDispatchers(viewGroup.getChildAt(i), landscape);
    340                 }
    341             }
    342         }
    343     }
    344 
    345     private boolean isSw600Dp() {
    346         Configuration configuration = mContext.getResources().getConfiguration();
    347         return (configuration.smallestScreenWidthDp >= 600);
    348     }
    349 
    350     /**
    351      * This manually sets the width of sw600dp landscape buttons because despite
    352      * overriding the configuration from the overridden resources aren't loaded currently.
    353      */
    354     private void setupLandButton(View v) {
    355         Resources res = mContext.getResources();
    356         v.getLayoutParams().width = res.getDimensionPixelOffset(
    357                 R.dimen.navigation_key_width_sw600dp_land);
    358         int padding = res.getDimensionPixelOffset(R.dimen.navigation_key_padding_sw600dp_land);
    359         v.setPadding(padding, v.getPaddingTop(), padding, v.getPaddingBottom());
    360     }
    361 
    362     private void clearViews() {
    363         if (mButtonDispatchers != null) {
    364             for (int i = 0; i < mButtonDispatchers.size(); i++) {
    365                 mButtonDispatchers.valueAt(i).clear();
    366             }
    367         }
    368         clearAllChildren((ViewGroup) mRot0.findViewById(R.id.nav_buttons));
    369         clearAllChildren((ViewGroup) mRot90.findViewById(R.id.nav_buttons));
    370     }
    371 
    372     private void clearAllChildren(ViewGroup group) {
    373         for (int i = 0; i < group.getChildCount(); i++) {
    374             ((ViewGroup) group.getChildAt(i)).removeAllViews();
    375         }
    376     }
    377 }
    378