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