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.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.res.TypedArray;
24import android.os.Handler;
25import android.os.Message;
26import android.util.AttributeSet;
27import android.util.Log;
28import android.view.RemotableViewMethod;
29import android.view.accessibility.AccessibilityEvent;
30import android.view.accessibility.AccessibilityNodeInfo;
31import android.widget.RemoteViews.RemoteView;
32
33/**
34 * Simple {@link ViewAnimator} that will animate between two or more views
35 * that have been added to it.  Only one child is shown at a time.  If
36 * requested, can automatically flip between each child at a regular interval.
37 *
38 * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
39 * @attr ref android.R.styleable#AdapterViewFlipper_autoStart
40 */
41@RemoteView
42public class AdapterViewFlipper extends AdapterViewAnimator {
43    private static final String TAG = "ViewFlipper";
44    private static final boolean LOGD = false;
45
46    private static final int DEFAULT_INTERVAL = 10000;
47
48    private int mFlipInterval = DEFAULT_INTERVAL;
49    private boolean mAutoStart = false;
50
51    private boolean mRunning = false;
52    private boolean mStarted = false;
53    private boolean mVisible = false;
54    private boolean mUserPresent = true;
55    private boolean mAdvancedByHost = false;
56
57    public AdapterViewFlipper(Context context) {
58        super(context);
59    }
60
61    public AdapterViewFlipper(Context context, AttributeSet attrs) {
62        super(context, attrs);
63
64        TypedArray a = context.obtainStyledAttributes(attrs,
65                com.android.internal.R.styleable.AdapterViewFlipper);
66        mFlipInterval = a.getInt(
67                com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL);
68        mAutoStart = a.getBoolean(
69                com.android.internal.R.styleable.AdapterViewFlipper_autoStart, false);
70
71        // A view flipper should cycle through the views
72        mLoopViews = true;
73
74        a.recycle();
75    }
76
77    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
78        @Override
79        public void onReceive(Context context, Intent intent) {
80            final String action = intent.getAction();
81            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
82                mUserPresent = false;
83                updateRunning();
84            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
85                mUserPresent = true;
86                updateRunning(false);
87            }
88        }
89    };
90
91    @Override
92    protected void onAttachedToWindow() {
93        super.onAttachedToWindow();
94
95        // Listen for broadcasts related to user-presence
96        final IntentFilter filter = new IntentFilter();
97        filter.addAction(Intent.ACTION_SCREEN_OFF);
98        filter.addAction(Intent.ACTION_USER_PRESENT);
99        getContext().registerReceiver(mReceiver, filter);
100
101        if (mAutoStart) {
102            // Automatically start when requested
103            startFlipping();
104        }
105    }
106
107    @Override
108    protected void onDetachedFromWindow() {
109        super.onDetachedFromWindow();
110        mVisible = false;
111
112        getContext().unregisterReceiver(mReceiver);
113        updateRunning();
114    }
115
116    @Override
117    protected void onWindowVisibilityChanged(int visibility) {
118        super.onWindowVisibilityChanged(visibility);
119        mVisible = (visibility == VISIBLE);
120        updateRunning(false);
121    }
122
123    @Override
124    public void setAdapter(Adapter adapter) {
125        super.setAdapter(adapter);
126        updateRunning();
127    }
128
129    /**
130     * Returns the flip interval, in milliseconds.
131     *
132     * @return the flip interval in milliseconds
133     *
134     * @see #setFlipInterval(int)
135     *
136     * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
137     */
138    public int getFlipInterval() {
139        return mFlipInterval;
140    }
141
142    /**
143     * How long to wait before flipping to the next view.
144     *
145     * @param flipInterval flip interval in milliseconds
146     *
147     * @see #getFlipInterval()
148     *
149     * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
150     */
151    public void setFlipInterval(int flipInterval) {
152        mFlipInterval = flipInterval;
153    }
154
155    /**
156     * Start a timer to cycle through child views
157     */
158    public void startFlipping() {
159        mStarted = true;
160        updateRunning();
161    }
162
163    /**
164     * No more flips
165     */
166    public void stopFlipping() {
167        mStarted = false;
168        updateRunning();
169    }
170
171    /**
172    * {@inheritDoc}
173    */
174   @Override
175   @RemotableViewMethod
176   public void showNext() {
177       // if the flipper is currently flipping automatically, and showNext() is called
178       // we should we should make sure to reset the timer
179       if (mRunning) {
180           mHandler.removeMessages(FLIP_MSG);
181           Message msg = mHandler.obtainMessage(FLIP_MSG);
182           mHandler.sendMessageDelayed(msg, mFlipInterval);
183       }
184       super.showNext();
185   }
186
187   /**
188    * {@inheritDoc}
189    */
190   @Override
191   @RemotableViewMethod
192   public void showPrevious() {
193       // if the flipper is currently flipping automatically, and showPrevious() is called
194       // we should we should make sure to reset the timer
195       if (mRunning) {
196           mHandler.removeMessages(FLIP_MSG);
197           Message msg = mHandler.obtainMessage(FLIP_MSG);
198           mHandler.sendMessageDelayed(msg, mFlipInterval);
199       }
200       super.showPrevious();
201   }
202
203    /**
204     * Internal method to start or stop dispatching flip {@link Message} based
205     * on {@link #mRunning} and {@link #mVisible} state.
206     */
207    private void updateRunning() {
208        // by default when we update running, we want the
209        // current view to animate in
210        updateRunning(true);
211    }
212
213    /**
214     * Internal method to start or stop dispatching flip {@link Message} based
215     * on {@link #mRunning} and {@link #mVisible} state.
216     *
217     * @param flipNow Determines whether or not to execute the animation now, in
218     *            addition to queuing future flips. If omitted, defaults to
219     *            true.
220     */
221    private void updateRunning(boolean flipNow) {
222        boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent
223                && mAdapter != null;
224        if (running != mRunning) {
225            if (running) {
226                showOnly(mWhichChild, flipNow);
227                Message msg = mHandler.obtainMessage(FLIP_MSG);
228                mHandler.sendMessageDelayed(msg, mFlipInterval);
229            } else {
230                mHandler.removeMessages(FLIP_MSG);
231            }
232            mRunning = running;
233        }
234        if (LOGD) {
235            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
236                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
237        }
238    }
239
240    /**
241     * Returns true if the child views are flipping.
242     */
243    public boolean isFlipping() {
244        return mStarted;
245    }
246
247    /**
248     * Set if this view automatically calls {@link #startFlipping()} when it
249     * becomes attached to a window.
250     */
251    public void setAutoStart(boolean autoStart) {
252        mAutoStart = autoStart;
253    }
254
255    /**
256     * Returns true if this view automatically calls {@link #startFlipping()}
257     * when it becomes attached to a window.
258     */
259    public boolean isAutoStart() {
260        return mAutoStart;
261    }
262
263    private final int FLIP_MSG = 1;
264
265    private final Handler mHandler = new Handler() {
266        @Override
267        public void handleMessage(Message msg) {
268            if (msg.what == FLIP_MSG) {
269                if (mRunning) {
270                    showNext();
271                }
272            }
273        }
274    };
275
276    /**
277     * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
278     * automatically advancing the views of this {@link AdapterViewFlipper} by calling
279     * {@link AdapterViewFlipper#advance()} at some point in the future. This allows
280     * {@link AdapterViewFlipper} to prepare by no longer Advancing its children.
281     */
282    @Override
283    public void fyiWillBeAdvancedByHostKThx() {
284        mAdvancedByHost = true;
285        updateRunning(false);
286    }
287
288    @Override
289    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
290        super.onInitializeAccessibilityEvent(event);
291        event.setClassName(AdapterViewFlipper.class.getName());
292    }
293
294    @Override
295    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
296        super.onInitializeAccessibilityNodeInfo(info);
297        info.setClassName(AdapterViewFlipper.class.getName());
298    }
299}
300