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