• 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.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      */
Toast(Context context)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      */
show()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      */
cancel()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      */
setView(View view)138     public void setView(View view) {
139         mNextView = view;
140     }
141 
142     /**
143      * Return the view.
144      * @see #setView
145      */
getView()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      */
setDuration(int duration)155     public void setDuration(int duration) {
156         mDuration = duration;
157     }
158 
159     /**
160      * Return the duration.
161      * @see #setDuration
162      */
getDuration()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      */
setMargin(float horizontalMargin, float verticalMargin)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      */
getHorizontalMargin()185     public float getHorizontalMargin() {
186         return mTN.mHorizontalMargin;
187     }
188 
189     /**
190      * Return the vertical margin.
191      */
getVerticalMargin()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      */
setGravity(int gravity, int xOffset, int yOffset)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      */
getGravity()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      */
getXOffset()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      */
getYOffset()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      */
makeText(Context context, CharSequence text, int duration)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      */
makeText(Context context, int resId, int duration)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      */
setText(int resId)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      */
setText(CharSequence s)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 
getService()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 
TN()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
show()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
hide()368         public void hide() {
369             if (localLOGV) Log.v(TAG, "HIDE: " + this);
370             mHandler.post(mHide);
371         }
372 
handleShow()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 
trySendAccessibilityEvent()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 
handleHide()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