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