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