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