blob: c3bb9a0201d04c01df837f06c043d00cd796163d [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;
Sumir Katariad80525d2019-02-06 16:16:03 -080024import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.os.Parcelable;
26import android.os.SystemClock;
27import android.util.AttributeSet;
28import android.util.SparseArray;
29import android.view.ContextMenu;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070030import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.view.SoundEffectConstants;
svetoslavganov75986cf2009-05-14 22:28:01 -070032import android.view.View;
33import android.view.ViewDebug;
34import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070035import android.view.ViewHierarchyEncoder;
Felipe Leme7e4c2052017-04-18 09:45:58 -070036import android.view.ViewStructure;
svetoslavganov75986cf2009-05-14 22:28:01 -070037import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -070038import android.view.accessibility.AccessibilityManager;
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -070039import android.view.accessibility.AccessibilityNodeInfo;
Felipe Leme640f30a2017-03-06 15:44:06 -080040import android.view.autofill.AutofillManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042/**
43 * An AdapterView is a view whose children are determined by an {@link Adapter}.
44 *
45 * <p>
46 * See {@link ListView}, {@link GridView}, {@link Spinner} and
47 * {@link Gallery} for commonly used subclasses of AdapterView.
Joe Fernandez61fd1e82011-10-26 13:39:11 -070048 *
49 * <div class="special reference">
50 * <h3>Developer Guides</h3>
51 * <p>For more information about using AdapterView, read the
52 * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
53 * developer guide.</p></div>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 */
55public abstract class AdapterView<T extends Adapter> extends ViewGroup {
56
57 /**
58 * The item view type returned by {@link Adapter#getItemViewType(int)} when
59 * the adapter does not want the item's view recycled.
60 */
61 public static final int ITEM_VIEW_TYPE_IGNORE = -1;
62
63 /**
64 * The item view type returned by {@link Adapter#getItemViewType(int)} when
65 * the item is a header or footer.
66 */
67 public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
68
69 /**
70 * The position of the first child displayed
71 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -070072 @ViewDebug.ExportedProperty(category = "scrolling")
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010073 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 int mFirstPosition = 0;
75
76 /**
77 * The offset in pixels from the top of the AdapterView to the top
78 * of the view to select during the next layout.
79 */
80 int mSpecificTop;
81
82 /**
83 * Position from which to start looking for mSyncRowId
84 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010085 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 int mSyncPosition;
87
88 /**
89 * Row id to look for when data has changed
90 */
91 long mSyncRowId = INVALID_ROW_ID;
92
93 /**
94 * Height of the view when mSyncPosition and mSyncRowId where set
95 */
96 long mSyncHeight;
97
98 /**
99 * True if we need to sync to mSyncRowId
100 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100101 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 boolean mNeedSync = false;
103
104 /**
105 * Indicates whether to sync based on the selection or position. Possible
106 * values are {@link #SYNC_SELECTED_POSITION} or
107 * {@link #SYNC_FIRST_POSITION}.
108 */
109 int mSyncMode;
110
111 /**
112 * Our height after the last layout
113 */
114 private int mLayoutHeight;
115
116 /**
117 * Sync based on the selected child
118 */
119 static final int SYNC_SELECTED_POSITION = 0;
120
121 /**
122 * Sync based on the first child displayed
123 */
124 static final int SYNC_FIRST_POSITION = 1;
125
126 /**
127 * Maximum amount of time to spend in {@link #findSyncPosition()}
128 */
129 static final int SYNC_MAX_DURATION_MILLIS = 100;
130
131 /**
132 * Indicates that this view is currently being laid out.
133 */
134 boolean mInLayout = false;
135
136 /**
137 * The listener that receives notifications when an item is selected.
138 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100139 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 OnItemSelectedListener mOnItemSelectedListener;
141
142 /**
143 * The listener that receives notifications when an item is clicked.
144 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100145 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 OnItemClickListener mOnItemClickListener;
147
148 /**
149 * The listener that receives notifications when an item is long clicked.
150 */
151 OnItemLongClickListener mOnItemLongClickListener;
152
153 /**
154 * True if the data has changed since the last layout
155 */
Sumir Katariad80525d2019-02-06 16:16:03 -0800156 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768524)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 boolean mDataChanged;
158
159 /**
160 * The position within the adapter's data set of the item to select
161 * during the next layout.
162 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700163 @ViewDebug.ExportedProperty(category = "list")
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100164 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 int mNextSelectedPosition = INVALID_POSITION;
166
167 /**
168 * The item id of the item to select during the next layout.
169 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100170 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 long mNextSelectedRowId = INVALID_ROW_ID;
172
173 /**
174 * The position within the adapter's data set of the currently selected item.
175 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700176 @ViewDebug.ExportedProperty(category = "list")
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100177 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 int mSelectedPosition = INVALID_POSITION;
179
180 /**
181 * The item id of the currently selected item.
182 */
183 long mSelectedRowId = INVALID_ROW_ID;
184
185 /**
186 * View to show if there are no items to show.
187 */
Mihai Predab6af5332009-04-28 14:21:57 +0200188 private View mEmptyView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189
190 /**
191 * The number of items in the current adapter.
192 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700193 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 int mItemCount;
195
196 /**
Chet Haase5e25c2c2010-09-16 11:15:56 -0700197 * The number of items in the adapter before a data changed event occurred.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 */
199 int mOldItemCount;
200
201 /**
202 * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
203 * number of items in the current adapter.
204 */
205 public static final int INVALID_POSITION = -1;
206
207 /**
208 * Represents an empty or invalid row id
209 */
210 public static final long INVALID_ROW_ID = Long.MIN_VALUE;
211
212 /**
213 * The last selected position we used when notifying
214 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100215 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 int mOldSelectedPosition = INVALID_POSITION;
Andrew Solovay237c1892017-10-27 15:11:48 -0700217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 /**
219 * The id of the last selected position we used when notifying
220 */
221 long mOldSelectedRowId = INVALID_ROW_ID;
222
223 /**
224 * Indicates what focusable state is requested when calling setFocusable().
225 * In addition to this, this view has other criteria for actually
226 * determining the focusable state (such as whether its empty or the text
227 * filter is shown).
228 *
229 * @see #setFocusable(boolean)
230 * @see #checkFocus()
231 */
Evan Roskyb8372c02017-04-05 15:07:31 -0700232 private int mDesiredFocusableState = FOCUSABLE_AUTO;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 private boolean mDesiredFocusableInTouchModeState;
234
Alan Viveretteec8e7202014-10-06 15:33:24 -0700235 /** Lazily-constructed runnable for dispatching selection events. */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 private SelectionNotifier mSelectionNotifier;
Alan Viveretteec8e7202014-10-06 15:33:24 -0700237
238 /** Selection notifier that's waiting for the next layout pass. */
239 private SelectionNotifier mPendingSelectionNotifier;
240
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 /**
242 * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
243 * This is used to layout the children during a layout pass.
244 */
245 boolean mBlockLayoutRequests = false;
246
247 public AdapterView(Context context) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700248 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 }
250
251 public AdapterView(Context context, AttributeSet attrs) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700252 this(context, attrs, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 }
254
Alan Viverette617feb92013-09-09 18:09:13 -0700255 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
256 this(context, attrs, defStyleAttr, 0);
257 }
258
259 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
260 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700261
262 // If not explicitly specified this view is important for accessibility.
263 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
264 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
265 }
Evan Roskyb8372c02017-04-05 15:07:31 -0700266
267 mDesiredFocusableState = getFocusable();
268 if (mDesiredFocusableState == FOCUSABLE_AUTO) {
269 // Starts off without an adapter, so NOT_FOCUSABLE by default.
270 super.setFocusable(NOT_FOCUSABLE);
271 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 }
273
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 /**
275 * Interface definition for a callback to be invoked when an item in this
276 * AdapterView has been clicked.
277 */
278 public interface OnItemClickListener {
279
280 /**
281 * Callback method to be invoked when an item in this AdapterView has
282 * been clicked.
283 * <p>
284 * Implementers can call getItemAtPosition(position) if they need
285 * to access the data associated with the selected item.
286 *
287 * @param parent The AdapterView where the click happened.
288 * @param view The view within the AdapterView that was clicked (this
289 * will be a view provided by the adapter)
290 * @param position The position of the view in the adapter.
291 * @param id The row id of the item that was clicked.
292 */
293 void onItemClick(AdapterView<?> parent, View view, int position, long id);
294 }
295
296 /**
297 * Register a callback to be invoked when an item in this AdapterView has
298 * been clicked.
299 *
300 * @param listener The callback that will be invoked.
301 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800302 public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 mOnItemClickListener = listener;
304 }
305
306 /**
307 * @return The callback to be invoked with an item in this AdapterView has
Shrijana Ghimirefb53dec2018-06-11 17:14:13 -0700308 * been clicked, or null if no callback has been set.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800310 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 public final OnItemClickListener getOnItemClickListener() {
312 return mOnItemClickListener;
313 }
314
315 /**
Alan Viverettefed60952013-09-17 13:00:49 -0700316 * Call the OnItemClickListener, if it is defined. Performs all normal
317 * actions associated with clicking: reporting accessibility event, playing
318 * a sound, etc.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 *
320 * @param view The view within the AdapterView that was clicked.
321 * @param position The position of the view in the adapter.
322 * @param id The row id of the item that was clicked.
323 * @return True if there was an assigned OnItemClickListener that was
324 * called, false otherwise is returned.
325 */
326 public boolean performItemClick(View view, int position, long id) {
Alan Viverette376c32f2015-06-01 16:41:42 -0700327 final boolean result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 if (mOnItemClickListener != null) {
329 playSoundEffect(SoundEffectConstants.CLICK);
Alan Viverette996a6382014-09-02 16:35:45 -0700330 mOnItemClickListener.onItemClick(this, view, position, id);
Alan Viverette376c32f2015-06-01 16:41:42 -0700331 result = true;
332 } else {
333 result = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 }
335
Alan Viverette376c32f2015-06-01 16:41:42 -0700336 if (view != null) {
337 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
338 }
339 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 }
341
342 /**
343 * Interface definition for a callback to be invoked when an item in this
344 * view has been clicked and held.
345 */
346 public interface OnItemLongClickListener {
347 /**
348 * Callback method to be invoked when an item in this view has been
349 * clicked and held.
350 *
351 * Implementers can call getItemAtPosition(position) if they need to access
352 * the data associated with the selected item.
353 *
354 * @param parent The AbsListView where the click happened
355 * @param view The view within the AbsListView that was clicked
356 * @param position The position of the view in the list
357 * @param id The row id of the item that was clicked
358 *
359 * @return true if the callback consumed the long click, false otherwise
360 */
361 boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
362 }
363
364
365 /**
366 * Register a callback to be invoked when an item in this AdapterView has
367 * been clicked and held
368 *
369 * @param listener The callback that will run
370 */
371 public void setOnItemLongClickListener(OnItemLongClickListener listener) {
372 if (!isLongClickable()) {
373 setLongClickable(true);
374 }
375 mOnItemLongClickListener = listener;
376 }
377
378 /**
379 * @return The callback to be invoked with an item in this AdapterView has
Shrijana Ghimirefb53dec2018-06-11 17:14:13 -0700380 * been clicked and held, or null if no callback has been set.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 */
382 public final OnItemLongClickListener getOnItemLongClickListener() {
383 return mOnItemLongClickListener;
384 }
385
386 /**
387 * Interface definition for a callback to be invoked when
388 * an item in this view has been selected.
389 */
390 public interface OnItemSelectedListener {
391 /**
Romain Guyf18c8dd2011-09-16 16:16:36 -0700392 * <p>Callback method to be invoked when an item in this view has been
393 * selected. This callback is invoked only when the newly selected
394 * position is different from the previously selected position or if
395 * there was no selected item.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 *
Andrew Solovay237c1892017-10-27 15:11:48 -0700397 * Implementers can call getItemAtPosition(position) if they need to access the
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 * data associated with the selected item.
399 *
400 * @param parent The AdapterView where the selection happened
401 * @param view The view within the AdapterView that was clicked
402 * @param position The position of the view in the adapter
403 * @param id The row id of the item that is selected
404 */
405 void onItemSelected(AdapterView<?> parent, View view, int position, long id);
406
407 /**
408 * Callback method to be invoked when the selection disappears from this
409 * view. The selection can disappear for instance when touch is activated
410 * or when the adapter becomes empty.
411 *
412 * @param parent The AdapterView that now contains no selected item.
413 */
414 void onNothingSelected(AdapterView<?> parent);
415 }
416
417
418 /**
419 * Register a callback to be invoked when an item in this AdapterView has
420 * been selected.
421 *
422 * @param listener The callback that will run
423 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800424 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 mOnItemSelectedListener = listener;
426 }
427
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800428 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 public final OnItemSelectedListener getOnItemSelectedListener() {
430 return mOnItemSelectedListener;
431 }
432
433 /**
434 * Extra menu information provided to the
435 * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
436 * callback when a context menu is brought up for this AdapterView.
437 *
438 */
439 public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
440
441 public AdapterContextMenuInfo(View targetView, int position, long id) {
442 this.targetView = targetView;
443 this.position = position;
444 this.id = id;
445 }
446
447 /**
448 * The child view for which the context menu is being displayed. This
449 * will be one of the children of this AdapterView.
450 */
451 public View targetView;
452
453 /**
454 * The position in the adapter for which the context menu is being
455 * displayed.
456 */
457 public int position;
458
459 /**
460 * The row id of the item for which the context menu is being displayed.
461 */
462 public long id;
463 }
464
465 /**
466 * Returns the adapter currently associated with this widget.
467 *
468 * @return The adapter used to provide this view's content.
469 */
470 public abstract T getAdapter();
471
472 /**
473 * Sets the adapter that provides the data and the views to represent the data
474 * in this widget.
475 *
476 * @param adapter The adapter to use to create this view's content.
477 */
478 public abstract void setAdapter(T adapter);
479
480 /**
481 * This method is not supported and throws an UnsupportedOperationException when called.
482 *
483 * @param child Ignored.
484 *
485 * @throws UnsupportedOperationException Every time this method is invoked.
486 */
487 @Override
488 public void addView(View child) {
489 throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
490 }
491
492 /**
493 * This method is not supported and throws an UnsupportedOperationException when called.
494 *
495 * @param child Ignored.
496 * @param index Ignored.
497 *
498 * @throws UnsupportedOperationException Every time this method is invoked.
499 */
500 @Override
501 public void addView(View child, int index) {
502 throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
503 }
504
505 /**
506 * This method is not supported and throws an UnsupportedOperationException when called.
507 *
508 * @param child Ignored.
509 * @param params Ignored.
510 *
511 * @throws UnsupportedOperationException Every time this method is invoked.
512 */
513 @Override
514 public void addView(View child, LayoutParams params) {
515 throw new UnsupportedOperationException("addView(View, LayoutParams) "
516 + "is not supported in AdapterView");
517 }
518
519 /**
520 * This method is not supported and throws an UnsupportedOperationException when called.
521 *
522 * @param child Ignored.
523 * @param index Ignored.
524 * @param params Ignored.
525 *
526 * @throws UnsupportedOperationException Every time this method is invoked.
527 */
528 @Override
529 public void addView(View child, int index, LayoutParams params) {
530 throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
531 + "is not supported in AdapterView");
532 }
533
534 /**
535 * This method is not supported and throws an UnsupportedOperationException when called.
536 *
537 * @param child Ignored.
538 *
539 * @throws UnsupportedOperationException Every time this method is invoked.
540 */
541 @Override
542 public void removeView(View child) {
543 throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
544 }
545
546 /**
547 * This method is not supported and throws an UnsupportedOperationException when called.
548 *
549 * @param index Ignored.
550 *
551 * @throws UnsupportedOperationException Every time this method is invoked.
552 */
553 @Override
554 public void removeViewAt(int index) {
555 throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
556 }
557
558 /**
559 * This method is not supported and throws an UnsupportedOperationException when called.
560 *
561 * @throws UnsupportedOperationException Every time this method is invoked.
562 */
563 @Override
564 public void removeAllViews() {
565 throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
566 }
567
568 @Override
569 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
570 mLayoutHeight = getHeight();
571 }
572
573 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000574 * Return the position of the currently selected item within the adapter's data set
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 *
Alan Viverette31ff78b12015-06-04 17:18:34 +0000576 * @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 -0800577 */
578 @ViewDebug.CapturedViewProperty
579 public int getSelectedItemPosition() {
580 return mNextSelectedPosition;
581 }
582
583 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000584 * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
585 * if nothing is selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 */
587 @ViewDebug.CapturedViewProperty
588 public long getSelectedItemId() {
589 return mNextSelectedRowId;
590 }
591
592 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000593 * @return The view corresponding to the currently selected item, or null
594 * if nothing is selected
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800595 */
596 public abstract View getSelectedView();
597
598 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000599 * @return The data corresponding to the currently selected item, or
600 * null if there is nothing selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 */
602 public Object getSelectedItem() {
Alan Viverette31ff78b12015-06-04 17:18:34 +0000603 T adapter = getAdapter();
604 int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
606 return adapter.getItem(selection);
607 } else {
608 return null;
609 }
610 }
611
612 /**
613 * @return The number of items owned by the Adapter associated with this
614 * AdapterView. (This is the number of data items, which may be
Chet Haase5e25c2c2010-09-16 11:15:56 -0700615 * larger than the number of visible views.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 */
617 @ViewDebug.CapturedViewProperty
618 public int getCount() {
619 return mItemCount;
620 }
621
622 /**
Alan Viverette92539d52015-09-14 10:49:25 -0400623 * Returns the position within the adapter's data set for the view, where
624 * view is a an adapter item or a descendant of an adapter item.
625 * <p>
626 * <strong>Note:</strong> The result of this method only reflects the
627 * position of the data bound to <var>view</var> during the most recent
628 * layout pass. If the adapter's data set has changed without a subsequent
629 * layout pass, the position returned by this method may not match the
630 * current position of the data within the adapter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 *
Alan Viverette92539d52015-09-14 10:49:25 -0400632 * @param view an adapter item, or a descendant of an adapter item. This
633 * must be visible in this AdapterView at the time of the call.
634 * @return the position within the adapter's data set of the view, or
635 * {@link #INVALID_POSITION} if the view does not correspond to a
636 * list item (or it is not currently visible)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 */
638 public int getPositionForView(View view) {
639 View listItem = view;
640 try {
641 View v;
Alan Viverette898c7042015-08-26 15:21:39 -0400642 while ((v = (View) listItem.getParent()) != null && !v.equals(this)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 listItem = v;
644 }
645 } catch (ClassCastException e) {
646 // We made it up to the window without find this list view
647 return INVALID_POSITION;
648 }
649
Alan Viverette898c7042015-08-26 15:21:39 -0400650 if (listItem != null) {
651 // Search the children for the list item
652 final int childCount = getChildCount();
653 for (int i = 0; i < childCount; i++) {
654 if (getChildAt(i).equals(listItem)) {
655 return mFirstPosition + i;
656 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657 }
658 }
659
660 // Child not found!
661 return INVALID_POSITION;
662 }
663
664 /**
665 * Returns the position within the adapter's data set for the first item
666 * displayed on screen.
667 *
668 * @return The position within the adapter's data set
669 */
670 public int getFirstVisiblePosition() {
671 return mFirstPosition;
672 }
673
674 /**
675 * Returns the position within the adapter's data set for the last item
676 * displayed on screen.
677 *
678 * @return The position within the adapter's data set
679 */
680 public int getLastVisiblePosition() {
681 return mFirstPosition + getChildCount() - 1;
682 }
683
684 /**
svetoslavganov75986cf2009-05-14 22:28:01 -0700685 * Sets the currently selected item. To support accessibility subclasses that
koprivadebd4ee2018-09-13 10:59:46 -0700686 * override this method must invoke the overridden super method first.
svetoslavganov75986cf2009-05-14 22:28:01 -0700687 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 * @param position Index (starting at 0) of the data item to be selected.
689 */
690 public abstract void setSelection(int position);
691
692 /**
693 * Sets the view to show if the adapter is empty
694 */
Adam Cohen1480fdd2010-08-25 17:24:53 -0700695 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 public void setEmptyView(View emptyView) {
697 mEmptyView = emptyView;
698
Svetoslav Ganov42138042012-03-20 11:51:39 -0700699 // If not explicitly specified this view is important for accessibility.
Mindy Pereiraf9373132012-04-16 08:58:53 -0700700 if (emptyView != null
701 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700702 emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
703 }
704
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705 final T adapter = getAdapter();
706 final boolean empty = ((adapter == null) || adapter.isEmpty());
707 updateEmptyStatus(empty);
708 }
709
710 /**
711 * When the current adapter is empty, the AdapterView can display a special view
Mark Doliner9525f2a2014-01-02 11:17:47 -0800712 * 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 -0800713 * that no data is available in this AdapterView.
714 *
715 * @return The view to show if the adapter is empty.
716 */
717 public View getEmptyView() {
718 return mEmptyView;
719 }
720
721 /**
722 * Indicates whether this view is in filter mode. Filter mode can for instance
723 * be enabled by a user when typing on the keyboard.
724 *
725 * @return True if the view is in filter mode, false otherwise.
726 */
727 boolean isInFilterMode() {
728 return false;
729 }
730
731 @Override
Evan Roskyb8372c02017-04-05 15:07:31 -0700732 public void setFocusable(@Focusable int focusable) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 final T adapter = getAdapter();
734 final boolean empty = adapter == null || adapter.getCount() == 0;
735
736 mDesiredFocusableState = focusable;
Evan Roskyb8372c02017-04-05 15:07:31 -0700737 if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 mDesiredFocusableInTouchModeState = false;
739 }
740
Evan Roskyb8372c02017-04-05 15:07:31 -0700741 super.setFocusable((!empty || isInFilterMode()) ? focusable : NOT_FOCUSABLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 }
743
744 @Override
745 public void setFocusableInTouchMode(boolean focusable) {
746 final T adapter = getAdapter();
747 final boolean empty = adapter == null || adapter.getCount() == 0;
748
749 mDesiredFocusableInTouchModeState = focusable;
750 if (focusable) {
Evan Roskyb8372c02017-04-05 15:07:31 -0700751 mDesiredFocusableState = FOCUSABLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800752 }
753
754 super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
755 }
756
757 void checkFocus() {
758 final T adapter = getAdapter();
759 final boolean empty = adapter == null || adapter.getCount() == 0;
760 final boolean focusable = !empty || isInFilterMode();
761 // The order in which we set focusable in touch mode/focusable may matter
762 // for the client, see View.setFocusableInTouchMode() comments for more
763 // details
764 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
Evan Roskyb8372c02017-04-05 15:07:31 -0700765 super.setFocusable(focusable ? mDesiredFocusableState : NOT_FOCUSABLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 if (mEmptyView != null) {
767 updateEmptyStatus((adapter == null) || adapter.isEmpty());
768 }
769 }
770
771 /**
772 * Update the status of the list based on the empty parameter. If empty is true and
773 * we have an empty view, display it. In all the other cases, make sure that the listview
774 * is VISIBLE and that the empty view is GONE (if it's not null).
775 */
776 private void updateEmptyStatus(boolean empty) {
777 if (isInFilterMode()) {
778 empty = false;
779 }
780
781 if (empty) {
782 if (mEmptyView != null) {
783 mEmptyView.setVisibility(View.VISIBLE);
784 setVisibility(View.GONE);
785 } else {
786 // If the caller just removed our empty view, make sure the list view is visible
787 setVisibility(View.VISIBLE);
788 }
789
790 // We are now GONE, so pending layouts will not be dispatched.
791 // Force one here to make sure that the state of the list matches
792 // the state of the adapter.
Andrew Solovay237c1892017-10-27 15:11:48 -0700793 if (mDataChanged) {
794 this.onLayout(false, mLeft, mTop, mRight, mBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 }
796 } else {
797 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
798 setVisibility(View.VISIBLE);
799 }
800 }
801
802 /**
803 * Gets the data associated with the specified position in the list.
804 *
805 * @param position Which data to get
806 * @return The data associated with the specified position in the list
807 */
808 public Object getItemAtPosition(int position) {
809 T adapter = getAdapter();
810 return (adapter == null || position < 0) ? null : adapter.getItem(position);
811 }
812
813 public long getItemIdAtPosition(int position) {
814 T adapter = getAdapter();
815 return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
816 }
817
818 @Override
819 public void setOnClickListener(OnClickListener l) {
820 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
821 + "You probably want setOnItemClickListener instead");
822 }
823
824 /**
825 * Override to prevent freezing of any views created by the adapter.
826 */
827 @Override
828 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
829 dispatchFreezeSelfOnly(container);
830 }
831
832 /**
833 * Override to prevent thawing of any views created by the adapter.
834 */
835 @Override
836 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
837 dispatchThawSelfOnly(container);
838 }
839
840 class AdapterDataSetObserver extends DataSetObserver {
841
842 private Parcelable mInstanceState = null;
843
844 @Override
845 public void onChanged() {
846 mDataChanged = true;
847 mOldItemCount = mItemCount;
848 mItemCount = getAdapter().getCount();
849
850 // Detect the case where a cursor that was previously invalidated has
851 // been repopulated with new data.
852 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
853 && mOldItemCount == 0 && mItemCount > 0) {
854 AdapterView.this.onRestoreInstanceState(mInstanceState);
855 mInstanceState = null;
856 } else {
857 rememberSyncState();
858 }
859 checkFocus();
860 requestLayout();
861 }
862
863 @Override
864 public void onInvalidated() {
865 mDataChanged = true;
866
867 if (AdapterView.this.getAdapter().hasStableIds()) {
868 // Remember the current state for the case where our hosting activity is being
869 // stopped and later restarted
870 mInstanceState = AdapterView.this.onSaveInstanceState();
871 }
872
873 // Data is invalid so we should reset our state
874 mOldItemCount = mItemCount;
875 mItemCount = 0;
876 mSelectedPosition = INVALID_POSITION;
877 mSelectedRowId = INVALID_ROW_ID;
878 mNextSelectedPosition = INVALID_POSITION;
879 mNextSelectedRowId = INVALID_ROW_ID;
880 mNeedSync = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800881
882 checkFocus();
883 requestLayout();
884 }
885
886 public void clearSavedState() {
887 mInstanceState = null;
888 }
889 }
890
Adam Powell9c17a4c12010-08-30 18:04:15 -0700891 @Override
892 protected void onDetachedFromWindow() {
893 super.onDetachedFromWindow();
894 removeCallbacks(mSelectionNotifier);
895 }
896
897 private class SelectionNotifier implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 public void run() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700899 mPendingSelectionNotifier = null;
900
Svet Ganov99a82432014-10-24 16:27:38 -0700901 if (mDataChanged && getViewRootImpl() != null
902 && getViewRootImpl().isLayoutRequested()) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700903 // Data has changed between when this SelectionNotifier was
904 // posted and now. Postpone the notification until the next
905 // layout is complete and we run checkSelectionChanged().
Adam Powell9c17a4c12010-08-30 18:04:15 -0700906 if (getAdapter() != null) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700907 mPendingSelectionNotifier = this;
Adam Powell9c17a4c12010-08-30 18:04:15 -0700908 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700910 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 }
912 }
913 }
914
Mathew Inwooda85f4eb2018-08-21 16:08:34 +0100915 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 void selectionChanged() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700917 // We're about to post or run the selection notifier, so we don't need
918 // a pending notifier.
919 mPendingSelectionNotifier = null;
920
Svetoslav Ganov42138042012-03-20 11:51:39 -0700921 if (mOnItemSelectedListener != null
922 || AccessibilityManager.getInstance(mContext).isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 if (mInLayout || mBlockLayoutRequests) {
924 // If we are in a layout traversal, defer notification
925 // by posting. This ensures that the view tree is
Alan Viveretteec8e7202014-10-06 15:33:24 -0700926 // in a consistent state and is able to accommodate
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927 // new layout or invalidate requests.
928 if (mSelectionNotifier == null) {
929 mSelectionNotifier = new SelectionNotifier();
Alan Viveretteec8e7202014-10-06 15:33:24 -0700930 } else {
931 removeCallbacks(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 }
Adam Powell9c17a4c12010-08-30 18:04:15 -0700933 post(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700935 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800936 }
937 }
Felipe Leme640f30a2017-03-06 15:44:06 -0800938 // Always notify AutoFillManager - it will return right away if autofill is disabled.
939 final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
Felipe Lemed09ccb82017-02-22 15:02:03 -0800940 if (afm != null) {
Svet Ganov2f8fb1f2017-03-13 00:21:04 -0700941 afm.notifyValueChanged(this);
Felipe Lemed09ccb82017-02-22 15:02:03 -0800942 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943 }
944
Alan Viveretteec8e7202014-10-06 15:33:24 -0700945 private void dispatchOnItemSelected() {
946 fireOnSelected();
947 performAccessibilityActionsOnSelected();
948 }
949
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800950 private void fireOnSelected() {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700951 if (mOnItemSelectedListener == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952 return;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700953 }
954 final int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 if (selection >= 0) {
956 View v = getSelectedView();
957 mOnItemSelectedListener.onItemSelected(this, v, selection,
958 getAdapter().getItemId(selection));
959 } else {
960 mOnItemSelectedListener.onNothingSelected(this);
961 }
962 }
963
Svetoslav Ganov42138042012-03-20 11:51:39 -0700964 private void performAccessibilityActionsOnSelected() {
965 if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
966 return;
967 }
968 final int position = getSelectedItemPosition();
969 if (position >= 0) {
970 // we fire selection events here not in View
971 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
972 }
973 }
974
Alan Viverettea54956a2015-01-07 16:05:02 -0800975 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800976 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800977 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700978 View selectedView = getSelectedView();
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700979 if (selectedView != null && selectedView.getVisibility() == VISIBLE
980 && selectedView.dispatchPopulateAccessibilityEvent(event)) {
981 return true;
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700982 }
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700983 return false;
984 }
985
Alan Viverettea54956a2015-01-07 16:05:02 -0800986 /** @hide */
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700987 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800988 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
989 if (super.onRequestSendAccessibilityEventInternal(child, event)) {
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700990 // Add a record for ourselves as well.
991 AccessibilityEvent record = AccessibilityEvent.obtain();
992 onInitializeAccessibilityEvent(record);
993 // Populate with the text of the requesting child.
994 child.dispatchPopulateAccessibilityEvent(record);
995 event.appendRecord(record);
996 return true;
997 }
998 return false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700999 }
Adam Powell3fb3d7c2011-04-22 17:08:55 -07001000
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001001 @Override
1002 public CharSequence getAccessibilityClassName() {
1003 return AdapterView.class.getName();
1004 }
1005
Alan Viverettea54956a2015-01-07 16:05:02 -08001006 /** @hide */
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001007 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001008 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1009 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001010 info.setScrollable(isScrollableForAccessibility());
1011 View selectedView = getSelectedView();
1012 if (selectedView != null) {
1013 info.setEnabled(selectedView.isEnabled());
1014 }
1015 }
1016
Alan Viverettea54956a2015-01-07 16:05:02 -08001017 /** @hide */
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001018 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001019 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
1020 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001021 event.setScrollable(isScrollableForAccessibility());
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001022 View selectedView = getSelectedView();
1023 if (selectedView != null) {
1024 event.setEnabled(selectedView.isEnabled());
1025 }
Svetoslav Ganoveb0c52e2011-10-10 18:15:41 -07001026 event.setCurrentItemIndex(getSelectedItemPosition());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001027 event.setFromIndex(getFirstVisiblePosition());
1028 event.setToIndex(getLastVisiblePosition());
Svetoslav Ganov0e6fa8b2011-10-19 12:38:04 -07001029 event.setItemCount(getCount());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001030 }
1031
1032 private boolean isScrollableForAccessibility() {
Svetoslav Ganov98348512011-10-10 17:18:19 -07001033 T adapter = getAdapter();
1034 if (adapter != null) {
1035 final int itemCount = adapter.getCount();
1036 return itemCount > 0
1037 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
1038 }
1039 return false;
svetoslavganov75986cf2009-05-14 22:28:01 -07001040 }
1041
1042 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001043 protected boolean canAnimate() {
1044 return super.canAnimate() && mItemCount > 0;
1045 }
1046
1047 void handleDataChanged() {
1048 final int count = mItemCount;
1049 boolean found = false;
1050
1051 if (count > 0) {
1052
1053 int newPos;
1054
1055 // Find the row we are supposed to sync to
1056 if (mNeedSync) {
1057 // Update this first, since setNextSelectedPositionInt inspects
1058 // it
1059 mNeedSync = false;
1060
1061 // See if we can find a position in the new data with the same
1062 // id as the old selection
1063 newPos = findSyncPosition();
1064 if (newPos >= 0) {
1065 // Verify that new selection is selectable
1066 int selectablePos = lookForSelectablePosition(newPos, true);
1067 if (selectablePos == newPos) {
1068 // Same row id is selected
1069 setNextSelectedPositionInt(newPos);
1070 found = true;
1071 }
1072 }
1073 }
1074 if (!found) {
1075 // Try to use the same position if we can't find matching data
1076 newPos = getSelectedItemPosition();
1077
1078 // Pin position to the available range
1079 if (newPos >= count) {
1080 newPos = count - 1;
1081 }
1082 if (newPos < 0) {
1083 newPos = 0;
1084 }
1085
1086 // Make sure we select something selectable -- first look down
1087 int selectablePos = lookForSelectablePosition(newPos, true);
1088 if (selectablePos < 0) {
1089 // Looking down didn't work -- try looking up
1090 selectablePos = lookForSelectablePosition(newPos, false);
1091 }
1092 if (selectablePos >= 0) {
1093 setNextSelectedPositionInt(selectablePos);
1094 checkSelectionChanged();
1095 found = true;
1096 }
1097 }
1098 }
1099 if (!found) {
1100 // Nothing is selected
1101 mSelectedPosition = INVALID_POSITION;
1102 mSelectedRowId = INVALID_ROW_ID;
1103 mNextSelectedPosition = INVALID_POSITION;
1104 mNextSelectedRowId = INVALID_ROW_ID;
1105 mNeedSync = false;
1106 checkSelectionChanged();
1107 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001108
Eugene Susla72c510f2018-01-23 21:12:11 +00001109 notifySubtreeAccessibilityStateChangedIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 }
1111
Alan Viveretteec8e7202014-10-06 15:33:24 -07001112 /**
1113 * Called after layout to determine whether the selection position needs to
1114 * be updated. Also used to fire any pending selection events.
1115 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001116 void checkSelectionChanged() {
1117 if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
1118 selectionChanged();
1119 mOldSelectedPosition = mSelectedPosition;
1120 mOldSelectedRowId = mSelectedRowId;
1121 }
Alan Viveretteec8e7202014-10-06 15:33:24 -07001122
1123 // If we have a pending selection notification -- and we won't if we
1124 // just fired one in selectionChanged() -- run it now.
1125 if (mPendingSelectionNotifier != null) {
1126 mPendingSelectionNotifier.run();
1127 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001128 }
1129
1130 /**
1131 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1132 * and then alternates between moving up and moving down until 1) we find the right position, or
1133 * 2) we run out of time, or 3) we have looked at every position
1134 *
1135 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1136 * be found
1137 */
1138 int findSyncPosition() {
1139 int count = mItemCount;
1140
1141 if (count == 0) {
1142 return INVALID_POSITION;
1143 }
1144
1145 long idToMatch = mSyncRowId;
1146 int seed = mSyncPosition;
1147
1148 // If there isn't a selection don't hunt for it
1149 if (idToMatch == INVALID_ROW_ID) {
1150 return INVALID_POSITION;
1151 }
1152
1153 // Pin seed to reasonable values
1154 seed = Math.max(0, seed);
1155 seed = Math.min(count - 1, seed);
1156
1157 long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
1158
1159 long rowId;
1160
1161 // first position scanned so far
1162 int first = seed;
1163
1164 // last position scanned so far
1165 int last = seed;
1166
1167 // True if we should move down on the next iteration
1168 boolean next = false;
1169
1170 // True when we have looked at the first item in the data
1171 boolean hitFirst;
1172
1173 // True when we have looked at the last item in the data
1174 boolean hitLast;
1175
1176 // Get the item ID locally (instead of getItemIdAtPosition), so
1177 // we need the adapter
1178 T adapter = getAdapter();
1179 if (adapter == null) {
1180 return INVALID_POSITION;
1181 }
1182
1183 while (SystemClock.uptimeMillis() <= endTime) {
1184 rowId = adapter.getItemId(seed);
1185 if (rowId == idToMatch) {
1186 // Found it!
1187 return seed;
1188 }
1189
1190 hitLast = last == count - 1;
1191 hitFirst = first == 0;
1192
1193 if (hitLast && hitFirst) {
1194 // Looked at everything
1195 break;
1196 }
1197
1198 if (hitFirst || (next && !hitLast)) {
1199 // Either we hit the top, or we are trying to move down
1200 last++;
1201 seed = last;
1202 // Try going up next time
1203 next = false;
1204 } else if (hitLast || (!next && !hitFirst)) {
1205 // Either we hit the bottom, or we are trying to move up
1206 first--;
1207 seed = first;
1208 // Try going down next time
1209 next = true;
1210 }
1211
1212 }
1213
1214 return INVALID_POSITION;
1215 }
1216
1217 /**
1218 * Find a position that can be selected (i.e., is not a separator).
1219 *
1220 * @param position The starting position to look at.
1221 * @param lookDown Whether to look down for other positions.
1222 * @return The next selectable position starting at position and then searching either up or
1223 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1224 */
1225 int lookForSelectablePosition(int position, boolean lookDown) {
1226 return position;
1227 }
1228
1229 /**
1230 * Utility to keep mSelectedPosition and mSelectedRowId in sync
1231 * @param position Our current position
1232 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +01001233 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001234 void setSelectedPositionInt(int position) {
1235 mSelectedPosition = position;
1236 mSelectedRowId = getItemIdAtPosition(position);
1237 }
1238
1239 /**
1240 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1241 * @param position Intended value for mSelectedPosition the next time we go
1242 * through layout
1243 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +01001244 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001245 void setNextSelectedPositionInt(int position) {
1246 mNextSelectedPosition = position;
1247 mNextSelectedRowId = getItemIdAtPosition(position);
1248 // If we are trying to sync to the selection, update that too
1249 if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
1250 mSyncPosition = position;
1251 mSyncRowId = mNextSelectedRowId;
1252 }
1253 }
1254
1255 /**
1256 * Remember enough information to restore the screen state when the data has
1257 * changed.
1258 *
1259 */
1260 void rememberSyncState() {
1261 if (getChildCount() > 0) {
1262 mNeedSync = true;
1263 mSyncHeight = mLayoutHeight;
1264 if (mSelectedPosition >= 0) {
1265 // Sync the selection state
1266 View v = getChildAt(mSelectedPosition - mFirstPosition);
1267 mSyncRowId = mNextSelectedRowId;
1268 mSyncPosition = mNextSelectedPosition;
1269 if (v != null) {
1270 mSpecificTop = v.getTop();
1271 }
1272 mSyncMode = SYNC_SELECTED_POSITION;
1273 } else {
1274 // Sync the based on the offset of the first view
1275 View v = getChildAt(0);
1276 T adapter = getAdapter();
1277 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
1278 mSyncRowId = adapter.getItemId(mFirstPosition);
1279 } else {
1280 mSyncRowId = NO_ID;
1281 }
1282 mSyncPosition = mFirstPosition;
1283 if (v != null) {
1284 mSpecificTop = v.getTop();
1285 }
1286 mSyncMode = SYNC_FIRST_POSITION;
1287 }
1288 }
1289 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07001290
1291 /** @hide */
1292 @Override
1293 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
1294 super.encodeProperties(encoder);
1295
1296 encoder.addProperty("scrolling:firstPosition", mFirstPosition);
1297 encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition);
1298 encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId);
1299 encoder.addProperty("list:selectedPosition", mSelectedPosition);
1300 encoder.addProperty("list:itemCount", mItemCount);
1301 }
Felipe Leme7e4c2052017-04-18 09:45:58 -07001302
1303 /**
1304 * {@inheritDoc}
1305 *
1306 * <p>It also sets the autofill options in the structure; when overridden, it should set it as
1307 * well, either explicitly by calling {@link ViewStructure#setAutofillOptions(CharSequence[])}
1308 * or implicitly by calling {@code super.onProvideAutofillStructure(structure, flags)}.
1309 */
1310 @Override
1311 public void onProvideAutofillStructure(ViewStructure structure, int flags) {
1312 super.onProvideAutofillStructure(structure, flags);
Felipe Leme92736c12018-11-13 12:00:59 -08001313 }
Felipe Leme7e4c2052017-04-18 09:45:58 -07001314
Felipe Leme92736c12018-11-13 12:00:59 -08001315 /** @hide */
1316 @Override
1317 protected void onProvideStructure(@NonNull ViewStructure structure,
1318 @ViewStructureType int viewFor, int flags) {
1319 super.onProvideStructure(structure, viewFor, flags);
Felipe Leme7e4c2052017-04-18 09:45:58 -07001320
Felipe Lemeef1c0b32019-04-12 11:53:16 -07001321 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
Felipe Leme92736c12018-11-13 12:00:59 -08001322 final Adapter adapter = getAdapter();
1323 if (adapter == null) return;
1324
1325 final CharSequence[] options = adapter.getAutofillOptions();
1326 if (options != null) {
1327 structure.setAutofillOptions(options);
1328 }
Felipe Leme7e4c2052017-04-18 09:45:58 -07001329 }
1330 }
Andrew Solovay237c1892017-10-27 15:11:48 -07001331}