blob: 4e77f0bf59b61efa44f57fff656be7baaea7619e [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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
17package android.widget;
18
Siva Velusamy94a6d152015-05-05 15:07:00 -070019import android.annotation.NonNull;
Scott Kennedyed2b5f82015-03-06 17:24:58 -080020import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.Context;
22import android.database.DataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.os.Parcelable;
24import android.os.SystemClock;
25import android.util.AttributeSet;
26import android.util.SparseArray;
27import android.view.ContextMenu;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070028import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.view.SoundEffectConstants;
svetoslavganov75986cf2009-05-14 22:28:01 -070030import android.view.View;
31import android.view.ViewDebug;
32import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070033import android.view.ViewHierarchyEncoder;
Felipe Leme7e4c2052017-04-18 09:45:58 -070034import android.view.ViewStructure;
svetoslavganov75986cf2009-05-14 22:28:01 -070035import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -070036import android.view.accessibility.AccessibilityManager;
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -070037import android.view.accessibility.AccessibilityNodeInfo;
Felipe Leme640f30a2017-03-06 15:44:06 -080038import android.view.autofill.AutofillManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040/**
41 * An AdapterView is a view whose children are determined by an {@link Adapter}.
42 *
43 * <p>
44 * See {@link ListView}, {@link GridView}, {@link Spinner} and
45 * {@link Gallery} for commonly used subclasses of AdapterView.
Joe Fernandez61fd1e82011-10-26 13:39:11 -070046 *
47 * <div class="special reference">
48 * <h3>Developer Guides</h3>
49 * <p>For more information about using AdapterView, read the
50 * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
51 * developer guide.</p></div>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 */
53public abstract class AdapterView<T extends Adapter> extends ViewGroup {
54
55 /**
56 * The item view type returned by {@link Adapter#getItemViewType(int)} when
57 * the adapter does not want the item's view recycled.
58 */
59 public static final int ITEM_VIEW_TYPE_IGNORE = -1;
60
61 /**
62 * The item view type returned by {@link Adapter#getItemViewType(int)} when
63 * the item is a header or footer.
64 */
65 public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
66
67 /**
68 * The position of the first child displayed
69 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -070070 @ViewDebug.ExportedProperty(category = "scrolling")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 int mFirstPosition = 0;
72
73 /**
74 * The offset in pixels from the top of the AdapterView to the top
75 * of the view to select during the next layout.
76 */
77 int mSpecificTop;
78
79 /**
80 * Position from which to start looking for mSyncRowId
81 */
82 int mSyncPosition;
83
84 /**
85 * Row id to look for when data has changed
86 */
87 long mSyncRowId = INVALID_ROW_ID;
88
89 /**
90 * Height of the view when mSyncPosition and mSyncRowId where set
91 */
92 long mSyncHeight;
93
94 /**
95 * True if we need to sync to mSyncRowId
96 */
97 boolean mNeedSync = false;
98
99 /**
100 * Indicates whether to sync based on the selection or position. Possible
101 * values are {@link #SYNC_SELECTED_POSITION} or
102 * {@link #SYNC_FIRST_POSITION}.
103 */
104 int mSyncMode;
105
106 /**
107 * Our height after the last layout
108 */
109 private int mLayoutHeight;
110
111 /**
112 * Sync based on the selected child
113 */
114 static final int SYNC_SELECTED_POSITION = 0;
115
116 /**
117 * Sync based on the first child displayed
118 */
119 static final int SYNC_FIRST_POSITION = 1;
120
121 /**
122 * Maximum amount of time to spend in {@link #findSyncPosition()}
123 */
124 static final int SYNC_MAX_DURATION_MILLIS = 100;
125
126 /**
127 * Indicates that this view is currently being laid out.
128 */
129 boolean mInLayout = false;
130
131 /**
132 * The listener that receives notifications when an item is selected.
133 */
134 OnItemSelectedListener mOnItemSelectedListener;
135
136 /**
137 * The listener that receives notifications when an item is clicked.
138 */
139 OnItemClickListener mOnItemClickListener;
140
141 /**
142 * The listener that receives notifications when an item is long clicked.
143 */
144 OnItemLongClickListener mOnItemLongClickListener;
145
146 /**
147 * True if the data has changed since the last layout
148 */
149 boolean mDataChanged;
150
151 /**
152 * The position within the adapter's data set of the item to select
153 * during the next layout.
154 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700155 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 int mNextSelectedPosition = INVALID_POSITION;
157
158 /**
159 * The item id of the item to select during the next layout.
160 */
161 long mNextSelectedRowId = INVALID_ROW_ID;
162
163 /**
164 * The position within the adapter's data set of the currently selected item.
165 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700166 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 int mSelectedPosition = INVALID_POSITION;
168
169 /**
170 * The item id of the currently selected item.
171 */
172 long mSelectedRowId = INVALID_ROW_ID;
173
174 /**
175 * View to show if there are no items to show.
176 */
Mihai Predab6af5332009-04-28 14:21:57 +0200177 private View mEmptyView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178
179 /**
180 * The number of items in the current adapter.
181 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700182 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 int mItemCount;
184
185 /**
Chet Haase5e25c2c2010-09-16 11:15:56 -0700186 * The number of items in the adapter before a data changed event occurred.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 */
188 int mOldItemCount;
189
190 /**
191 * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
192 * number of items in the current adapter.
193 */
194 public static final int INVALID_POSITION = -1;
195
196 /**
197 * Represents an empty or invalid row id
198 */
199 public static final long INVALID_ROW_ID = Long.MIN_VALUE;
200
201 /**
202 * The last selected position we used when notifying
203 */
204 int mOldSelectedPosition = INVALID_POSITION;
Andrew Solovay237c1892017-10-27 15:11:48 -0700205
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 /**
207 * The id of the last selected position we used when notifying
208 */
209 long mOldSelectedRowId = INVALID_ROW_ID;
210
211 /**
212 * Indicates what focusable state is requested when calling setFocusable().
213 * In addition to this, this view has other criteria for actually
214 * determining the focusable state (such as whether its empty or the text
215 * filter is shown).
216 *
217 * @see #setFocusable(boolean)
218 * @see #checkFocus()
219 */
Evan Roskyb8372c02017-04-05 15:07:31 -0700220 private int mDesiredFocusableState = FOCUSABLE_AUTO;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 private boolean mDesiredFocusableInTouchModeState;
222
Alan Viveretteec8e7202014-10-06 15:33:24 -0700223 /** Lazily-constructed runnable for dispatching selection events. */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 private SelectionNotifier mSelectionNotifier;
Alan Viveretteec8e7202014-10-06 15:33:24 -0700225
226 /** Selection notifier that's waiting for the next layout pass. */
227 private SelectionNotifier mPendingSelectionNotifier;
228
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 /**
230 * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
231 * This is used to layout the children during a layout pass.
232 */
233 boolean mBlockLayoutRequests = false;
234
235 public AdapterView(Context context) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700236 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 }
238
239 public AdapterView(Context context, AttributeSet attrs) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700240 this(context, attrs, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 }
242
Alan Viverette617feb92013-09-09 18:09:13 -0700243 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
244 this(context, attrs, defStyleAttr, 0);
245 }
246
247 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
248 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700249
250 // If not explicitly specified this view is important for accessibility.
251 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
252 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
253 }
Evan Roskyb8372c02017-04-05 15:07:31 -0700254
255 mDesiredFocusableState = getFocusable();
256 if (mDesiredFocusableState == FOCUSABLE_AUTO) {
257 // Starts off without an adapter, so NOT_FOCUSABLE by default.
258 super.setFocusable(NOT_FOCUSABLE);
259 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 }
261
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262 /**
263 * Interface definition for a callback to be invoked when an item in this
264 * AdapterView has been clicked.
265 */
266 public interface OnItemClickListener {
267
268 /**
269 * Callback method to be invoked when an item in this AdapterView has
270 * been clicked.
271 * <p>
272 * Implementers can call getItemAtPosition(position) if they need
273 * to access the data associated with the selected item.
274 *
275 * @param parent The AdapterView where the click happened.
276 * @param view The view within the AdapterView that was clicked (this
277 * will be a view provided by the adapter)
278 * @param position The position of the view in the adapter.
279 * @param id The row id of the item that was clicked.
280 */
281 void onItemClick(AdapterView<?> parent, View view, int position, long id);
282 }
283
284 /**
285 * Register a callback to be invoked when an item in this AdapterView has
286 * been clicked.
287 *
288 * @param listener The callback that will be invoked.
289 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800290 public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 mOnItemClickListener = listener;
292 }
293
294 /**
295 * @return The callback to be invoked with an item in this AdapterView has
Shrijana Ghimirefb53dec2018-06-11 17:14:13 -0700296 * been clicked, or null if no callback has been set.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800298 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 public final OnItemClickListener getOnItemClickListener() {
300 return mOnItemClickListener;
301 }
302
303 /**
Alan Viverettefed60952013-09-17 13:00:49 -0700304 * Call the OnItemClickListener, if it is defined. Performs all normal
305 * actions associated with clicking: reporting accessibility event, playing
306 * a sound, etc.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 *
308 * @param view The view within the AdapterView that was clicked.
309 * @param position The position of the view in the adapter.
310 * @param id The row id of the item that was clicked.
311 * @return True if there was an assigned OnItemClickListener that was
312 * called, false otherwise is returned.
313 */
314 public boolean performItemClick(View view, int position, long id) {
Alan Viverette376c32f2015-06-01 16:41:42 -0700315 final boolean result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316 if (mOnItemClickListener != null) {
317 playSoundEffect(SoundEffectConstants.CLICK);
Alan Viverette996a6382014-09-02 16:35:45 -0700318 mOnItemClickListener.onItemClick(this, view, position, id);
Alan Viverette376c32f2015-06-01 16:41:42 -0700319 result = true;
320 } else {
321 result = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 }
323
Alan Viverette376c32f2015-06-01 16:41:42 -0700324 if (view != null) {
325 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
326 }
327 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 }
329
330 /**
331 * Interface definition for a callback to be invoked when an item in this
332 * view has been clicked and held.
333 */
334 public interface OnItemLongClickListener {
335 /**
336 * Callback method to be invoked when an item in this view has been
337 * clicked and held.
338 *
339 * Implementers can call getItemAtPosition(position) if they need to access
340 * the data associated with the selected item.
341 *
342 * @param parent The AbsListView where the click happened
343 * @param view The view within the AbsListView that was clicked
344 * @param position The position of the view in the list
345 * @param id The row id of the item that was clicked
346 *
347 * @return true if the callback consumed the long click, false otherwise
348 */
349 boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
350 }
351
352
353 /**
354 * Register a callback to be invoked when an item in this AdapterView has
355 * been clicked and held
356 *
357 * @param listener The callback that will run
358 */
359 public void setOnItemLongClickListener(OnItemLongClickListener listener) {
360 if (!isLongClickable()) {
361 setLongClickable(true);
362 }
363 mOnItemLongClickListener = listener;
364 }
365
366 /**
367 * @return The callback to be invoked with an item in this AdapterView has
Shrijana Ghimirefb53dec2018-06-11 17:14:13 -0700368 * been clicked and held, or null if no callback has been set.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 */
370 public final OnItemLongClickListener getOnItemLongClickListener() {
371 return mOnItemLongClickListener;
372 }
373
374 /**
375 * Interface definition for a callback to be invoked when
376 * an item in this view has been selected.
377 */
378 public interface OnItemSelectedListener {
379 /**
Romain Guyf18c8dd2011-09-16 16:16:36 -0700380 * <p>Callback method to be invoked when an item in this view has been
381 * selected. This callback is invoked only when the newly selected
382 * position is different from the previously selected position or if
383 * there was no selected item.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 *
Andrew Solovay237c1892017-10-27 15:11:48 -0700385 * Implementers can call getItemAtPosition(position) if they need to access the
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 * data associated with the selected item.
387 *
388 * @param parent The AdapterView where the selection happened
389 * @param view The view within the AdapterView that was clicked
390 * @param position The position of the view in the adapter
391 * @param id The row id of the item that is selected
392 */
393 void onItemSelected(AdapterView<?> parent, View view, int position, long id);
394
395 /**
396 * Callback method to be invoked when the selection disappears from this
397 * view. The selection can disappear for instance when touch is activated
398 * or when the adapter becomes empty.
399 *
400 * @param parent The AdapterView that now contains no selected item.
401 */
402 void onNothingSelected(AdapterView<?> parent);
403 }
404
405
406 /**
407 * Register a callback to be invoked when an item in this AdapterView has
408 * been selected.
409 *
410 * @param listener The callback that will run
411 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800412 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 mOnItemSelectedListener = listener;
414 }
415
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800416 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 public final OnItemSelectedListener getOnItemSelectedListener() {
418 return mOnItemSelectedListener;
419 }
420
421 /**
422 * Extra menu information provided to the
423 * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
424 * callback when a context menu is brought up for this AdapterView.
425 *
426 */
427 public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
428
429 public AdapterContextMenuInfo(View targetView, int position, long id) {
430 this.targetView = targetView;
431 this.position = position;
432 this.id = id;
433 }
434
435 /**
436 * The child view for which the context menu is being displayed. This
437 * will be one of the children of this AdapterView.
438 */
439 public View targetView;
440
441 /**
442 * The position in the adapter for which the context menu is being
443 * displayed.
444 */
445 public int position;
446
447 /**
448 * The row id of the item for which the context menu is being displayed.
449 */
450 public long id;
451 }
452
453 /**
454 * Returns the adapter currently associated with this widget.
455 *
456 * @return The adapter used to provide this view's content.
457 */
458 public abstract T getAdapter();
459
460 /**
461 * Sets the adapter that provides the data and the views to represent the data
462 * in this widget.
463 *
464 * @param adapter The adapter to use to create this view's content.
465 */
466 public abstract void setAdapter(T adapter);
467
468 /**
469 * This method is not supported and throws an UnsupportedOperationException when called.
470 *
471 * @param child Ignored.
472 *
473 * @throws UnsupportedOperationException Every time this method is invoked.
474 */
475 @Override
476 public void addView(View child) {
477 throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
478 }
479
480 /**
481 * This method is not supported and throws an UnsupportedOperationException when called.
482 *
483 * @param child Ignored.
484 * @param index Ignored.
485 *
486 * @throws UnsupportedOperationException Every time this method is invoked.
487 */
488 @Override
489 public void addView(View child, int index) {
490 throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
491 }
492
493 /**
494 * This method is not supported and throws an UnsupportedOperationException when called.
495 *
496 * @param child Ignored.
497 * @param params Ignored.
498 *
499 * @throws UnsupportedOperationException Every time this method is invoked.
500 */
501 @Override
502 public void addView(View child, LayoutParams params) {
503 throw new UnsupportedOperationException("addView(View, LayoutParams) "
504 + "is not supported in AdapterView");
505 }
506
507 /**
508 * This method is not supported and throws an UnsupportedOperationException when called.
509 *
510 * @param child Ignored.
511 * @param index Ignored.
512 * @param params Ignored.
513 *
514 * @throws UnsupportedOperationException Every time this method is invoked.
515 */
516 @Override
517 public void addView(View child, int index, LayoutParams params) {
518 throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
519 + "is not supported in AdapterView");
520 }
521
522 /**
523 * This method is not supported and throws an UnsupportedOperationException when called.
524 *
525 * @param child Ignored.
526 *
527 * @throws UnsupportedOperationException Every time this method is invoked.
528 */
529 @Override
530 public void removeView(View child) {
531 throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
532 }
533
534 /**
535 * This method is not supported and throws an UnsupportedOperationException when called.
536 *
537 * @param index Ignored.
538 *
539 * @throws UnsupportedOperationException Every time this method is invoked.
540 */
541 @Override
542 public void removeViewAt(int index) {
543 throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
544 }
545
546 /**
547 * This method is not supported and throws an UnsupportedOperationException when called.
548 *
549 * @throws UnsupportedOperationException Every time this method is invoked.
550 */
551 @Override
552 public void removeAllViews() {
553 throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
554 }
555
556 @Override
557 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
558 mLayoutHeight = getHeight();
559 }
560
561 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000562 * Return the position of the currently selected item within the adapter's data set
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 *
Alan Viverette31ff78b12015-06-04 17:18:34 +0000564 * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 */
566 @ViewDebug.CapturedViewProperty
567 public int getSelectedItemPosition() {
568 return mNextSelectedPosition;
569 }
570
571 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000572 * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
573 * if nothing is selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 */
575 @ViewDebug.CapturedViewProperty
576 public long getSelectedItemId() {
577 return mNextSelectedRowId;
578 }
579
580 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000581 * @return The view corresponding to the currently selected item, or null
582 * if nothing is selected
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 */
584 public abstract View getSelectedView();
585
586 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000587 * @return The data corresponding to the currently selected item, or
588 * null if there is nothing selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 */
590 public Object getSelectedItem() {
Alan Viverette31ff78b12015-06-04 17:18:34 +0000591 T adapter = getAdapter();
592 int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
594 return adapter.getItem(selection);
595 } else {
596 return null;
597 }
598 }
599
600 /**
601 * @return The number of items owned by the Adapter associated with this
602 * AdapterView. (This is the number of data items, which may be
Chet Haase5e25c2c2010-09-16 11:15:56 -0700603 * larger than the number of visible views.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 */
605 @ViewDebug.CapturedViewProperty
606 public int getCount() {
607 return mItemCount;
608 }
609
610 /**
Alan Viverette92539d52015-09-14 10:49:25 -0400611 * Returns the position within the adapter's data set for the view, where
612 * view is a an adapter item or a descendant of an adapter item.
613 * <p>
614 * <strong>Note:</strong> The result of this method only reflects the
615 * position of the data bound to <var>view</var> during the most recent
616 * layout pass. If the adapter's data set has changed without a subsequent
617 * layout pass, the position returned by this method may not match the
618 * current position of the data within the adapter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 *
Alan Viverette92539d52015-09-14 10:49:25 -0400620 * @param view an adapter item, or a descendant of an adapter item. This
621 * must be visible in this AdapterView at the time of the call.
622 * @return the position within the adapter's data set of the view, or
623 * {@link #INVALID_POSITION} if the view does not correspond to a
624 * list item (or it is not currently visible)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 */
626 public int getPositionForView(View view) {
627 View listItem = view;
628 try {
629 View v;
Alan Viverette898c7042015-08-26 15:21:39 -0400630 while ((v = (View) listItem.getParent()) != null && !v.equals(this)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 listItem = v;
632 }
633 } catch (ClassCastException e) {
634 // We made it up to the window without find this list view
635 return INVALID_POSITION;
636 }
637
Alan Viverette898c7042015-08-26 15:21:39 -0400638 if (listItem != null) {
639 // Search the children for the list item
640 final int childCount = getChildCount();
641 for (int i = 0; i < childCount; i++) {
642 if (getChildAt(i).equals(listItem)) {
643 return mFirstPosition + i;
644 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800645 }
646 }
647
648 // Child not found!
649 return INVALID_POSITION;
650 }
651
652 /**
653 * Returns the position within the adapter's data set for the first item
654 * displayed on screen.
655 *
656 * @return The position within the adapter's data set
657 */
658 public int getFirstVisiblePosition() {
659 return mFirstPosition;
660 }
661
662 /**
663 * Returns the position within the adapter's data set for the last item
664 * displayed on screen.
665 *
666 * @return The position within the adapter's data set
667 */
668 public int getLastVisiblePosition() {
669 return mFirstPosition + getChildCount() - 1;
670 }
671
672 /**
svetoslavganov75986cf2009-05-14 22:28:01 -0700673 * Sets the currently selected item. To support accessibility subclasses that
674 * override this method must invoke the overriden super method first.
675 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 * @param position Index (starting at 0) of the data item to be selected.
677 */
678 public abstract void setSelection(int position);
679
680 /**
681 * Sets the view to show if the adapter is empty
682 */
Adam Cohen1480fdd2010-08-25 17:24:53 -0700683 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 public void setEmptyView(View emptyView) {
685 mEmptyView = emptyView;
686
Svetoslav Ganov42138042012-03-20 11:51:39 -0700687 // If not explicitly specified this view is important for accessibility.
Mindy Pereiraf9373132012-04-16 08:58:53 -0700688 if (emptyView != null
689 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700690 emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
691 }
692
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 final T adapter = getAdapter();
694 final boolean empty = ((adapter == null) || adapter.isEmpty());
695 updateEmptyStatus(empty);
696 }
697
698 /**
699 * When the current adapter is empty, the AdapterView can display a special view
Mark Doliner9525f2a2014-01-02 11:17:47 -0800700 * called the empty view. The empty view is used to provide feedback to the user
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 * that no data is available in this AdapterView.
702 *
703 * @return The view to show if the adapter is empty.
704 */
705 public View getEmptyView() {
706 return mEmptyView;
707 }
708
709 /**
710 * Indicates whether this view is in filter mode. Filter mode can for instance
711 * be enabled by a user when typing on the keyboard.
712 *
713 * @return True if the view is in filter mode, false otherwise.
714 */
715 boolean isInFilterMode() {
716 return false;
717 }
718
719 @Override
Evan Roskyb8372c02017-04-05 15:07:31 -0700720 public void setFocusable(@Focusable int focusable) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800721 final T adapter = getAdapter();
722 final boolean empty = adapter == null || adapter.getCount() == 0;
723
724 mDesiredFocusableState = focusable;
Evan Roskyb8372c02017-04-05 15:07:31 -0700725 if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 mDesiredFocusableInTouchModeState = false;
727 }
728
Evan Roskyb8372c02017-04-05 15:07:31 -0700729 super.setFocusable((!empty || isInFilterMode()) ? focusable : NOT_FOCUSABLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 }
731
732 @Override
733 public void setFocusableInTouchMode(boolean focusable) {
734 final T adapter = getAdapter();
735 final boolean empty = adapter == null || adapter.getCount() == 0;
736
737 mDesiredFocusableInTouchModeState = focusable;
738 if (focusable) {
Evan Roskyb8372c02017-04-05 15:07:31 -0700739 mDesiredFocusableState = FOCUSABLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 }
741
742 super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
743 }
744
745 void checkFocus() {
746 final T adapter = getAdapter();
747 final boolean empty = adapter == null || adapter.getCount() == 0;
748 final boolean focusable = !empty || isInFilterMode();
749 // The order in which we set focusable in touch mode/focusable may matter
750 // for the client, see View.setFocusableInTouchMode() comments for more
751 // details
752 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
Evan Roskyb8372c02017-04-05 15:07:31 -0700753 super.setFocusable(focusable ? mDesiredFocusableState : NOT_FOCUSABLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 if (mEmptyView != null) {
755 updateEmptyStatus((adapter == null) || adapter.isEmpty());
756 }
757 }
758
759 /**
760 * Update the status of the list based on the empty parameter. If empty is true and
761 * we have an empty view, display it. In all the other cases, make sure that the listview
762 * is VISIBLE and that the empty view is GONE (if it's not null).
763 */
764 private void updateEmptyStatus(boolean empty) {
765 if (isInFilterMode()) {
766 empty = false;
767 }
768
769 if (empty) {
770 if (mEmptyView != null) {
771 mEmptyView.setVisibility(View.VISIBLE);
772 setVisibility(View.GONE);
773 } else {
774 // If the caller just removed our empty view, make sure the list view is visible
775 setVisibility(View.VISIBLE);
776 }
777
778 // We are now GONE, so pending layouts will not be dispatched.
779 // Force one here to make sure that the state of the list matches
780 // the state of the adapter.
Andrew Solovay237c1892017-10-27 15:11:48 -0700781 if (mDataChanged) {
782 this.onLayout(false, mLeft, mTop, mRight, mBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 }
784 } else {
785 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
786 setVisibility(View.VISIBLE);
787 }
788 }
789
790 /**
791 * Gets the data associated with the specified position in the list.
792 *
793 * @param position Which data to get
794 * @return The data associated with the specified position in the list
795 */
796 public Object getItemAtPosition(int position) {
797 T adapter = getAdapter();
798 return (adapter == null || position < 0) ? null : adapter.getItem(position);
799 }
800
801 public long getItemIdAtPosition(int position) {
802 T adapter = getAdapter();
803 return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
804 }
805
806 @Override
807 public void setOnClickListener(OnClickListener l) {
808 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
809 + "You probably want setOnItemClickListener instead");
810 }
811
812 /**
813 * Override to prevent freezing of any views created by the adapter.
814 */
815 @Override
816 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
817 dispatchFreezeSelfOnly(container);
818 }
819
820 /**
821 * Override to prevent thawing of any views created by the adapter.
822 */
823 @Override
824 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
825 dispatchThawSelfOnly(container);
826 }
827
828 class AdapterDataSetObserver extends DataSetObserver {
829
830 private Parcelable mInstanceState = null;
831
832 @Override
833 public void onChanged() {
834 mDataChanged = true;
835 mOldItemCount = mItemCount;
836 mItemCount = getAdapter().getCount();
837
838 // Detect the case where a cursor that was previously invalidated has
839 // been repopulated with new data.
840 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
841 && mOldItemCount == 0 && mItemCount > 0) {
842 AdapterView.this.onRestoreInstanceState(mInstanceState);
843 mInstanceState = null;
844 } else {
845 rememberSyncState();
846 }
847 checkFocus();
848 requestLayout();
849 }
850
851 @Override
852 public void onInvalidated() {
853 mDataChanged = true;
854
855 if (AdapterView.this.getAdapter().hasStableIds()) {
856 // Remember the current state for the case where our hosting activity is being
857 // stopped and later restarted
858 mInstanceState = AdapterView.this.onSaveInstanceState();
859 }
860
861 // Data is invalid so we should reset our state
862 mOldItemCount = mItemCount;
863 mItemCount = 0;
864 mSelectedPosition = INVALID_POSITION;
865 mSelectedRowId = INVALID_ROW_ID;
866 mNextSelectedPosition = INVALID_POSITION;
867 mNextSelectedRowId = INVALID_ROW_ID;
868 mNeedSync = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869
870 checkFocus();
871 requestLayout();
872 }
873
874 public void clearSavedState() {
875 mInstanceState = null;
876 }
877 }
878
Adam Powell9c17a4c12010-08-30 18:04:15 -0700879 @Override
880 protected void onDetachedFromWindow() {
881 super.onDetachedFromWindow();
882 removeCallbacks(mSelectionNotifier);
883 }
884
885 private class SelectionNotifier implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800886 public void run() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700887 mPendingSelectionNotifier = null;
888
Svet Ganov99a82432014-10-24 16:27:38 -0700889 if (mDataChanged && getViewRootImpl() != null
890 && getViewRootImpl().isLayoutRequested()) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700891 // Data has changed between when this SelectionNotifier was
892 // posted and now. Postpone the notification until the next
893 // layout is complete and we run checkSelectionChanged().
Adam Powell9c17a4c12010-08-30 18:04:15 -0700894 if (getAdapter() != null) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700895 mPendingSelectionNotifier = this;
Adam Powell9c17a4c12010-08-30 18:04:15 -0700896 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800897 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700898 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 }
900 }
901 }
902
903 void selectionChanged() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700904 // We're about to post or run the selection notifier, so we don't need
905 // a pending notifier.
906 mPendingSelectionNotifier = null;
907
Svetoslav Ganov42138042012-03-20 11:51:39 -0700908 if (mOnItemSelectedListener != null
909 || AccessibilityManager.getInstance(mContext).isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910 if (mInLayout || mBlockLayoutRequests) {
911 // If we are in a layout traversal, defer notification
912 // by posting. This ensures that the view tree is
Alan Viveretteec8e7202014-10-06 15:33:24 -0700913 // in a consistent state and is able to accommodate
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800914 // new layout or invalidate requests.
915 if (mSelectionNotifier == null) {
916 mSelectionNotifier = new SelectionNotifier();
Alan Viveretteec8e7202014-10-06 15:33:24 -0700917 } else {
918 removeCallbacks(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 }
Adam Powell9c17a4c12010-08-30 18:04:15 -0700920 post(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800921 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700922 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 }
924 }
Felipe Leme640f30a2017-03-06 15:44:06 -0800925 // Always notify AutoFillManager - it will return right away if autofill is disabled.
926 final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
Felipe Lemed09ccb82017-02-22 15:02:03 -0800927 if (afm != null) {
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700928 afm.notifyValueChanged(this);
Felipe Lemed09ccb82017-02-22 15:02:03 -0800929 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 }
931
Alan Viveretteec8e7202014-10-06 15:33:24 -0700932 private void dispatchOnItemSelected() {
933 fireOnSelected();
934 performAccessibilityActionsOnSelected();
935 }
936
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937 private void fireOnSelected() {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700938 if (mOnItemSelectedListener == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 return;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700940 }
941 final int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800942 if (selection >= 0) {
943 View v = getSelectedView();
944 mOnItemSelectedListener.onItemSelected(this, v, selection,
945 getAdapter().getItemId(selection));
946 } else {
947 mOnItemSelectedListener.onNothingSelected(this);
948 }
949 }
950
Svetoslav Ganov42138042012-03-20 11:51:39 -0700951 private void performAccessibilityActionsOnSelected() {
952 if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
953 return;
954 }
955 final int position = getSelectedItemPosition();
956 if (position >= 0) {
957 // we fire selection events here not in View
958 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
959 }
960 }
961
Alan Viverettea54956a2015-01-07 16:05:02 -0800962 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800964 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700965 View selectedView = getSelectedView();
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700966 if (selectedView != null && selectedView.getVisibility() == VISIBLE
967 && selectedView.dispatchPopulateAccessibilityEvent(event)) {
968 return true;
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700969 }
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700970 return false;
971 }
972
Alan Viverettea54956a2015-01-07 16:05:02 -0800973 /** @hide */
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700974 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800975 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
976 if (super.onRequestSendAccessibilityEventInternal(child, event)) {
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700977 // Add a record for ourselves as well.
978 AccessibilityEvent record = AccessibilityEvent.obtain();
979 onInitializeAccessibilityEvent(record);
980 // Populate with the text of the requesting child.
981 child.dispatchPopulateAccessibilityEvent(record);
982 event.appendRecord(record);
983 return true;
984 }
985 return false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700986 }
Adam Powell3fb3d7c2011-04-22 17:08:55 -0700987
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800988 @Override
989 public CharSequence getAccessibilityClassName() {
990 return AdapterView.class.getName();
991 }
992
Alan Viverettea54956a2015-01-07 16:05:02 -0800993 /** @hide */
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700994 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800995 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
996 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700997 info.setScrollable(isScrollableForAccessibility());
998 View selectedView = getSelectedView();
999 if (selectedView != null) {
1000 info.setEnabled(selectedView.isEnabled());
1001 }
1002 }
1003
Alan Viverettea54956a2015-01-07 16:05:02 -08001004 /** @hide */
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001005 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001006 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
1007 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001008 event.setScrollable(isScrollableForAccessibility());
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001009 View selectedView = getSelectedView();
1010 if (selectedView != null) {
1011 event.setEnabled(selectedView.isEnabled());
1012 }
Svetoslav Ganoveb0c52e2011-10-10 18:15:41 -07001013 event.setCurrentItemIndex(getSelectedItemPosition());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001014 event.setFromIndex(getFirstVisiblePosition());
1015 event.setToIndex(getLastVisiblePosition());
Svetoslav Ganov0e6fa8b2011-10-19 12:38:04 -07001016 event.setItemCount(getCount());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001017 }
1018
1019 private boolean isScrollableForAccessibility() {
Svetoslav Ganov98348512011-10-10 17:18:19 -07001020 T adapter = getAdapter();
1021 if (adapter != null) {
1022 final int itemCount = adapter.getCount();
1023 return itemCount > 0
1024 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
1025 }
1026 return false;
svetoslavganov75986cf2009-05-14 22:28:01 -07001027 }
1028
1029 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 protected boolean canAnimate() {
1031 return super.canAnimate() && mItemCount > 0;
1032 }
1033
1034 void handleDataChanged() {
1035 final int count = mItemCount;
1036 boolean found = false;
1037
1038 if (count > 0) {
1039
1040 int newPos;
1041
1042 // Find the row we are supposed to sync to
1043 if (mNeedSync) {
1044 // Update this first, since setNextSelectedPositionInt inspects
1045 // it
1046 mNeedSync = false;
1047
1048 // See if we can find a position in the new data with the same
1049 // id as the old selection
1050 newPos = findSyncPosition();
1051 if (newPos >= 0) {
1052 // Verify that new selection is selectable
1053 int selectablePos = lookForSelectablePosition(newPos, true);
1054 if (selectablePos == newPos) {
1055 // Same row id is selected
1056 setNextSelectedPositionInt(newPos);
1057 found = true;
1058 }
1059 }
1060 }
1061 if (!found) {
1062 // Try to use the same position if we can't find matching data
1063 newPos = getSelectedItemPosition();
1064
1065 // Pin position to the available range
1066 if (newPos >= count) {
1067 newPos = count - 1;
1068 }
1069 if (newPos < 0) {
1070 newPos = 0;
1071 }
1072
1073 // Make sure we select something selectable -- first look down
1074 int selectablePos = lookForSelectablePosition(newPos, true);
1075 if (selectablePos < 0) {
1076 // Looking down didn't work -- try looking up
1077 selectablePos = lookForSelectablePosition(newPos, false);
1078 }
1079 if (selectablePos >= 0) {
1080 setNextSelectedPositionInt(selectablePos);
1081 checkSelectionChanged();
1082 found = true;
1083 }
1084 }
1085 }
1086 if (!found) {
1087 // Nothing is selected
1088 mSelectedPosition = INVALID_POSITION;
1089 mSelectedRowId = INVALID_ROW_ID;
1090 mNextSelectedPosition = INVALID_POSITION;
1091 mNextSelectedRowId = INVALID_ROW_ID;
1092 mNeedSync = false;
1093 checkSelectionChanged();
1094 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001095
Eugene Susla72c510f2018-01-23 21:12:11 +00001096 notifySubtreeAccessibilityStateChangedIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001097 }
1098
Alan Viveretteec8e7202014-10-06 15:33:24 -07001099 /**
1100 * Called after layout to determine whether the selection position needs to
1101 * be updated. Also used to fire any pending selection events.
1102 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 void checkSelectionChanged() {
1104 if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
1105 selectionChanged();
1106 mOldSelectedPosition = mSelectedPosition;
1107 mOldSelectedRowId = mSelectedRowId;
1108 }
Alan Viveretteec8e7202014-10-06 15:33:24 -07001109
1110 // If we have a pending selection notification -- and we won't if we
1111 // just fired one in selectionChanged() -- run it now.
1112 if (mPendingSelectionNotifier != null) {
1113 mPendingSelectionNotifier.run();
1114 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 }
1116
1117 /**
1118 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1119 * and then alternates between moving up and moving down until 1) we find the right position, or
1120 * 2) we run out of time, or 3) we have looked at every position
1121 *
1122 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1123 * be found
1124 */
1125 int findSyncPosition() {
1126 int count = mItemCount;
1127
1128 if (count == 0) {
1129 return INVALID_POSITION;
1130 }
1131
1132 long idToMatch = mSyncRowId;
1133 int seed = mSyncPosition;
1134
1135 // If there isn't a selection don't hunt for it
1136 if (idToMatch == INVALID_ROW_ID) {
1137 return INVALID_POSITION;
1138 }
1139
1140 // Pin seed to reasonable values
1141 seed = Math.max(0, seed);
1142 seed = Math.min(count - 1, seed);
1143
1144 long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
1145
1146 long rowId;
1147
1148 // first position scanned so far
1149 int first = seed;
1150
1151 // last position scanned so far
1152 int last = seed;
1153
1154 // True if we should move down on the next iteration
1155 boolean next = false;
1156
1157 // True when we have looked at the first item in the data
1158 boolean hitFirst;
1159
1160 // True when we have looked at the last item in the data
1161 boolean hitLast;
1162
1163 // Get the item ID locally (instead of getItemIdAtPosition), so
1164 // we need the adapter
1165 T adapter = getAdapter();
1166 if (adapter == null) {
1167 return INVALID_POSITION;
1168 }
1169
1170 while (SystemClock.uptimeMillis() <= endTime) {
1171 rowId = adapter.getItemId(seed);
1172 if (rowId == idToMatch) {
1173 // Found it!
1174 return seed;
1175 }
1176
1177 hitLast = last == count - 1;
1178 hitFirst = first == 0;
1179
1180 if (hitLast && hitFirst) {
1181 // Looked at everything
1182 break;
1183 }
1184
1185 if (hitFirst || (next && !hitLast)) {
1186 // Either we hit the top, or we are trying to move down
1187 last++;
1188 seed = last;
1189 // Try going up next time
1190 next = false;
1191 } else if (hitLast || (!next && !hitFirst)) {
1192 // Either we hit the bottom, or we are trying to move up
1193 first--;
1194 seed = first;
1195 // Try going down next time
1196 next = true;
1197 }
1198
1199 }
1200
1201 return INVALID_POSITION;
1202 }
1203
1204 /**
1205 * Find a position that can be selected (i.e., is not a separator).
1206 *
1207 * @param position The starting position to look at.
1208 * @param lookDown Whether to look down for other positions.
1209 * @return The next selectable position starting at position and then searching either up or
1210 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1211 */
1212 int lookForSelectablePosition(int position, boolean lookDown) {
1213 return position;
1214 }
1215
1216 /**
1217 * Utility to keep mSelectedPosition and mSelectedRowId in sync
1218 * @param position Our current position
1219 */
1220 void setSelectedPositionInt(int position) {
1221 mSelectedPosition = position;
1222 mSelectedRowId = getItemIdAtPosition(position);
1223 }
1224
1225 /**
1226 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1227 * @param position Intended value for mSelectedPosition the next time we go
1228 * through layout
1229 */
1230 void setNextSelectedPositionInt(int position) {
1231 mNextSelectedPosition = position;
1232 mNextSelectedRowId = getItemIdAtPosition(position);
1233 // If we are trying to sync to the selection, update that too
1234 if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
1235 mSyncPosition = position;
1236 mSyncRowId = mNextSelectedRowId;
1237 }
1238 }
1239
1240 /**
1241 * Remember enough information to restore the screen state when the data has
1242 * changed.
1243 *
1244 */
1245 void rememberSyncState() {
1246 if (getChildCount() > 0) {
1247 mNeedSync = true;
1248 mSyncHeight = mLayoutHeight;
1249 if (mSelectedPosition >= 0) {
1250 // Sync the selection state
1251 View v = getChildAt(mSelectedPosition - mFirstPosition);
1252 mSyncRowId = mNextSelectedRowId;
1253 mSyncPosition = mNextSelectedPosition;
1254 if (v != null) {
1255 mSpecificTop = v.getTop();
1256 }
1257 mSyncMode = SYNC_SELECTED_POSITION;
1258 } else {
1259 // Sync the based on the offset of the first view
1260 View v = getChildAt(0);
1261 T adapter = getAdapter();
1262 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
1263 mSyncRowId = adapter.getItemId(mFirstPosition);
1264 } else {
1265 mSyncRowId = NO_ID;
1266 }
1267 mSyncPosition = mFirstPosition;
1268 if (v != null) {
1269 mSpecificTop = v.getTop();
1270 }
1271 mSyncMode = SYNC_FIRST_POSITION;
1272 }
1273 }
1274 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07001275
1276 /** @hide */
1277 @Override
1278 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
1279 super.encodeProperties(encoder);
1280
1281 encoder.addProperty("scrolling:firstPosition", mFirstPosition);
1282 encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition);
1283 encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId);
1284 encoder.addProperty("list:selectedPosition", mSelectedPosition);
1285 encoder.addProperty("list:itemCount", mItemCount);
1286 }
Felipe Leme7e4c2052017-04-18 09:45:58 -07001287
1288 /**
1289 * {@inheritDoc}
1290 *
1291 * <p>It also sets the autofill options in the structure; when overridden, it should set it as
1292 * well, either explicitly by calling {@link ViewStructure#setAutofillOptions(CharSequence[])}
1293 * or implicitly by calling {@code super.onProvideAutofillStructure(structure, flags)}.
1294 */
1295 @Override
1296 public void onProvideAutofillStructure(ViewStructure structure, int flags) {
1297 super.onProvideAutofillStructure(structure, flags);
1298
1299 final Adapter adapter = getAdapter();
1300 if (adapter == null) return;
1301
1302 final CharSequence[] options = adapter.getAutofillOptions();
1303 if (options != null) {
1304 structure.setAutofillOptions(options);
1305 }
1306 }
Andrew Solovay237c1892017-10-27 15:11:48 -07001307}