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