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