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