blob: f3fe16e8a6758beed2b92e99b5c481d0d63fcacb [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;
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010021import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
23import android.database.DataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.os.Parcelable;
25import android.os.SystemClock;
26import android.util.AttributeSet;
27import android.util.SparseArray;
28import android.view.ContextMenu;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070029import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.view.SoundEffectConstants;
svetoslavganov75986cf2009-05-14 22:28:01 -070031import android.view.View;
32import android.view.ViewDebug;
33import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070034import android.view.ViewHierarchyEncoder;
Felipe Leme7e4c2052017-04-18 09:45:58 -070035import android.view.ViewStructure;
svetoslavganov75986cf2009-05-14 22:28:01 -070036import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -070037import android.view.accessibility.AccessibilityManager;
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -070038import android.view.accessibility.AccessibilityNodeInfo;
Felipe Leme640f30a2017-03-06 15:44:06 -080039import android.view.autofill.AutofillManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041/**
42 * An AdapterView is a view whose children are determined by an {@link Adapter}.
43 *
44 * <p>
45 * See {@link ListView}, {@link GridView}, {@link Spinner} and
46 * {@link Gallery} for commonly used subclasses of AdapterView.
Joe Fernandez61fd1e82011-10-26 13:39:11 -070047 *
48 * <div class="special reference">
49 * <h3>Developer Guides</h3>
50 * <p>For more information about using AdapterView, read the
51 * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
52 * developer guide.</p></div>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 */
54public abstract class AdapterView<T extends Adapter> extends ViewGroup {
55
56 /**
57 * The item view type returned by {@link Adapter#getItemViewType(int)} when
58 * the adapter does not want the item's view recycled.
59 */
60 public static final int ITEM_VIEW_TYPE_IGNORE = -1;
61
62 /**
63 * The item view type returned by {@link Adapter#getItemViewType(int)} when
64 * the item is a header or footer.
65 */
66 public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
67
68 /**
69 * The position of the first child displayed
70 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -070071 @ViewDebug.ExportedProperty(category = "scrolling")
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010072 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 int mFirstPosition = 0;
74
75 /**
76 * The offset in pixels from the top of the AdapterView to the top
77 * of the view to select during the next layout.
78 */
79 int mSpecificTop;
80
81 /**
82 * Position from which to start looking for mSyncRowId
83 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010084 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 int mSyncPosition;
86
87 /**
88 * Row id to look for when data has changed
89 */
90 long mSyncRowId = INVALID_ROW_ID;
91
92 /**
93 * Height of the view when mSyncPosition and mSyncRowId where set
94 */
95 long mSyncHeight;
96
97 /**
98 * True if we need to sync to mSyncRowId
99 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100100 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 boolean mNeedSync = false;
102
103 /**
104 * Indicates whether to sync based on the selection or position. Possible
105 * values are {@link #SYNC_SELECTED_POSITION} or
106 * {@link #SYNC_FIRST_POSITION}.
107 */
108 int mSyncMode;
109
110 /**
111 * Our height after the last layout
112 */
113 private int mLayoutHeight;
114
115 /**
116 * Sync based on the selected child
117 */
118 static final int SYNC_SELECTED_POSITION = 0;
119
120 /**
121 * Sync based on the first child displayed
122 */
123 static final int SYNC_FIRST_POSITION = 1;
124
125 /**
126 * Maximum amount of time to spend in {@link #findSyncPosition()}
127 */
128 static final int SYNC_MAX_DURATION_MILLIS = 100;
129
130 /**
131 * Indicates that this view is currently being laid out.
132 */
133 boolean mInLayout = false;
134
135 /**
136 * The listener that receives notifications when an item is selected.
137 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100138 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 OnItemSelectedListener mOnItemSelectedListener;
140
141 /**
142 * The listener that receives notifications when an item is clicked.
143 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100144 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 OnItemClickListener mOnItemClickListener;
146
147 /**
148 * The listener that receives notifications when an item is long clicked.
149 */
150 OnItemLongClickListener mOnItemLongClickListener;
151
152 /**
153 * True if the data has changed since the last layout
154 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100155 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 boolean mDataChanged;
157
158 /**
159 * The position within the adapter's data set of the item to select
160 * during the next layout.
161 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700162 @ViewDebug.ExportedProperty(category = "list")
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100163 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 int mNextSelectedPosition = INVALID_POSITION;
165
166 /**
167 * The item id of the item to select during the next layout.
168 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100169 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 long mNextSelectedRowId = INVALID_ROW_ID;
171
172 /**
173 * The position within the adapter's data set of the currently selected item.
174 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700175 @ViewDebug.ExportedProperty(category = "list")
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100176 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 int mSelectedPosition = INVALID_POSITION;
178
179 /**
180 * The item id of the currently selected item.
181 */
182 long mSelectedRowId = INVALID_ROW_ID;
183
184 /**
185 * View to show if there are no items to show.
186 */
Mihai Predab6af5332009-04-28 14:21:57 +0200187 private View mEmptyView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188
189 /**
190 * The number of items in the current adapter.
191 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700192 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 int mItemCount;
194
195 /**
Chet Haase5e25c2c2010-09-16 11:15:56 -0700196 * The number of items in the adapter before a data changed event occurred.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 */
198 int mOldItemCount;
199
200 /**
201 * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
202 * number of items in the current adapter.
203 */
204 public static final int INVALID_POSITION = -1;
205
206 /**
207 * Represents an empty or invalid row id
208 */
209 public static final long INVALID_ROW_ID = Long.MIN_VALUE;
210
211 /**
212 * The last selected position we used when notifying
213 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100214 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 int mOldSelectedPosition = INVALID_POSITION;
Andrew Solovay237c1892017-10-27 15:11:48 -0700216
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 /**
218 * The id of the last selected position we used when notifying
219 */
220 long mOldSelectedRowId = INVALID_ROW_ID;
221
222 /**
223 * Indicates what focusable state is requested when calling setFocusable().
224 * In addition to this, this view has other criteria for actually
225 * determining the focusable state (such as whether its empty or the text
226 * filter is shown).
227 *
228 * @see #setFocusable(boolean)
229 * @see #checkFocus()
230 */
Evan Roskyb8372c02017-04-05 15:07:31 -0700231 private int mDesiredFocusableState = FOCUSABLE_AUTO;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 private boolean mDesiredFocusableInTouchModeState;
233
Alan Viveretteec8e7202014-10-06 15:33:24 -0700234 /** Lazily-constructed runnable for dispatching selection events. */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 private SelectionNotifier mSelectionNotifier;
Alan Viveretteec8e7202014-10-06 15:33:24 -0700236
237 /** Selection notifier that's waiting for the next layout pass. */
238 private SelectionNotifier mPendingSelectionNotifier;
239
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 /**
241 * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
242 * This is used to layout the children during a layout pass.
243 */
244 boolean mBlockLayoutRequests = false;
245
246 public AdapterView(Context context) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700247 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 }
249
250 public AdapterView(Context context, AttributeSet attrs) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700251 this(context, attrs, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 }
253
Alan Viverette617feb92013-09-09 18:09:13 -0700254 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
255 this(context, attrs, defStyleAttr, 0);
256 }
257
258 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
259 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700260
261 // If not explicitly specified this view is important for accessibility.
262 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
263 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
264 }
Evan Roskyb8372c02017-04-05 15:07:31 -0700265
266 mDesiredFocusableState = getFocusable();
267 if (mDesiredFocusableState == FOCUSABLE_AUTO) {
268 // Starts off without an adapter, so NOT_FOCUSABLE by default.
269 super.setFocusable(NOT_FOCUSABLE);
270 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 }
272
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 /**
274 * Interface definition for a callback to be invoked when an item in this
275 * AdapterView has been clicked.
276 */
277 public interface OnItemClickListener {
278
279 /**
280 * Callback method to be invoked when an item in this AdapterView has
281 * been clicked.
282 * <p>
283 * Implementers can call getItemAtPosition(position) if they need
284 * to access the data associated with the selected item.
285 *
286 * @param parent The AdapterView where the click happened.
287 * @param view The view within the AdapterView that was clicked (this
288 * will be a view provided by the adapter)
289 * @param position The position of the view in the adapter.
290 * @param id The row id of the item that was clicked.
291 */
292 void onItemClick(AdapterView<?> parent, View view, int position, long id);
293 }
294
295 /**
296 * Register a callback to be invoked when an item in this AdapterView has
297 * been clicked.
298 *
299 * @param listener The callback that will be invoked.
300 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800301 public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 mOnItemClickListener = listener;
303 }
304
305 /**
306 * @return The callback to be invoked with an item in this AdapterView has
Shrijana Ghimirefb53dec2018-06-11 17:14:13 -0700307 * been clicked, or null if no callback has been set.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800309 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310 public final OnItemClickListener getOnItemClickListener() {
311 return mOnItemClickListener;
312 }
313
314 /**
Alan Viverettefed60952013-09-17 13:00:49 -0700315 * Call the OnItemClickListener, if it is defined. Performs all normal
316 * actions associated with clicking: reporting accessibility event, playing
317 * a sound, etc.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 *
319 * @param view The view within the AdapterView that was clicked.
320 * @param position The position of the view in the adapter.
321 * @param id The row id of the item that was clicked.
322 * @return True if there was an assigned OnItemClickListener that was
323 * called, false otherwise is returned.
324 */
325 public boolean performItemClick(View view, int position, long id) {
Alan Viverette376c32f2015-06-01 16:41:42 -0700326 final boolean result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 if (mOnItemClickListener != null) {
328 playSoundEffect(SoundEffectConstants.CLICK);
Alan Viverette996a6382014-09-02 16:35:45 -0700329 mOnItemClickListener.onItemClick(this, view, position, id);
Alan Viverette376c32f2015-06-01 16:41:42 -0700330 result = true;
331 } else {
332 result = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 }
334
Alan Viverette376c32f2015-06-01 16:41:42 -0700335 if (view != null) {
336 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
337 }
338 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 }
340
341 /**
342 * Interface definition for a callback to be invoked when an item in this
343 * view has been clicked and held.
344 */
345 public interface OnItemLongClickListener {
346 /**
347 * Callback method to be invoked when an item in this view has been
348 * clicked and held.
349 *
350 * Implementers can call getItemAtPosition(position) if they need to access
351 * the data associated with the selected item.
352 *
353 * @param parent The AbsListView where the click happened
354 * @param view The view within the AbsListView that was clicked
355 * @param position The position of the view in the list
356 * @param id The row id of the item that was clicked
357 *
358 * @return true if the callback consumed the long click, false otherwise
359 */
360 boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
361 }
362
363
364 /**
365 * Register a callback to be invoked when an item in this AdapterView has
366 * been clicked and held
367 *
368 * @param listener The callback that will run
369 */
370 public void setOnItemLongClickListener(OnItemLongClickListener listener) {
371 if (!isLongClickable()) {
372 setLongClickable(true);
373 }
374 mOnItemLongClickListener = listener;
375 }
376
377 /**
378 * @return The callback to be invoked with an item in this AdapterView has
Shrijana Ghimirefb53dec2018-06-11 17:14:13 -0700379 * been clicked and held, or null if no callback has been set.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 */
381 public final OnItemLongClickListener getOnItemLongClickListener() {
382 return mOnItemLongClickListener;
383 }
384
385 /**
386 * Interface definition for a callback to be invoked when
387 * an item in this view has been selected.
388 */
389 public interface OnItemSelectedListener {
390 /**
Romain Guyf18c8dd2011-09-16 16:16:36 -0700391 * <p>Callback method to be invoked when an item in this view has been
392 * selected. This callback is invoked only when the newly selected
393 * position is different from the previously selected position or if
394 * there was no selected item.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 *
Andrew Solovay237c1892017-10-27 15:11:48 -0700396 * Implementers can call getItemAtPosition(position) if they need to access the
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 * data associated with the selected item.
398 *
399 * @param parent The AdapterView where the selection happened
400 * @param view The view within the AdapterView that was clicked
401 * @param position The position of the view in the adapter
402 * @param id The row id of the item that is selected
403 */
404 void onItemSelected(AdapterView<?> parent, View view, int position, long id);
405
406 /**
407 * Callback method to be invoked when the selection disappears from this
408 * view. The selection can disappear for instance when touch is activated
409 * or when the adapter becomes empty.
410 *
411 * @param parent The AdapterView that now contains no selected item.
412 */
413 void onNothingSelected(AdapterView<?> parent);
414 }
415
416
417 /**
418 * Register a callback to be invoked when an item in this AdapterView has
419 * been selected.
420 *
421 * @param listener The callback that will run
422 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800423 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 mOnItemSelectedListener = listener;
425 }
426
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800427 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 public final OnItemSelectedListener getOnItemSelectedListener() {
429 return mOnItemSelectedListener;
430 }
431
432 /**
433 * Extra menu information provided to the
434 * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
435 * callback when a context menu is brought up for this AdapterView.
436 *
437 */
438 public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
439
440 public AdapterContextMenuInfo(View targetView, int position, long id) {
441 this.targetView = targetView;
442 this.position = position;
443 this.id = id;
444 }
445
446 /**
447 * The child view for which the context menu is being displayed. This
448 * will be one of the children of this AdapterView.
449 */
450 public View targetView;
451
452 /**
453 * The position in the adapter for which the context menu is being
454 * displayed.
455 */
456 public int position;
457
458 /**
459 * The row id of the item for which the context menu is being displayed.
460 */
461 public long id;
462 }
463
464 /**
465 * Returns the adapter currently associated with this widget.
466 *
467 * @return The adapter used to provide this view's content.
468 */
469 public abstract T getAdapter();
470
471 /**
472 * Sets the adapter that provides the data and the views to represent the data
473 * in this widget.
474 *
475 * @param adapter The adapter to use to create this view's content.
476 */
477 public abstract void setAdapter(T adapter);
478
479 /**
480 * This method is not supported and throws an UnsupportedOperationException when called.
481 *
482 * @param child Ignored.
483 *
484 * @throws UnsupportedOperationException Every time this method is invoked.
485 */
486 @Override
487 public void addView(View child) {
488 throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
489 }
490
491 /**
492 * This method is not supported and throws an UnsupportedOperationException when called.
493 *
494 * @param child Ignored.
495 * @param index Ignored.
496 *
497 * @throws UnsupportedOperationException Every time this method is invoked.
498 */
499 @Override
500 public void addView(View child, int index) {
501 throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
502 }
503
504 /**
505 * This method is not supported and throws an UnsupportedOperationException when called.
506 *
507 * @param child Ignored.
508 * @param params Ignored.
509 *
510 * @throws UnsupportedOperationException Every time this method is invoked.
511 */
512 @Override
513 public void addView(View child, LayoutParams params) {
514 throw new UnsupportedOperationException("addView(View, LayoutParams) "
515 + "is not supported in AdapterView");
516 }
517
518 /**
519 * This method is not supported and throws an UnsupportedOperationException when called.
520 *
521 * @param child Ignored.
522 * @param index Ignored.
523 * @param params Ignored.
524 *
525 * @throws UnsupportedOperationException Every time this method is invoked.
526 */
527 @Override
528 public void addView(View child, int index, LayoutParams params) {
529 throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
530 + "is not supported in AdapterView");
531 }
532
533 /**
534 * This method is not supported and throws an UnsupportedOperationException when called.
535 *
536 * @param child Ignored.
537 *
538 * @throws UnsupportedOperationException Every time this method is invoked.
539 */
540 @Override
541 public void removeView(View child) {
542 throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
543 }
544
545 /**
546 * This method is not supported and throws an UnsupportedOperationException when called.
547 *
548 * @param index Ignored.
549 *
550 * @throws UnsupportedOperationException Every time this method is invoked.
551 */
552 @Override
553 public void removeViewAt(int index) {
554 throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
555 }
556
557 /**
558 * This method is not supported and throws an UnsupportedOperationException when called.
559 *
560 * @throws UnsupportedOperationException Every time this method is invoked.
561 */
562 @Override
563 public void removeAllViews() {
564 throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
565 }
566
567 @Override
568 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
569 mLayoutHeight = getHeight();
570 }
571
572 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000573 * Return the position of the currently selected item within the adapter's data set
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 *
Alan Viverette31ff78b12015-06-04 17:18:34 +0000575 * @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 -0800576 */
577 @ViewDebug.CapturedViewProperty
578 public int getSelectedItemPosition() {
579 return mNextSelectedPosition;
580 }
581
582 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000583 * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
584 * if nothing is selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 */
586 @ViewDebug.CapturedViewProperty
587 public long getSelectedItemId() {
588 return mNextSelectedRowId;
589 }
590
591 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000592 * @return The view corresponding to the currently selected item, or null
593 * if nothing is selected
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594 */
595 public abstract View getSelectedView();
596
597 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000598 * @return The data corresponding to the currently selected item, or
599 * null if there is nothing selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 */
601 public Object getSelectedItem() {
Alan Viverette31ff78b12015-06-04 17:18:34 +0000602 T adapter = getAdapter();
603 int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
605 return adapter.getItem(selection);
606 } else {
607 return null;
608 }
609 }
610
611 /**
612 * @return The number of items owned by the Adapter associated with this
613 * AdapterView. (This is the number of data items, which may be
Chet Haase5e25c2c2010-09-16 11:15:56 -0700614 * larger than the number of visible views.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 */
616 @ViewDebug.CapturedViewProperty
617 public int getCount() {
618 return mItemCount;
619 }
620
621 /**
Alan Viverette92539d52015-09-14 10:49:25 -0400622 * Returns the position within the adapter's data set for the view, where
623 * view is a an adapter item or a descendant of an adapter item.
624 * <p>
625 * <strong>Note:</strong> The result of this method only reflects the
626 * position of the data bound to <var>view</var> during the most recent
627 * layout pass. If the adapter's data set has changed without a subsequent
628 * layout pass, the position returned by this method may not match the
629 * current position of the data within the adapter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 *
Alan Viverette92539d52015-09-14 10:49:25 -0400631 * @param view an adapter item, or a descendant of an adapter item. This
632 * must be visible in this AdapterView at the time of the call.
633 * @return the position within the adapter's data set of the view, or
634 * {@link #INVALID_POSITION} if the view does not correspond to a
635 * list item (or it is not currently visible)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636 */
637 public int getPositionForView(View view) {
638 View listItem = view;
639 try {
640 View v;
Alan Viverette898c7042015-08-26 15:21:39 -0400641 while ((v = (View) listItem.getParent()) != null && !v.equals(this)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 listItem = v;
643 }
644 } catch (ClassCastException e) {
645 // We made it up to the window without find this list view
646 return INVALID_POSITION;
647 }
648
Alan Viverette898c7042015-08-26 15:21:39 -0400649 if (listItem != null) {
650 // Search the children for the list item
651 final int childCount = getChildCount();
652 for (int i = 0; i < childCount; i++) {
653 if (getChildAt(i).equals(listItem)) {
654 return mFirstPosition + i;
655 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 }
657 }
658
659 // Child not found!
660 return INVALID_POSITION;
661 }
662
663 /**
664 * Returns the position within the adapter's data set for the first item
665 * displayed on screen.
666 *
667 * @return The position within the adapter's data set
668 */
669 public int getFirstVisiblePosition() {
670 return mFirstPosition;
671 }
672
673 /**
674 * Returns the position within the adapter's data set for the last item
675 * displayed on screen.
676 *
677 * @return The position within the adapter's data set
678 */
679 public int getLastVisiblePosition() {
680 return mFirstPosition + getChildCount() - 1;
681 }
682
683 /**
svetoslavganov75986cf2009-05-14 22:28:01 -0700684 * Sets the currently selected item. To support accessibility subclasses that
koprivadebd4ee2018-09-13 10:59:46 -0700685 * override this method must invoke the overridden super method first.
svetoslavganov75986cf2009-05-14 22:28:01 -0700686 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 * @param position Index (starting at 0) of the data item to be selected.
688 */
689 public abstract void setSelection(int position);
690
691 /**
692 * Sets the view to show if the adapter is empty
693 */
Adam Cohen1480fdd2010-08-25 17:24:53 -0700694 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800695 public void setEmptyView(View emptyView) {
696 mEmptyView = emptyView;
697
Svetoslav Ganov42138042012-03-20 11:51:39 -0700698 // If not explicitly specified this view is important for accessibility.
Mindy Pereiraf9373132012-04-16 08:58:53 -0700699 if (emptyView != null
700 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700701 emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
702 }
703
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 final T adapter = getAdapter();
705 final boolean empty = ((adapter == null) || adapter.isEmpty());
706 updateEmptyStatus(empty);
707 }
708
709 /**
710 * When the current adapter is empty, the AdapterView can display a special view
Mark Doliner9525f2a2014-01-02 11:17:47 -0800711 * 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 -0800712 * that no data is available in this AdapterView.
713 *
714 * @return The view to show if the adapter is empty.
715 */
716 public View getEmptyView() {
717 return mEmptyView;
718 }
719
720 /**
721 * Indicates whether this view is in filter mode. Filter mode can for instance
722 * be enabled by a user when typing on the keyboard.
723 *
724 * @return True if the view is in filter mode, false otherwise.
725 */
726 boolean isInFilterMode() {
727 return false;
728 }
729
730 @Override
Evan Roskyb8372c02017-04-05 15:07:31 -0700731 public void setFocusable(@Focusable int focusable) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 final T adapter = getAdapter();
733 final boolean empty = adapter == null || adapter.getCount() == 0;
734
735 mDesiredFocusableState = focusable;
Evan Roskyb8372c02017-04-05 15:07:31 -0700736 if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737 mDesiredFocusableInTouchModeState = false;
738 }
739
Evan Roskyb8372c02017-04-05 15:07:31 -0700740 super.setFocusable((!empty || isInFilterMode()) ? focusable : NOT_FOCUSABLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741 }
742
743 @Override
744 public void setFocusableInTouchMode(boolean focusable) {
745 final T adapter = getAdapter();
746 final boolean empty = adapter == null || adapter.getCount() == 0;
747
748 mDesiredFocusableInTouchModeState = focusable;
749 if (focusable) {
Evan Roskyb8372c02017-04-05 15:07:31 -0700750 mDesiredFocusableState = FOCUSABLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 }
752
753 super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
754 }
755
756 void checkFocus() {
757 final T adapter = getAdapter();
758 final boolean empty = adapter == null || adapter.getCount() == 0;
759 final boolean focusable = !empty || isInFilterMode();
760 // The order in which we set focusable in touch mode/focusable may matter
761 // for the client, see View.setFocusableInTouchMode() comments for more
762 // details
763 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
Evan Roskyb8372c02017-04-05 15:07:31 -0700764 super.setFocusable(focusable ? mDesiredFocusableState : NOT_FOCUSABLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 if (mEmptyView != null) {
766 updateEmptyStatus((adapter == null) || adapter.isEmpty());
767 }
768 }
769
770 /**
771 * Update the status of the list based on the empty parameter. If empty is true and
772 * we have an empty view, display it. In all the other cases, make sure that the listview
773 * is VISIBLE and that the empty view is GONE (if it's not null).
774 */
775 private void updateEmptyStatus(boolean empty) {
776 if (isInFilterMode()) {
777 empty = false;
778 }
779
780 if (empty) {
781 if (mEmptyView != null) {
782 mEmptyView.setVisibility(View.VISIBLE);
783 setVisibility(View.GONE);
784 } else {
785 // If the caller just removed our empty view, make sure the list view is visible
786 setVisibility(View.VISIBLE);
787 }
788
789 // We are now GONE, so pending layouts will not be dispatched.
790 // Force one here to make sure that the state of the list matches
791 // the state of the adapter.
Andrew Solovay237c1892017-10-27 15:11:48 -0700792 if (mDataChanged) {
793 this.onLayout(false, mLeft, mTop, mRight, mBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 }
795 } else {
796 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
797 setVisibility(View.VISIBLE);
798 }
799 }
800
801 /**
802 * Gets the data associated with the specified position in the list.
803 *
804 * @param position Which data to get
805 * @return The data associated with the specified position in the list
806 */
807 public Object getItemAtPosition(int position) {
808 T adapter = getAdapter();
809 return (adapter == null || position < 0) ? null : adapter.getItem(position);
810 }
811
812 public long getItemIdAtPosition(int position) {
813 T adapter = getAdapter();
814 return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
815 }
816
817 @Override
818 public void setOnClickListener(OnClickListener l) {
819 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
820 + "You probably want setOnItemClickListener instead");
821 }
822
823 /**
824 * Override to prevent freezing of any views created by the adapter.
825 */
826 @Override
827 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
828 dispatchFreezeSelfOnly(container);
829 }
830
831 /**
832 * Override to prevent thawing of any views created by the adapter.
833 */
834 @Override
835 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
836 dispatchThawSelfOnly(container);
837 }
838
839 class AdapterDataSetObserver extends DataSetObserver {
840
841 private Parcelable mInstanceState = null;
842
843 @Override
844 public void onChanged() {
845 mDataChanged = true;
846 mOldItemCount = mItemCount;
847 mItemCount = getAdapter().getCount();
848
849 // Detect the case where a cursor that was previously invalidated has
850 // been repopulated with new data.
851 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
852 && mOldItemCount == 0 && mItemCount > 0) {
853 AdapterView.this.onRestoreInstanceState(mInstanceState);
854 mInstanceState = null;
855 } else {
856 rememberSyncState();
857 }
858 checkFocus();
859 requestLayout();
860 }
861
862 @Override
863 public void onInvalidated() {
864 mDataChanged = true;
865
866 if (AdapterView.this.getAdapter().hasStableIds()) {
867 // Remember the current state for the case where our hosting activity is being
868 // stopped and later restarted
869 mInstanceState = AdapterView.this.onSaveInstanceState();
870 }
871
872 // Data is invalid so we should reset our state
873 mOldItemCount = mItemCount;
874 mItemCount = 0;
875 mSelectedPosition = INVALID_POSITION;
876 mSelectedRowId = INVALID_ROW_ID;
877 mNextSelectedPosition = INVALID_POSITION;
878 mNextSelectedRowId = INVALID_ROW_ID;
879 mNeedSync = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880
881 checkFocus();
882 requestLayout();
883 }
884
885 public void clearSavedState() {
886 mInstanceState = null;
887 }
888 }
889
Adam Powell9c17a4c12010-08-30 18:04:15 -0700890 @Override
891 protected void onDetachedFromWindow() {
892 super.onDetachedFromWindow();
893 removeCallbacks(mSelectionNotifier);
894 }
895
896 private class SelectionNotifier implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800897 public void run() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700898 mPendingSelectionNotifier = null;
899
Svet Ganov99a82432014-10-24 16:27:38 -0700900 if (mDataChanged && getViewRootImpl() != null
901 && getViewRootImpl().isLayoutRequested()) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700902 // Data has changed between when this SelectionNotifier was
903 // posted and now. Postpone the notification until the next
904 // layout is complete and we run checkSelectionChanged().
Adam Powell9c17a4c12010-08-30 18:04:15 -0700905 if (getAdapter() != null) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700906 mPendingSelectionNotifier = this;
Adam Powell9c17a4c12010-08-30 18:04:15 -0700907 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800908 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700909 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910 }
911 }
912 }
913
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100914 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 void selectionChanged() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700916 // We're about to post or run the selection notifier, so we don't need
917 // a pending notifier.
918 mPendingSelectionNotifier = null;
919
Svetoslav Ganov42138042012-03-20 11:51:39 -0700920 if (mOnItemSelectedListener != null
921 || AccessibilityManager.getInstance(mContext).isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922 if (mInLayout || mBlockLayoutRequests) {
923 // If we are in a layout traversal, defer notification
924 // by posting. This ensures that the view tree is
Alan Viveretteec8e7202014-10-06 15:33:24 -0700925 // in a consistent state and is able to accommodate
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 // new layout or invalidate requests.
927 if (mSelectionNotifier == null) {
928 mSelectionNotifier = new SelectionNotifier();
Alan Viveretteec8e7202014-10-06 15:33:24 -0700929 } else {
930 removeCallbacks(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 }
Adam Powell9c17a4c12010-08-30 18:04:15 -0700932 post(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700934 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935 }
936 }
Felipe Leme640f30a2017-03-06 15:44:06 -0800937 // Always notify AutoFillManager - it will return right away if autofill is disabled.
938 final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
Felipe Lemed09ccb82017-02-22 15:02:03 -0800939 if (afm != null) {
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700940 afm.notifyValueChanged(this);
Felipe Lemed09ccb82017-02-22 15:02:03 -0800941 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800942 }
943
Alan Viveretteec8e7202014-10-06 15:33:24 -0700944 private void dispatchOnItemSelected() {
945 fireOnSelected();
946 performAccessibilityActionsOnSelected();
947 }
948
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 private void fireOnSelected() {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700950 if (mOnItemSelectedListener == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800951 return;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700952 }
953 final int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 if (selection >= 0) {
955 View v = getSelectedView();
956 mOnItemSelectedListener.onItemSelected(this, v, selection,
957 getAdapter().getItemId(selection));
958 } else {
959 mOnItemSelectedListener.onNothingSelected(this);
960 }
961 }
962
Svetoslav Ganov42138042012-03-20 11:51:39 -0700963 private void performAccessibilityActionsOnSelected() {
964 if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
965 return;
966 }
967 final int position = getSelectedItemPosition();
968 if (position >= 0) {
969 // we fire selection events here not in View
970 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
971 }
972 }
973
Alan Viverettea54956a2015-01-07 16:05:02 -0800974 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800976 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700977 View selectedView = getSelectedView();
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700978 if (selectedView != null && selectedView.getVisibility() == VISIBLE
979 && selectedView.dispatchPopulateAccessibilityEvent(event)) {
980 return true;
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700981 }
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700982 return false;
983 }
984
Alan Viverettea54956a2015-01-07 16:05:02 -0800985 /** @hide */
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700986 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800987 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
988 if (super.onRequestSendAccessibilityEventInternal(child, event)) {
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700989 // Add a record for ourselves as well.
990 AccessibilityEvent record = AccessibilityEvent.obtain();
991 onInitializeAccessibilityEvent(record);
992 // Populate with the text of the requesting child.
993 child.dispatchPopulateAccessibilityEvent(record);
994 event.appendRecord(record);
995 return true;
996 }
997 return false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700998 }
Adam Powell3fb3d7c2011-04-22 17:08:55 -0700999
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001000 @Override
1001 public CharSequence getAccessibilityClassName() {
1002 return AdapterView.class.getName();
1003 }
1004
Alan Viverettea54956a2015-01-07 16:05:02 -08001005 /** @hide */
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001006 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001007 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1008 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001009 info.setScrollable(isScrollableForAccessibility());
1010 View selectedView = getSelectedView();
1011 if (selectedView != null) {
1012 info.setEnabled(selectedView.isEnabled());
1013 }
1014 }
1015
Alan Viverettea54956a2015-01-07 16:05:02 -08001016 /** @hide */
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001017 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001018 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
1019 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001020 event.setScrollable(isScrollableForAccessibility());
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001021 View selectedView = getSelectedView();
1022 if (selectedView != null) {
1023 event.setEnabled(selectedView.isEnabled());
1024 }
Svetoslav Ganoveb0c52e2011-10-10 18:15:41 -07001025 event.setCurrentItemIndex(getSelectedItemPosition());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001026 event.setFromIndex(getFirstVisiblePosition());
1027 event.setToIndex(getLastVisiblePosition());
Svetoslav Ganov0e6fa8b2011-10-19 12:38:04 -07001028 event.setItemCount(getCount());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001029 }
1030
1031 private boolean isScrollableForAccessibility() {
Svetoslav Ganov98348512011-10-10 17:18:19 -07001032 T adapter = getAdapter();
1033 if (adapter != null) {
1034 final int itemCount = adapter.getCount();
1035 return itemCount > 0
1036 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
1037 }
1038 return false;
svetoslavganov75986cf2009-05-14 22:28:01 -07001039 }
1040
1041 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 protected boolean canAnimate() {
1043 return super.canAnimate() && mItemCount > 0;
1044 }
1045
1046 void handleDataChanged() {
1047 final int count = mItemCount;
1048 boolean found = false;
1049
1050 if (count > 0) {
1051
1052 int newPos;
1053
1054 // Find the row we are supposed to sync to
1055 if (mNeedSync) {
1056 // Update this first, since setNextSelectedPositionInt inspects
1057 // it
1058 mNeedSync = false;
1059
1060 // See if we can find a position in the new data with the same
1061 // id as the old selection
1062 newPos = findSyncPosition();
1063 if (newPos >= 0) {
1064 // Verify that new selection is selectable
1065 int selectablePos = lookForSelectablePosition(newPos, true);
1066 if (selectablePos == newPos) {
1067 // Same row id is selected
1068 setNextSelectedPositionInt(newPos);
1069 found = true;
1070 }
1071 }
1072 }
1073 if (!found) {
1074 // Try to use the same position if we can't find matching data
1075 newPos = getSelectedItemPosition();
1076
1077 // Pin position to the available range
1078 if (newPos >= count) {
1079 newPos = count - 1;
1080 }
1081 if (newPos < 0) {
1082 newPos = 0;
1083 }
1084
1085 // Make sure we select something selectable -- first look down
1086 int selectablePos = lookForSelectablePosition(newPos, true);
1087 if (selectablePos < 0) {
1088 // Looking down didn't work -- try looking up
1089 selectablePos = lookForSelectablePosition(newPos, false);
1090 }
1091 if (selectablePos >= 0) {
1092 setNextSelectedPositionInt(selectablePos);
1093 checkSelectionChanged();
1094 found = true;
1095 }
1096 }
1097 }
1098 if (!found) {
1099 // Nothing is selected
1100 mSelectedPosition = INVALID_POSITION;
1101 mSelectedRowId = INVALID_ROW_ID;
1102 mNextSelectedPosition = INVALID_POSITION;
1103 mNextSelectedRowId = INVALID_ROW_ID;
1104 mNeedSync = false;
1105 checkSelectionChanged();
1106 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001107
Eugene Susla72c510f2018-01-23 21:12:11 +00001108 notifySubtreeAccessibilityStateChangedIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109 }
1110
Alan Viveretteec8e7202014-10-06 15:33:24 -07001111 /**
1112 * Called after layout to determine whether the selection position needs to
1113 * be updated. Also used to fire any pending selection events.
1114 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 void checkSelectionChanged() {
1116 if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
1117 selectionChanged();
1118 mOldSelectedPosition = mSelectedPosition;
1119 mOldSelectedRowId = mSelectedRowId;
1120 }
Alan Viveretteec8e7202014-10-06 15:33:24 -07001121
1122 // If we have a pending selection notification -- and we won't if we
1123 // just fired one in selectionChanged() -- run it now.
1124 if (mPendingSelectionNotifier != null) {
1125 mPendingSelectionNotifier.run();
1126 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127 }
1128
1129 /**
1130 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1131 * and then alternates between moving up and moving down until 1) we find the right position, or
1132 * 2) we run out of time, or 3) we have looked at every position
1133 *
1134 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1135 * be found
1136 */
1137 int findSyncPosition() {
1138 int count = mItemCount;
1139
1140 if (count == 0) {
1141 return INVALID_POSITION;
1142 }
1143
1144 long idToMatch = mSyncRowId;
1145 int seed = mSyncPosition;
1146
1147 // If there isn't a selection don't hunt for it
1148 if (idToMatch == INVALID_ROW_ID) {
1149 return INVALID_POSITION;
1150 }
1151
1152 // Pin seed to reasonable values
1153 seed = Math.max(0, seed);
1154 seed = Math.min(count - 1, seed);
1155
1156 long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
1157
1158 long rowId;
1159
1160 // first position scanned so far
1161 int first = seed;
1162
1163 // last position scanned so far
1164 int last = seed;
1165
1166 // True if we should move down on the next iteration
1167 boolean next = false;
1168
1169 // True when we have looked at the first item in the data
1170 boolean hitFirst;
1171
1172 // True when we have looked at the last item in the data
1173 boolean hitLast;
1174
1175 // Get the item ID locally (instead of getItemIdAtPosition), so
1176 // we need the adapter
1177 T adapter = getAdapter();
1178 if (adapter == null) {
1179 return INVALID_POSITION;
1180 }
1181
1182 while (SystemClock.uptimeMillis() <= endTime) {
1183 rowId = adapter.getItemId(seed);
1184 if (rowId == idToMatch) {
1185 // Found it!
1186 return seed;
1187 }
1188
1189 hitLast = last == count - 1;
1190 hitFirst = first == 0;
1191
1192 if (hitLast && hitFirst) {
1193 // Looked at everything
1194 break;
1195 }
1196
1197 if (hitFirst || (next && !hitLast)) {
1198 // Either we hit the top, or we are trying to move down
1199 last++;
1200 seed = last;
1201 // Try going up next time
1202 next = false;
1203 } else if (hitLast || (!next && !hitFirst)) {
1204 // Either we hit the bottom, or we are trying to move up
1205 first--;
1206 seed = first;
1207 // Try going down next time
1208 next = true;
1209 }
1210
1211 }
1212
1213 return INVALID_POSITION;
1214 }
1215
1216 /**
1217 * Find a position that can be selected (i.e., is not a separator).
1218 *
1219 * @param position The starting position to look at.
1220 * @param lookDown Whether to look down for other positions.
1221 * @return The next selectable position starting at position and then searching either up or
1222 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1223 */
1224 int lookForSelectablePosition(int position, boolean lookDown) {
1225 return position;
1226 }
1227
1228 /**
1229 * Utility to keep mSelectedPosition and mSelectedRowId in sync
1230 * @param position Our current position
1231 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +01001232 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 void setSelectedPositionInt(int position) {
1234 mSelectedPosition = position;
1235 mSelectedRowId = getItemIdAtPosition(position);
1236 }
1237
1238 /**
1239 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1240 * @param position Intended value for mSelectedPosition the next time we go
1241 * through layout
1242 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +01001243 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001244 void setNextSelectedPositionInt(int position) {
1245 mNextSelectedPosition = position;
1246 mNextSelectedRowId = getItemIdAtPosition(position);
1247 // If we are trying to sync to the selection, update that too
1248 if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
1249 mSyncPosition = position;
1250 mSyncRowId = mNextSelectedRowId;
1251 }
1252 }
1253
1254 /**
1255 * Remember enough information to restore the screen state when the data has
1256 * changed.
1257 *
1258 */
1259 void rememberSyncState() {
1260 if (getChildCount() > 0) {
1261 mNeedSync = true;
1262 mSyncHeight = mLayoutHeight;
1263 if (mSelectedPosition >= 0) {
1264 // Sync the selection state
1265 View v = getChildAt(mSelectedPosition - mFirstPosition);
1266 mSyncRowId = mNextSelectedRowId;
1267 mSyncPosition = mNextSelectedPosition;
1268 if (v != null) {
1269 mSpecificTop = v.getTop();
1270 }
1271 mSyncMode = SYNC_SELECTED_POSITION;
1272 } else {
1273 // Sync the based on the offset of the first view
1274 View v = getChildAt(0);
1275 T adapter = getAdapter();
1276 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
1277 mSyncRowId = adapter.getItemId(mFirstPosition);
1278 } else {
1279 mSyncRowId = NO_ID;
1280 }
1281 mSyncPosition = mFirstPosition;
1282 if (v != null) {
1283 mSpecificTop = v.getTop();
1284 }
1285 mSyncMode = SYNC_FIRST_POSITION;
1286 }
1287 }
1288 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07001289
1290 /** @hide */
1291 @Override
1292 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
1293 super.encodeProperties(encoder);
1294
1295 encoder.addProperty("scrolling:firstPosition", mFirstPosition);
1296 encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition);
1297 encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId);
1298 encoder.addProperty("list:selectedPosition", mSelectedPosition);
1299 encoder.addProperty("list:itemCount", mItemCount);
1300 }
Felipe Leme7e4c2052017-04-18 09:45:58 -07001301
1302 /**
1303 * {@inheritDoc}
1304 *
1305 * <p>It also sets the autofill options in the structure; when overridden, it should set it as
1306 * well, either explicitly by calling {@link ViewStructure#setAutofillOptions(CharSequence[])}
1307 * or implicitly by calling {@code super.onProvideAutofillStructure(structure, flags)}.
1308 */
1309 @Override
1310 public void onProvideAutofillStructure(ViewStructure structure, int flags) {
1311 super.onProvideAutofillStructure(structure, flags);
1312
1313 final Adapter adapter = getAdapter();
1314 if (adapter == null) return;
1315
1316 final CharSequence[] options = adapter.getAutofillOptions();
1317 if (options != null) {
1318 structure.setAutofillOptions(options);
1319 }
1320 }
Andrew Solovay237c1892017-10-27 15:11:48 -07001321}