NavBarTuner.java revision 3ebd23943b87d5f9d9a59889b4316e631d0e7d15
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.tuner; 16 17import com.android.systemui.R; 18 19import android.annotation.Nullable; 20import android.app.AlertDialog; 21import android.app.Fragment; 22import android.content.Context; 23import android.content.DialogInterface; 24import android.content.res.ColorStateList; 25import android.content.res.TypedArray; 26import android.graphics.Canvas; 27import android.graphics.drawable.Drawable; 28import android.os.Bundle; 29import android.provider.Settings; 30import android.support.v7.widget.LinearLayoutManager; 31import android.support.v7.widget.RecyclerView; 32import android.support.v7.widget.helper.ItemTouchHelper; 33import android.util.TypedValue; 34import android.view.LayoutInflater; 35import android.view.Menu; 36import android.view.MenuInflater; 37import android.view.MenuItem; 38import android.view.MotionEvent; 39import android.view.View; 40import android.view.ViewGroup; 41import android.widget.ImageView; 42import android.widget.TextView; 43 44import java.util.ArrayList; 45import java.util.List; 46 47import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK; 48import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR; 49import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR; 50import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME; 51import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME; 52import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE; 53import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS; 54import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.RECENT; 55 56public class NavBarTuner extends Fragment implements TunerService.Tunable { 57 58 private static final int SAVE = Menu.FIRST + 1; 59 private static final int RESET = Menu.FIRST + 2; 60 61 private NavBarAdapter mNavBarAdapter; 62 63 @Override 64 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 65 Bundle savedInstanceState) { 66 return new RecyclerView(getContext()); 67 } 68 69 @Override 70 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 71 super.onViewCreated(view, savedInstanceState); 72 RecyclerView recyclerView = (RecyclerView) view; 73 final Context context = getContext(); 74 recyclerView.setLayoutManager(new LinearLayoutManager(context)); 75 mNavBarAdapter = new NavBarAdapter(context); 76 recyclerView.setAdapter(mNavBarAdapter); 77 recyclerView.addItemDecoration(new Dividers(context)); 78 final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mNavBarAdapter.mCallbacks); 79 mNavBarAdapter.setTouchHelper(itemTouchHelper); 80 itemTouchHelper.attachToRecyclerView(recyclerView); 81 82 TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS); 83 } 84 85 @Override 86 public void onDestroyView() { 87 super.onDestroyView(); 88 TunerService.get(getContext()).removeTunable(this); 89 } 90 91 @Override 92 public void onTuningChanged(String key, String navLayout) { 93 if (!NAV_BAR_VIEWS.equals(key)) return; 94 Context context = getContext(); 95 if (navLayout == null) { 96 navLayout = context.getString(R.string.config_navBarLayout); 97 } 98 String[] views = navLayout.split(GRAVITY_SEPARATOR); 99 String[] groups = new String[] { NavBarAdapter.START, NavBarAdapter.CENTER, 100 NavBarAdapter.END}; 101 CharSequence[] groupLabels = new String[] { getString(R.string.start), 102 getString(R.string.center), getString(R.string.end) }; 103 mNavBarAdapter.clear(); 104 for (int i = 0; i < 3; i++) { 105 mNavBarAdapter.addButton(groups[i], groupLabels[i]); 106 for (String button : views[i].split(BUTTON_SEPARATOR)) { 107 mNavBarAdapter.addButton(button, getLabel(button, context)); 108 } 109 } 110 mNavBarAdapter.addButton(NavBarAdapter.ADD, getString(R.string.add_button)); 111 setHasOptionsMenu(true); 112 } 113 114 @Override 115 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 116 super.onCreateOptionsMenu(menu, inflater); 117 // TODO: Show save button conditionally, only when there are changes. 118 menu.add(Menu.NONE, SAVE, Menu.NONE, getString(R.string.save)) 119 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 120 menu.add(Menu.NONE, RESET, Menu.NONE, getString(R.string.reset)); 121 } 122 123 @Override 124 public boolean onOptionsItemSelected(MenuItem item) { 125 if (item.getItemId() == SAVE) { 126 if (!mNavBarAdapter.hasHomeButton()) { 127 new AlertDialog.Builder(getContext()) 128 .setTitle(R.string.no_home_title) 129 .setMessage(R.string.no_home_message) 130 .setPositiveButton(android.R.string.ok, null) 131 .show(); 132 } else { 133 Settings.Secure.putString(getContext().getContentResolver(), 134 NAV_BAR_VIEWS, mNavBarAdapter.getNavString()); 135 } 136 return true; 137 } else if (item.getItemId() == RESET) { 138 Settings.Secure.putString(getContext().getContentResolver(), 139 NAV_BAR_VIEWS, null); 140 return true; 141 } 142 return super.onOptionsItemSelected(item); 143 } 144 145 private static CharSequence getLabel(String button, Context context) { 146 if (HOME.equals(button)) { 147 return context.getString(R.string.accessibility_home); 148 } else if (BACK.equals(button)) { 149 return context.getString(R.string.accessibility_back); 150 } else if (RECENT.equals(button)) { 151 return context.getString(R.string.accessibility_recent); 152 } else if (NAVSPACE.equals(button)) { 153 return context.getString(R.string.space); 154 } else if (MENU_IME.equals(button)) { 155 return context.getString(R.string.menu_ime); 156 } 157 return button; 158 } 159 160 private static class Holder extends RecyclerView.ViewHolder { 161 private TextView title; 162 163 public Holder(View itemView) { 164 super(itemView); 165 title = (TextView) itemView.findViewById(android.R.id.title); 166 } 167 } 168 169 private static class Dividers extends RecyclerView.ItemDecoration { 170 private final Drawable mDivider; 171 172 public Dividers(Context context) { 173 TypedValue value = new TypedValue(); 174 context.getTheme().resolveAttribute(android.R.attr.listDivider, value, true); 175 mDivider = context.getDrawable(value.resourceId); 176 } 177 178 @Override 179 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 180 super.onDraw(c, parent, state); 181 final int left = parent.getPaddingLeft(); 182 final int right = parent.getWidth() - parent.getPaddingRight(); 183 184 final int childCount = parent.getChildCount(); 185 for (int i = 0; i < childCount; i++) { 186 final View child = parent.getChildAt(i); 187 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 188 .getLayoutParams(); 189 final int top = child.getBottom() + params.bottomMargin; 190 final int bottom = top + mDivider.getIntrinsicHeight(); 191 mDivider.setBounds(left, top, right, bottom); 192 mDivider.draw(c); 193 } 194 } 195 } 196 197 private static class NavBarAdapter extends RecyclerView.Adapter<Holder> 198 implements View.OnClickListener { 199 200 private static final String START = "start"; 201 private static final String CENTER = "center"; 202 private static final String END = "end"; 203 private static final String ADD = "add"; 204 205 private static final int ADD_ID = 0; 206 private static final int BUTTON_ID = 1; 207 private static final int CATEGORY_ID = 2; 208 209 private List<String> mButtons = new ArrayList<>(); 210 private List<CharSequence> mLabels = new ArrayList<>(); 211 private int mCategoryLayout; 212 private int mButtonLayout; 213 private ItemTouchHelper mTouchHelper; 214 215 public NavBarAdapter(Context context) { 216 TypedArray attrs = context.getTheme().obtainStyledAttributes(null, 217 android.R.styleable.Preference, android.R.attr.preferenceStyle, 0); 218 mButtonLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0); 219 attrs = context.getTheme().obtainStyledAttributes(null, 220 android.R.styleable.Preference, android.R.attr.preferenceCategoryStyle, 0); 221 mCategoryLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0); 222 } 223 224 public void setTouchHelper(ItemTouchHelper itemTouchHelper) { 225 mTouchHelper = itemTouchHelper; 226 } 227 228 public void clear() { 229 mButtons.clear(); 230 mLabels.clear(); 231 notifyDataSetChanged(); 232 } 233 234 public void addButton(String button, CharSequence label) { 235 mButtons.add(button); 236 mLabels.add(label); 237 notifyItemInserted(mLabels.size() - 1); 238 } 239 240 public boolean hasHomeButton() { 241 return mButtons.contains(HOME); 242 } 243 244 public String getNavString() { 245 StringBuilder builder = new StringBuilder(); 246 for (int i = 1; i < mButtons.size() - 1; i++) { 247 String button = mButtons.get(i); 248 if (button.equals(CENTER) || button.equals(END)) { 249 if (builder.length() == 0 || builder.toString().endsWith(GRAVITY_SEPARATOR)) { 250 // No start or center buttons, fill with a space. 251 builder.append(NAVSPACE); 252 } 253 builder.append(GRAVITY_SEPARATOR); 254 continue; 255 } else if (builder.length() != 0 && !builder.toString().endsWith( 256 GRAVITY_SEPARATOR)) { 257 builder.append(BUTTON_SEPARATOR); 258 } 259 builder.append(button); 260 } 261 if (builder.toString().endsWith(GRAVITY_SEPARATOR)) { 262 // No end buttons, fill with space. 263 builder.append(NAVSPACE); 264 } 265 return builder.toString(); 266 } 267 268 @Override 269 public int getItemViewType(int position) { 270 String button = mButtons.get(position); 271 if (button.equals(START) || button.equals(CENTER) || button.equals(END)) { 272 return CATEGORY_ID; 273 } 274 if (button.equals(ADD)) { 275 return ADD_ID; 276 } 277 return BUTTON_ID; 278 } 279 280 @Override 281 public Holder onCreateViewHolder(ViewGroup parent, int viewType) { 282 final Context context = parent.getContext(); 283 final LayoutInflater inflater = LayoutInflater.from(context); 284 final View view = inflater.inflate(getLayoutId(viewType), parent, false); 285 if (viewType == BUTTON_ID) { 286 inflater.inflate(R.layout.nav_control_widget, 287 (ViewGroup) view.findViewById(android.R.id.widget_frame)); 288 } 289 return new Holder(view); 290 } 291 292 private int getLayoutId(int viewType) { 293 if (viewType == CATEGORY_ID) { 294 return mCategoryLayout; 295 } 296 return mButtonLayout; 297 } 298 299 @Override 300 public void onBindViewHolder(Holder holder, int position) { 301 holder.title.setText(mLabels.get(position)); 302 if (holder.getItemViewType() == BUTTON_ID) { 303 bindButton(holder, position); 304 } else if (holder.getItemViewType() == ADD_ID) { 305 bindAdd(holder); 306 } 307 } 308 309 private void bindAdd(Holder holder) { 310 TypedValue value = new TypedValue(); 311 final Context context = holder.itemView.getContext(); 312 context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true); 313 final ImageView icon = (ImageView) holder.itemView.findViewById(android.R.id.icon); 314 icon.setImageResource(R.drawable.ic_add); 315 icon.setImageTintList(ColorStateList.valueOf(context.getColor(value.resourceId))); 316 holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE); 317 holder.itemView.setClickable(true); 318 holder.itemView.setOnClickListener(new View.OnClickListener() { 319 @Override 320 public void onClick(View v) { 321 showAddDialog(v.getContext()); 322 } 323 }); 324 } 325 326 private void bindButton(final Holder holder, int position) { 327 holder.itemView.findViewById(android.R.id.icon_frame).setVisibility(View.GONE); 328 holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE); 329 bindClick(holder.itemView.findViewById(R.id.close), holder); 330 bindClick(holder.itemView.findViewById(R.id.width), holder); 331 holder.itemView.findViewById(R.id.drag).setOnTouchListener(new View.OnTouchListener() { 332 @Override 333 public boolean onTouch(View v, MotionEvent event) { 334 mTouchHelper.startDrag(holder); 335 return true; 336 } 337 }); 338 } 339 340 private void showAddDialog(Context context) { 341 final String[] options = new String[] { 342 BACK, HOME, RECENT, MENU_IME, NAVSPACE, 343 }; 344 final CharSequence[] labels = new CharSequence[options.length]; 345 for (int i = 0; i < options.length; i++) { 346 labels[i] = getLabel(options[i], context); 347 } 348 new AlertDialog.Builder(context) 349 .setTitle(R.string.select_button) 350 .setItems(labels, new DialogInterface.OnClickListener() { 351 @Override 352 public void onClick(DialogInterface dialog, int which) { 353 int index = mButtons.size() - 1; 354 mButtons.add(index, options[which]); 355 mLabels.add(index, labels[which]); 356 notifyItemInserted(index); 357 } 358 }).setNegativeButton(android.R.string.cancel, null) 359 .show(); 360 } 361 362 private void bindClick(View view, Holder holder) { 363 view.setOnClickListener(this); 364 view.setTag(holder); 365 } 366 367 @Override 368 public void onClick(View v) { 369 Holder holder = (Holder) v.getTag(); 370 int position = holder.getAdapterPosition(); 371 if (v.getId() == R.id.width) { 372 // TODO: Handle width control. 373 } else if (v.getId() == R.id.close) { 374 mButtons.remove(position); 375 mLabels.remove(position); 376 notifyItemRemoved(position); 377 } 378 } 379 380 @Override 381 public int getItemCount() { 382 return mButtons.size(); 383 } 384 385 private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() { 386 @Override 387 public boolean isLongPressDragEnabled() { 388 return false; 389 } 390 391 @Override 392 public boolean isItemViewSwipeEnabled() { 393 return false; 394 } 395 396 @Override 397 public int getMovementFlags(RecyclerView recyclerView, 398 RecyclerView.ViewHolder viewHolder) { 399 if (viewHolder.getItemViewType() != BUTTON_ID) { 400 return makeMovementFlags(0, 0); 401 } 402 int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; 403 return makeMovementFlags(dragFlags, 0); 404 } 405 406 @Override 407 public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, 408 RecyclerView.ViewHolder target) { 409 int from = viewHolder.getAdapterPosition(); 410 int to = target.getAdapterPosition(); 411 if (to == 0) { 412 // Can't go above the top. 413 return false; 414 } 415 move(from, to, mButtons); 416 move(from, to, mLabels); 417 notifyItemMoved(from, to); 418 return true; 419 } 420 421 private <T> void move(int from, int to, List<T> list) { 422 list.add(from > to ? to : to + 1, list.get(from)); 423 list.remove(from > to ? from + 1 : from); 424 } 425 426 @Override 427 public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 428 // Don't care. 429 } 430 }; 431 } 432} 433