/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; //import android.app.INotificationManager; //import android.app.ITransientNotification; import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.Handler; //import android.os.RemoteException; //import android.os.ServiceManager; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerImpl; /** * A toast is a view containing a quick little message for the user. The toast class * helps you create and show those. * {@more} * * <p> * When the view is shown to the user, appears as a floating view over the * application. It will never receive focus. The user will probably be in the * middle of typing something else. The idea is to be as unobtrusive as * possible, while still showing the user the information you want them to see. * Two examples are the volume control, and the brief message saying that your * settings have been saved. * <p> * The easiest way to use this class is to call one of the static methods that constructs * everything you need and returns a new Toast object. */ public class Toast { static final String TAG = "Toast"; static final boolean localLOGV = false; /** * Show the view or text notification for a short period of time. This time * could be user-definable. This is the default. * @see #setDuration */ public static final int LENGTH_SHORT = 0; /** * Show the view or text notification for a long period of time. This time * could be user-definable. * @see #setDuration */ public static final int LENGTH_LONG = 1; private static final int LONG_DELAY = 3500; // 3.5 seconds private static final int SHORT_DELAY = 2000; // 2 seconds final Handler mHandler = new Handler(); final Context mContext; final TN mTN; int mDuration; int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; int mX, mY; float mHorizontalMargin; float mVerticalMargin; View mView; View mNextView; Window mWindow; View mDecor; /** * Construct an empty Toast object. You must call {@link #setView} before you * can call {@link #show}. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. */ public Toast(Context context) { if (context == null) { throw new NullPointerException("Toast.Toast -> context is null"); } mContext = context; mTN = new TN(); mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); WindowManager mWindowManager = (WindowManagerImpl)WindowManagerImpl.getDefault(); Window w = new Window(mContext); w.removeBackKeyInLocalFeature(); mWindow = w; //w.setCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); } /** * Show the view for the specified duration. */ public void show() { if (mNextView == null) { throw new RuntimeException("setView must have been called"); } // INotificationManager service = getService(); String pkg = mContext.getPackageName(); TN tn = mTN; tn.show(); // try { // service.enqueueToast(pkg, tn, mDuration); // } catch (RemoteException e) { // // Empty // } } /** * Close the view if it's showing, or don't show it if it isn't showing yet. * You do not normally have to call this. Normally view will disappear on its own * after the appropriate duration. */ public void cancel() { mTN.hide(); // TODO this still needs to cancel the inflight notification if any } /** * Set the view to show. * @see #getView */ public void setView(View view) { mNextView = view; mWindow.setContentView(view); } /** * Return the view. * @see #setView */ public View getView() { return mNextView; } /** * Set how long to show the view for. * @see #LENGTH_SHORT * @see #LENGTH_LONG */ public void setDuration(int duration) { mDuration = duration; } /** * Return the duration. * @see #setDuration */ public int getDuration() { return mDuration; } /** * Set the margins of the view. * * @param horizontalMargin The horizontal margin, in percentage of the * container width, between the container's edges and the * notification * @param verticalMargin The vertical margin, in percentage of the * container height, between the container's edges and the * notification */ public void setMargin(float horizontalMargin, float verticalMargin) { mHorizontalMargin = horizontalMargin; mVerticalMargin = verticalMargin; } /** * Return the horizontal margin. */ public float getHorizontalMargin() { return mHorizontalMargin; } /** * Return the vertical margin. */ public float getVerticalMargin() { return mVerticalMargin; } /** * Set the location at which the notification should appear on the screen. * @see android.view.Gravity * @see #getGravity */ public void setGravity(int gravity, int xOffset, int yOffset) { mGravity = gravity; mX = xOffset; mY = yOffset; } /** * Get the location at which the notification should appear on the screen. * @see android.view.Gravity * @see #getGravity */ public int getGravity() { return mGravity; } /** * Return the X offset in pixels to apply to the gravity's location. */ public int getXOffset() { return mX; } /** * Return the Y offset in pixels to apply to the gravity's location. */ public int getYOffset() { return mY; } /** * Make a standard toast that just contains a text view with the text from a resource. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. * @param resId The resource id of the string resource to use. Can be formatted text. * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or * {@link #LENGTH_LONG} * * @throws Resources.NotFoundException if the resource can't be found. */ public static Toast makeText(Context context, int resId, int duration) { if (context == null) { throw new NullPointerException("Toast.makeText -> context is null"); } CharSequence text = context.getResources().getText(resId); if (text == null) { return makeText(context, "", duration); } else { return makeText(context, text, duration); } } /** * Make a standard toast that just contains a text view. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. * @param text The text to show. Can be formatted text. * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or * {@link #LENGTH_LONG} * */ public static Toast makeText(Context context, CharSequence text, int duration) { Toast result = new Toast(context); LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); //result.mNextView = v; result.setView(v); result.mDuration = duration; return result; } /** * Update the text in a Toast that was previously created using one of the makeText() methods. * @param resId The new text for the Toast. */ public void setText(int resId) { if (mContext == null) { throw new NullPointerException("Toast.setText -> mContext is null"); } CharSequence text = mContext.getText(resId); if(text == null) { setText(""); } else { setText(text); } } /** * Update the text in a Toast that was previously created using one of the makeText() methods. * @param s The new text for the Toast. */ public void setText(CharSequence s) { if (mNextView == null) { throw new RuntimeException("This Toast was not created with Toast.makeText()"); } TextView tv = (TextView) mNextView.findViewById(com.android.internal.R.id.message); if (tv == null) { throw new RuntimeException("This Toast was not created with Toast.makeText()"); } tv.setText(s); } // ======================================================================================= // All the gunk below is the interaction with the Notification Service, which handles // the proper ordering of these system-wide. // ======================================================================================= // private static INotificationManager sService; // // static private INotificationManager getService() { // if (sService != null) { // return sService; // } // sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); // return sService; // } private class TN /*extends ITransientNotification.Stub*/ { final Runnable mShow = new Runnable() { public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { public void run() { handleHide(); } }; private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); WindowManagerImpl mWM; TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. final WindowManager.LayoutParams params = mParams; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = com.android.internal.R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setTitle("Toast"); } /** * schedule handleShow into the right thread */ public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); // We will destroy it after duration long delay = (mDuration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY); mHandler.postDelayed(mHide, delay); } /** * schedule handleHide into the right thread */ public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } public void handleShow() { if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + mNextView); mDecor = mWindow.getDecorView(); if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; mWM = WindowManagerImpl.getDefault(); final int gravity = mGravity; mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; if (mView.getParent() != null) { if (localLOGV) Log.v( TAG, "REMOVE! " + mView + " in " + this); //mWM.removeView(mView); } if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); //mWM.addView(mView, mParams); // MayLoon added mDecor = mWindow.getDecorView(); // Let the Toast on top of the current Activity. mDecor.zIndex = mContext.getActivityManager().mCurActivity.mZIndex; if (mParams.gravity == Gravity.FILL) { mDecor.alpha = 0.0f; } mWM.addView(mDecor, mParams); mDecor.setVisibility(View.VISIBLE); } } public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); mDecor = mWindow.getDecorView(); if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v( TAG, "REMOVE! " + mView + " in " + this); //mWM.removeView(mView); mWM.removeView(mDecor); } mView = null; } } } }