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.qs.customize;
16 
17 import android.app.AlertDialog;
18 import android.app.AlertDialog.Builder;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.res.Resources;
23 import android.graphics.Canvas;
24 import android.graphics.drawable.Drawable;
25 import android.os.Handler;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.View.OnLayoutChangeListener;
30 import android.view.ViewGroup;
31 import android.view.accessibility.AccessibilityManager;
32 import android.widget.FrameLayout;
33 import android.widget.TextView;
34 
35 import androidx.core.view.ViewCompat;
36 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
37 import androidx.recyclerview.widget.ItemTouchHelper;
38 import androidx.recyclerview.widget.RecyclerView;
39 import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
40 import androidx.recyclerview.widget.RecyclerView.State;
41 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
42 
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.nano.MetricsProto;
45 import com.android.systemui.R;
46 import com.android.systemui.qs.QSTileHost;
47 import com.android.systemui.qs.customize.TileAdapter.Holder;
48 import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
49 import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
50 import com.android.systemui.qs.external.CustomTile;
51 import com.android.systemui.qs.tileimpl.QSIconViewImpl;
52 import com.android.systemui.statusbar.phone.SystemUIDialog;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 
57 public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
58     private static final long DRAG_LENGTH = 100;
59     private static final float DRAG_SCALE = 1.2f;
60     public static final long MOVE_DURATION = 150;
61 
62     private static final int TYPE_TILE = 0;
63     private static final int TYPE_EDIT = 1;
64     private static final int TYPE_ACCESSIBLE_DROP = 2;
65     private static final int TYPE_HEADER = 3;
66     private static final int TYPE_DIVIDER = 4;
67 
68     private static final long EDIT_ID = 10000;
69     private static final long DIVIDER_ID = 20000;
70 
71     private static final int ACTION_NONE = 0;
72     private static final int ACTION_ADD = 1;
73     private static final int ACTION_MOVE = 2;
74 
75     private final Context mContext;
76 
77     private final Handler mHandler = new Handler();
78     private final List<TileInfo> mTiles = new ArrayList<>();
79     private final ItemTouchHelper mItemTouchHelper;
80     private final ItemDecoration mDecoration;
81     private final AccessibilityManager mAccessibilityManager;
82     private final int mMinNumTiles;
83     private int mEditIndex;
84     private int mTileDividerIndex;
85     private boolean mNeedsFocus;
86     private List<String> mCurrentSpecs;
87     private List<TileInfo> mOtherTiles;
88     private List<TileInfo> mAllTiles;
89 
90     private Holder mCurrentDrag;
91     private int mAccessibilityAction = ACTION_NONE;
92     private int mAccessibilityFromIndex;
93     private CharSequence mAccessibilityFromLabel;
94     private QSTileHost mHost;
95 
TileAdapter(Context context)96     public TileAdapter(Context context) {
97         mContext = context;
98         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
99         mItemTouchHelper = new ItemTouchHelper(mCallbacks);
100         mDecoration = new TileItemDecoration(context);
101         mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
102     }
103 
setHost(QSTileHost host)104     public void setHost(QSTileHost host) {
105         mHost = host;
106     }
107 
getItemTouchHelper()108     public ItemTouchHelper getItemTouchHelper() {
109         return mItemTouchHelper;
110     }
111 
getItemDecoration()112     public ItemDecoration getItemDecoration() {
113         return mDecoration;
114     }
115 
saveSpecs(QSTileHost host)116     public void saveSpecs(QSTileHost host) {
117         List<String> newSpecs = new ArrayList<>();
118         clearAccessibilityState();
119         for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
120             newSpecs.add(mTiles.get(i).spec);
121         }
122         host.changeTiles(mCurrentSpecs, newSpecs);
123         mCurrentSpecs = newSpecs;
124     }
125 
clearAccessibilityState()126     private void clearAccessibilityState() {
127         if (mAccessibilityAction == ACTION_ADD) {
128             // Remove blank tile from last spot
129             mTiles.remove(--mEditIndex);
130             // Update the tile divider position
131             mTileDividerIndex--;
132             notifyDataSetChanged();
133         }
134         mAccessibilityAction = ACTION_NONE;
135     }
136 
resetTileSpecs(QSTileHost host, List<String> specs)137     public void resetTileSpecs(QSTileHost host, List<String> specs) {
138         // Notify the host so the tiles get removed callbacks.
139         host.changeTiles(mCurrentSpecs, specs);
140         setTileSpecs(specs);
141     }
142 
setTileSpecs(List<String> currentSpecs)143     public void setTileSpecs(List<String> currentSpecs) {
144         if (currentSpecs.equals(mCurrentSpecs)) {
145             return;
146         }
147         mCurrentSpecs = currentSpecs;
148         recalcSpecs();
149     }
150 
151     @Override
onTilesChanged(List<TileInfo> tiles)152     public void onTilesChanged(List<TileInfo> tiles) {
153         mAllTiles = tiles;
154         recalcSpecs();
155     }
156 
recalcSpecs()157     private void recalcSpecs() {
158         if (mCurrentSpecs == null || mAllTiles == null) {
159             return;
160         }
161         mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
162         mTiles.clear();
163         mTiles.add(null);
164         for (int i = 0; i < mCurrentSpecs.size(); i++) {
165             final TileInfo tile = getAndRemoveOther(mCurrentSpecs.get(i));
166             if (tile != null) {
167                 mTiles.add(tile);
168             }
169         }
170         mTiles.add(null);
171         for (int i = 0; i < mOtherTiles.size(); i++) {
172             final TileInfo tile = mOtherTiles.get(i);
173             if (tile.isSystem) {
174                 mOtherTiles.remove(i--);
175                 mTiles.add(tile);
176             }
177         }
178         mTileDividerIndex = mTiles.size();
179         mTiles.add(null);
180         mTiles.addAll(mOtherTiles);
181         updateDividerLocations();
182         notifyDataSetChanged();
183     }
184 
getAndRemoveOther(String s)185     private TileInfo getAndRemoveOther(String s) {
186         for (int i = 0; i < mOtherTiles.size(); i++) {
187             if (mOtherTiles.get(i).spec.equals(s)) {
188                 return mOtherTiles.remove(i);
189             }
190         }
191         return null;
192     }
193 
194     @Override
getItemViewType(int position)195     public int getItemViewType(int position) {
196         if (position == 0) {
197             return TYPE_HEADER;
198         }
199         if (mAccessibilityAction == ACTION_ADD && position == mEditIndex - 1) {
200             return TYPE_ACCESSIBLE_DROP;
201         }
202         if (position == mTileDividerIndex) {
203             return TYPE_DIVIDER;
204         }
205         if (mTiles.get(position) == null) {
206             return TYPE_EDIT;
207         }
208         return TYPE_TILE;
209     }
210 
211     @Override
onCreateViewHolder(ViewGroup parent, int viewType)212     public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
213         final Context context = parent.getContext();
214         LayoutInflater inflater = LayoutInflater.from(context);
215         if (viewType == TYPE_HEADER) {
216             return new Holder(inflater.inflate(R.layout.qs_customize_header, parent, false));
217         }
218         if (viewType == TYPE_DIVIDER) {
219             return new Holder(inflater.inflate(R.layout.qs_customize_tile_divider, parent, false));
220         }
221         if (viewType == TYPE_EDIT) {
222             return new Holder(inflater.inflate(R.layout.qs_customize_divider, parent, false));
223         }
224         FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
225                 false);
226         frame.addView(new CustomizeTileView(context, new QSIconViewImpl(context)));
227         return new Holder(frame);
228     }
229 
230     @Override
getItemCount()231     public int getItemCount() {
232         return mTiles.size();
233     }
234 
235     @Override
onFailedToRecycleView(Holder holder)236     public boolean onFailedToRecycleView(Holder holder) {
237         holder.clearDrag();
238         return true;
239     }
240 
241     @Override
onBindViewHolder(final Holder holder, int position)242     public void onBindViewHolder(final Holder holder, int position) {
243         if (holder.getItemViewType() == TYPE_HEADER) {
244             return;
245         }
246         if (holder.getItemViewType() == TYPE_DIVIDER) {
247             holder.itemView.setVisibility(mTileDividerIndex < mTiles.size() - 1 ? View.VISIBLE
248                     : View.INVISIBLE);
249             return;
250         }
251         if (holder.getItemViewType() == TYPE_EDIT) {
252             final String titleText;
253             Resources res = mContext.getResources();
254             if (mCurrentDrag == null) {
255                 titleText = res.getString(R.string.drag_to_add_tiles);
256             } else if (!canRemoveTiles() && mCurrentDrag.getAdapterPosition() < mEditIndex) {
257                 titleText = res.getString(R.string.drag_to_remove_disabled, mMinNumTiles);
258             } else {
259                 titleText = res.getString(R.string.drag_to_remove_tiles);
260             }
261 
262             ((TextView) holder.itemView.findViewById(android.R.id.title)).setText(titleText);
263             return;
264         }
265         if (holder.getItemViewType() == TYPE_ACCESSIBLE_DROP) {
266             holder.mTileView.setClickable(true);
267             holder.mTileView.setFocusable(true);
268             holder.mTileView.setFocusableInTouchMode(true);
269             holder.mTileView.setVisibility(View.VISIBLE);
270             holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
271             holder.mTileView.setContentDescription(mContext.getString(
272                     R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel,
273                     position));
274             holder.mTileView.setOnClickListener(new OnClickListener() {
275                 @Override
276                 public void onClick(View v) {
277                     selectPosition(holder.getAdapterPosition(), v);
278                 }
279             });
280             if (mNeedsFocus) {
281                 // Wait for this to get laid out then set its focus.
282                 // Ensure that tile gets laid out so we get the callback.
283                 holder.mTileView.requestLayout();
284                 holder.mTileView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
285                     @Override
286                     public void onLayoutChange(View v, int left, int top, int right, int bottom,
287                             int oldLeft, int oldTop, int oldRight, int oldBottom) {
288                         holder.mTileView.removeOnLayoutChangeListener(this);
289                         holder.mTileView.requestFocus();
290                     }
291                 });
292                 mNeedsFocus = false;
293             }
294             return;
295         }
296 
297         TileInfo info = mTiles.get(position);
298 
299         if (position > mEditIndex) {
300             info.state.contentDescription = mContext.getString(
301                     R.string.accessibility_qs_edit_add_tile_label, info.state.label);
302         } else if (mAccessibilityAction == ACTION_ADD) {
303             info.state.contentDescription = mContext.getString(
304                     R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, position);
305         } else if (mAccessibilityAction == ACTION_MOVE) {
306             info.state.contentDescription = mContext.getString(
307                     R.string.accessibility_qs_edit_tile_move, mAccessibilityFromLabel, position);
308         } else {
309             info.state.contentDescription = mContext.getString(
310                     R.string.accessibility_qs_edit_tile_label, position, info.state.label);
311         }
312         holder.mTileView.handleStateChanged(info.state);
313         holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem);
314 
315         if (mAccessibilityManager.isTouchExplorationEnabled()) {
316             final boolean selectable = mAccessibilityAction == ACTION_NONE || position < mEditIndex;
317             holder.mTileView.setClickable(selectable);
318             holder.mTileView.setFocusable(selectable);
319             holder.mTileView.setImportantForAccessibility(selectable
320                     ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
321                     : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
322             if (selectable) {
323                 holder.mTileView.setOnClickListener(new OnClickListener() {
324                     @Override
325                     public void onClick(View v) {
326                         int position = holder.getAdapterPosition();
327                         if (position == RecyclerView.NO_POSITION) return;
328                         if (mAccessibilityAction != ACTION_NONE) {
329                             selectPosition(position, v);
330                         } else {
331                             if (position < mEditIndex && canRemoveTiles()) {
332                                 showAccessibilityDialog(position, v);
333                             } else {
334                                 startAccessibleAdd(position);
335                             }
336                         }
337                     }
338                 });
339             }
340         }
341     }
342 
343     private boolean canRemoveTiles() {
344         return mCurrentSpecs.size() > mMinNumTiles;
345     }
346 
347     private void selectPosition(int position, View v) {
348         if (mAccessibilityAction == ACTION_ADD) {
349             // Remove the placeholder.
350             mTiles.remove(mEditIndex--);
351             notifyItemRemoved(mEditIndex);
352         }
353         mAccessibilityAction = ACTION_NONE;
354         move(mAccessibilityFromIndex, position, v);
355         notifyDataSetChanged();
356     }
357 
358     private void showAccessibilityDialog(final int position, final View v) {
359         final TileInfo info = mTiles.get(position);
360         CharSequence[] options = new CharSequence[] {
361                 mContext.getString(R.string.accessibility_qs_edit_move_tile, info.state.label),
362                 mContext.getString(R.string.accessibility_qs_edit_remove_tile, info.state.label),
363         };
364         AlertDialog dialog = new Builder(mContext)
365                 .setItems(options, new DialogInterface.OnClickListener() {
366                     @Override
367                     public void onClick(DialogInterface dialog, int which) {
368                         if (which == 0) {
369                             startAccessibleMove(position);
370                         } else {
371                             move(position, info.isSystem ? mEditIndex : mTileDividerIndex, v);
372                             notifyItemChanged(mTileDividerIndex);
373                             notifyDataSetChanged();
374                         }
375                     }
376                 }).setNegativeButton(android.R.string.cancel, null)
377                 .create();
378         SystemUIDialog.setShowForAllUsers(dialog, true);
379         SystemUIDialog.applyFlags(dialog);
380         dialog.show();
381     }
382 
383     private void startAccessibleAdd(int position) {
384         mAccessibilityFromIndex = position;
385         mAccessibilityFromLabel = mTiles.get(position).state.label;
386         mAccessibilityAction = ACTION_ADD;
387         // Add placeholder for last slot.
388         mTiles.add(mEditIndex++, null);
389         // Update the tile divider position
390         mTileDividerIndex++;
391         mNeedsFocus = true;
392         notifyDataSetChanged();
393     }
394 
395     private void startAccessibleMove(int position) {
396         mAccessibilityFromIndex = position;
397         mAccessibilityFromLabel = mTiles.get(position).state.label;
398         mAccessibilityAction = ACTION_MOVE;
399         notifyDataSetChanged();
400     }
401 
402     public SpanSizeLookup getSizeLookup() {
403         return mSizeLookup;
404     }
405 
406     private boolean move(int from, int to, View v) {
407         if (to == from) {
408             return true;
409         }
410         CharSequence fromLabel = mTiles.get(from).state.label;
411         move(from, to, mTiles);
412         updateDividerLocations();
413         if (to >= mEditIndex) {
414             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE_SPEC,
415                     strip(mTiles.get(to)));
416             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE,
417                     from);
418         } else if (from >= mEditIndex) {
419             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD_SPEC,
420                     strip(mTiles.get(to)));
421             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD,
422                     to);
423         } else {
424             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE_SPEC,
425                     strip(mTiles.get(to)));
426             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE,
427                     to);
428         }
429         saveSpecs(mHost);
430         return true;
431     }
432 
updateDividerLocations()433     private void updateDividerLocations() {
434         // The first null is the header label (index 0) so we can skip it,
435         // the second null is the edit tiles label, the third null is the tile divider.
436         // If there is no third null, then there are no non-system tiles.
437         mEditIndex = -1;
438         mTileDividerIndex = mTiles.size();
439         for (int i = 1; i < mTiles.size(); i++) {
440             if (mTiles.get(i) == null) {
441                 if (mEditIndex == -1) {
442                     mEditIndex = i;
443                 } else {
444                     mTileDividerIndex = i;
445                 }
446             }
447         }
448         if (mTiles.size() - 1 == mTileDividerIndex) {
449             notifyItemChanged(mTileDividerIndex);
450         }
451     }
452 
strip(TileInfo tileInfo)453     private static String strip(TileInfo tileInfo) {
454         String spec = tileInfo.spec;
455         if (spec.startsWith(CustomTile.PREFIX)) {
456             ComponentName component = CustomTile.getComponentFromSpec(spec);
457             return component.getPackageName();
458         }
459         return spec;
460     }
461 
move(int from, int to, List<T> list)462     private <T> void move(int from, int to, List<T> list) {
463         list.add(to, list.remove(from));
464         notifyItemMoved(from, to);
465     }
466 
467     public class Holder extends ViewHolder {
468         private CustomizeTileView mTileView;
469 
Holder(View itemView)470         public Holder(View itemView) {
471             super(itemView);
472             if (itemView instanceof FrameLayout) {
473                 mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0);
474                 mTileView.setBackground(null);
475                 mTileView.getIcon().disableAnimation();
476             }
477         }
478 
clearDrag()479         public void clearDrag() {
480             itemView.clearAnimation();
481             mTileView.findViewById(R.id.tile_label).clearAnimation();
482             mTileView.findViewById(R.id.tile_label).setAlpha(1);
483             mTileView.getAppLabel().clearAnimation();
484             mTileView.getAppLabel().setAlpha(.6f);
485         }
486 
startDrag()487         public void startDrag() {
488             itemView.animate()
489                     .setDuration(DRAG_LENGTH)
490                     .scaleX(DRAG_SCALE)
491                     .scaleY(DRAG_SCALE);
492             mTileView.findViewById(R.id.tile_label).animate()
493                     .setDuration(DRAG_LENGTH)
494                     .alpha(0);
495             mTileView.getAppLabel().animate()
496                     .setDuration(DRAG_LENGTH)
497                     .alpha(0);
498         }
499 
stopDrag()500         public void stopDrag() {
501             itemView.animate()
502                     .setDuration(DRAG_LENGTH)
503                     .scaleX(1)
504                     .scaleY(1);
505             mTileView.findViewById(R.id.tile_label).animate()
506                     .setDuration(DRAG_LENGTH)
507                     .alpha(1);
508             mTileView.getAppLabel().animate()
509                     .setDuration(DRAG_LENGTH)
510                     .alpha(.6f);
511         }
512     }
513 
514     private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
515         @Override
516         public int getSpanSize(int position) {
517             final int type = getItemViewType(position);
518             return type == TYPE_EDIT || type == TYPE_DIVIDER || type == TYPE_HEADER ? 3 : 1;
519         }
520     };
521 
522     private class TileItemDecoration extends ItemDecoration {
523         private final Drawable mDrawable;
524 
TileItemDecoration(Context context)525         private TileItemDecoration(Context context) {
526             mDrawable = context.getDrawable(R.drawable.qs_customize_tile_decoration);
527         }
528 
529 
530         @Override
onDraw(Canvas c, RecyclerView parent, State state)531         public void onDraw(Canvas c, RecyclerView parent, State state) {
532             super.onDraw(c, parent, state);
533 
534             final int childCount = parent.getChildCount();
535             final int width = parent.getWidth();
536             final int bottom = parent.getBottom();
537             for (int i = 0; i < childCount; i++) {
538                 final View child = parent.getChildAt(i);
539                 final ViewHolder holder = parent.getChildViewHolder(child);
540                 if (holder.getAdapterPosition() == 0 ||
541                         holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
542                     continue;
543                 }
544 
545                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
546                         .getLayoutParams();
547                 final int top = child.getTop() + params.topMargin +
548                         Math.round(ViewCompat.getTranslationY(child));
549                 // Draw full width, in case there aren't tiles all the way across.
550                 mDrawable.setBounds(0, top, width, bottom);
551                 mDrawable.draw(c);
552                 break;
553             }
554         }
555     }
556 
557     private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
558 
559         @Override
560         public boolean isLongPressDragEnabled() {
561             return true;
562         }
563 
564         @Override
565         public boolean isItemViewSwipeEnabled() {
566             return false;
567         }
568 
569         @Override
570         public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
571             super.onSelectedChanged(viewHolder, actionState);
572             if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) {
573                 viewHolder = null;
574             }
575             if (viewHolder == mCurrentDrag) return;
576             if (mCurrentDrag != null) {
577                 int position = mCurrentDrag.getAdapterPosition();
578                 if (position == RecyclerView.NO_POSITION) return;
579                 TileInfo info = mTiles.get(position);
580                 mCurrentDrag.mTileView.setShowAppLabel(
581                         position > mEditIndex && !info.isSystem);
582                 mCurrentDrag.stopDrag();
583                 mCurrentDrag = null;
584             }
585             if (viewHolder != null) {
586                 mCurrentDrag = (Holder) viewHolder;
587                 mCurrentDrag.startDrag();
588             }
589             mHandler.post(new Runnable() {
590                 @Override
591                 public void run() {
592                     notifyItemChanged(mEditIndex);
593                 }
594             });
595         }
596 
597         @Override
598         public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
599                 ViewHolder target) {
600             final int position = target.getAdapterPosition();
601             if (position == 0 || position == RecyclerView.NO_POSITION){
602                 return false;
603             }
604             if (!canRemoveTiles() && current.getAdapterPosition() < mEditIndex) {
605                 return position < mEditIndex;
606             }
607             return position <= mEditIndex + 1;
608         }
609 
610         @Override
611         public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
612             switch (viewHolder.getItemViewType()) {
613                 case TYPE_EDIT:
614                 case TYPE_DIVIDER:
615                 case TYPE_HEADER:
616                     // Fall through
617                     return makeMovementFlags(0, 0);
618                 default:
619                     int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN
620                             | ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT;
621                     return makeMovementFlags(dragFlags, 0);
622             }
623         }
624 
625         @Override
626         public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
627             int from = viewHolder.getAdapterPosition();
628             int to = target.getAdapterPosition();
629             if (from == 0 || from == RecyclerView.NO_POSITION ||
630                     to == 0 || to == RecyclerView.NO_POSITION) {
631                 return false;
632             }
633             return move(from, to, target.itemView);
634         }
635 
636         @Override
637         public void onSwiped(ViewHolder viewHolder, int direction) {
638         }
639     };
640 }
641