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.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 @Override 309 public void run() { 310 handleShow(); 311 } 312 }; 313 314 final Runnable mHide = new Runnable() { 315 @Override 316 public void run() { 317 handleHide(); 318 // Don't do this in handleHide() because it is also invoked by handleShow() 319 mNextView = null; 320 } 321 }; 322 323 private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); 324 final Handler mHandler = new Handler(); 325 326 int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 327 int mX, mY; 328 float mHorizontalMargin; 329 float mVerticalMargin; 330 331 332 View mView; 333 View mNextView; 334 335 WindowManager mWM; 336 337 TN() { 338 // XXX This should be changed to use a Dialog, with a Theme.Toast 339 // defined that sets up the layout params appropriately. 340 final WindowManager.LayoutParams params = mParams; 341 params.height = WindowManager.LayoutParams.WRAP_CONTENT; 342 params.width = WindowManager.LayoutParams.WRAP_CONTENT; 343 params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 344 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 345 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 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 } 351 352 /** 353 * schedule handleShow into the right thread 354 */ 355 @Override 356 public void show() { 357 if (localLOGV) Log.v(TAG, "SHOW: " + this); 358 mHandler.post(mShow); 359 } 360 361 /** 362 * schedule handleHide into the right thread 363 */ 364 @Override 365 public void hide() { 366 if (localLOGV) Log.v(TAG, "HIDE: " + this); 367 mHandler.post(mHide); 368 } 369 370 public void handleShow() { 371 if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView 372 + " mNextView=" + mNextView); 373 if (mView != mNextView) { 374 // remove the old view if necessary 375 handleHide(); 376 mView = mNextView; 377 mWM = (WindowManager)mView.getContext().getApplicationContext() 378 .getSystemService(Context.WINDOW_SERVICE); 379 // We can resolve the Gravity here by using the Locale for getting 380 // the layout direction 381 final Configuration config = mView.getContext().getResources().getConfiguration(); 382 final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); 383 mParams.gravity = gravity; 384 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { 385 mParams.horizontalWeight = 1.0f; 386 } 387 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { 388 mParams.verticalWeight = 1.0f; 389 } 390 mParams.x = mX; 391 mParams.y = mY; 392 mParams.verticalMargin = mVerticalMargin; 393 mParams.horizontalMargin = mHorizontalMargin; 394 if (mView.getParent() != null) { 395 if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); 396 mWM.removeView(mView); 397 } 398 if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); 399 mWM.addView(mView, mParams); 400 trySendAccessibilityEvent(); 401 } 402 } 403 404 private void trySendAccessibilityEvent() { 405 AccessibilityManager accessibilityManager = 406 AccessibilityManager.getInstance(mView.getContext()); 407 if (!accessibilityManager.isEnabled()) { 408 return; 409 } 410 // treat toasts as notifications since they are used to 411 // announce a transient piece of information to the user 412 AccessibilityEvent event = AccessibilityEvent.obtain( 413 AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); 414 event.setClassName(getClass().getName()); 415 event.setPackageName(mView.getContext().getPackageName()); 416 mView.dispatchPopulateAccessibilityEvent(event); 417 accessibilityManager.sendAccessibilityEvent(event); 418 } 419 420 public void handleHide() { 421 if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); 422 if (mView != null) { 423 // note: checking parent() just to make sure the view has 424 // been added... i have seen cases where we get here when 425 // the view isn't yet added, so let's try not to crash. 426 if (mView.getParent() != null) { 427 if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); 428 mWM.removeView(mView); 429 } 430 431 mView = null; 432 } 433 } 434 } 435} 436