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