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