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