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