• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tuner;
16 
17 import android.annotation.Nullable;
18 import android.app.Activity;
19 import android.app.AlertDialog;
20 import android.app.Fragment;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.res.ColorStateList;
25 import android.content.res.Configuration;
26 import android.content.res.TypedArray;
27 import android.graphics.Canvas;
28 import android.graphics.drawable.Drawable;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.provider.Settings;
32 import android.support.v7.widget.LinearLayoutManager;
33 import android.support.v7.widget.RecyclerView;
34 import android.support.v7.widget.helper.ItemTouchHelper;
35 import android.util.TypedValue;
36 import android.view.Display;
37 import android.view.LayoutInflater;
38 import android.view.Menu;
39 import android.view.MenuInflater;
40 import android.view.MenuItem;
41 import android.view.MotionEvent;
42 import android.view.Surface;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.widget.ImageView;
46 import android.widget.SeekBar;
47 import android.widget.TextView;
48 
49 import com.android.systemui.R;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 
54 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK;
55 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR;
56 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.CLIPBOARD;
57 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR;
58 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME;
59 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
60 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
61 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
62 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
63 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
64 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
65 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
66 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.RECENT;
67 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_END;
68 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_START;
69 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
70 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractSize;
71 
72 public class NavBarTuner extends Fragment implements TunerService.Tunable {
73 
74     private static final int SAVE = Menu.FIRST + 1;
75     private static final int RESET = Menu.FIRST + 2;
76     private static final int READ_REQUEST = 42;
77 
78     private static final float PREVIEW_SCALE = .95f;
79     private static final float PREVIEW_SCALE_LANDSCAPE = .75f;
80 
81     private NavBarAdapter mNavBarAdapter;
82     private PreviewNavInflater mPreview;
83 
84     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)85     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
86             Bundle savedInstanceState) {
87         final View view = inflater.inflate(R.layout.nav_bar_tuner, container, false);
88         inflatePreview((ViewGroup) view.findViewById(R.id.nav_preview_frame));
89         return view;
90     }
91 
inflatePreview(ViewGroup view)92     private void inflatePreview(ViewGroup view) {
93         Display display = getActivity().getWindowManager().getDefaultDisplay();
94         boolean isRotated = display.getRotation() == Surface.ROTATION_90
95                 || display.getRotation() == Surface.ROTATION_270;
96 
97         Configuration config = new Configuration(getContext().getResources().getConfiguration());
98         boolean isPhoneLandscape = isRotated && (config.smallestScreenWidthDp < 600);
99         final float scale = isPhoneLandscape ? PREVIEW_SCALE_LANDSCAPE : PREVIEW_SCALE;
100         config.densityDpi = (int) (config.densityDpi * scale);
101 
102         mPreview = (PreviewNavInflater) LayoutInflater.from(getContext().createConfigurationContext(
103                 config)).inflate(R.layout.nav_bar_tuner_inflater, view, false);
104         final ViewGroup.LayoutParams layoutParams = mPreview.getLayoutParams();
105         layoutParams.width = (int) ((isPhoneLandscape ? display.getHeight() : display.getWidth())
106                 * scale);
107         // Not sure why, but the height dimen is not being scaled with the dp, set it manually
108         // for now.
109         layoutParams.height = (int) (layoutParams.height * scale);
110         if (isPhoneLandscape) {
111             int width = layoutParams.width;
112             layoutParams.width = layoutParams.height;
113             layoutParams.height = width;
114         }
115         view.addView(mPreview);
116 
117         if (isRotated) {
118             mPreview.findViewById(R.id.rot0).setVisibility(View.GONE);
119             final View rot90 = mPreview.findViewById(R.id.rot90);
120         } else {
121             mPreview.findViewById(R.id.rot90).setVisibility(View.GONE);
122             final View rot0 = mPreview.findViewById(R.id.rot0);
123         }
124     }
125 
notifyChanged()126     private void notifyChanged() {
127         mPreview.onTuningChanged(NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
128     }
129 
130     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)131     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
132         super.onViewCreated(view, savedInstanceState);
133         RecyclerView recyclerView = (RecyclerView) view.findViewById(android.R.id.list);
134         final Context context = getContext();
135         recyclerView.setLayoutManager(new LinearLayoutManager(context));
136         mNavBarAdapter = new NavBarAdapter(context);
137         recyclerView.setAdapter(mNavBarAdapter);
138         recyclerView.addItemDecoration(new Dividers(context));
139         final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mNavBarAdapter.mCallbacks);
140         mNavBarAdapter.setTouchHelper(itemTouchHelper);
141         itemTouchHelper.attachToRecyclerView(recyclerView);
142 
143         TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
144     }
145 
146     @Override
onDestroyView()147     public void onDestroyView() {
148         super.onDestroyView();
149         TunerService.get(getContext()).removeTunable(this);
150     }
151 
152     @Override
onTuningChanged(String key, String navLayout)153     public void onTuningChanged(String key, String navLayout) {
154         if (!NAV_BAR_VIEWS.equals(key)) return;
155         Context context = getContext();
156         if (navLayout == null) {
157             navLayout = context.getString(R.string.config_navBarLayout);
158         }
159         String[] views = navLayout.split(GRAVITY_SEPARATOR);
160         String[] groups = new String[] { NavBarAdapter.START, NavBarAdapter.CENTER,
161                 NavBarAdapter.END};
162         CharSequence[] groupLabels = new String[] { getString(R.string.start),
163                 getString(R.string.center), getString(R.string.end) };
164         mNavBarAdapter.clear();
165         for (int i = 0; i < 3; i++) {
166             mNavBarAdapter.addButton(groups[i], groupLabels[i]);
167             for (String button : views[i].split(BUTTON_SEPARATOR)) {
168                 mNavBarAdapter.addButton(button, getLabel(button, context));
169             }
170         }
171         mNavBarAdapter.addButton(NavBarAdapter.ADD, getString(R.string.add_button));
172         setHasOptionsMenu(true);
173     }
174 
175     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)176     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
177         super.onCreateOptionsMenu(menu, inflater);
178         // TODO: Show save button conditionally, only when there are changes.
179         menu.add(Menu.NONE, SAVE, Menu.NONE, getString(R.string.save))
180                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
181         menu.add(Menu.NONE, RESET, Menu.NONE, getString(R.string.reset));
182     }
183 
184     @Override
onOptionsItemSelected(MenuItem item)185     public boolean onOptionsItemSelected(MenuItem item) {
186         if (item.getItemId() == SAVE) {
187             if (!mNavBarAdapter.hasHomeButton()) {
188                 new AlertDialog.Builder(getContext())
189                         .setTitle(R.string.no_home_title)
190                         .setMessage(R.string.no_home_message)
191                         .setPositiveButton(android.R.string.ok, null)
192                         .show();
193             } else {
194                 Settings.Secure.putString(getContext().getContentResolver(),
195                         NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
196             }
197             return true;
198         } else if (item.getItemId() == RESET) {
199             Settings.Secure.putString(getContext().getContentResolver(),
200                     NAV_BAR_VIEWS, null);
201             return true;
202         }
203         return super.onOptionsItemSelected(item);
204     }
205 
getLabel(String button, Context context)206     private static CharSequence getLabel(String button, Context context) {
207         if (button.startsWith(HOME)) {
208             return context.getString(R.string.accessibility_home);
209         } else if (button.startsWith(BACK)) {
210             return context.getString(R.string.accessibility_back);
211         } else if (button.startsWith(RECENT)) {
212             return context.getString(R.string.accessibility_recent);
213         } else if (button.startsWith(NAVSPACE)) {
214             return context.getString(R.string.space);
215         } else if (button.startsWith(MENU_IME)) {
216             return context.getString(R.string.menu_ime);
217         } else if (button.startsWith(CLIPBOARD)) {
218             return context.getString(R.string.clipboard);
219         } else if (button.startsWith(KEY)) {
220             return context.getString(R.string.keycode);
221         }
222         return button;
223     }
224 
225     private static class Holder extends RecyclerView.ViewHolder {
226         private TextView title;
227 
Holder(View itemView)228         public Holder(View itemView) {
229             super(itemView);
230             title = (TextView) itemView.findViewById(android.R.id.title);
231         }
232     }
233 
234     private static class Dividers extends RecyclerView.ItemDecoration {
235         private final Drawable mDivider;
236 
Dividers(Context context)237         public Dividers(Context context) {
238             TypedValue value = new TypedValue();
239             context.getTheme().resolveAttribute(android.R.attr.listDivider, value, true);
240             mDivider = context.getDrawable(value.resourceId);
241         }
242 
243         @Override
onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)244         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
245             super.onDraw(c, parent, state);
246             final int left = parent.getPaddingLeft();
247             final int right = parent.getWidth() - parent.getPaddingRight();
248 
249             final int childCount = parent.getChildCount();
250             for (int i = 0; i < childCount; i++) {
251                 final View child = parent.getChildAt(i);
252                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
253                         .getLayoutParams();
254                 final int top = child.getBottom() + params.bottomMargin;
255                 final int bottom = top + mDivider.getIntrinsicHeight();
256                 mDivider.setBounds(left, top, right, bottom);
257                 mDivider.draw(c);
258             }
259         }
260     }
261 
selectImage()262     private void selectImage() {
263         startActivityForResult(KeycodeSelectionHelper.getSelectImageIntent(), READ_REQUEST);
264     }
265 
266     @Override
onActivityResult(int requestCode, int resultCode, Intent data)267     public void onActivityResult(int requestCode, int resultCode, Intent data) {
268         if (requestCode == READ_REQUEST && resultCode == Activity.RESULT_OK && data != null) {
269             final Uri uri = data.getData();
270             final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
271             getContext().getContentResolver().takePersistableUriPermission(uri, takeFlags);
272             mNavBarAdapter.onImageSelected(uri);
273         } else {
274             super.onActivityResult(requestCode, resultCode, data);
275         }
276     }
277 
278     private class NavBarAdapter extends RecyclerView.Adapter<Holder>
279             implements View.OnClickListener {
280 
281         private static final String START = "start";
282         private static final String CENTER = "center";
283         private static final String END = "end";
284         private static final String ADD = "add";
285 
286         private static final int ADD_ID = 0;
287         private static final int BUTTON_ID = 1;
288         private static final int CATEGORY_ID = 2;
289 
290         private List<String> mButtons = new ArrayList<>();
291         private List<CharSequence> mLabels = new ArrayList<>();
292         private int mCategoryLayout;
293         private int mButtonLayout;
294         private ItemTouchHelper mTouchHelper;
295 
296         // Stored keycode while we wait for image selection on a KEY.
297         private int mKeycode;
298 
NavBarAdapter(Context context)299         public NavBarAdapter(Context context) {
300             TypedArray attrs = context.getTheme().obtainStyledAttributes(null,
301                     android.R.styleable.Preference, android.R.attr.preferenceStyle, 0);
302             mButtonLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
303             attrs = context.getTheme().obtainStyledAttributes(null,
304                     android.R.styleable.Preference, android.R.attr.preferenceCategoryStyle, 0);
305             mCategoryLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
306         }
307 
setTouchHelper(ItemTouchHelper itemTouchHelper)308         public void setTouchHelper(ItemTouchHelper itemTouchHelper) {
309             mTouchHelper = itemTouchHelper;
310         }
311 
clear()312         public void clear() {
313             mButtons.clear();
314             mLabels.clear();
315             notifyDataSetChanged();
316         }
317 
addButton(String button, CharSequence label)318         public void addButton(String button, CharSequence label) {
319             mButtons.add(button);
320             mLabels.add(label);
321             notifyItemInserted(mLabels.size() - 1);
322             notifyChanged();
323         }
324 
hasHomeButton()325         public boolean hasHomeButton() {
326             final int N = mButtons.size();
327             for (int i = 0; i < N; i++) {
328                 if (mButtons.get(i).startsWith(HOME)) {
329                     return true;
330                 }
331             }
332             return false;
333         }
334 
getNavString()335         public String getNavString() {
336             StringBuilder builder = new StringBuilder();
337             for (int i = 1; i < mButtons.size() - 1; i++) {
338                 String button = mButtons.get(i);
339                 if (button.equals(CENTER) || button.equals(END)) {
340                     if (builder.length() == 0 || builder.toString().endsWith(GRAVITY_SEPARATOR)) {
341                         // No start or center buttons, fill with a space.
342                         builder.append(NAVSPACE);
343                     }
344                     builder.append(GRAVITY_SEPARATOR);
345                     continue;
346                 } else if (builder.length() != 0 && !builder.toString().endsWith(
347                         GRAVITY_SEPARATOR)) {
348                     builder.append(BUTTON_SEPARATOR);
349                 }
350                 builder.append(button);
351             }
352             if (builder.toString().endsWith(GRAVITY_SEPARATOR)) {
353                 // No end buttons, fill with space.
354                 builder.append(NAVSPACE);
355             }
356             return builder.toString();
357         }
358 
359         @Override
getItemViewType(int position)360         public int getItemViewType(int position) {
361             String button = mButtons.get(position);
362             if (button.equals(START) || button.equals(CENTER) || button.equals(END)) {
363                 return CATEGORY_ID;
364             }
365             if (button.equals(ADD)) {
366                 return ADD_ID;
367             }
368             return BUTTON_ID;
369         }
370 
371         @Override
onCreateViewHolder(ViewGroup parent, int viewType)372         public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
373             final Context context = parent.getContext();
374             final LayoutInflater inflater = LayoutInflater.from(context);
375             final View view = inflater.inflate(getLayoutId(viewType), parent, false);
376             if (viewType == BUTTON_ID) {
377                 inflater.inflate(R.layout.nav_control_widget,
378                         (ViewGroup) view.findViewById(android.R.id.widget_frame));
379             }
380             return new Holder(view);
381         }
382 
getLayoutId(int viewType)383         private int getLayoutId(int viewType) {
384             if (viewType == CATEGORY_ID) {
385                 return mCategoryLayout;
386             }
387             return mButtonLayout;
388         }
389 
390         @Override
onBindViewHolder(Holder holder, int position)391         public void onBindViewHolder(Holder holder, int position) {
392             holder.title.setText(mLabels.get(position));
393             if (holder.getItemViewType() == BUTTON_ID) {
394                 bindButton(holder, position);
395             } else if (holder.getItemViewType() == ADD_ID) {
396                 bindAdd(holder);
397             }
398         }
399 
bindAdd(Holder holder)400         private void bindAdd(Holder holder) {
401             TypedValue value = new TypedValue();
402             final Context context = holder.itemView.getContext();
403             context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
404             final ImageView icon = (ImageView) holder.itemView.findViewById(android.R.id.icon);
405             icon.setImageResource(R.drawable.ic_add);
406             icon.setImageTintList(ColorStateList.valueOf(context.getColor(value.resourceId)));
407             holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
408             holder.itemView.setClickable(true);
409             holder.itemView.setOnClickListener(new View.OnClickListener() {
410                 @Override
411                 public void onClick(View v) {
412                     showAddDialog(v.getContext());
413                 }
414             });
415         }
416 
bindButton(final Holder holder, int position)417         private void bindButton(final Holder holder, int position) {
418             holder.itemView.findViewById(android.R.id.icon_frame).setVisibility(View.GONE);
419             holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
420             bindClick(holder.itemView.findViewById(R.id.close), holder);
421             bindClick(holder.itemView.findViewById(R.id.width), holder);
422             holder.itemView.findViewById(R.id.drag).setOnTouchListener(new View.OnTouchListener() {
423                 @Override
424                 public boolean onTouch(View v, MotionEvent event) {
425                     mTouchHelper.startDrag(holder);
426                     return true;
427                 }
428             });
429         }
430 
showAddDialog(final Context context)431         private void showAddDialog(final Context context) {
432             final String[] options = new String[] {
433                     BACK, HOME, RECENT, MENU_IME, NAVSPACE, CLIPBOARD, KEY,
434             };
435             final CharSequence[] labels = new CharSequence[options.length];
436             for (int i = 0; i < options.length; i++) {
437                 labels[i] = getLabel(options[i], context);
438             }
439             new AlertDialog.Builder(context)
440                     .setTitle(R.string.select_button)
441                     .setItems(labels, new DialogInterface.OnClickListener() {
442                         @Override
443                         public void onClick(DialogInterface dialog, int which) {
444                             if (KEY.equals(options[which])) {
445                                 showKeyDialogs(context);
446                             } else {
447                                 int index = mButtons.size() - 1;
448                                 showAddedMessage(context, options[which]);
449                                 mButtons.add(index, options[which]);
450                                 mLabels.add(index, labels[which]);
451 
452                                 notifyItemInserted(index);
453                                 notifyChanged();
454                             }
455                         }
456                     }).setNegativeButton(android.R.string.cancel, null)
457                     .show();
458         }
459 
onImageSelected(Uri uri)460         private void onImageSelected(Uri uri) {
461             int index = mButtons.size() - 1;
462             mButtons.add(index, KEY + KEY_CODE_START + mKeycode + KEY_IMAGE_DELIM + uri.toString()
463                     + KEY_CODE_END);
464             mLabels.add(index, getLabel(KEY, getContext()));
465 
466             notifyItemInserted(index);
467             notifyChanged();
468         }
469 
showKeyDialogs(final Context context)470         private void showKeyDialogs(final Context context) {
471             final KeycodeSelectionHelper.OnSelectionComplete listener =
472                     new KeycodeSelectionHelper.OnSelectionComplete() {
473                         @Override
474                         public void onSelectionComplete(int code) {
475                             mKeycode = code;
476                             selectImage();
477                         }
478                     };
479             new AlertDialog.Builder(context)
480                     .setTitle(R.string.keycode)
481                     .setMessage(R.string.keycode_description)
482                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
483                         @Override
484                         public void onClick(DialogInterface dialog, int which) {
485                             KeycodeSelectionHelper.showKeycodeSelect(context, listener);
486                         }
487                     }).show();
488         }
489 
showAddedMessage(Context context, String button)490         private void showAddedMessage(Context context, String button) {
491             if (CLIPBOARD.equals(button)) {
492                 new AlertDialog.Builder(context)
493                         .setTitle(R.string.clipboard)
494                         .setMessage(R.string.clipboard_description)
495                         .setPositiveButton(android.R.string.ok, null)
496                         .show();
497             }
498         }
499 
bindClick(View view, Holder holder)500         private void bindClick(View view, Holder holder) {
501             view.setOnClickListener(this);
502             view.setTag(holder);
503         }
504 
505         @Override
onClick(View v)506         public void onClick(View v) {
507             Holder holder = (Holder) v.getTag();
508             if (v.getId() == R.id.width) {
509                 showWidthDialog(holder, v.getContext());
510             } else if (v.getId() == R.id.close) {
511                 int position = holder.getAdapterPosition();
512                 mButtons.remove(position);
513                 mLabels.remove(position);
514                 notifyItemRemoved(position);
515                 notifyChanged();
516             }
517         }
518 
showWidthDialog(final Holder holder, Context context)519         private void showWidthDialog(final Holder holder, Context context) {
520             final String buttonSpec = mButtons.get(holder.getAdapterPosition());
521             float amount = extractSize(buttonSpec);
522             final AlertDialog dialog = new AlertDialog.Builder(context)
523                     .setTitle(R.string.adjust_button_width)
524                     .setView(R.layout.nav_width_view)
525                     .setNegativeButton(android.R.string.cancel, null).create();
526             dialog.setButton(DialogInterface.BUTTON_POSITIVE,
527                     context.getString(android.R.string.ok),
528                     new DialogInterface.OnClickListener() {
529                         @Override
530                         public void onClick(DialogInterface d, int which) {
531                             final String button = extractButton(buttonSpec);
532                             SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
533                             if (seekBar.getProgress() == 75) {
534                                 mButtons.set(holder.getAdapterPosition(), button);
535                             } else {
536                                 float amount = (seekBar.getProgress() + 25) / 100f;
537                                 mButtons.set(holder.getAdapterPosition(), button
538                                         + SIZE_MOD_START + amount + SIZE_MOD_END);
539                             }
540                             notifyChanged();
541                         }
542                     });
543             dialog.show();
544             SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
545             // Range is .25 - 1.75.
546             seekBar.setMax(150);
547             seekBar.setProgress((int) ((amount - .25f) * 100));
548         }
549 
550         @Override
getItemCount()551         public int getItemCount() {
552             return mButtons.size();
553         }
554 
555         private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
556             @Override
557             public boolean isLongPressDragEnabled() {
558                 return false;
559             }
560 
561             @Override
562             public boolean isItemViewSwipeEnabled() {
563                 return false;
564             }
565 
566             @Override
567             public int getMovementFlags(RecyclerView recyclerView,
568                     RecyclerView.ViewHolder viewHolder) {
569                 if (viewHolder.getItemViewType() != BUTTON_ID) {
570                     return makeMovementFlags(0, 0);
571                 }
572                 int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
573                 return makeMovementFlags(dragFlags, 0);
574             }
575 
576             @Override
577             public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
578                     RecyclerView.ViewHolder target) {
579                 int from = viewHolder.getAdapterPosition();
580                 int to = target.getAdapterPosition();
581                 if (to == 0) {
582                     // Can't go above the top.
583                     return false;
584                 }
585                 move(from, to, mButtons);
586                 move(from, to, mLabels);
587                 notifyChanged();
588                 notifyItemMoved(from, to);
589                 return true;
590             }
591 
592             private <T> void move(int from, int to, List<T> list) {
593                 list.add(from > to ? to : to + 1, list.get(from));
594                 list.remove(from > to ? from + 1 : from);
595             }
596 
597             @Override
598             public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
599                 // Don't care.
600             }
601         };
602     }
603 }
604