1/*
2 * Copyright (C) 2010 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
19import android.animation.AnimatorInflater;
20import android.animation.ObjectAnimator;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.TypedArray;
24import android.os.Handler;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.util.AttributeSet;
28import android.view.MotionEvent;
29import android.view.View;
30import android.view.ViewConfiguration;
31import android.view.ViewGroup;
32import android.widget.RemoteViews.OnClickHandler;
33
34import java.util.ArrayList;
35import java.util.HashMap;
36
37/**
38 * Base class for a {@link AdapterView} that will perform animations
39 * when switching between its views.
40 *
41 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
42 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
43 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
44 * @attr ref android.R.styleable#AdapterViewAnimator_loopViews
45 */
46public abstract class AdapterViewAnimator extends AdapterView<Adapter>
47        implements RemoteViewsAdapter.RemoteAdapterConnectionCallback, Advanceable {
48    private static final String TAG = "RemoteViewAnimator";
49
50    /**
51     * The index of the current child, which appears anywhere from the beginning
52     * to the end of the current set of children, as specified by {@link #mActiveOffset}
53     */
54    int mWhichChild = 0;
55
56    /**
57     * The index of the child to restore after the asynchronous connection from the
58     * RemoteViewsAdapter has been.
59     */
60    private int mRestoreWhichChild = -1;
61
62    /**
63     * Whether or not the first view(s) should be animated in
64     */
65    boolean mAnimateFirstTime = true;
66
67    /**
68     *  Represents where the in the current window of
69     *  views the current <code>mDisplayedChild</code> sits
70     */
71    int mActiveOffset = 0;
72
73    /**
74     * The number of views that the {@link AdapterViewAnimator} keeps as children at any
75     * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
76     */
77    int mMaxNumActiveViews = 1;
78
79    /**
80     * Map of the children of the {@link AdapterViewAnimator}.
81     */
82    HashMap<Integer, ViewAndMetaData> mViewsMap = new HashMap<Integer, ViewAndMetaData>();
83
84    /**
85     * List of views pending removal from the {@link AdapterViewAnimator}
86     */
87    ArrayList<Integer> mPreviousViews;
88
89    /**
90     * The index, relative to the adapter, of the beginning of the window of views
91     */
92    int mCurrentWindowStart = 0;
93
94    /**
95     * The index, relative to the adapter, of the end of the window of views
96     */
97    int mCurrentWindowEnd = -1;
98
99    /**
100     * The same as {@link #mCurrentWindowStart}, except when the we have bounded
101     * {@link #mCurrentWindowStart} to be non-negative
102     */
103    int mCurrentWindowStartUnbounded = 0;
104
105    /**
106     * Listens for data changes from the adapter
107     */
108    AdapterDataSetObserver mDataSetObserver;
109
110    /**
111     * The {@link Adapter} for this {@link AdapterViewAnimator}
112     */
113    Adapter mAdapter;
114
115    /**
116     * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
117     */
118    RemoteViewsAdapter mRemoteViewsAdapter;
119
120    /**
121     * The remote adapter containing the data to be displayed by this view to be set
122     */
123    boolean mDeferNotifyDataSetChanged = false;
124
125    /**
126     * Specifies whether this is the first time the animator is showing views
127     */
128    boolean mFirstTime = true;
129
130    /**
131     * Specifies if the animator should wrap from 0 to the end and vice versa
132     * or have hard boundaries at the beginning and end
133     */
134    boolean mLoopViews = true;
135
136    /**
137     * The width and height of some child, used as a size reference in-case our
138     * dimensions are unspecified by the parent.
139     */
140    int mReferenceChildWidth = -1;
141    int mReferenceChildHeight = -1;
142
143    /**
144     * In and out animations.
145     */
146    ObjectAnimator mInAnimation;
147    ObjectAnimator mOutAnimation;
148
149    /**
150     * Current touch state.
151     */
152    private int mTouchMode = TOUCH_MODE_NONE;
153
154    /**
155     * Private touch states.
156     */
157    static final int TOUCH_MODE_NONE = 0;
158    static final int TOUCH_MODE_DOWN_IN_CURRENT_VIEW = 1;
159    static final int TOUCH_MODE_HANDLED = 2;
160
161    private Runnable mPendingCheckForTap;
162
163    private static final int DEFAULT_ANIMATION_DURATION = 200;
164
165    public AdapterViewAnimator(Context context) {
166        this(context, null);
167    }
168
169    public AdapterViewAnimator(Context context, AttributeSet attrs) {
170        this(context, attrs, 0);
171    }
172
173    public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
174        this(context, attrs, defStyleAttr, 0);
175    }
176
177    public AdapterViewAnimator(
178            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
179        super(context, attrs, defStyleAttr, defStyleRes);
180
181        final TypedArray a = context.obtainStyledAttributes(attrs,
182                com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, defStyleRes);
183        int resource = a.getResourceId(
184                com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
185        if (resource > 0) {
186            setInAnimation(context, resource);
187        } else {
188            setInAnimation(getDefaultInAnimation());
189        }
190
191        resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
192        if (resource > 0) {
193            setOutAnimation(context, resource);
194        } else {
195            setOutAnimation(getDefaultOutAnimation());
196        }
197
198        boolean flag = a.getBoolean(
199                com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
200        setAnimateFirstView(flag);
201
202        mLoopViews = a.getBoolean(
203                com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
204
205        a.recycle();
206
207        initViewAnimator();
208    }
209
210    /**
211     * Initialize this {@link AdapterViewAnimator}
212     */
213    private void initViewAnimator() {
214        mPreviousViews = new ArrayList<Integer>();
215    }
216
217    class ViewAndMetaData {
218        View view;
219        int relativeIndex;
220        int adapterPosition;
221        long itemId;
222
223        ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId) {
224            this.view = view;
225            this.relativeIndex = relativeIndex;
226            this.adapterPosition = adapterPosition;
227            this.itemId = itemId;
228        }
229    }
230
231    /**
232     * This method is used by subclasses to configure the animator to display the
233     * desired number of views, and specify the offset
234     *
235     * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
236     * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild})
237     *        sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
238     *        and {@link #setDisplayedChild(int)} is called with 10, then the effective window will
239     *        be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
240     *        window would instead contain indexes 10, 11 and 12.
241     * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
242     *        we loop back to the end, or do we do nothing
243     */
244     void configureViewAnimator(int numVisibleViews, int activeOffset) {
245        if (activeOffset > numVisibleViews - 1) {
246            // Throw an exception here.
247        }
248        mMaxNumActiveViews = numVisibleViews;
249        mActiveOffset = activeOffset;
250        mPreviousViews.clear();
251        mViewsMap.clear();
252        removeAllViewsInLayout();
253        mCurrentWindowStart = 0;
254        mCurrentWindowEnd = -1;
255    }
256
257    /**
258     * This class should be overridden by subclasses to customize view transitions within
259     * the set of visible views
260     *
261     * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
262     *        in the window
263     * @param toIndex The relative index within the window that the view is going to, -1 if it is
264     *        being removed
265     * @param view The view that is being animated
266     */
267    void transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate) {
268        if (fromIndex == -1) {
269            mInAnimation.setTarget(view);
270            mInAnimation.start();
271        } else if (toIndex == -1) {
272            mOutAnimation.setTarget(view);
273            mOutAnimation.start();
274        }
275    }
276
277    ObjectAnimator getDefaultInAnimation() {
278        ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f);
279        anim.setDuration(DEFAULT_ANIMATION_DURATION);
280        return anim;
281    }
282
283    ObjectAnimator getDefaultOutAnimation() {
284        ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f);
285        anim.setDuration(DEFAULT_ANIMATION_DURATION);
286        return anim;
287    }
288
289    /**
290     * Sets which child view will be displayed.
291     *
292     * @param whichChild the index of the child view to display
293     */
294    @android.view.RemotableViewMethod
295    public void setDisplayedChild(int whichChild) {
296        setDisplayedChild(whichChild, true);
297    }
298
299    private void setDisplayedChild(int whichChild, boolean animate) {
300        if (mAdapter != null) {
301            mWhichChild = whichChild;
302            if (whichChild >= getWindowSize()) {
303                mWhichChild = mLoopViews ? 0 : getWindowSize() - 1;
304            } else if (whichChild < 0) {
305                mWhichChild = mLoopViews ? getWindowSize() - 1 : 0;
306            }
307
308            boolean hasFocus = getFocusedChild() != null;
309            // This will clear old focus if we had it
310            showOnly(mWhichChild, animate);
311            if (hasFocus) {
312                // Try to retake focus if we had it
313                requestFocus(FOCUS_FORWARD);
314            }
315        }
316    }
317
318    /**
319     * To be overridden by subclasses. This method applies a view / index specific
320     * transform to the child view.
321     *
322     * @param child
323     * @param relativeIndex
324     */
325    void applyTransformForChildAtIndex(View child, int relativeIndex) {
326    }
327
328    /**
329     * Returns the index of the currently displayed child view.
330     */
331    public int getDisplayedChild() {
332        return mWhichChild;
333    }
334
335    /**
336     * Manually shows the next child.
337     */
338    public void showNext() {
339        setDisplayedChild(mWhichChild + 1);
340    }
341
342    /**
343     * Manually shows the previous child.
344     */
345    public void showPrevious() {
346        setDisplayedChild(mWhichChild - 1);
347    }
348
349    int modulo(int pos, int size) {
350        if (size > 0) {
351            return (size + (pos % size)) % size;
352        } else {
353            return 0;
354        }
355    }
356
357    /**
358     * Get the view at this index relative to the current window's start
359     *
360     * @param relativeIndex Position relative to the current window's start
361     * @return View at this index, null if the index is outside the bounds
362     */
363    View getViewAtRelativeIndex(int relativeIndex) {
364        if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) {
365            int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize());
366            if (mViewsMap.get(i) != null) {
367                return mViewsMap.get(i).view;
368            }
369        }
370        return null;
371    }
372
373    int getNumActiveViews() {
374        if (mAdapter != null) {
375            return Math.min(getCount() + 1, mMaxNumActiveViews);
376        } else {
377            return mMaxNumActiveViews;
378        }
379    }
380
381    int getWindowSize() {
382        if (mAdapter != null) {
383            int adapterCount = getCount();
384            if (adapterCount <= getNumActiveViews() && mLoopViews) {
385                return adapterCount*mMaxNumActiveViews;
386            } else {
387                return adapterCount;
388            }
389        } else {
390            return 0;
391        }
392    }
393
394    private ViewAndMetaData getMetaDataForChild(View child) {
395        for (ViewAndMetaData vm: mViewsMap.values()) {
396            if (vm.view == child) {
397                return vm;
398            }
399        }
400        return null;
401     }
402
403    LayoutParams createOrReuseLayoutParams(View v) {
404        final LayoutParams currentLp = v.getLayoutParams();
405        if (currentLp != null) {
406            return currentLp;
407        }
408        return new LayoutParams(0, 0);
409    }
410
411    void refreshChildren() {
412        if (mAdapter == null) return;
413        for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
414            int index = modulo(i, getWindowSize());
415
416            int adapterCount = getCount();
417            // get the fresh child from the adapter
418            final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
419
420            if (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
421                updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
422            }
423
424            if (mViewsMap.containsKey(index)) {
425                final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
426                // add the new child to the frame, if it exists
427                if (updatedChild != null) {
428                    // flush out the old child
429                    fl.removeAllViewsInLayout();
430                    fl.addView(updatedChild);
431                }
432            }
433        }
434    }
435
436    /**
437     * This method can be overridden so that subclasses can provide a custom frame in which their
438     * children can live. For example, StackView adds padding to its childrens' frames so as to
439     * accomodate for the highlight effect.
440     *
441     * @return The FrameLayout into which children can be placed.
442     */
443    FrameLayout getFrameForChild() {
444        return new FrameLayout(mContext);
445    }
446
447    /**
448     * Shows only the specified child. The other displays Views exit the screen,
449     * optionally with the with the {@link #getOutAnimation() out animation} and
450     * the specified child enters the screen, optionally with the
451     * {@link #getInAnimation() in animation}.
452     *
453     * @param childIndex The index of the child to be shown.
454     * @param animate Whether or not to use the in and out animations, defaults
455     *            to true.
456     */
457    void showOnly(int childIndex, boolean animate) {
458        if (mAdapter == null) return;
459        final int adapterCount = getCount();
460        if (adapterCount == 0) return;
461
462        for (int i = 0; i < mPreviousViews.size(); i++) {
463            View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
464            mViewsMap.remove(mPreviousViews.get(i));
465            viewToRemove.clearAnimation();
466            if (viewToRemove instanceof ViewGroup) {
467                ViewGroup vg = (ViewGroup) viewToRemove;
468                vg.removeAllViewsInLayout();
469            }
470            // applyTransformForChildAtIndex here just allows for any cleanup
471            // associated with this view that may need to be done by a subclass
472            applyTransformForChildAtIndex(viewToRemove, -1);
473
474            removeViewInLayout(viewToRemove);
475        }
476        mPreviousViews.clear();
477        int newWindowStartUnbounded = childIndex - mActiveOffset;
478        int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1;
479        int newWindowStart = Math.max(0, newWindowStartUnbounded);
480        int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
481
482        if (mLoopViews) {
483            newWindowStart = newWindowStartUnbounded;
484            newWindowEnd = newWindowEndUnbounded;
485        }
486        int rangeStart = modulo(newWindowStart, getWindowSize());
487        int rangeEnd = modulo(newWindowEnd, getWindowSize());
488
489        boolean wrap = false;
490        if (rangeStart > rangeEnd) {
491            wrap = true;
492        }
493
494        // This section clears out any items that are in our active views list
495        // but are outside the effective bounds of our window (this is becomes an issue
496        // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
497        // newWindowEndUnbounded > adapterCount - 1
498        for (Integer index : mViewsMap.keySet()) {
499            boolean remove = false;
500            if (!wrap && (index < rangeStart || index > rangeEnd)) {
501                remove = true;
502            } else if (wrap && (index > rangeEnd && index < rangeStart)) {
503                remove = true;
504            }
505
506            if (remove) {
507                View previousView = mViewsMap.get(index).view;
508                int oldRelativeIndex = mViewsMap.get(index).relativeIndex;
509
510                mPreviousViews.add(index);
511                transformViewForTransition(oldRelativeIndex, -1, previousView, animate);
512            }
513        }
514
515        // If the window has changed
516        if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd &&
517              newWindowStartUnbounded == mCurrentWindowStartUnbounded)) {
518            // Run through the indices in the new range
519            for (int i = newWindowStart; i <= newWindowEnd; i++) {
520
521                int index = modulo(i, getWindowSize());
522                int oldRelativeIndex;
523                if (mViewsMap.containsKey(index)) {
524                    oldRelativeIndex = mViewsMap.get(index).relativeIndex;
525                } else {
526                    oldRelativeIndex = -1;
527                }
528                int newRelativeIndex = i - newWindowStartUnbounded;
529
530                // If this item is in the current window, great, we just need to apply
531                // the transform for it's new relative position in the window, and animate
532                // between it's current and new relative positions
533                boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
534
535                if (inOldRange) {
536                    View view = mViewsMap.get(index).view;
537                    mViewsMap.get(index).relativeIndex = newRelativeIndex;
538                    applyTransformForChildAtIndex(view, newRelativeIndex);
539                    transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate);
540
541                // Otherwise this view is new to the window
542                } else {
543                    // Get the new view from the adapter, add it and apply any transform / animation
544                    final int adapterPosition = modulo(i, adapterCount);
545                    View newView = mAdapter.getView(adapterPosition, null, this);
546                    long itemId = mAdapter.getItemId(adapterPosition);
547
548                    // We wrap the new view in a FrameLayout so as to respect the contract
549                    // with the adapter, that is, that we don't modify this view directly
550                    FrameLayout fl = getFrameForChild();
551
552                    // If the view from the adapter is null, we still keep an empty frame in place
553                    if (newView != null) {
554                       fl.addView(newView);
555                    }
556                    mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex,
557                            adapterPosition, itemId));
558                    addChild(fl);
559                    applyTransformForChildAtIndex(fl, newRelativeIndex);
560                    transformViewForTransition(-1, newRelativeIndex, fl, animate);
561                }
562                mViewsMap.get(index).view.bringToFront();
563            }
564            mCurrentWindowStart = newWindowStart;
565            mCurrentWindowEnd = newWindowEnd;
566            mCurrentWindowStartUnbounded = newWindowStartUnbounded;
567            if (mRemoteViewsAdapter != null) {
568                int adapterStart = modulo(mCurrentWindowStart, adapterCount);
569                int adapterEnd = modulo(mCurrentWindowEnd, adapterCount);
570                mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd);
571            }
572        }
573        requestLayout();
574        invalidate();
575    }
576
577    private void addChild(View child) {
578        addViewInLayout(child, -1, createOrReuseLayoutParams(child));
579
580        // This code is used to obtain a reference width and height of a child in case we need
581        // to decide our own size. TODO: Do we want to update the size of the child that we're
582        // using for reference size? If so, when?
583        if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
584            int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
585            child.measure(measureSpec, measureSpec);
586            mReferenceChildWidth = child.getMeasuredWidth();
587            mReferenceChildHeight = child.getMeasuredHeight();
588        }
589    }
590
591    void showTapFeedback(View v) {
592        v.setPressed(true);
593    }
594
595    void hideTapFeedback(View v) {
596        v.setPressed(false);
597    }
598
599    void cancelHandleClick() {
600        View v = getCurrentView();
601        if (v != null) {
602            hideTapFeedback(v);
603        }
604        mTouchMode = TOUCH_MODE_NONE;
605    }
606
607    final class CheckForTap implements Runnable {
608        public void run() {
609            if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
610                View v = getCurrentView();
611                showTapFeedback(v);
612            }
613        }
614    }
615
616    @Override
617    public boolean onTouchEvent(MotionEvent ev) {
618        int action = ev.getAction();
619        boolean handled = false;
620        switch (action) {
621            case MotionEvent.ACTION_DOWN: {
622                View v = getCurrentView();
623                if (v != null) {
624                    if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
625                        if (mPendingCheckForTap == null) {
626                            mPendingCheckForTap = new CheckForTap();
627                        }
628                        mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW;
629                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
630                    }
631                }
632                break;
633            }
634            case MotionEvent.ACTION_MOVE: break;
635            case MotionEvent.ACTION_POINTER_UP: break;
636            case MotionEvent.ACTION_UP: {
637                if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
638                    final View v = getCurrentView();
639                    final ViewAndMetaData viewData = getMetaDataForChild(v);
640                    if (v != null) {
641                        if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
642                            final Handler handler = getHandler();
643                            if (handler != null) {
644                                handler.removeCallbacks(mPendingCheckForTap);
645                            }
646                            showTapFeedback(v);
647                            postDelayed(new Runnable() {
648                                public void run() {
649                                    hideTapFeedback(v);
650                                    post(new Runnable() {
651                                        public void run() {
652                                            if (viewData != null) {
653                                                performItemClick(v, viewData.adapterPosition,
654                                                        viewData.itemId);
655                                            } else {
656                                                performItemClick(v, 0, 0);
657                                            }
658                                        }
659                                    });
660                                }
661                            }, ViewConfiguration.getPressedStateDuration());
662                            handled = true;
663                        }
664                    }
665                }
666                mTouchMode = TOUCH_MODE_NONE;
667                break;
668            }
669            case MotionEvent.ACTION_CANCEL: {
670                View v = getCurrentView();
671                if (v != null) {
672                    hideTapFeedback(v);
673                }
674                mTouchMode = TOUCH_MODE_NONE;
675            }
676        }
677        return handled;
678    }
679
680    private void measureChildren() {
681        final int count = getChildCount();
682        final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
683        final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom;
684
685        for (int i = 0; i < count; i++) {
686            final View child = getChildAt(i);
687            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
688                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
689        }
690    }
691
692    @Override
693    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
694        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
695        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
696        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
697        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
698
699        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
700
701        // We need to deal with the case where our parent hasn't told us how
702        // big we should be. In this case we try to use the desired size of the first
703        // child added.
704        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
705            heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
706                    mPaddingBottom : 0;
707        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
708            if (haveChildRefSize) {
709                int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom;
710                if (height > heightSpecSize) {
711                    heightSpecSize |= MEASURED_STATE_TOO_SMALL;
712                } else {
713                    heightSpecSize = height;
714                }
715            }
716        }
717
718        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
719            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
720                    mPaddingRight : 0;
721        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
722            if (haveChildRefSize) {
723                int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
724                if (width > widthSpecSize) {
725                    widthSpecSize |= MEASURED_STATE_TOO_SMALL;
726                } else {
727                    widthSpecSize = width;
728                }
729            }
730        }
731
732        setMeasuredDimension(widthSpecSize, heightSpecSize);
733        measureChildren();
734    }
735
736    void checkForAndHandleDataChanged() {
737        boolean dataChanged = mDataChanged;
738        if (dataChanged) {
739            post(new Runnable() {
740                public void run() {
741                    handleDataChanged();
742                    // if the data changes, mWhichChild might be out of the bounds of the adapter
743                    // in this case, we reset mWhichChild to the beginning
744                    if (mWhichChild >= getWindowSize()) {
745                        mWhichChild = 0;
746
747                        showOnly(mWhichChild, false);
748                    } else if (mOldItemCount != getCount()) {
749                        showOnly(mWhichChild, false);
750                    }
751                    refreshChildren();
752                    requestLayout();
753                }
754            });
755        }
756        mDataChanged = false;
757    }
758
759    @Override
760    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
761        checkForAndHandleDataChanged();
762
763        final int childCount = getChildCount();
764        for (int i = 0; i < childCount; i++) {
765            final View child = getChildAt(i);
766
767            int childRight = mPaddingLeft + child.getMeasuredWidth();
768            int childBottom = mPaddingTop + child.getMeasuredHeight();
769
770            child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
771        }
772    }
773
774    static class SavedState extends BaseSavedState {
775        int whichChild;
776
777        /**
778         * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
779         */
780        SavedState(Parcelable superState, int whichChild) {
781            super(superState);
782            this.whichChild = whichChild;
783        }
784
785        /**
786         * Constructor called from {@link #CREATOR}
787         */
788        private SavedState(Parcel in) {
789            super(in);
790            this.whichChild = in.readInt();
791        }
792
793        @Override
794        public void writeToParcel(Parcel out, int flags) {
795            super.writeToParcel(out, flags);
796            out.writeInt(this.whichChild);
797        }
798
799        @Override
800        public String toString() {
801            return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
802        }
803
804        public static final Parcelable.Creator<SavedState> CREATOR
805                = new Parcelable.Creator<SavedState>() {
806            public SavedState createFromParcel(Parcel in) {
807                return new SavedState(in);
808            }
809
810            public SavedState[] newArray(int size) {
811                return new SavedState[size];
812            }
813        };
814    }
815
816    @Override
817    public Parcelable onSaveInstanceState() {
818        Parcelable superState = super.onSaveInstanceState();
819        if (mRemoteViewsAdapter != null) {
820            mRemoteViewsAdapter.saveRemoteViewsCache();
821        }
822        return new SavedState(superState, mWhichChild);
823    }
824
825    @Override
826    public void onRestoreInstanceState(Parcelable state) {
827        SavedState ss = (SavedState) state;
828        super.onRestoreInstanceState(ss.getSuperState());
829
830        // Here we set mWhichChild in addition to setDisplayedChild
831        // We do the former in case mAdapter is null, and hence setDisplayedChild won't
832        // set mWhichChild
833        mWhichChild = ss.whichChild;
834
835        // When using RemoteAdapters, the async connection process can lead to
836        // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
837        // values to restore the list position after we connect, and can skip setting the displayed
838        // child until then.
839        if (mRemoteViewsAdapter != null && mAdapter == null) {
840            mRestoreWhichChild = mWhichChild;
841        } else {
842            setDisplayedChild(mWhichChild, false);
843        }
844    }
845
846    /**
847     * Returns the View corresponding to the currently displayed child.
848     *
849     * @return The View currently displayed.
850     *
851     * @see #getDisplayedChild()
852     */
853    public View getCurrentView() {
854        return getViewAtRelativeIndex(mActiveOffset);
855    }
856
857    /**
858     * Returns the current animation used to animate a View that enters the screen.
859     *
860     * @return An Animation or null if none is set.
861     *
862     * @see #setInAnimation(android.animation.ObjectAnimator)
863     * @see #setInAnimation(android.content.Context, int)
864     */
865    public ObjectAnimator getInAnimation() {
866        return mInAnimation;
867    }
868
869    /**
870     * Specifies the animation used to animate a View that enters the screen.
871     *
872     * @param inAnimation The animation started when a View enters the screen.
873     *
874     * @see #getInAnimation()
875     * @see #setInAnimation(android.content.Context, int)
876     */
877    public void setInAnimation(ObjectAnimator inAnimation) {
878        mInAnimation = inAnimation;
879    }
880
881    /**
882     * Returns the current animation used to animate a View that exits the screen.
883     *
884     * @return An Animation or null if none is set.
885     *
886     * @see #setOutAnimation(android.animation.ObjectAnimator)
887     * @see #setOutAnimation(android.content.Context, int)
888     */
889    public ObjectAnimator getOutAnimation() {
890        return mOutAnimation;
891    }
892
893    /**
894     * Specifies the animation used to animate a View that exit the screen.
895     *
896     * @param outAnimation The animation started when a View exit the screen.
897     *
898     * @see #getOutAnimation()
899     * @see #setOutAnimation(android.content.Context, int)
900     */
901    public void setOutAnimation(ObjectAnimator outAnimation) {
902        mOutAnimation = outAnimation;
903    }
904
905    /**
906     * Specifies the animation used to animate a View that enters the screen.
907     *
908     * @param context The application's environment.
909     * @param resourceID The resource id of the animation.
910     *
911     * @see #getInAnimation()
912     * @see #setInAnimation(android.animation.ObjectAnimator)
913     */
914    public void setInAnimation(Context context, int resourceID) {
915        setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
916    }
917
918    /**
919     * Specifies the animation used to animate a View that exit the screen.
920     *
921     * @param context The application's environment.
922     * @param resourceID The resource id of the animation.
923     *
924     * @see #getOutAnimation()
925     * @see #setOutAnimation(android.animation.ObjectAnimator)
926     */
927    public void setOutAnimation(Context context, int resourceID) {
928        setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
929    }
930
931    /**
932     * Indicates whether the current View should be animated the first time
933     * the ViewAnimation is displayed.
934     *
935     * @param animate True to animate the current View the first time it is displayed,
936     *                false otherwise.
937     */
938    public void setAnimateFirstView(boolean animate) {
939        mAnimateFirstTime = animate;
940    }
941
942    @Override
943    public int getBaseline() {
944        return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
945    }
946
947    @Override
948    public Adapter getAdapter() {
949        return mAdapter;
950    }
951
952    @Override
953    public void setAdapter(Adapter adapter) {
954        if (mAdapter != null && mDataSetObserver != null) {
955            mAdapter.unregisterDataSetObserver(mDataSetObserver);
956        }
957
958        mAdapter = adapter;
959        checkFocus();
960
961        if (mAdapter != null) {
962            mDataSetObserver = new AdapterDataSetObserver();
963            mAdapter.registerDataSetObserver(mDataSetObserver);
964            mItemCount = mAdapter.getCount();
965        }
966        setFocusable(true);
967        mWhichChild = 0;
968        showOnly(mWhichChild, false);
969    }
970
971    /**
972     * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
973     * RemoteViewsService through the specified intent.
974     *
975     * @param intent the intent used to identify the RemoteViewsService for the adapter to
976     *        connect to.
977     */
978    @android.view.RemotableViewMethod(asyncImpl="setRemoteViewsAdapterAsync")
979    public void setRemoteViewsAdapter(Intent intent) {
980        setRemoteViewsAdapter(intent, false);
981    }
982
983    /** @hide **/
984    public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
985        return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
986    }
987
988    /** @hide **/
989    @Override
990    public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
991        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
992        // service handling the specified intent.
993        if (mRemoteViewsAdapter != null) {
994            Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
995            Intent.FilterComparison fcOld = new Intent.FilterComparison(
996                    mRemoteViewsAdapter.getRemoteViewsServiceIntent());
997            if (fcNew.equals(fcOld)) {
998                return;
999            }
1000        }
1001        mDeferNotifyDataSetChanged = false;
1002        // Otherwise, create a new RemoteViewsAdapter for binding
1003        mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
1004        if (mRemoteViewsAdapter.isDataReady()) {
1005            setAdapter(mRemoteViewsAdapter);
1006        }
1007    }
1008
1009    /**
1010     * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
1011     *
1012     * @param handler The OnClickHandler to use when inflating RemoteViews.
1013     *
1014     * @hide
1015     */
1016    public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
1017        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
1018        // service handling the specified intent.
1019        if (mRemoteViewsAdapter != null) {
1020            mRemoteViewsAdapter.setRemoteViewsOnClickHandler(handler);
1021        }
1022    }
1023
1024    @Override
1025    public void setSelection(int position) {
1026        setDisplayedChild(position);
1027    }
1028
1029    @Override
1030    public View getSelectedView() {
1031        return getViewAtRelativeIndex(mActiveOffset);
1032    }
1033
1034    /**
1035     * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
1036     * connected yet.
1037     */
1038    public void deferNotifyDataSetChanged() {
1039        mDeferNotifyDataSetChanged = true;
1040    }
1041
1042    /**
1043     * Called back when the adapter connects to the RemoteViewsService.
1044     */
1045    public boolean onRemoteAdapterConnected() {
1046        if (mRemoteViewsAdapter != mAdapter) {
1047            setAdapter(mRemoteViewsAdapter);
1048
1049            if (mDeferNotifyDataSetChanged) {
1050                mRemoteViewsAdapter.notifyDataSetChanged();
1051                mDeferNotifyDataSetChanged = false;
1052            }
1053
1054            // Restore the previous position (see onRestoreInstanceState)
1055            if (mRestoreWhichChild > -1) {
1056                setDisplayedChild(mRestoreWhichChild, false);
1057                mRestoreWhichChild = -1;
1058            }
1059            return false;
1060        } else if (mRemoteViewsAdapter != null) {
1061            mRemoteViewsAdapter.superNotifyDataSetChanged();
1062            return true;
1063        }
1064        return false;
1065    }
1066
1067    /**
1068     * Called back when the adapter disconnects from the RemoteViewsService.
1069     */
1070    public void onRemoteAdapterDisconnected() {
1071        // If the remote adapter disconnects, we keep it around
1072        // since the currently displayed items are still cached.
1073        // Further, we want the service to eventually reconnect
1074        // when necessary, as triggered by this view requesting
1075        // items from the Adapter.
1076    }
1077
1078    /**
1079     * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when
1080     * it is being used within an app widget.
1081     */
1082    public void advance() {
1083        showNext();
1084    }
1085
1086    /**
1087     * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
1088     * automatically advancing the views of this {@link AdapterViewAnimator} by calling
1089     * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to
1090     * perform any required setup, for example, to stop automatically advancing their children.
1091     */
1092    public void fyiWillBeAdvancedByHostKThx() {
1093    }
1094
1095    @Override
1096    public CharSequence getAccessibilityClassName() {
1097        return AdapterViewAnimator.class.getName();
1098    }
1099}
1100