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