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        this(context, attrs, 0);
63    }
64
65    public AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr) {
66        this(context, attrs, defStyleAttr, 0);
67    }
68
69    public AdapterViewFlipper(
70            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
71        super(context, attrs, defStyleAttr, defStyleRes);
72
73        final TypedArray a = context.obtainStyledAttributes(attrs,
74                com.android.internal.R.styleable.AdapterViewFlipper, defStyleAttr, defStyleRes);
75        mFlipInterval = a.getInt(
76                com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL);
77        mAutoStart = a.getBoolean(
78                com.android.internal.R.styleable.AdapterViewFlipper_autoStart, false);
79
80        // A view flipper should cycle through the views
81        mLoopViews = true;
82
83        a.recycle();
84    }
85
86    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
87        @Override
88        public void onReceive(Context context, Intent intent) {
89            final String action = intent.getAction();
90            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
91                mUserPresent = false;
92                updateRunning();
93            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
94                mUserPresent = true;
95                updateRunning(false);
96            }
97        }
98    };
99
100    @Override
101    protected void onAttachedToWindow() {
102        super.onAttachedToWindow();
103
104        // Listen for broadcasts related to user-presence
105        final IntentFilter filter = new IntentFilter();
106        filter.addAction(Intent.ACTION_SCREEN_OFF);
107        filter.addAction(Intent.ACTION_USER_PRESENT);
108
109        // OK, this is gross but needed. This class is supported by the
110        // remote views machanism and as a part of that the remote views
111        // can be inflated by a context for another user without the app
112        // having interact users permission - just for loading resources.
113        // For exmaple, when adding widgets from a user profile to the
114        // home screen. Therefore, we register the receiver as the current
115        // user not the one the context is for.
116        getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
117                filter, null, mHandler);
118
119
120        if (mAutoStart) {
121            // Automatically start when requested
122            startFlipping();
123        }
124    }
125
126    @Override
127    protected void onDetachedFromWindow() {
128        super.onDetachedFromWindow();
129        mVisible = false;
130
131        getContext().unregisterReceiver(mReceiver);
132        updateRunning();
133    }
134
135    @Override
136    protected void onWindowVisibilityChanged(int visibility) {
137        super.onWindowVisibilityChanged(visibility);
138        mVisible = (visibility == VISIBLE);
139        updateRunning(false);
140    }
141
142    @Override
143    public void setAdapter(Adapter adapter) {
144        super.setAdapter(adapter);
145        updateRunning();
146    }
147
148    /**
149     * Returns the flip interval, in milliseconds.
150     *
151     * @return the flip interval in milliseconds
152     *
153     * @see #setFlipInterval(int)
154     *
155     * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
156     */
157    public int getFlipInterval() {
158        return mFlipInterval;
159    }
160
161    /**
162     * How long to wait before flipping to the next view.
163     *
164     * @param flipInterval flip interval in milliseconds
165     *
166     * @see #getFlipInterval()
167     *
168     * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
169     */
170    public void setFlipInterval(int flipInterval) {
171        mFlipInterval = flipInterval;
172    }
173
174    /**
175     * Start a timer to cycle through child views
176     */
177    public void startFlipping() {
178        mStarted = true;
179        updateRunning();
180    }
181
182    /**
183     * No more flips
184     */
185    public void stopFlipping() {
186        mStarted = false;
187        updateRunning();
188    }
189
190    /**
191    * {@inheritDoc}
192    */
193   @Override
194   @RemotableViewMethod
195   public void showNext() {
196       // if the flipper is currently flipping automatically, and showNext() is called
197       // we should we should make sure to reset the timer
198       if (mRunning) {
199           mHandler.removeMessages(FLIP_MSG);
200           Message msg = mHandler.obtainMessage(FLIP_MSG);
201           mHandler.sendMessageDelayed(msg, mFlipInterval);
202       }
203       super.showNext();
204   }
205
206   /**
207    * {@inheritDoc}
208    */
209   @Override
210   @RemotableViewMethod
211   public void showPrevious() {
212       // if the flipper is currently flipping automatically, and showPrevious() is called
213       // we should we should make sure to reset the timer
214       if (mRunning) {
215           mHandler.removeMessages(FLIP_MSG);
216           Message msg = mHandler.obtainMessage(FLIP_MSG);
217           mHandler.sendMessageDelayed(msg, mFlipInterval);
218       }
219       super.showPrevious();
220   }
221
222    /**
223     * Internal method to start or stop dispatching flip {@link Message} based
224     * on {@link #mRunning} and {@link #mVisible} state.
225     */
226    private void updateRunning() {
227        // by default when we update running, we want the
228        // current view to animate in
229        updateRunning(true);
230    }
231
232    /**
233     * Internal method to start or stop dispatching flip {@link Message} based
234     * on {@link #mRunning} and {@link #mVisible} state.
235     *
236     * @param flipNow Determines whether or not to execute the animation now, in
237     *            addition to queuing future flips. If omitted, defaults to
238     *            true.
239     */
240    private void updateRunning(boolean flipNow) {
241        boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent
242                && mAdapter != null;
243        if (running != mRunning) {
244            if (running) {
245                showOnly(mWhichChild, flipNow);
246                Message msg = mHandler.obtainMessage(FLIP_MSG);
247                mHandler.sendMessageDelayed(msg, mFlipInterval);
248            } else {
249                mHandler.removeMessages(FLIP_MSG);
250            }
251            mRunning = running;
252        }
253        if (LOGD) {
254            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
255                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
256        }
257    }
258
259    /**
260     * Returns true if the child views are flipping.
261     */
262    public boolean isFlipping() {
263        return mStarted;
264    }
265
266    /**
267     * Set if this view automatically calls {@link #startFlipping()} when it
268     * becomes attached to a window.
269     */
270    public void setAutoStart(boolean autoStart) {
271        mAutoStart = autoStart;
272    }
273
274    /**
275     * Returns true if this view automatically calls {@link #startFlipping()}
276     * when it becomes attached to a window.
277     */
278    public boolean isAutoStart() {
279        return mAutoStart;
280    }
281
282    private final int FLIP_MSG = 1;
283
284    private final Handler mHandler = new Handler() {
285        @Override
286        public void handleMessage(Message msg) {
287            if (msg.what == FLIP_MSG) {
288                if (mRunning) {
289                    showNext();
290                }
291            }
292        }
293    };
294
295    /**
296     * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
297     * automatically advancing the views of this {@link AdapterViewFlipper} by calling
298     * {@link AdapterViewFlipper#advance()} at some point in the future. This allows
299     * {@link AdapterViewFlipper} to prepare by no longer Advancing its children.
300     */
301    @Override
302    public void fyiWillBeAdvancedByHostKThx() {
303        mAdvancedByHost = true;
304        updateRunning(false);
305    }
306
307    @Override
308    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
309        super.onInitializeAccessibilityEvent(event);
310        event.setClassName(AdapterViewFlipper.class.getName());
311    }
312
313    @Override
314    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
315        super.onInitializeAccessibilityNodeInfo(info);
316        info.setClassName(AdapterViewFlipper.class.getName());
317    }
318}
319