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