Home | History | Annotate | Download | only in tuner
      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
     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 
     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 
    126     private void notifyChanged() {
    127         mPreview.onTuningChanged(NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
    128     }
    129 
    130     @Override
    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
    147     public void onDestroyView() {
    148         super.onDestroyView();
    149         TunerService.get(getContext()).removeTunable(this);
    150     }
    151 
    152     @Override
    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
    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
    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 
    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 
    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 
    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
    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 
    262     private void selectImage() {
    263         startActivityForResult(KeycodeSelectionHelper.getSelectImageIntent(), READ_REQUEST);
    264     }
    265 
    266     @Override
    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 
    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 
    308         public void setTouchHelper(ItemTouchHelper itemTouchHelper) {
    309             mTouchHelper = itemTouchHelper;
    310         }
    311 
    312         public void clear() {
    313             mButtons.clear();
    314             mLabels.clear();
    315             notifyDataSetChanged();
    316         }
    317 
    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 
    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 
    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
    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
    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 
    383         private int getLayoutId(int viewType) {
    384             if (viewType == CATEGORY_ID) {
    385                 return mCategoryLayout;
    386             }
    387             return mButtonLayout;
    388         }
    389 
    390         @Override
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    500         private void bindClick(View view, Holder holder) {
    501             view.setOnClickListener(this);
    502             view.setTag(holder);
    503         }
    504 
    505         @Override
    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 
    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
    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