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