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 34 /** 35 * A toast is a view containing a quick little message for the user. The toast class 36 * helps you create and show those. 37 * {@more} 38 * 39 * <p> 40 * When the view is shown to the user, appears as a floating view over the 41 * application. It will never receive focus. The user will probably be in the 42 * middle of typing something else. The idea is to be as unobtrusive as 43 * possible, while still showing the user the information you want them to see. 44 * Two examples are the volume control, and the brief message saying that your 45 * settings have been saved. 46 * <p> 47 * The easiest way to use this class is to call one of the static methods that constructs 48 * everything you need and returns a new Toast object. 49 */ 50 public class Toast { 51 static final String TAG = "Toast"; 52 static final boolean localLOGV = false; 53 54 /** 55 * Show the view or text notification for a short period of time. This time 56 * could be user-definable. This is the default. 57 * @see #setDuration 58 */ 59 public static final int LENGTH_SHORT = 0; 60 61 /** 62 * Show the view or text notification for a long period of time. This time 63 * could be user-definable. 64 * @see #setDuration 65 */ 66 public static final int LENGTH_LONG = 1; 67 68 final Handler mHandler = new Handler(); 69 final Context mContext; 70 final TN mTN; 71 int mDuration; 72 int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 73 int mX, mY; 74 float mHorizontalMargin; 75 float mVerticalMargin; 76 View mView; 77 View mNextView; 78 79 /** 80 * Construct an empty Toast object. You must call {@link #setView} before you 81 * can call {@link #show}. 82 * 83 * @param context The context to use. Usually your {@link android.app.Application} 84 * or {@link android.app.Activity} object. 85 */ Toast(Context context)86 public Toast(Context context) { 87 mContext = context; 88 mTN = new TN(); 89 mY = context.getResources().getDimensionPixelSize( 90 com.android.internal.R.dimen.toast_y_offset); 91 } 92 93 /** 94 * Show the view for the specified duration. 95 */ show()96 public void show() { 97 if (mNextView == null) { 98 throw new RuntimeException("setView must have been called"); 99 } 100 101 INotificationManager service = getService(); 102 103 String pkg = mContext.getPackageName(); 104 105 TN tn = mTN; 106 107 try { 108 service.enqueueToast(pkg, tn, mDuration); 109 } catch (RemoteException e) { 110 // Empty 111 } 112 } 113 114 /** 115 * Close the view if it's showing, or don't show it if it isn't showing yet. 116 * You do not normally have to call this. Normally view will disappear on its own 117 * after the appropriate duration. 118 */ cancel()119 public void cancel() { 120 mTN.hide(); 121 // TODO this still needs to cancel the inflight notification if any 122 } 123 124 /** 125 * Set the view to show. 126 * @see #getView 127 */ setView(View view)128 public void setView(View view) { 129 mNextView = view; 130 } 131 132 /** 133 * Return the view. 134 * @see #setView 135 */ getView()136 public View getView() { 137 return mNextView; 138 } 139 140 /** 141 * Set how long to show the view for. 142 * @see #LENGTH_SHORT 143 * @see #LENGTH_LONG 144 */ setDuration(int duration)145 public void setDuration(int duration) { 146 mDuration = duration; 147 } 148 149 /** 150 * Return the duration. 151 * @see #setDuration 152 */ getDuration()153 public int getDuration() { 154 return mDuration; 155 } 156 157 /** 158 * Set the margins of the view. 159 * 160 * @param horizontalMargin The horizontal margin, in percentage of the 161 * container width, between the container's edges and the 162 * notification 163 * @param verticalMargin The vertical margin, in percentage of the 164 * container height, between the container's edges and the 165 * notification 166 */ setMargin(float horizontalMargin, float verticalMargin)167 public void setMargin(float horizontalMargin, float verticalMargin) { 168 mHorizontalMargin = horizontalMargin; 169 mVerticalMargin = verticalMargin; 170 } 171 172 /** 173 * Return the horizontal margin. 174 */ getHorizontalMargin()175 public float getHorizontalMargin() { 176 return mHorizontalMargin; 177 } 178 179 /** 180 * Return the vertical margin. 181 */ getVerticalMargin()182 public float getVerticalMargin() { 183 return mVerticalMargin; 184 } 185 186 /** 187 * Set the location at which the notification should appear on the screen. 188 * @see android.view.Gravity 189 * @see #getGravity 190 */ setGravity(int gravity, int xOffset, int yOffset)191 public void setGravity(int gravity, int xOffset, int yOffset) { 192 mGravity = gravity; 193 mX = xOffset; 194 mY = yOffset; 195 } 196 197 /** 198 * Get the location at which the notification should appear on the screen. 199 * @see android.view.Gravity 200 * @see #getGravity 201 */ getGravity()202 public int getGravity() { 203 return mGravity; 204 } 205 206 /** 207 * Return the X offset in pixels to apply to the gravity's location. 208 */ getXOffset()209 public int getXOffset() { 210 return mX; 211 } 212 213 /** 214 * Return the Y offset in pixels to apply to the gravity's location. 215 */ getYOffset()216 public int getYOffset() { 217 return mY; 218 } 219 220 /** 221 * Make a standard toast that just contains a text view. 222 * 223 * @param context The context to use. Usually your {@link android.app.Application} 224 * or {@link android.app.Activity} object. 225 * @param text The text to show. Can be formatted text. 226 * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or 227 * {@link #LENGTH_LONG} 228 * 229 */ makeText(Context context, CharSequence text, int duration)230 public static Toast makeText(Context context, CharSequence text, int duration) { 231 Toast result = new Toast(context); 232 233 LayoutInflater inflate = (LayoutInflater) 234 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 235 View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); 236 TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); 237 tv.setText(text); 238 239 result.mNextView = v; 240 result.mDuration = duration; 241 242 return result; 243 } 244 245 /** 246 * Make a standard toast that just contains a text view with the text from a resource. 247 * 248 * @param context The context to use. Usually your {@link android.app.Application} 249 * or {@link android.app.Activity} object. 250 * @param resId The resource id of the string resource to use. Can be formatted text. 251 * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or 252 * {@link #LENGTH_LONG} 253 * 254 * @throws Resources.NotFoundException if the resource can't be found. 255 */ makeText(Context context, int resId, int duration)256 public static Toast makeText(Context context, int resId, int duration) 257 throws Resources.NotFoundException { 258 return makeText(context, context.getResources().getText(resId), duration); 259 } 260 261 /** 262 * Update the text in a Toast that was previously created using one of the makeText() methods. 263 * @param resId The new text for the Toast. 264 */ setText(int resId)265 public void setText(int resId) { 266 setText(mContext.getText(resId)); 267 } 268 269 /** 270 * Update the text in a Toast that was previously created using one of the makeText() methods. 271 * @param s The new text for the Toast. 272 */ setText(CharSequence s)273 public void setText(CharSequence s) { 274 if (mNextView == null) { 275 throw new RuntimeException("This Toast was not created with Toast.makeText()"); 276 } 277 TextView tv = (TextView) mNextView.findViewById(com.android.internal.R.id.message); 278 if (tv == null) { 279 throw new RuntimeException("This Toast was not created with Toast.makeText()"); 280 } 281 tv.setText(s); 282 } 283 284 // ======================================================================================= 285 // All the gunk below is the interaction with the Notification Service, which handles 286 // the proper ordering of these system-wide. 287 // ======================================================================================= 288 289 private static INotificationManager sService; 290 getService()291 static private INotificationManager getService() { 292 if (sService != null) { 293 return sService; 294 } 295 sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); 296 return sService; 297 } 298 299 private class TN extends ITransientNotification.Stub { 300 final Runnable mShow = new Runnable() { 301 public void run() { 302 handleShow(); 303 } 304 }; 305 306 final Runnable mHide = new Runnable() { 307 public void run() { 308 handleHide(); 309 } 310 }; 311 312 private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); 313 314 WindowManagerImpl mWM; 315 TN()316 TN() { 317 // XXX This should be changed to use a Dialog, with a Theme.Toast 318 // defined that sets up the layout params appropriately. 319 final WindowManager.LayoutParams params = mParams; 320 params.height = WindowManager.LayoutParams.WRAP_CONTENT; 321 params.width = WindowManager.LayoutParams.WRAP_CONTENT; 322 params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 323 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 324 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 325 params.format = PixelFormat.TRANSLUCENT; 326 params.windowAnimations = com.android.internal.R.style.Animation_Toast; 327 params.type = WindowManager.LayoutParams.TYPE_TOAST; 328 params.setTitle("Toast"); 329 } 330 331 /** 332 * schedule handleShow into the right thread 333 */ show()334 public void show() { 335 if (localLOGV) Log.v(TAG, "SHOW: " + this); 336 mHandler.post(mShow); 337 } 338 339 /** 340 * schedule handleHide into the right thread 341 */ hide()342 public void hide() { 343 if (localLOGV) Log.v(TAG, "HIDE: " + this); 344 mHandler.post(mHide); 345 } 346 handleShow()347 public void handleShow() { 348 if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView 349 + " mNextView=" + mNextView); 350 if (mView != mNextView) { 351 // remove the old view if necessary 352 handleHide(); 353 mView = mNextView; 354 mWM = WindowManagerImpl.getDefault(); 355 final int gravity = mGravity; 356 mParams.gravity = gravity; 357 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { 358 mParams.horizontalWeight = 1.0f; 359 } 360 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { 361 mParams.verticalWeight = 1.0f; 362 } 363 mParams.x = mX; 364 mParams.y = mY; 365 mParams.verticalMargin = mVerticalMargin; 366 mParams.horizontalMargin = mHorizontalMargin; 367 if (mView.getParent() != null) { 368 if (localLOGV) Log.v( 369 TAG, "REMOVE! " + mView + " in " + this); 370 mWM.removeView(mView); 371 } 372 if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); 373 mWM.addView(mView, mParams); 374 } 375 } 376 handleHide()377 public void handleHide() { 378 if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); 379 if (mView != null) { 380 // note: checking parent() just to make sure the view has 381 // been added... i have seen cases where we get here when 382 // the view isn't yet added, so let's try not to crash. 383 if (mView.getParent() != null) { 384 if (localLOGV) Log.v( 385 TAG, "REMOVE! " + mView + " in " + this); 386 mWM.removeView(mView); 387 } 388 389 mView = null; 390 } 391 } 392 } 393 } 394