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