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 
17 package android.widget;
18 
19 import android.annotation.IntRange;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.res.TypedArray;
26 import android.os.Build;
27 import android.os.Message;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.view.inspector.InspectableProperty;
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#ViewFlipper_flipInterval
39  * @attr ref android.R.styleable#ViewFlipper_autoStart
40  */
41 @RemoteView
42 public class ViewFlipper extends ViewAnimator {
43     private static final String TAG = "ViewFlipper";
44     private static final boolean LOGD = false;
45 
46     private static final int DEFAULT_INTERVAL = 3000;
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     @UnsupportedAppUsage
55     private boolean mUserPresent = true;
56 
ViewFlipper(Context context)57     public ViewFlipper(Context context) {
58         super(context);
59     }
60 
ViewFlipper(Context context, AttributeSet attrs)61     public ViewFlipper(Context context, AttributeSet attrs) {
62         super(context, attrs);
63 
64         TypedArray a = context.obtainStyledAttributes(attrs,
65                 com.android.internal.R.styleable.ViewFlipper);
66         mFlipInterval = a.getInt(
67                 com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
68         mAutoStart = a.getBoolean(
69                 com.android.internal.R.styleable.ViewFlipper_autoStart, false);
70         a.recycle();
71     }
72 
73     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
74         @Override
75         public void onReceive(Context context, Intent intent) {
76             final String action = intent.getAction();
77             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
78                 mUserPresent = false;
79                 updateRunning();
80             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
81                 mUserPresent = true;
82                 updateRunning(false);
83             }
84         }
85     };
86 
87     @Override
onAttachedToWindow()88     protected void onAttachedToWindow() {
89         super.onAttachedToWindow();
90 
91         // Listen for broadcasts related to user-presence
92         final IntentFilter filter = new IntentFilter();
93         filter.addAction(Intent.ACTION_SCREEN_OFF);
94         filter.addAction(Intent.ACTION_USER_PRESENT);
95 
96         // OK, this is gross but needed. This class is supported by the
97         // remote views machanism and as a part of that the remote views
98         // can be inflated by a context for another user without the app
99         // having interact users permission - just for loading resources.
100         // For exmaple, when adding widgets from a user profile to the
101         // home screen. Therefore, we register the receiver as the current
102         // user not the one the context is for.
103         getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
104                 filter, null, getHandler());
105 
106         if (mAutoStart) {
107             // Automatically start when requested
108             startFlipping();
109         }
110     }
111 
112     @Override
onDetachedFromWindow()113     protected void onDetachedFromWindow() {
114         super.onDetachedFromWindow();
115         mVisible = false;
116 
117         getContext().unregisterReceiver(mReceiver);
118         updateRunning();
119     }
120 
121     @Override
onWindowVisibilityChanged(int visibility)122     protected void onWindowVisibilityChanged(int visibility) {
123         super.onWindowVisibilityChanged(visibility);
124         mVisible = visibility == VISIBLE;
125         updateRunning(false);
126     }
127 
128     /**
129      * How long to wait before flipping to the next view
130      *
131      * @param milliseconds
132      *            time in milliseconds
133      */
134     @android.view.RemotableViewMethod
setFlipInterval(@ntRangefrom = 0) int milliseconds)135     public void setFlipInterval(@IntRange(from = 0) int milliseconds) {
136         mFlipInterval = milliseconds;
137     }
138 
139     /**
140      * Get the delay before flipping to the next view.
141      *
142      * @return delay time in milliseconds
143      */
144     @InspectableProperty
145     @IntRange(from = 0)
getFlipInterval()146     public int getFlipInterval() {
147         return mFlipInterval;
148     }
149 
150     /**
151      * Start a timer to cycle through child views
152      */
startFlipping()153     public void startFlipping() {
154         mStarted = true;
155         updateRunning();
156     }
157 
158     /**
159      * No more flips
160      */
stopFlipping()161     public void stopFlipping() {
162         mStarted = false;
163         updateRunning();
164     }
165 
166     @Override
getAccessibilityClassName()167     public CharSequence getAccessibilityClassName() {
168         return ViewFlipper.class.getName();
169     }
170 
171     /**
172      * Internal method to start or stop dispatching flip {@link Message} based
173      * on {@link #mRunning} and {@link #mVisible} state.
174      */
updateRunning()175     private void updateRunning() {
176         updateRunning(true);
177     }
178 
179     /**
180      * Internal method to start or stop dispatching flip {@link Message} based
181      * on {@link #mRunning} and {@link #mVisible} state.
182      *
183      * @param flipNow Determines whether or not to execute the animation now, in
184      *            addition to queuing future flips. If omitted, defaults to
185      *            true.
186      */
187     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
updateRunning(boolean flipNow)188     private void updateRunning(boolean flipNow) {
189         boolean running = mVisible && mStarted && mUserPresent;
190         if (running != mRunning) {
191             if (running) {
192                 showOnly(mWhichChild, flipNow);
193                 postDelayed(mFlipRunnable, mFlipInterval);
194             } else {
195                 removeCallbacks(mFlipRunnable);
196             }
197             mRunning = running;
198         }
199         if (LOGD) {
200             Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
201                     + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
202         }
203     }
204 
205     /**
206      * Returns true if the child views are flipping.
207      */
208     @InspectableProperty(hasAttributeId = false)
isFlipping()209     public boolean isFlipping() {
210         return mStarted;
211     }
212 
213     /**
214      * Set if this view automatically calls {@link #startFlipping()} when it
215      * becomes attached to a window.
216      */
setAutoStart(boolean autoStart)217     public void setAutoStart(boolean autoStart) {
218         mAutoStart = autoStart;
219     }
220 
221     /**
222      * Returns true if this view automatically calls {@link #startFlipping()}
223      * when it becomes attached to a window.
224      */
225     @InspectableProperty
isAutoStart()226     public boolean isAutoStart() {
227         return mAutoStart;
228     }
229 
230     private final Runnable mFlipRunnable = new Runnable() {
231         @Override
232         public void run() {
233             if (mRunning) {
234                 showNext();
235                 postDelayed(mFlipRunnable, mFlipInterval);
236             }
237         }
238     };
239 }
240