Toast.java revision 025f9602ee99ba5883512a97f2a46f8132ece5d6
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
17package android.widget;
18
19import android.app.INotificationManager;
20import android.app.ITransientNotification;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.content.res.Resources;
24import android.graphics.PixelFormat;
25import android.os.Handler;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.util.Log;
29import android.view.Gravity;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.WindowManager;
33import android.view.WindowManagerImpl;
34import android.view.accessibility.AccessibilityEvent;
35import 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 */
60public 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    }
96
97    /**
98     * Show the view for the specified duration.
99     */
100    public void show() {
101        if (mNextView == null) {
102            throw new RuntimeException("setView must have been called");
103        }
104
105        INotificationManager service = getService();
106        String pkg = mContext.getPackageName();
107        TN tn = mTN;
108        tn.mNextView = mNextView;
109
110        try {
111            service.enqueueToast(pkg, tn, mDuration);
112        } catch (RemoteException e) {
113            // Empty
114        }
115    }
116
117    /**
118     * Close the view if it's showing, or don't show it if it isn't showing yet.
119     * You do not normally have to call this.  Normally view will disappear on its own
120     * after the appropriate duration.
121     */
122    public void cancel() {
123        mTN.hide();
124
125        try {
126            getService().cancelToast(mContext.getPackageName(), mTN);
127        } catch (RemoteException e) {
128            // Empty
129        }
130    }
131
132    /**
133     * Set the view to show.
134     * @see #getView
135     */
136    public void setView(View view) {
137        mNextView = view;
138    }
139
140    /**
141     * Return the view.
142     * @see #setView
143     */
144    public View getView() {
145        return mNextView;
146    }
147
148    /**
149     * Set how long to show the view for.
150     * @see #LENGTH_SHORT
151     * @see #LENGTH_LONG
152     */
153    public void setDuration(int duration) {
154        mDuration = duration;
155    }
156
157    /**
158     * Return the duration.
159     * @see #setDuration
160     */
161    public int getDuration() {
162        return mDuration;
163    }
164
165    /**
166     * Set the margins of the view.
167     *
168     * @param horizontalMargin The horizontal margin, in percentage of the
169     *        container width, between the container's edges and the
170     *        notification
171     * @param verticalMargin The vertical margin, in percentage of the
172     *        container height, between the container's edges and the
173     *        notification
174     */
175    public void setMargin(float horizontalMargin, float verticalMargin) {
176        mTN.mHorizontalMargin = horizontalMargin;
177        mTN.mVerticalMargin = verticalMargin;
178    }
179
180    /**
181     * Return the horizontal margin.
182     */
183    public float getHorizontalMargin() {
184        return mTN.mHorizontalMargin;
185    }
186
187    /**
188     * Return the vertical margin.
189     */
190    public float getVerticalMargin() {
191        return mTN.mVerticalMargin;
192    }
193
194    /**
195     * Set the location at which the notification should appear on the screen.
196     * @see android.view.Gravity
197     * @see #getGravity
198     */
199    public void setGravity(int gravity, int xOffset, int yOffset) {
200        mTN.mGravity = gravity;
201        mTN.mX = xOffset;
202        mTN.mY = yOffset;
203    }
204
205     /**
206     * Get the location at which the notification should appear on the screen.
207     * @see android.view.Gravity
208     * @see #getGravity
209     */
210    public int getGravity() {
211        return mTN.mGravity;
212    }
213
214    /**
215     * Return the X offset in pixels to apply to the gravity's location.
216     */
217    public int getXOffset() {
218        return mTN.mX;
219    }
220
221    /**
222     * Return the Y offset in pixels to apply to the gravity's location.
223     */
224    public int getYOffset() {
225        return mTN.mY;
226    }
227
228    /**
229     * Make a standard toast that just contains a text view.
230     *
231     * @param context  The context to use.  Usually your {@link android.app.Application}
232     *                 or {@link android.app.Activity} object.
233     * @param text     The text to show.  Can be formatted text.
234     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
235     *                 {@link #LENGTH_LONG}
236     *
237     */
238    public static Toast makeText(Context context, CharSequence text, int duration) {
239        Toast result = new Toast(context);
240
241        LayoutInflater inflate = (LayoutInflater)
242                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
243        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
244        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
245        tv.setText(text);
246
247        result.mNextView = v;
248        result.mDuration = duration;
249
250        return result;
251    }
252
253    /**
254     * Make a standard toast that just contains a text view with the text from a resource.
255     *
256     * @param context  The context to use.  Usually your {@link android.app.Application}
257     *                 or {@link android.app.Activity} object.
258     * @param resId    The resource id of the string resource to use.  Can be formatted text.
259     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
260     *                 {@link #LENGTH_LONG}
261     *
262     * @throws Resources.NotFoundException if the resource can't be found.
263     */
264    public static Toast makeText(Context context, int resId, int duration)
265                                throws Resources.NotFoundException {
266        return makeText(context, context.getResources().getText(resId), duration);
267    }
268
269    /**
270     * Update the text in a Toast that was previously created using one of the makeText() methods.
271     * @param resId The new text for the Toast.
272     */
273    public void setText(int resId) {
274        setText(mContext.getText(resId));
275    }
276
277    /**
278     * Update the text in a Toast that was previously created using one of the makeText() methods.
279     * @param s The new text for the Toast.
280     */
281    public void setText(CharSequence s) {
282        if (mNextView == null) {
283            throw new RuntimeException("This Toast was not created with Toast.makeText()");
284        }
285        TextView tv = (TextView) mNextView.findViewById(com.android.internal.R.id.message);
286        if (tv == null) {
287            throw new RuntimeException("This Toast was not created with Toast.makeText()");
288        }
289        tv.setText(s);
290    }
291
292    // =======================================================================================
293    // All the gunk below is the interaction with the Notification Service, which handles
294    // the proper ordering of these system-wide.
295    // =======================================================================================
296
297    private static INotificationManager sService;
298
299    static private INotificationManager getService() {
300        if (sService != null) {
301            return sService;
302        }
303        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
304        return sService;
305    }
306
307    private static class TN extends ITransientNotification.Stub {
308        final Runnable mShow = new Runnable() {
309            public void run() {
310                handleShow();
311            }
312        };
313
314        final Runnable mHide = new Runnable() {
315            public void run() {
316                handleHide();
317                // Don't do this in handleHide() because it is also invoked by handleShow()
318                mNextView = null;
319            }
320        };
321
322        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
323        final Handler mHandler = new Handler();
324
325        int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
326        int mX, mY;
327        float mHorizontalMargin;
328        float mVerticalMargin;
329
330
331        View mView;
332        View mNextView;
333
334        WindowManagerImpl mWM;
335
336        TN() {
337            // XXX This should be changed to use a Dialog, with a Theme.Toast
338            // defined that sets up the layout params appropriately.
339            final WindowManager.LayoutParams params = mParams;
340            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
341            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
342            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
343                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
344                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
345            params.format = PixelFormat.TRANSLUCENT;
346            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
347            params.type = WindowManager.LayoutParams.TYPE_TOAST;
348            params.setTitle("Toast");
349        }
350
351        /**
352         * schedule handleShow into the right thread
353         */
354        public void show() {
355            if (localLOGV) Log.v(TAG, "SHOW: " + this);
356            mHandler.post(mShow);
357        }
358
359        /**
360         * schedule handleHide into the right thread
361         */
362        public void hide() {
363            if (localLOGV) Log.v(TAG, "HIDE: " + this);
364            mHandler.post(mHide);
365        }
366
367        public void handleShow() {
368            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
369                    + " mNextView=" + mNextView);
370            if (mView != mNextView) {
371                // remove the old view if necessary
372                handleHide();
373                mView = mNextView;
374                mWM = WindowManagerImpl.getDefault();
375                // We can resolve the Gravity here by using the Locale for getting
376                // the layout direction
377                final Configuration config = mView.getContext().getResources().getConfiguration();
378                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.layoutDirection);
379                mParams.gravity = gravity;
380                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
381                    mParams.horizontalWeight = 1.0f;
382                }
383                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
384                    mParams.verticalWeight = 1.0f;
385                }
386                mParams.x = mX;
387                mParams.y = mY;
388                mParams.verticalMargin = mVerticalMargin;
389                mParams.horizontalMargin = mHorizontalMargin;
390                if (mView.getParent() != null) {
391                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
392                    mWM.removeView(mView);
393                }
394                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
395                mWM.addView(mView, mParams);
396                trySendAccessibilityEvent();
397            }
398        }
399
400        private void trySendAccessibilityEvent() {
401            AccessibilityManager accessibilityManager =
402                    AccessibilityManager.getInstance(mView.getContext());
403            if (!accessibilityManager.isEnabled()) {
404                return;
405            }
406            // treat toasts as notifications since they are used to
407            // announce a transient piece of information to the user
408            AccessibilityEvent event = AccessibilityEvent.obtain(
409                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
410            event.setClassName(getClass().getName());
411            event.setPackageName(mView.getContext().getPackageName());
412            mView.dispatchPopulateAccessibilityEvent(event);
413            accessibilityManager.sendAccessibilityEvent(event);
414        }
415
416        public void handleHide() {
417            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
418            if (mView != null) {
419                // note: checking parent() just to make sure the view has
420                // been added...  i have seen cases where we get here when
421                // the view isn't yet added, so let's try not to crash.
422                if (mView.getParent() != null) {
423                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
424                    mWM.removeView(mView);
425                }
426
427                mView = null;
428            }
429        }
430    }
431}
432