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