• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.widget;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.res.TypedArray;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.view.RemotableViewMethod;
29 import android.view.accessibility.AccessibilityEvent;
30 import android.view.accessibility.AccessibilityNodeInfo;
31 import 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
42 public 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 
AdapterViewFlipper(Context context)57     public AdapterViewFlipper(Context context) {
58         super(context);
59     }
60 
AdapterViewFlipper(Context context, AttributeSet attrs)61     public AdapterViewFlipper(Context context, AttributeSet attrs) {
62         this(context, attrs, 0);
63     }
64 
AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr)65     public AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr) {
66         this(context, attrs, defStyleAttr, 0);
67     }
68 
AdapterViewFlipper( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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
onAttachedToWindow()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
onDetachedFromWindow()127     protected void onDetachedFromWindow() {
128         super.onDetachedFromWindow();
129         mVisible = false;
130 
131         getContext().unregisterReceiver(mReceiver);
132         updateRunning();
133     }
134 
135     @Override
onWindowVisibilityChanged(int visibility)136     protected void onWindowVisibilityChanged(int visibility) {
137         super.onWindowVisibilityChanged(visibility);
138         mVisible = (visibility == VISIBLE);
139         updateRunning(false);
140     }
141 
142     @Override
setAdapter(Adapter adapter)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      */
getFlipInterval()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      */
setFlipInterval(int flipInterval)170     public void setFlipInterval(int flipInterval) {
171         mFlipInterval = flipInterval;
172     }
173 
174     /**
175      * Start a timer to cycle through child views
176      */
startFlipping()177     public void startFlipping() {
178         mStarted = true;
179         updateRunning();
180     }
181 
182     /**
183      * No more flips
184      */
stopFlipping()185     public void stopFlipping() {
186         mStarted = false;
187         updateRunning();
188     }
189 
190     /**
191     * {@inheritDoc}
192     */
193    @Override
194    @RemotableViewMethod
showNext()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
showPrevious()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      */
updateRunning()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      */
updateRunning(boolean flipNow)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      */
isFlipping()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      */
setAutoStart(boolean autoStart)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      */
isAutoStart()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
fyiWillBeAdvancedByHostKThx()302     public void fyiWillBeAdvancedByHostKThx() {
303         mAdvancedByHost = true;
304         updateRunning(false);
305     }
306 
307     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)308     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
309         super.onInitializeAccessibilityEvent(event);
310         event.setClassName(AdapterViewFlipper.class.getName());
311     }
312 
313     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)314     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
315         super.onInitializeAccessibilityNodeInfo(info);
316         info.setClassName(AdapterViewFlipper.class.getName());
317     }
318 }
319