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