1/*
2 * Copyright (C) 2006 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.accessibility.AccessibilityEvent;
29import android.view.accessibility.AccessibilityNodeInfo;
30import android.widget.RemoteViews.RemoteView;
31
32/**
33 * Simple {@link ViewAnimator} that will animate between two or more views
34 * that have been added to it.  Only one child is shown at a time.  If
35 * requested, can automatically flip between each child at a regular interval.
36 *
37 * @attr ref android.R.styleable#ViewFlipper_flipInterval
38 * @attr ref android.R.styleable#ViewFlipper_autoStart
39 */
40@RemoteView
41public class ViewFlipper extends ViewAnimator {
42    private static final String TAG = "ViewFlipper";
43    private static final boolean LOGD = false;
44
45    private static final int DEFAULT_INTERVAL = 3000;
46
47    private int mFlipInterval = DEFAULT_INTERVAL;
48    private boolean mAutoStart = false;
49
50    private boolean mRunning = false;
51    private boolean mStarted = false;
52    private boolean mVisible = false;
53    private boolean mUserPresent = true;
54
55    public ViewFlipper(Context context) {
56        super(context);
57    }
58
59    public ViewFlipper(Context context, AttributeSet attrs) {
60        super(context, attrs);
61
62        TypedArray a = context.obtainStyledAttributes(attrs,
63                com.android.internal.R.styleable.ViewFlipper);
64        mFlipInterval = a.getInt(
65                com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
66        mAutoStart = a.getBoolean(
67                com.android.internal.R.styleable.ViewFlipper_autoStart, false);
68        a.recycle();
69    }
70
71    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
72        @Override
73        public void onReceive(Context context, Intent intent) {
74            final String action = intent.getAction();
75            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
76                mUserPresent = false;
77                updateRunning();
78            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
79                mUserPresent = true;
80                updateRunning(false);
81            }
82        }
83    };
84
85    @Override
86    protected void onAttachedToWindow() {
87        super.onAttachedToWindow();
88
89        // Listen for broadcasts related to user-presence
90        final IntentFilter filter = new IntentFilter();
91        filter.addAction(Intent.ACTION_SCREEN_OFF);
92        filter.addAction(Intent.ACTION_USER_PRESENT);
93        getContext().registerReceiver(mReceiver, filter, null, mHandler);
94
95        if (mAutoStart) {
96            // Automatically start when requested
97            startFlipping();
98        }
99    }
100
101    @Override
102    protected void onDetachedFromWindow() {
103        super.onDetachedFromWindow();
104        mVisible = false;
105
106        getContext().unregisterReceiver(mReceiver);
107        updateRunning();
108    }
109
110    @Override
111    protected void onWindowVisibilityChanged(int visibility) {
112        super.onWindowVisibilityChanged(visibility);
113        mVisible = visibility == VISIBLE;
114        updateRunning(false);
115    }
116
117    /**
118     * How long to wait before flipping to the next view
119     *
120     * @param milliseconds
121     *            time in milliseconds
122     */
123    @android.view.RemotableViewMethod
124    public void setFlipInterval(int milliseconds) {
125        mFlipInterval = milliseconds;
126    }
127
128    /**
129     * Start a timer to cycle through child views
130     */
131    public void startFlipping() {
132        mStarted = true;
133        updateRunning();
134    }
135
136    /**
137     * No more flips
138     */
139    public void stopFlipping() {
140        mStarted = false;
141        updateRunning();
142    }
143
144    @Override
145    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
146        super.onInitializeAccessibilityEvent(event);
147        event.setClassName(ViewFlipper.class.getName());
148    }
149
150    @Override
151    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
152        super.onInitializeAccessibilityNodeInfo(info);
153        info.setClassName(ViewFlipper.class.getName());
154    }
155
156    /**
157     * Internal method to start or stop dispatching flip {@link Message} based
158     * on {@link #mRunning} and {@link #mVisible} state.
159     */
160    private void updateRunning() {
161        updateRunning(true);
162    }
163
164    /**
165     * Internal method to start or stop dispatching flip {@link Message} based
166     * on {@link #mRunning} and {@link #mVisible} state.
167     *
168     * @param flipNow Determines whether or not to execute the animation now, in
169     *            addition to queuing future flips. If omitted, defaults to
170     *            true.
171     */
172    private void updateRunning(boolean flipNow) {
173        boolean running = mVisible && mStarted && mUserPresent;
174        if (running != mRunning) {
175            if (running) {
176                showOnly(mWhichChild, flipNow);
177                Message msg = mHandler.obtainMessage(FLIP_MSG);
178                mHandler.sendMessageDelayed(msg, mFlipInterval);
179            } else {
180                mHandler.removeMessages(FLIP_MSG);
181            }
182            mRunning = running;
183        }
184        if (LOGD) {
185            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
186                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
187        }
188    }
189
190    /**
191     * Returns true if the child views are flipping.
192     */
193    public boolean isFlipping() {
194        return mStarted;
195    }
196
197    /**
198     * Set if this view automatically calls {@link #startFlipping()} when it
199     * becomes attached to a window.
200     */
201    public void setAutoStart(boolean autoStart) {
202        mAutoStart = autoStart;
203    }
204
205    /**
206     * Returns true if this view automatically calls {@link #startFlipping()}
207     * when it becomes attached to a window.
208     */
209    public boolean isAutoStart() {
210        return mAutoStart;
211    }
212
213    private final int FLIP_MSG = 1;
214
215    private final Handler mHandler = new Handler() {
216        @Override
217        public void handleMessage(Message msg) {
218            if (msg.what == FLIP_MSG) {
219                if (mRunning) {
220                    showNext();
221                    msg = obtainMessage(FLIP_MSG);
222                    sendMessageDelayed(msg, mFlipInterval);
223                }
224            }
225        }
226    };
227}
228