• 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.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      */
Toast(Context context)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      */
show()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      */
cancel()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      */
setView(View view)147     public void setView(View view) {
148         mNextView = view;
149     }
150 
151     /**
152      * Return the view.
153      * @see #setView
154      */
getView()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      */
setDuration(@uration int duration)164     public void setDuration(@Duration int duration) {
165         mDuration = duration;
166         mTN.mDuration = duration;
167     }
168 
169     /**
170      * Return the duration.
171      * @see #setDuration
172      */
173     @Duration
getDuration()174     public int getDuration() {
175         return mDuration;
176     }
177 
178     /**
179      * Set the margins of the view.
180      *
181      * @param horizontalMargin The horizontal margin, in percentage of the
182      *        container width, between the container's edges and the
183      *        notification
184      * @param verticalMargin The vertical margin, in percentage of the
185      *        container height, between the container's edges and the
186      *        notification
187      */
setMargin(float horizontalMargin, float verticalMargin)188     public void setMargin(float horizontalMargin, float verticalMargin) {
189         mTN.mHorizontalMargin = horizontalMargin;
190         mTN.mVerticalMargin = verticalMargin;
191     }
192 
193     /**
194      * Return the horizontal margin.
195      */
getHorizontalMargin()196     public float getHorizontalMargin() {
197         return mTN.mHorizontalMargin;
198     }
199 
200     /**
201      * Return the vertical margin.
202      */
getVerticalMargin()203     public float getVerticalMargin() {
204         return mTN.mVerticalMargin;
205     }
206 
207     /**
208      * Set the location at which the notification should appear on the screen.
209      * @see android.view.Gravity
210      * @see #getGravity
211      */
setGravity(int gravity, int xOffset, int yOffset)212     public void setGravity(int gravity, int xOffset, int yOffset) {
213         mTN.mGravity = gravity;
214         mTN.mX = xOffset;
215         mTN.mY = yOffset;
216     }
217 
218      /**
219      * Get the location at which the notification should appear on the screen.
220      * @see android.view.Gravity
221      * @see #getGravity
222      */
getGravity()223     public int getGravity() {
224         return mTN.mGravity;
225     }
226 
227     /**
228      * Return the X offset in pixels to apply to the gravity's location.
229      */
getXOffset()230     public int getXOffset() {
231         return mTN.mX;
232     }
233 
234     /**
235      * Return the Y offset in pixels to apply to the gravity's location.
236      */
getYOffset()237     public int getYOffset() {
238         return mTN.mY;
239     }
240 
241     /**
242      * Gets the LayoutParams for the Toast window.
243      * @hide
244      */
getWindowParams()245     public WindowManager.LayoutParams getWindowParams() {
246         return mTN.mParams;
247     }
248 
249     /**
250      * Make a standard toast that just contains a text view.
251      *
252      * @param context  The context to use.  Usually your {@link android.app.Application}
253      *                 or {@link android.app.Activity} object.
254      * @param text     The text to show.  Can be formatted text.
255      * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
256      *                 {@link #LENGTH_LONG}
257      *
258      */
makeText(Context context, CharSequence text, @Duration int duration)259     public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
260         Toast result = new Toast(context);
261 
262         LayoutInflater inflate = (LayoutInflater)
263                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
264         View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
265         TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
266         tv.setText(text);
267 
268         result.mNextView = v;
269         result.mDuration = duration;
270 
271         return result;
272     }
273 
274     /**
275      * Make a standard toast that just contains a text view with the text from a resource.
276      *
277      * @param context  The context to use.  Usually your {@link android.app.Application}
278      *                 or {@link android.app.Activity} object.
279      * @param resId    The resource id of the string resource to use.  Can be formatted text.
280      * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
281      *                 {@link #LENGTH_LONG}
282      *
283      * @throws Resources.NotFoundException if the resource can't be found.
284      */
makeText(Context context, @StringRes int resId, @Duration int duration)285     public static Toast makeText(Context context, @StringRes int resId, @Duration int duration)
286                                 throws Resources.NotFoundException {
287         return makeText(context, context.getResources().getText(resId), duration);
288     }
289 
290     /**
291      * Update the text in a Toast that was previously created using one of the makeText() methods.
292      * @param resId The new text for the Toast.
293      */
setText(@tringRes int resId)294     public void setText(@StringRes int resId) {
295         setText(mContext.getText(resId));
296     }
297 
298     /**
299      * Update the text in a Toast that was previously created using one of the makeText() methods.
300      * @param s The new text for the Toast.
301      */
setText(CharSequence s)302     public void setText(CharSequence s) {
303         if (mNextView == null) {
304             throw new RuntimeException("This Toast was not created with Toast.makeText()");
305         }
306         TextView tv = (TextView) mNextView.findViewById(com.android.internal.R.id.message);
307         if (tv == null) {
308             throw new RuntimeException("This Toast was not created with Toast.makeText()");
309         }
310         tv.setText(s);
311     }
312 
313     // =======================================================================================
314     // All the gunk below is the interaction with the Notification Service, which handles
315     // the proper ordering of these system-wide.
316     // =======================================================================================
317 
318     private static INotificationManager sService;
319 
getService()320     static private INotificationManager getService() {
321         if (sService != null) {
322             return sService;
323         }
324         sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
325         return sService;
326     }
327 
328     private static class TN extends ITransientNotification.Stub {
329         final Runnable mShow = new Runnable() {
330             @Override
331             public void run() {
332                 handleShow();
333             }
334         };
335 
336         final Runnable mHide = new Runnable() {
337             @Override
338             public void run() {
339                 handleHide();
340                 // Don't do this in handleHide() because it is also invoked by handleShow()
341                 mNextView = null;
342             }
343         };
344 
345         private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
346         final Handler mHandler = new Handler();
347 
348         int mGravity;
349         int mX, mY;
350         float mHorizontalMargin;
351         float mVerticalMargin;
352 
353 
354         View mView;
355         View mNextView;
356         int mDuration;
357 
358         WindowManager mWM;
359 
360         static final long SHORT_DURATION_TIMEOUT = 5000;
361         static final long LONG_DURATION_TIMEOUT = 1000;
362 
TN()363         TN() {
364             // XXX This should be changed to use a Dialog, with a Theme.Toast
365             // defined that sets up the layout params appropriately.
366             final WindowManager.LayoutParams params = mParams;
367             params.height = WindowManager.LayoutParams.WRAP_CONTENT;
368             params.width = WindowManager.LayoutParams.WRAP_CONTENT;
369             params.format = PixelFormat.TRANSLUCENT;
370             params.windowAnimations = com.android.internal.R.style.Animation_Toast;
371             params.type = WindowManager.LayoutParams.TYPE_TOAST;
372             params.setTitle("Toast");
373             params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
374                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
375                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
376         }
377 
378         /**
379          * schedule handleShow into the right thread
380          */
381         @Override
show()382         public void show() {
383             if (localLOGV) Log.v(TAG, "SHOW: " + this);
384             mHandler.post(mShow);
385         }
386 
387         /**
388          * schedule handleHide into the right thread
389          */
390         @Override
hide()391         public void hide() {
392             if (localLOGV) Log.v(TAG, "HIDE: " + this);
393             mHandler.post(mHide);
394         }
395 
handleShow()396         public void handleShow() {
397             if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
398                     + " mNextView=" + mNextView);
399             if (mView != mNextView) {
400                 // remove the old view if necessary
401                 handleHide();
402                 mView = mNextView;
403                 Context context = mView.getContext().getApplicationContext();
404                 String packageName = mView.getContext().getOpPackageName();
405                 if (context == null) {
406                     context = mView.getContext();
407                 }
408                 mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
409                 // We can resolve the Gravity here by using the Locale for getting
410                 // the layout direction
411                 final Configuration config = mView.getContext().getResources().getConfiguration();
412                 final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
413                 mParams.gravity = gravity;
414                 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
415                     mParams.horizontalWeight = 1.0f;
416                 }
417                 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
418                     mParams.verticalWeight = 1.0f;
419                 }
420                 mParams.x = mX;
421                 mParams.y = mY;
422                 mParams.verticalMargin = mVerticalMargin;
423                 mParams.horizontalMargin = mHorizontalMargin;
424                 mParams.packageName = packageName;
425                 mParams.removeTimeoutMilliseconds = mDuration ==
426                     Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
427                 if (mView.getParent() != null) {
428                     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
429                     mWM.removeView(mView);
430                 }
431                 if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
432                 mWM.addView(mView, mParams);
433                 trySendAccessibilityEvent();
434             }
435         }
436 
trySendAccessibilityEvent()437         private void trySendAccessibilityEvent() {
438             AccessibilityManager accessibilityManager =
439                     AccessibilityManager.getInstance(mView.getContext());
440             if (!accessibilityManager.isEnabled()) {
441                 return;
442             }
443             // treat toasts as notifications since they are used to
444             // announce a transient piece of information to the user
445             AccessibilityEvent event = AccessibilityEvent.obtain(
446                     AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
447             event.setClassName(getClass().getName());
448             event.setPackageName(mView.getContext().getPackageName());
449             mView.dispatchPopulateAccessibilityEvent(event);
450             accessibilityManager.sendAccessibilityEvent(event);
451         }
452 
handleHide()453         public void handleHide() {
454             if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
455             if (mView != null) {
456                 // note: checking parent() just to make sure the view has
457                 // been added...  i have seen cases where we get here when
458                 // the view isn't yet added, so let's try not to crash.
459                 if (mView.getParent() != null) {
460                     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
461                     mWM.removeView(mView);
462                 }
463 
464                 mView = null;
465             }
466         }
467     }
468 }
469