Home | History | Annotate | Download | only in documentsui
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.documentsui;
     18 
     19 import static com.android.documentsui.base.DocumentInfo.getCursorString;
     20 import static com.android.documentsui.base.Shared.DEBUG;
     21 import static com.android.internal.util.Preconditions.checkNotNull;
     22 
     23 import android.annotation.ColorRes;
     24 import android.annotation.Nullable;
     25 import android.database.Cursor;
     26 import android.os.Handler;
     27 import android.os.Looper;
     28 import android.os.SystemClock;
     29 import android.provider.DocumentsContract.Document;
     30 import android.support.v7.widget.GridLayoutManager;
     31 import android.support.v7.widget.RecyclerView;
     32 import android.text.Editable;
     33 import android.text.Spannable;
     34 import android.text.method.KeyListener;
     35 import android.text.method.TextKeyListener;
     36 import android.text.method.TextKeyListener.Capitalize;
     37 import android.text.style.BackgroundColorSpan;
     38 import android.util.Log;
     39 import android.view.KeyEvent;
     40 import android.view.View;
     41 import android.widget.TextView;
     42 
     43 import com.android.documentsui.base.EventListener;
     44 import com.android.documentsui.base.Events;
     45 import com.android.documentsui.base.Features;
     46 import com.android.documentsui.base.Procedure;
     47 import com.android.documentsui.dirlist.DocumentHolder;
     48 import com.android.documentsui.dirlist.DocumentsAdapter;
     49 import com.android.documentsui.dirlist.FocusHandler;
     50 import com.android.documentsui.Model.Update;
     51 import com.android.documentsui.selection.SelectionManager;
     52 
     53 import java.util.ArrayList;
     54 import java.util.List;
     55 import java.util.Timer;
     56 import java.util.TimerTask;
     57 
     58 public final class FocusManager implements FocusHandler {
     59     private static final String TAG = "FocusManager";
     60 
     61     private final ContentScope mScope = new ContentScope();
     62 
     63     private final Features mFeatures;
     64     private final SelectionManager mSelectionMgr;
     65     private final DrawerController mDrawer;
     66     private final Procedure mRootsFocuser;
     67     private final TitleSearchHelper mSearchHelper;
     68 
     69     private boolean mNavDrawerHasFocus;
     70 
     71     public FocusManager(
     72             Features features,
     73             SelectionManager selectionMgr,
     74             DrawerController drawer,
     75             Procedure rootsFocuser,
     76             @ColorRes int color) {
     77 
     78         mFeatures = checkNotNull(features);
     79         mSelectionMgr = selectionMgr;
     80         mDrawer = drawer;
     81         mRootsFocuser = rootsFocuser;
     82 
     83         mSearchHelper = new TitleSearchHelper(color);
     84     }
     85 
     86     @Override
     87     public boolean advanceFocusArea() {
     88         // This should only be called in pre-O devices.
     89         // O has built-in keyboard navigation support.
     90         assert(!mFeatures.isSystemKeyboardNavigationEnabled());
     91         boolean focusChanged = false;
     92         if (mNavDrawerHasFocus) {
     93             mDrawer.setOpen(false);
     94             focusChanged = focusDirectoryList();
     95         } else {
     96             mDrawer.setOpen(true);
     97             focusChanged = mRootsFocuser.run();
     98         }
     99 
    100         if (focusChanged) {
    101             mNavDrawerHasFocus = !mNavDrawerHasFocus;
    102             return true;
    103         }
    104 
    105         return false;
    106     }
    107 
    108     @Override
    109     public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
    110         // Search helper gets first crack, for doing type-to-focus.
    111         if (mSearchHelper.handleKey(doc, keyCode, event)) {
    112             return true;
    113         }
    114 
    115         if (Events.isNavigationKeyCode(keyCode)) {
    116             // Find the target item and focus it.
    117             int endPos = findTargetPosition(doc.itemView, keyCode, event);
    118 
    119             if (endPos != RecyclerView.NO_POSITION) {
    120                 focusItem(endPos);
    121             }
    122             // Swallow all navigation keystrokes. Otherwise they go to the app's global
    123             // key-handler, which will route them back to the DF and cause focus to be reset.
    124             return true;
    125         }
    126         return false;
    127     }
    128 
    129     @Override
    130     public void onFocusChange(View v, boolean hasFocus) {
    131         // Remember focus events on items.
    132         if (hasFocus && v.getParent() == mScope.view) {
    133             mScope.lastFocusPosition = mScope.view.getChildAdapterPosition(v);
    134         }
    135     }
    136 
    137     @Override
    138     public boolean focusDirectoryList() {
    139         if (mScope.adapter.getItemCount() == 0) {
    140             if (DEBUG) Log.v(TAG, "Nothing to focus.");
    141             return false;
    142         }
    143 
    144         // If there's a selection going on, we don't want to grant user the ability to focus
    145         // on any individfocusSomethingual item to prevent ambiguity in operations (Cut selection
    146         // vs. Cut focused
    147         // item)
    148         if (mSelectionMgr.hasSelection()) {
    149             if (DEBUG) Log.v(TAG, "Existing selection found. No focus will be done.");
    150             return false;
    151         }
    152 
    153         final int focusPos = (mScope.lastFocusPosition != RecyclerView.NO_POSITION)
    154                 ? mScope.lastFocusPosition
    155                 : mScope.layout.findFirstVisibleItemPosition();
    156         focusItem(focusPos);
    157         return true;
    158     }
    159 
    160     /*
    161      * Attempts to reset focus on the item corresponding to {@code mPendingFocusId} if it exists and
    162      * has a valid position in the adapter. It then automatically resets {@code mPendingFocusId}.
    163      */
    164     @Override
    165     public void onLayoutCompleted() {
    166         if (mScope.pendingFocusId == null) {
    167             return;
    168         }
    169 
    170         int pos = mScope.adapter.getModelIds().indexOf(mScope.pendingFocusId);
    171         if (pos != -1) {
    172             focusItem(pos);
    173         }
    174         mScope.pendingFocusId = null;
    175     }
    176 
    177     @Override
    178     public void clearFocus() {
    179         mScope.view.clearFocus();
    180     }
    181 
    182     /*
    183      * Attempts to put focus on the document associated with the given modelId. If item does not
    184      * exist yet in the layout, this sets a pending modelId to be used when {@code
    185      * #applyPendingFocus()} is called next time.
    186      */
    187     @Override
    188     public void focusDocument(String modelId) {
    189         int pos = mScope.adapter.getAdapterPosition(modelId);
    190         if (pos != -1 && mScope.view.findViewHolderForAdapterPosition(pos) != null) {
    191             focusItem(pos);
    192         } else {
    193             mScope.pendingFocusId = modelId;
    194         }
    195     }
    196 
    197     @Override
    198     public int getFocusPosition() {
    199         return mScope.lastFocusPosition;
    200     }
    201 
    202     @Override
    203     public boolean hasFocusedItem() {
    204         return mScope.lastFocusPosition != RecyclerView.NO_POSITION;
    205     }
    206 
    207     @Override
    208     public @Nullable String getFocusModelId() {
    209         if (mScope.lastFocusPosition != RecyclerView.NO_POSITION) {
    210             DocumentHolder holder = (DocumentHolder) mScope.view
    211                     .findViewHolderForAdapterPosition(mScope.lastFocusPosition);
    212             return holder.getModelId();
    213         }
    214         return null;
    215     }
    216 
    217     /**
    218      * Finds the destination position where the focus should land for a given navigation event.
    219      *
    220      * @param view The view that received the event.
    221      * @param keyCode The key code for the event.
    222      * @param event
    223      * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION.
    224      */
    225     private int findTargetPosition(View view, int keyCode, KeyEvent event) {
    226         switch (keyCode) {
    227             case KeyEvent.KEYCODE_MOVE_HOME:
    228                 return 0;
    229             case KeyEvent.KEYCODE_MOVE_END:
    230                 return mScope.adapter.getItemCount() - 1;
    231             case KeyEvent.KEYCODE_PAGE_UP:
    232             case KeyEvent.KEYCODE_PAGE_DOWN:
    233                 return findPagedTargetPosition(view, keyCode, event);
    234         }
    235 
    236         // Find a navigation target based on the arrow key that the user pressed.
    237         int searchDir = -1;
    238         switch (keyCode) {
    239             case KeyEvent.KEYCODE_DPAD_UP:
    240                 searchDir = View.FOCUS_UP;
    241                 break;
    242             case KeyEvent.KEYCODE_DPAD_DOWN:
    243                 searchDir = View.FOCUS_DOWN;
    244                 break;
    245         }
    246 
    247         if (inGridMode()) {
    248             int currentPosition = mScope.view.getChildAdapterPosition(view);
    249             // Left and right arrow keys only work in grid mode.
    250             switch (keyCode) {
    251                 case KeyEvent.KEYCODE_DPAD_LEFT:
    252                     if (currentPosition > 0) {
    253                         // Stop backward focus search at the first item, otherwise focus will wrap
    254                         // around to the last visible item.
    255                         searchDir = View.FOCUS_BACKWARD;
    256                     }
    257                     break;
    258                 case KeyEvent.KEYCODE_DPAD_RIGHT:
    259                     if (currentPosition < mScope.adapter.getItemCount() - 1) {
    260                         // Stop forward focus search at the last item, otherwise focus will wrap
    261                         // around to the first visible item.
    262                         searchDir = View.FOCUS_FORWARD;
    263                     }
    264                     break;
    265             }
    266         }
    267 
    268         if (searchDir != -1) {
    269             // Focus search behaves badly if the parent RecyclerView is focused. However, focusable
    270             // shouldn't be unset on RecyclerView, otherwise focus isn't properly restored after
    271             // events that cause a UI rebuild (like rotating the device). Compromise: turn focusable
    272             // off while performing the focus search.
    273             // TODO: Revisit this when RV focus issues are resolved.
    274             mScope.view.setFocusable(false);
    275             View targetView = view.focusSearch(searchDir);
    276             mScope.view.setFocusable(true);
    277             // TargetView can be null, for example, if the user pressed <down> at the bottom
    278             // of the list.
    279             if (targetView != null) {
    280                 // Ignore navigation targets that aren't items in the RecyclerView.
    281                 if (targetView.getParent() == mScope.view) {
    282                     return mScope.view.getChildAdapterPosition(targetView);
    283                 }
    284             }
    285         }
    286 
    287         return RecyclerView.NO_POSITION;
    288     }
    289 
    290     /**
    291      * Given a PgUp/PgDn event and the current view, find the position of the target view. This
    292      * returns:
    293      * <li>The position of the topmost (or bottom-most) visible item, if the current item is not the
    294      * top- or bottom-most visible item.
    295      * <li>The position of an item that is one page's worth of items up (or down) if the current
    296      * item is the top- or bottom-most visible item.
    297      * <li>The first (or last) item, if paging up (or down) would go past those limits.
    298      *
    299      * @param view The view that received the key event.
    300      * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN.
    301      * @param event
    302      * @return The adapter position of the target item.
    303      */
    304     private int findPagedTargetPosition(View view, int keyCode, KeyEvent event) {
    305         int first = mScope.layout.findFirstVisibleItemPosition();
    306         int last = mScope.layout.findLastVisibleItemPosition();
    307         int current = mScope.view.getChildAdapterPosition(view);
    308         int pageSize = last - first + 1;
    309 
    310         if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
    311             if (current > first) {
    312                 // If the current item isn't the first item, target the first item.
    313                 return first;
    314             } else {
    315                 // If the current item is the first item, target the item one page up.
    316                 int target = current - pageSize;
    317                 return target < 0 ? 0 : target;
    318             }
    319         }
    320 
    321         if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
    322             if (current < last) {
    323                 // If the current item isn't the last item, target the last item.
    324                 return last;
    325             } else {
    326                 // If the current item is the last item, target the item one page down.
    327                 int target = current + pageSize;
    328                 int max = mScope.adapter.getItemCount() - 1;
    329                 return target < max ? target : max;
    330             }
    331         }
    332 
    333         throw new IllegalArgumentException("Unsupported keyCode: " + keyCode);
    334     }
    335 
    336     /**
    337      * Requests focus for the item in the given adapter position, scrolling the RecyclerView if
    338      * necessary.
    339      *
    340      * @param pos
    341      */
    342     private void focusItem(final int pos) {
    343         focusItem(pos, null);
    344     }
    345 
    346     /**
    347      * Requests focus for the item in the given adapter position, scrolling the RecyclerView if
    348      * necessary.
    349      *
    350      * @param pos
    351      * @param callback A callback to call after the given item has been focused.
    352      */
    353     private void focusItem(final int pos, @Nullable final FocusCallback callback) {
    354         if (mScope.pendingFocusId != null) {
    355             Log.v(TAG, "clearing pending focus id: " + mScope.pendingFocusId);
    356             mScope.pendingFocusId = null;
    357         }
    358 
    359         final RecyclerView recyclerView = mScope.view;
    360         final RecyclerView.ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(pos);
    361 
    362         // If the item is already in view, focus it; otherwise, scroll to it and focus it.
    363         if (vh != null) {
    364             if (vh.itemView.requestFocus() && callback != null) {
    365                 callback.onFocus(vh.itemView);
    366             }
    367         } else {
    368             // Set a one-time listener to request focus when the scroll has completed.
    369             recyclerView.addOnScrollListener(
    370                     new RecyclerView.OnScrollListener() {
    371                         @Override
    372                         public void onScrollStateChanged(RecyclerView view, int newState) {
    373                             if (newState == RecyclerView.SCROLL_STATE_IDLE) {
    374                                 // When scrolling stops, find the item and focus it.
    375                                 RecyclerView.ViewHolder vh = view
    376                                         .findViewHolderForAdapterPosition(pos);
    377                                 if (vh != null) {
    378                                     if (vh.itemView.requestFocus() && callback != null) {
    379                                         callback.onFocus(vh.itemView);
    380                                     }
    381                                 } else {
    382                                     // This might happen in weird corner cases, e.g. if the user is
    383                                     // scrolling while a delete operation is in progress. In that
    384                                     // case, just don't attempt to focus the missing item.
    385                                     Log.w(TAG, "Unable to focus position " + pos + " after scroll");
    386                                 }
    387                                 view.removeOnScrollListener(this);
    388                             }
    389                         }
    390                     });
    391             recyclerView.smoothScrollToPosition(pos);
    392         }
    393     }
    394 
    395     /** @return Whether the layout manager is currently in a grid-configuration. */
    396     private boolean inGridMode() {
    397         return mScope.layout.getSpanCount() > 1;
    398     }
    399 
    400     private interface FocusCallback {
    401         public void onFocus(View view);
    402     }
    403 
    404     /**
    405      * A helper class for handling type-to-focus. Instantiate this class, and pass it KeyEvents via
    406      * the {@link #handleKey(DocumentHolder, int, KeyEvent)} method. The class internally will build
    407      * up a string from individual key events, and perform searching based on that string. When an
    408      * item is found that matches the search term, that item will be focused. This class also
    409      * highlights instances of the search term found in the view.
    410      */
    411     private class TitleSearchHelper {
    412         private static final int SEARCH_TIMEOUT = 500; // ms
    413 
    414         private final KeyListener mTextListener = new TextKeyListener(Capitalize.NONE, false);
    415         private final Editable mSearchString = Editable.Factory.getInstance().newEditable("");
    416         private final Highlighter mHighlighter = new Highlighter();
    417         private final BackgroundColorSpan mSpan;
    418 
    419         private List<String> mIndex;
    420         private boolean mActive;
    421         private Timer mTimer;
    422         private KeyEvent mLastEvent;
    423         private Handler mUiRunner;
    424 
    425         public TitleSearchHelper(@ColorRes int color) {
    426             mSpan = new BackgroundColorSpan(color);
    427             // Handler for running things on the main UI thread. Needed for updating the UI from a
    428             // timer (see #activate, below).
    429             mUiRunner = new Handler(Looper.getMainLooper());
    430         }
    431 
    432         /**
    433          * Handles alphanumeric keystrokes for type-to-focus. This method builds a search term out
    434          * of individual key events, and then performs a search for the given string.
    435          *
    436          * @param doc The document holder receiving the key event.
    437          * @param keyCode
    438          * @param event
    439          * @return Whether the event was handled.
    440          */
    441         public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
    442             switch (keyCode) {
    443                 case KeyEvent.KEYCODE_ESCAPE:
    444                 case KeyEvent.KEYCODE_ENTER:
    445                     if (mActive) {
    446                         // These keys end any active searches.
    447                         endSearch();
    448                         return true;
    449                     } else {
    450                         // Don't handle these key events if there is no active search.
    451                         return false;
    452                     }
    453                 case KeyEvent.KEYCODE_SPACE:
    454                     // This allows users to search for files with spaces in their names, but ignores
    455                     // spacebar events when a text search is not active. Ignoring the spacebar
    456                     // event is necessary because other handlers (see FocusManager#handleKey) also
    457                     // listen for and handle it.
    458                     if (!mActive) {
    459                         return false;
    460                     }
    461             }
    462 
    463             // Navigation keys also end active searches.
    464             if (Events.isNavigationKeyCode(keyCode)) {
    465                 endSearch();
    466                 // Don't handle the keycode, so navigation still occurs.
    467                 return false;
    468             }
    469 
    470             // Build up the search string, and perform the search.
    471             boolean handled = mTextListener.onKeyDown(doc.itemView, mSearchString, keyCode, event);
    472 
    473             // Delete is processed by the text listener, but not "handled". Check separately for it.
    474             if (keyCode == KeyEvent.KEYCODE_DEL) {
    475                 handled = true;
    476             }
    477 
    478             if (handled) {
    479                 mLastEvent = event;
    480                 if (mSearchString.length() == 0) {
    481                     // Don't perform empty searches.
    482                     return false;
    483                 }
    484                 search();
    485             }
    486 
    487             return handled;
    488         }
    489 
    490         /**
    491          * Activates the search helper, which changes its key handling and updates the search index
    492          * and highlights if necessary. Call this each time the search term is updated.
    493          */
    494         private void search() {
    495             if (!mActive) {
    496                 // The model listener invalidates the search index when the model changes.
    497                 mScope.model.addUpdateListener(mModelListener);
    498 
    499                 // Used to keep the current search alive until the timeout expires. If the user
    500                 // presses another key within that time, that keystroke is added to the current
    501                 // search. Otherwise, the current search ends, and subsequent keystrokes start a new
    502                 // search.
    503                 mTimer = new Timer();
    504                 mActive = true;
    505             }
    506 
    507             // If the search index was invalidated, rebuild it
    508             if (mIndex == null) {
    509                 buildIndex();
    510             }
    511 
    512             // Search for the current search term.
    513             // Perform case-insensitive search.
    514             String searchString = mSearchString.toString().toLowerCase();
    515             for (int pos = 0; pos < mIndex.size(); pos++) {
    516                 String title = mIndex.get(pos);
    517                 if (title != null && title.startsWith(searchString)) {
    518                     focusItem(
    519                             pos,
    520                             new FocusCallback() {
    521                                 @Override
    522                                 public void onFocus(View view) {
    523                                     mHighlighter.applyHighlight(view);
    524                                     // Using a timer repeat period of SEARCH_TIMEOUT/2 means the
    525                                     // amount of
    526                                     // time between the last keystroke and a search expiring is
    527                                     // actually
    528                                     // between 500 and 750 ms. A smaller timer period results in
    529                                     // less
    530                                     // variability but does more polling.
    531                                     mTimer.schedule(new TimeoutTask(), 0, SEARCH_TIMEOUT / 2);
    532                                 }
    533                             });
    534                     break;
    535                 }
    536             }
    537         }
    538 
    539         /** Ends the current search (see {@link #search()}. */
    540         private void endSearch() {
    541             if (mActive) {
    542                 mScope.model.removeUpdateListener(mModelListener);
    543                 mTimer.cancel();
    544             }
    545 
    546             mHighlighter.removeHighlight();
    547 
    548             mIndex = null;
    549             mSearchString.clear();
    550             mActive = false;
    551         }
    552 
    553         /**
    554          * Builds a search index for finding items by title. Queries the model and adapter, so both
    555          * must be set up before calling this method.
    556          */
    557         private void buildIndex() {
    558             int itemCount = mScope.adapter.getItemCount();
    559             List<String> index = new ArrayList<>(itemCount);
    560             for (int i = 0; i < itemCount; i++) {
    561                 String modelId = mScope.adapter.getModelId(i);
    562                 Cursor cursor = mScope.model.getItem(modelId);
    563                 if (modelId != null && cursor != null) {
    564                     String title = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
    565                     // Perform case-insensitive search.
    566                     index.add(title.toLowerCase());
    567                 } else {
    568                     index.add("");
    569                 }
    570             }
    571             mIndex = index;
    572         }
    573 
    574         private EventListener<Model.Update> mModelListener = new EventListener<Model.Update>() {
    575             @Override
    576             public void accept(Update event) {
    577                 // Invalidate the search index when the model updates.
    578                 mIndex = null;
    579             }
    580         };
    581 
    582         private class TimeoutTask extends TimerTask {
    583             @Override
    584             public void run() {
    585                 long last = mLastEvent.getEventTime();
    586                 long now = SystemClock.uptimeMillis();
    587                 if ((now - last) > SEARCH_TIMEOUT) {
    588                     // endSearch must run on the main thread because it does UI work
    589                     mUiRunner.post(
    590                             new Runnable() {
    591                                 @Override
    592                                 public void run() {
    593                                     endSearch();
    594                                 }
    595                             });
    596                 }
    597             }
    598         };
    599 
    600         private class Highlighter {
    601             private Spannable mCurrentHighlight;
    602 
    603             /**
    604              * Applies title highlights to the given view. The view must have a title field that is
    605              * a spannable text field. If this condition is not met, this function does nothing.
    606              *
    607              * @param view
    608              */
    609             private void applyHighlight(View view) {
    610                 TextView titleView = (TextView) view.findViewById(android.R.id.title);
    611                 if (titleView == null) {
    612                     return;
    613                 }
    614 
    615                 CharSequence tmpText = titleView.getText();
    616                 if (tmpText instanceof Spannable) {
    617                     if (mCurrentHighlight != null) {
    618                         mCurrentHighlight.removeSpan(mSpan);
    619                     }
    620                     mCurrentHighlight = (Spannable) tmpText;
    621                     mCurrentHighlight.setSpan(
    622                             mSpan, 0, mSearchString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    623                 }
    624             }
    625 
    626             /**
    627              * Removes title highlights from the given view. The view must have a title field that
    628              * is a spannable text field. If this condition is not met, this function does nothing.
    629              *
    630              * @param view
    631              */
    632             private void removeHighlight() {
    633                 if (mCurrentHighlight != null) {
    634                     mCurrentHighlight.removeSpan(mSpan);
    635                 }
    636             }
    637         };
    638     }
    639 
    640     public FocusManager reset(RecyclerView view, Model model) {
    641         assert (view != null);
    642         assert (model != null);
    643         mScope.view = view;
    644         mScope.adapter = (DocumentsAdapter) view.getAdapter();
    645         mScope.layout = (GridLayoutManager) view.getLayoutManager();
    646         mScope.model = model;
    647 
    648         mScope.lastFocusPosition = RecyclerView.NO_POSITION;
    649         mScope.pendingFocusId = null;
    650 
    651         return this;
    652     }
    653 
    654     private static final class ContentScope {
    655         private @Nullable RecyclerView view;
    656         private @Nullable DocumentsAdapter adapter;
    657         private @Nullable GridLayoutManager layout;
    658         private @Nullable Model model;
    659 
    660         private @Nullable String pendingFocusId;
    661         private int lastFocusPosition = RecyclerView.NO_POSITION;
    662     }
    663 }
    664