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