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