Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007 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.app.INotificationManager;
     20 import android.app.ITransientNotification;
     21 import android.content.Context;
     22 import android.content.res.Configuration;
     23 import android.content.res.Resources;
     24 import android.graphics.PixelFormat;
     25 import android.os.Handler;
     26 import android.os.RemoteException;
     27 import android.os.ServiceManager;
     28 import android.util.Log;
     29 import android.view.Gravity;
     30 import android.view.LayoutInflater;
     31 import android.view.View;
     32 import android.view.WindowManager;
     33 import android.view.accessibility.AccessibilityEvent;
     34 import android.view.accessibility.AccessibilityManager;
     35 
     36 /**
     37  * A toast is a view containing a quick little message for the user.  The toast class
     38  * helps you create and show those.
     39  * {@more}
     40  *
     41  * <p>
     42  * When the view is shown to the user, appears as a floating view over the
     43  * application.  It will never receive focus.  The user will probably be in the
     44  * middle of typing something else.  The idea is to be as unobtrusive as
     45  * possible, while still showing the user the information you want them to see.
     46  * Two examples are the volume control, and the brief message saying that your
     47  * settings have been saved.
     48  * <p>
     49  * The easiest way to use this class is to call one of the static methods that constructs
     50  * everything you need and returns a new Toast object.
     51  *
     52  * <div class="special reference">
     53  * <h3>Developer Guides</h3>
     54  * <p>For information about creating Toast notifications, read the
     55  * <a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Toast Notifications</a> developer
     56  * guide.</p>
     57  * </div>
     58  */
     59 public class Toast {
     60     static final String TAG = "Toast";
     61     static final boolean localLOGV = false;
     62 
     63     /**
     64      * Show the view or text notification for a short period of time.  This time
     65      * could be user-definable.  This is the default.
     66      * @see #setDuration
     67      */
     68     public static final int LENGTH_SHORT = 0;
     69 
     70     /**
     71      * Show the view or text notification for a long period of time.  This time
     72      * could be user-definable.
     73      * @see #setDuration
     74      */
     75     public static final int LENGTH_LONG = 1;
     76 
     77     final Context mContext;
     78     final TN mTN;
     79     int mDuration;
     80     View mNextView;
     81 
     82     /**
     83      * Construct an empty Toast object.  You must call {@link #setView} before you
     84      * can call {@link #show}.
     85      *
     86      * @param context  The context to use.  Usually your {@link android.app.Application}
     87      *                 or {@link android.app.Activity} object.
     88      */
     89     public Toast(Context context) {
     90         mContext = context;
     91         mTN = new TN();
     92         mTN.mY = context.getResources().getDimensionPixelSize(
     93                 com.android.internal.R.dimen.toast_y_offset);
     94     }
     95 
     96     /**
     97      * Show the view for the specified duration.
     98      */
     99     public void show() {
    100         if (mNextView == null) {
    101             throw new RuntimeException("setView must have been called");
    102         }
    103 
    104         INotificationManager service = getService();
    105         String pkg = mContext.getPackageName();
    106         TN tn = mTN;
    107         tn.mNextView = mNextView;
    108 
    109         try {
    110             service.enqueueToast(pkg, tn, mDuration);
    111         } catch (RemoteException e) {
    112             // Empty
    113         }
    114     }
    115 
    116     /**
    117      * Close the view if it's showing, or don't show it if it isn't showing yet.
    118      * You do not normally have to call this.  Normally view will disappear on its own
    119      * after the appropriate duration.
    120      */
    121     public void cancel() {
    122         mTN.hide();
    123 
    124         try {
    125             getService().cancelToast(mContext.getPackageName(), mTN);
    126         } catch (RemoteException e) {
    127             // Empty
    128         }
    129     }
    130 
    131     /**
    132      * Set the view to show.
    133      * @see #getView
    134      */
    135     public void setView(View view) {
    136         mNextView = view;
    137     }
    138 
    139     /**
    140      * Return the view.
    141      * @see #setView
    142      */
    143     public View getView() {
    144         return mNextView;
    145     }
    146 
    147     /**
    148      * Set how long to show the view for.
    149      * @see #LENGTH_SHORT
    150      * @see #LENGTH_LONG
    151      */
    152     public void setDuration(int duration) {
    153         mDuration = duration;
    154     }
    155 
    156     /**
    157      * Return the duration.
    158      * @see #setDuration
    159      */
    160     public int getDuration() {
    161         return mDuration;
    162     }
    163 
    164     /**
    165      * Set the margins of the view.
    166      *
    167      * @param horizontalMargin The horizontal margin, in percentage of the
    168      *        container width, between the container's edges and the
    169      *        notification
    170      * @param verticalMargin The vertical margin, in percentage of the
    171      *        container height, between the container's edges and the
    172      *        notification
    173      */
    174     public void setMargin(float horizontalMargin, float verticalMargin) {
    175         mTN.mHorizontalMargin = horizontalMargin;
    176         mTN.mVerticalMargin = verticalMargin;
    177     }
    178 
    179     /**
    180      * Return the horizontal margin.
    181      */
    182     public float getHorizontalMargin() {
    183         return mTN.mHorizontalMargin;
    184     }
    185 
    186     /**
    187      * Return the vertical margin.
    188      */
    189     public float getVerticalMargin() {
    190         return mTN.mVerticalMargin;
    191     }
    192 
    193     /**
    194      * Set the location at which the notification should appear on the screen.
    195      * @see android.view.Gravity
    196      * @see #getGravity
    197      */
    198     public void setGravity(int gravity, int xOffset, int yOffset) {
    199         mTN.mGravity = gravity;
    200         mTN.mX = xOffset;
    201         mTN.mY = yOffset;
    202     }
    203 
    204      /**
    205      * Get the location at which the notification should appear on the screen.
    206      * @see android.view.Gravity
    207      * @see #getGravity
    208      */
    209     public int getGravity() {
    210         return mTN.mGravity;
    211     }
    212 
    213     /**
    214      * Return the X offset in pixels to apply to the gravity's location.
    215      */
    216     public int getXOffset() {
    217         return mTN.mX;
    218     }
    219 
    220     /**
    221      * Return the Y offset in pixels to apply to the gravity's location.
    222      */
    223     public int getYOffset() {
    224         return mTN.mY;
    225     }
    226 
    227     /**
    228      * Make a standard toast that just contains a text view.
    229      *
    230      * @param context  The context to use.  Usually your {@link android.app.Application}
    231      *                 or {@link android.app.Activity} object.
    232      * @param text     The text to show.  Can be formatted text.
    233      * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
    234      *                 {@link #LENGTH_LONG}
    235      *
    236      */
    237     public static Toast makeText(Context context, CharSequence text, int duration) {
    238         Toast result = new Toast(context);
    239 
    240         LayoutInflater inflate = (LayoutInflater)
    241                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    242         View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
    243         TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
    244         tv.setText(text);
    245 
    246         result.mNextView = v;
    247         result.mDuration = duration;
    248 
    249         return result;
    250     }
    251 
    252     /**
    253      * Make a standard toast that just contains a text view with the text from a resource.
    254      *
    255      * @param context  The context to use.  Usually your {@link android.app.Application}
    256      *                 or {@link android.app.Activity} object.
    257      * @param resId    The resource id of the string resource to use.  Can be formatted text.
    258      * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
    259      *                 {@link #LENGTH_LONG}
    260      *
    261      * @throws Resources.NotFoundException if the resource can't be found.
    262      */
    263     public static Toast makeText(Context context, int resId, int duration)
    264                                 throws Resources.NotFoundException {
    265         return makeText(context, context.getResources().getText(resId), duration);
    266     }
    267 
    268     /**
    269      * Update the text in a Toast that was previously created using one of the makeText() methods.
    270      * @param resId The new text for the Toast.
    271      */
    272     public void setText(int resId) {
    273         setText(mContext.getText(resId));
    274     }
    275 
    276     /**
    277      * Update the text in a Toast that was previously created using one of the makeText() methods.
    278      * @param s The new text for the Toast.
    279      */
    280     public void setText(CharSequence s) {
    281         if (mNextView == null) {
    282             throw new RuntimeException("This Toast was not created with Toast.makeText()");
    283         }
    284         TextView tv = (TextView) mNextView.findViewById(com.android.internal.R.id.message);
    285         if (tv == null) {
    286             throw new RuntimeException("This Toast was not created with Toast.makeText()");
    287         }
    288         tv.setText(s);
    289     }
    290 
    291     // =======================================================================================
    292     // All the gunk below is the interaction with the Notification Service, which handles
    293     // the proper ordering of these system-wide.
    294     // =======================================================================================
    295 
    296     private static INotificationManager sService;
    297 
    298     static private INotificationManager getService() {
    299         if (sService != null) {
    300             return sService;
    301         }
    302         sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
    303         return sService;
    304     }
    305 
    306     private static class TN extends ITransientNotification.Stub {
    307         final Runnable mShow = new Runnable() {
    308             @Override
    309             public void run() {
    310                 handleShow();
    311             }
    312         };
    313 
    314         final Runnable mHide = new Runnable() {
    315             @Override
    316             public void run() {
    317                 handleHide();
    318                 // Don't do this in handleHide() because it is also invoked by handleShow()
    319                 mNextView = null;
    320             }
    321         };
    322 
    323         private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
    324         final Handler mHandler = new Handler();
    325 
    326         int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    327         int mX, mY;
    328         float mHorizontalMargin;
    329         float mVerticalMargin;
    330 
    331 
    332         View mView;
    333         View mNextView;
    334 
    335         WindowManager mWM;
    336 
    337         TN() {
    338             // XXX This should be changed to use a Dialog, with a Theme.Toast
    339             // defined that sets up the layout params appropriately.
    340             final WindowManager.LayoutParams params = mParams;
    341             params.height = WindowManager.LayoutParams.WRAP_CONTENT;
    342             params.width = WindowManager.LayoutParams.WRAP_CONTENT;
    343             params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    344                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    345                     | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
    346             params.format = PixelFormat.TRANSLUCENT;
    347             params.windowAnimations = com.android.internal.R.style.Animation_Toast;
    348             params.type = WindowManager.LayoutParams.TYPE_TOAST;
    349             params.setTitle("Toast");
    350         }
    351 
    352         /**
    353          * schedule handleShow into the right thread
    354          */
    355         @Override
    356         public void show() {
    357             if (localLOGV) Log.v(TAG, "SHOW: " + this);
    358             mHandler.post(mShow);
    359         }
    360 
    361         /**
    362          * schedule handleHide into the right thread
    363          */
    364         @Override
    365         public void hide() {
    366             if (localLOGV) Log.v(TAG, "HIDE: " + this);
    367             mHandler.post(mHide);
    368         }
    369 
    370         public void handleShow() {
    371             if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
    372                     + " mNextView=" + mNextView);
    373             if (mView != mNextView) {
    374                 // remove the old view if necessary
    375                 handleHide();
    376                 mView = mNextView;
    377                 Context context = mView.getContext().getApplicationContext();
    378                 if (context == null) {
    379                     context = mView.getContext();
    380                 }
    381                 mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    382                 // We can resolve the Gravity here by using the Locale for getting
    383                 // the layout direction
    384                 final Configuration config = mView.getContext().getResources().getConfiguration();
    385                 final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
    386                 mParams.gravity = gravity;
    387                 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
    388                     mParams.horizontalWeight = 1.0f;
    389                 }
    390                 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
    391                     mParams.verticalWeight = 1.0f;
    392                 }
    393                 mParams.x = mX;
    394                 mParams.y = mY;
    395                 mParams.verticalMargin = mVerticalMargin;
    396                 mParams.horizontalMargin = mHorizontalMargin;
    397                 if (mView.getParent() != null) {
    398                     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
    399                     mWM.removeView(mView);
    400                 }
    401                 if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
    402                 mWM.addView(mView, mParams);
    403                 trySendAccessibilityEvent();
    404             }
    405         }
    406 
    407         private void trySendAccessibilityEvent() {
    408             AccessibilityManager accessibilityManager =
    409                     AccessibilityManager.getInstance(mView.getContext());
    410             if (!accessibilityManager.isEnabled()) {
    411                 return;
    412             }
    413             // treat toasts as notifications since they are used to
    414             // announce a transient piece of information to the user
    415             AccessibilityEvent event = AccessibilityEvent.obtain(
    416                     AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
    417             event.setClassName(getClass().getName());
    418             event.setPackageName(mView.getContext().getPackageName());
    419             mView.dispatchPopulateAccessibilityEvent(event);
    420             accessibilityManager.sendAccessibilityEvent(event);
    421         }
    422 
    423         public void handleHide() {
    424             if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
    425             if (mView != null) {
    426                 // note: checking parent() just to make sure the view has
    427                 // been added...  i have seen cases where we get here when
    428                 // the view isn't yet added, so let's try not to crash.
    429                 if (mView.getParent() != null) {
    430                     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
    431                     mWM.removeView(mView);
    432                 }
    433 
    434                 mView = null;
    435             }
    436         }
    437     }
    438 }
    439