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.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     }
    135 
    136     protected String getDefaultLayout() {
    137         return mContext.getString(R.string.config_navBarLayout);
    138     }
    139 
    140     @Override
    141     protected void onAttachedToWindow() {
    142         super.onAttachedToWindow();
    143         Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
    144                 NAV_BAR_RIGHT);
    145         Dependency.get(PluginManager.class).addPluginListener(this,
    146                 NavBarButtonProvider.class, true /* Allow multiple */);
    147     }
    148 
    149     @Override
    150     protected void onDetachedFromWindow() {
    151         Dependency.get(TunerService.class).removeTunable(this);
    152         Dependency.get(PluginManager.class).removePluginListener(this);
    153         super.onDetachedFromWindow();
    154     }
    155 
    156     @Override
    157     public void onTuningChanged(String key, String newValue) {
    158         if (NAV_BAR_VIEWS.equals(key)) {
    159             if (!Objects.equals(mCurrentLayout, newValue)) {
    160                 clearViews();
    161                 inflateLayout(newValue);
    162             }
    163         } else if (NAV_BAR_LEFT.equals(key) || NAV_BAR_RIGHT.equals(key)) {
    164             clearViews();
    165             inflateLayout(mCurrentLayout);
    166         }
    167     }
    168 
    169     public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDisatchers) {
    170         mButtonDispatchers = buttonDisatchers;
    171         for (int i = 0; i < buttonDisatchers.size(); i++) {
    172             initiallyFill(buttonDisatchers.valueAt(i));
    173         }
    174     }
    175 
    176     public void setAlternativeOrder(boolean alternativeOrder) {
    177         if (alternativeOrder != mAlternativeOrder) {
    178             mAlternativeOrder = alternativeOrder;
    179             updateAlternativeOrder();
    180         }
    181     }
    182 
    183     private void updateAlternativeOrder() {
    184         updateAlternativeOrder(mRot0.findViewById(R.id.ends_group));
    185         updateAlternativeOrder(mRot0.findViewById(R.id.center_group));
    186         updateAlternativeOrder(mRot90.findViewById(R.id.ends_group));
    187         updateAlternativeOrder(mRot90.findViewById(R.id.center_group));
    188     }
    189 
    190     private void updateAlternativeOrder(View v) {
    191         if (v instanceof ReverseLinearLayout) {
    192             ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder);
    193         }
    194     }
    195 
    196     private void initiallyFill(ButtonDispatcher buttonDispatcher) {
    197         addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.ends_group));
    198         addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.center_group));
    199         addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.ends_group));
    200         addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.center_group));
    201     }
    202 
    203     private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
    204         for (int i = 0; i < parent.getChildCount(); i++) {
    205             // Need to manually search for each id, just in case each group has more than one
    206             // of a single id.  It probably mostly a waste of time, but shouldn't take long
    207             // and will only happen once.
    208             if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
    209                 buttonDispatcher.addView(parent.getChildAt(i));
    210             } else if (parent.getChildAt(i) instanceof ViewGroup) {
    211                 addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
    212             }
    213         }
    214     }
    215 
    216     protected void inflateLayout(String newLayout) {
    217         mCurrentLayout = newLayout;
    218         if (newLayout == null) {
    219             newLayout = getDefaultLayout();
    220         }
    221         String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
    222         String[] start = sets[0].split(BUTTON_SEPARATOR);
    223         String[] center = sets[1].split(BUTTON_SEPARATOR);
    224         String[] end = sets[2].split(BUTTON_SEPARATOR);
    225         // Inflate these in start to end order or accessibility traversal will be messed up.
    226         inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true);
    227         inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);
    228 
    229         inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
    230         inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);
    231 
    232         addGravitySpacer(mRot0.findViewById(R.id.ends_group));
    233         addGravitySpacer(mRot90.findViewById(R.id.ends_group));
    234 
    235         inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
    236         inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
    237     }
    238 
    239     private void addGravitySpacer(LinearLayout layout) {
    240         layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
    241     }
    242 
    243     private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
    244             boolean start) {
    245         for (int i = 0; i < buttons.length; i++) {
    246             inflateButton(buttons[i], parent, landscape, start);
    247         }
    248     }
    249 
    250     private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
    251         if (layoutParams instanceof LinearLayout.LayoutParams) {
    252             return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
    253                     ((LinearLayout.LayoutParams) layoutParams).weight);
    254         }
    255         return new LayoutParams(layoutParams.width, layoutParams.height);
    256     }
    257 
    258     @Nullable
    259     protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
    260             boolean start) {
    261         LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
    262         View v = createView(buttonSpec, parent, inflater);
    263         if (v == null) return null;
    264 
    265         v = applySize(v, buttonSpec, landscape, start);
    266         parent.addView(v);
    267         addToDispatchers(v);
    268         View lastView = landscape ? mLastLandscape : mLastPortrait;
    269         View accessibilityView = v;
    270         if (v instanceof ReverseFrameLayout) {
    271             accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
    272         }
    273         if (lastView != null) {
    274             accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
    275         }
    276         if (landscape) {
    277             mLastLandscape = accessibilityView;
    278         } else {
    279             mLastPortrait = accessibilityView;
    280         }
    281         return v;
    282     }
    283 
    284     private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
    285         String sizeStr = extractSize(buttonSpec);
    286         if (sizeStr == null) return v;
    287 
    288         if (sizeStr.contains(WEIGHT_SUFFIX)) {
    289             float weight = Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
    290             FrameLayout frame = new ReverseFrameLayout(mContext);
    291             LayoutParams childParams = new LayoutParams(v.getLayoutParams());
    292             if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
    293                 childParams.gravity = Gravity.CENTER;
    294             } else {
    295                 childParams.gravity = landscape ? (start ? Gravity.BOTTOM : Gravity.TOP)
    296                         : (start ? Gravity.START : Gravity.END);
    297             }
    298             frame.addView(v, childParams);
    299             frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
    300             frame.setClipChildren(false);
    301             frame.setClipToPadding(false);
    302             return frame;
    303         }
    304         float size = Float.parseFloat(sizeStr);
    305         ViewGroup.LayoutParams params = v.getLayoutParams();
    306         params.width = (int) (params.width * size);
    307         return v;
    308     }
    309 
    310     private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
    311         View v = null;
    312         String button = extractButton(buttonSpec);
    313         if (LEFT.equals(button)) {
    314             String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
    315             button = extractButton(s);
    316         } else if (RIGHT.equals(button)) {
    317             String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);
    318             button = extractButton(s);
    319         }
    320         // Let plugins go first so they can override a standard view if they want.
    321         for (NavBarButtonProvider provider : mPlugins) {
    322             v = provider.createView(buttonSpec, parent);
    323             if (v != null) return v;
    324         }
    325         if (HOME.equals(button)) {
    326             v = inflater.inflate(R.layout.home, parent, false);
    327         } else if (BACK.equals(button)) {
    328             v = inflater.inflate(R.layout.back, parent, false);
    329         } else if (RECENT.equals(button)) {
    330             v = inflater.inflate(R.layout.recent_apps, parent, false);
    331         } else if (MENU_IME.equals(button)) {
    332             v = inflater.inflate(R.layout.menu_ime, parent, false);
    333         } else if (NAVSPACE.equals(button)) {
    334             v = inflater.inflate(R.layout.nav_key_space, parent, false);
    335         } else if (CLIPBOARD.equals(button)) {
    336             v = inflater.inflate(R.layout.clipboard, parent, false);
    337         } else if (button.startsWith(KEY)) {
    338             String uri = extractImage(button);
    339             int code = extractKeycode(button);
    340             v = inflater.inflate(R.layout.custom_key, parent, false);
    341             ((KeyButtonView) v).setCode(code);
    342             if (uri != null) {
    343                 if (uri.contains(":")) {
    344                     ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
    345                 } else if (uri.contains("/")) {
    346                     int index = uri.indexOf('/');
    347                     String pkg = uri.substring(0, index);
    348                     int id = Integer.parseInt(uri.substring(index + 1));
    349                     ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
    350                 }
    351             }
    352         }
    353         return v;
    354     }
    355 
    356     public static String extractImage(String buttonSpec) {
    357         if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
    358             return null;
    359         }
    360         final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
    361         String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
    362         return subStr;
    363     }
    364 
    365     public static int extractKeycode(String buttonSpec) {
    366         if (!buttonSpec.contains(KEY_CODE_START)) {
    367             return 1;
    368         }
    369         final int start = buttonSpec.indexOf(KEY_CODE_START);
    370         String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
    371         return Integer.parseInt(subStr);
    372     }
    373 
    374     public static String extractSize(String buttonSpec) {
    375         if (!buttonSpec.contains(SIZE_MOD_START)) {
    376             return null;
    377         }
    378         final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
    379         return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
    380     }
    381 
    382     public static String extractButton(String buttonSpec) {
    383         if (!buttonSpec.contains(SIZE_MOD_START)) {
    384             return buttonSpec;
    385         }
    386         return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
    387     }
    388 
    389     private void addToDispatchers(View v) {
    390         if (mButtonDispatchers != null) {
    391             final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
    392             if (indexOfKey >= 0) {
    393                 mButtonDispatchers.valueAt(indexOfKey).addView(v);
    394             } else if (v instanceof ViewGroup) {
    395                 final ViewGroup viewGroup = (ViewGroup)v;
    396                 final int N = viewGroup.getChildCount();
    397                 for (int i = 0; i < N; i++) {
    398                     addToDispatchers(viewGroup.getChildAt(i));
    399                 }
    400             }
    401         }
    402     }
    403 
    404 
    405 
    406     private void clearViews() {
    407         if (mButtonDispatchers != null) {
    408             for (int i = 0; i < mButtonDispatchers.size(); i++) {
    409                 mButtonDispatchers.valueAt(i).clear();
    410             }
    411         }
    412         clearAllChildren(mRot0.findViewById(R.id.nav_buttons));
    413         clearAllChildren(mRot90.findViewById(R.id.nav_buttons));
    414     }
    415 
    416     private void clearAllChildren(ViewGroup group) {
    417         for (int i = 0; i < group.getChildCount(); i++) {
    418             ((ViewGroup) group.getChildAt(i)).removeAllViews();
    419         }
    420     }
    421 
    422     @Override
    423     public void onPluginConnected(NavBarButtonProvider plugin, Context context) {
    424         mPlugins.add(plugin);
    425         clearViews();
    426         inflateLayout(mCurrentLayout);
    427     }
    428 
    429     @Override
    430     public void onPluginDisconnected(NavBarButtonProvider plugin) {
    431         mPlugins.remove(plugin);
    432         clearViews();
    433         inflateLayout(mCurrentLayout);
    434     }
    435 }
    436