/*
* 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.Configuration;
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.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
/**
* Toast是为用户提供简短信息的视图.Toast类帮助你创建和显示该视图.
* {@more}
*
* <p>
* 该视图以浮于应用程序之上的形式呈现给用户.
* 因为它并不获得焦点,即使用户正在输入也不会受到影响.
* 它的目标是尽可能不中断用户操作,并使用户看到你提供的信息.
* 有两个典型的例子就是音量控制和设置信息保存成功提示.</p>
* <p>
* 使用该类最简单的方法就是调用其静态方法,让他来构造你需要的一切并返回一个新的 Toast 对象.
*
* <div class="special reference">
* <h3>开发者指南</h3>
* <p>关于创建 Toast 提示的使用,参见
* <a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">
* Toast 提示</a> 开发者指南.</p>
* </div>
* @author translate by cnmahj/jiahuibin(Android中文翻译组)
* @author convert by cnmahj
*/
public class Toast {
static final String TAG = "Toast";
static final boolean localLOGV = false;
/**
* 持续显示视图或文本提示较短时间.该时间长度可定制.该值为默认值.
* @see #setDuration
*/
public static final int LENGTH_SHORT = 0;
/**
* 持续显示视图或文本提示较长时间.该时间长度可定制.
* @see #setDuration
*/
public static final int LENGTH_LONG = 1;
final Context mContext;
final TN mTN;
int mDuration;
View mNextView;
/**
* 构造一个空的 Toast 对象.在调用 {@link #show} 之前,必须先调用 {@link #setView}.
*
* @param context 使用的上下文.通常是你的 {@link android.app.Application} 或
* {@link android.app.Activity} 对象.
*/
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
}
/**
* 按照指定的存续期间显示提示信息.
*/
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.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
/**
* 如果视图已经显示则将其关闭,还没有显示则不再显示.一般不需要调用该方法.
* 正常情况下,视图会在超过存续期间后消失.
*/
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
/**
* 设置要显示的视图.
* @see #getView
*/
public void setView(View view) {
mNextView = view;
}
/**
* 返回视图对象.
* @see #setView
*/
public View getView() {
return mNextView;
}
/**
* 设置存续期间.
* @see #LENGTH_SHORT
* @see #LENGTH_LONG
*/
public void setDuration(int duration) {
mDuration = duration;
}
/**
* 返回存续期间.
* @see #setDuration
*/
public int getDuration() {
return mDuration;
}
/**
* 设置视图的栏外空白.
*
* @param horizontalMargin 容器的边缘与提示信息的横向空白(与容器宽度的比).
* @param verticalMargin 容器的边缘与提示信息的纵向空白(与容器高度的比).
*/
public void setMargin(float horizontalMargin, float verticalMargin) {
mTN.mHorizontalMargin = horizontalMargin;
mTN.mVerticalMargin = verticalMargin;
}
/**
* 返回横向栏外空白.
*/
public float getHorizontalMargin() {
return mTN.mHorizontalMargin;
}
/**
* 返回纵向栏外空白.
*/
public float getVerticalMargin() {
return mTN.mVerticalMargin;
}
/**
* 设置提示信息在屏幕上的显示位置.
* @see android.view.Gravity
* @see #getGravity
*/
public void setGravity(int gravity, int xOffset, int yOffset) {
mTN.mGravity = gravity;
mTN.mX = xOffset;
mTN.mY = yOffset;
}
/**
* 取得提示信息在屏幕上显示位置.
* @see android.view.Gravity
* @see #getGravity
*/
public int getGravity() {
return mTN.mGravity;
}
/**
* 返回相对于指定显示位置的横向偏移像素量.
*/
public int getXOffset() {
return mTN.mX;
}
/**
* 返回相对于指定显示位置的纵向偏移像素量.
*/
public int getYOffset() {
return mTN.mY;
}
/**
* 生成一个包含文本的标准 Toast 视图对象.
*
* @param context 使用的上下文.通常是你的 {@link android.app.Application}
* 或 {@link android.app.Activity} 对象.
* @param text 要显示的文本,可以是已格式化文本.
* @param duration 该信息的存续期间.值为 {@link #LENGTH_SHORT} 或 {@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.mDuration = duration;
return result;
}
/**
* 生成一个从资源中取得的包含文本视图的标准 Toast 对象.
*
* @param context 使用的上下文.通常是你的 {@link android.app.Application}
* 或 {@link android.app.Activity} 对象.
* @param resId 要使用的字符串资源ID,可以是已格式化文本.
* @param duration 该信息的存续期间.值为 {@link #LENGTH_SHORT} 或 {@link #LENGTH_LONG}.
*
* @throws Resources.NotFoundException 当资源未找到时
*/
public static Toast makeText(Context context, int resId, int duration)
throws Resources.NotFoundException {
return makeText(context, context.getResources().getText(resId), duration);
}
/**
* 更新之前通过 makeText() 方法生成的 Toast 对象的文本内容.
* @param resId 为 Toast 指定的新的字符串资源ID.
*/
public void setText(int resId) {
setText(mContext.getText(resId));
}
/**
* 更新之前通过 makeText() 方法生成的 Toast 对象的文本内容.
* @param s 为 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 static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
final Handler mHandler = new Handler();
int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
int mX, mY;
float mHorizontalMargin;
float mVerticalMargin;
View mView;
View mNextView;
WindowManager 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
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* schedule handleHide into the right thread
*/
@Override
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);
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
if (context == null) {
context = mView.getContext();
}
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// We can resolve the Gravity here by using the Locale for getting
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
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);
trySendAccessibilityEvent();
}
}
private void trySendAccessibilityEvent() {
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(mView.getContext());
if (!accessibilityManager.isEnabled()) {
return;
}
// treat toasts as notifications since they are used to
// announce a transient piece of information to the user
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
event.setClassName(getClass().getName());
event.setPackageName(mView.getContext().getPackageName());
mView.dispatchPopulateAccessibilityEvent(event);
accessibilityManager.sendAccessibilityEvent(event);
}
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
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);
}
mView = null;
}
}
}
}