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