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