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