谷歌輸入法PinyinIme 程式碼註釋

keanbin發表於2013-12-04

最近一段時間,公司要開發安卓輸入法,看了幾款開源的例子,其中對 PinyinIME 整篇閱讀,並進行了相應的註釋,現在把註釋的程式碼發上來,和大家分享學習。


下載改過包名並進行過註釋的PinyinIME:http://download.csdn.net/detail/keanbin/6656347


下載沒有改過包名的PinyinIME(也進行了程式碼註釋):http://download.csdn.net/detail/keanbin/6656395


下載原裝的PinyinIME:http://download.csdn.net/detail/keanbin/6656443


1、BalloonHint.java :

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.PopupWindow;

/**
 * Subclass of PopupWindow used as the feedback when user presses on a soft key
 * or a candidate. 氣泡對話方塊
 */
public class BalloonHint extends PopupWindow {
	/**
	 * Delayed time to show the balloon hint. 延時多長時間顯示
	 */
	public static final int TIME_DELAY_SHOW = 0;

	/**
	 * Delayed time to dismiss the balloon hint. 延時多長時間消失
	 */
	public static final int TIME_DELAY_DISMISS = 200;

	/**
	 * The padding information of the balloon. Because PopupWindow's background
	 * can not be changed unless it is dismissed and shown again, we set the
	 * real background drawable to the content view, and make the PopupWindow's
	 * background transparent. So actually this padding information is for the
	 * content view.
	 */
	private Rect mPaddingRect = new Rect();

	/**
	 * The context used to create this balloon hint object.
	 */
	private Context mContext;

	/**
	 * Parent used to show the balloon window.
	 */
	private View mParent;

	/**
	 * The content view of the balloon. 氣泡View
	 */
	BalloonView mBalloonView;

	/**
	 * The measuring specification used to determine its size. Key-press
	 * balloons and candidates balloons have different measuring specifications.
	 * 按鍵氣泡和候選詞氣泡有不同的測量模式。
	 */
	private int mMeasureSpecMode;

	/**
	 * Used to indicate whether the balloon needs to be dismissed forcibly.
	 * 氣泡是否需要強行銷燬。
	 */
	private boolean mForceDismiss;

	/**
	 * Timer used to show/dismiss the balloon window with some time delay.
	 * 氣泡顯示和銷燬的定時器
	 */
	private BalloonTimer mBalloonTimer;

	private int mParentLocationInWindow[] = new int[2];

	public BalloonHint(Context context, View parent, int measureSpecMode) {
		super(context);
		mParent = parent;
		mMeasureSpecMode = measureSpecMode;

		setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
		setTouchable(false);
		setBackgroundDrawable(new ColorDrawable(0));

		mBalloonView = new BalloonView(context);
		mBalloonView.setClickable(false);
		setContentView(mBalloonView);

		mBalloonTimer = new BalloonTimer();
	}

	public Context getContext() {
		return mContext;
	}

	public Rect getPadding() {
		return mPaddingRect;
	}

	public void setBalloonBackground(Drawable drawable) {
		// We usually pick up a background from a soft keyboard template,
		// and the object may has been set to this balloon before.
		if (mBalloonView.getBackground() == drawable) {
			return;
		}

		mBalloonView.setBackgroundDrawable(drawable);

		if (null != drawable) {
			drawable.getPadding(mPaddingRect);
		} else {
			mPaddingRect.set(0, 0, 0, 0);
		}
	}

	/**
	 * Set configurations to show text label in this balloon.
	 * 
	 * @param label
	 *            The text label to show in the balloon.
	 * @param textSize
	 *            The text size used to show label.
	 * @param textBold
	 *            Used to indicate whether the label should be bold.
	 * @param textColor
	 *            The text color used to show label.
	 * @param width
	 *            The desired width of the balloon. The real width is determined
	 *            by the desired width and balloon's measuring specification.
	 * @param height
	 *            The desired width of the balloon. The real width is determined
	 *            by the desired width and balloon's measuring specification.
	 */
	public void setBalloonConfig(String label, float textSize,
			boolean textBold, int textColor, int width, int height) {
		mBalloonView.setTextConfig(label, textSize, textBold, textColor);
		setBalloonSize(width, height);
	}

	/**
	 * Set configurations to show text label in this balloon.
	 * 
	 * @param icon
	 *            The icon used to shown in this balloon.
	 * @param width
	 *            The desired width of the balloon. The real width is determined
	 *            by the desired width and balloon's measuring specification.
	 * @param height
	 *            The desired width of the balloon. The real width is determined
	 *            by the desired width and balloon's measuring specification.
	 */
	public void setBalloonConfig(Drawable icon, int width, int height) {
		mBalloonView.setIcon(icon);
		setBalloonSize(width, height);
	}

	public boolean needForceDismiss() {
		return mForceDismiss;
	}

	public int getPaddingLeft() {
		return mPaddingRect.left;
	}

	public int getPaddingTop() {
		return mPaddingRect.top;
	}

	public int getPaddingRight() {
		return mPaddingRect.right;
	}

	public int getPaddingBottom() {
		return mPaddingRect.bottom;
	}

	/**
	 * 延時顯示氣泡
	 * 
	 * @param delay
	 *            延時的時間
	 * @param locationInParent
	 *            氣泡顯示的位置,相對於父檢視
	 */
	public void delayedShow(long delay, int locationInParent[]) {
		if (mBalloonTimer.isPending()) {
			mBalloonTimer.removeTimer();
		}
		if (delay <= 0) {
			mParent.getLocationInWindow(mParentLocationInWindow);
			showAtLocation(mParent, Gravity.LEFT | Gravity.TOP,
					locationInParent[0], locationInParent[1]
							+ mParentLocationInWindow[1]);
		} else {
			mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_SHOW,
					locationInParent, -1, -1);
		}
	}

	/**
	 * 延時更新氣泡
	 * 
	 * @param delay
	 *            延時的時間
	 * @param locationInParent
	 *            氣泡顯示的位置,相對於父檢視
	 */
	public void delayedUpdate(long delay, int locationInParent[], int width,
			int height) {
		mBalloonView.invalidate();
		if (mBalloonTimer.isPending()) {
			mBalloonTimer.removeTimer();
		}
		if (delay <= 0) {
			mParent.getLocationInWindow(mParentLocationInWindow);
			update(locationInParent[0], locationInParent[1]
					+ mParentLocationInWindow[1], width, height);
		} else {
			mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_UPDATE,
					locationInParent, width, height);
		}
	}

	/**
	 * 氣泡延時消失
	 * 
	 * @param delay
	 */
	public void delayedDismiss(long delay) {
		if (mBalloonTimer.isPending()) {
			mBalloonTimer.removeTimer();
			int pendingAction = mBalloonTimer.getAction();
			if (0 != delay && BalloonTimer.ACTION_HIDE != pendingAction) {
				mBalloonTimer.run();
			}
		}
		if (delay <= 0) {
			dismiss();
		} else {
			mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_HIDE, null, -1,
					-1);
		}
	}

	public void removeTimer() {
		if (mBalloonTimer.isPending()) {
			mBalloonTimer.removeTimer();
		}
	}

	private void setBalloonSize(int width, int height) {
		int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
				mMeasureSpecMode);
		int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
				mMeasureSpecMode);
		mBalloonView.measure(widthMeasureSpec, heightMeasureSpec);

		int oldWidth = getWidth();
		int oldHeight = getHeight();
		int newWidth = mBalloonView.getMeasuredWidth() + getPaddingLeft()
				+ getPaddingRight();
		int newHeight = mBalloonView.getMeasuredHeight() + getPaddingTop()
				+ getPaddingBottom();
		setWidth(newWidth);
		setHeight(newHeight);

		// If update() is called to update both size and position, the system
		// will first MOVE the PopupWindow to the new position, and then
		// perform a size-updating operation, so there will be a flash in
		// PopupWindow if user presses a key and moves finger to next one whose
		// size is different.
		// PopupWindow will handle the updating issue in one go in the future,
		// but before that, if we find the size is changed, a mandatory dismiss
		// operation is required. In our UI design, normal QWERTY keys' width
		// can be different in 1-pixel, and we do not dismiss the balloon when
		// user move between QWERTY keys.
		// 呼叫update()去更新位置和大小,系統會先移動對話方塊到新的位置,然後再去更新大小,所以如果需要更新大小,那麼我們就需要先強制去銷燬它,再去顯示。
		mForceDismiss = false;
		if (isShowing()) {
			mForceDismiss = oldWidth - newWidth > 1 || newWidth - oldWidth > 1;
		}
	}

	private class BalloonTimer extends Handler implements Runnable {
		public static final int ACTION_SHOW = 1;
		public static final int ACTION_HIDE = 2;
		public static final int ACTION_UPDATE = 3;

		/**
		 * The pending action.
		 */
		private int mAction;

		private int mPositionInParent[] = new int[2];
		private int mWidth;
		private int mHeight;

		private boolean mTimerPending = false;

		public void startTimer(long time, int action, int positionInParent[],
				int width, int height) {
			mAction = action;
			if (ACTION_HIDE != action) {
				mPositionInParent[0] = positionInParent[0];
				mPositionInParent[1] = positionInParent[1];
			}
			mWidth = width;
			mHeight = height;
			postDelayed(this, time);
			mTimerPending = true;
		}

		public boolean isPending() {
			return mTimerPending;
		}

		public boolean removeTimer() {
			if (mTimerPending) {
				mTimerPending = false;
				removeCallbacks(this);
				return true;
			}

			return false;
		}

		public int getAction() {
			return mAction;
		}

		public void run() {
			switch (mAction) {
			case ACTION_SHOW:
				mParent.getLocationInWindow(mParentLocationInWindow);
				showAtLocation(mParent, Gravity.LEFT | Gravity.TOP,
						mPositionInParent[0], mPositionInParent[1]
								+ mParentLocationInWindow[1]);
				break;
			case ACTION_HIDE:
				dismiss();
				break;
			case ACTION_UPDATE:
				mParent.getLocationInWindow(mParentLocationInWindow);
				update(mPositionInParent[0], mPositionInParent[1]
						+ mParentLocationInWindow[1], mWidth, mHeight);
			}
			mTimerPending = false;
		}
	}

	/**
	 * 氣泡View
	 * 
	 * @author keanbin
	 * 
	 */
	private class BalloonView extends View {
		/**
		 * Suspension points used to display long items.
		 */
		private static final String SUSPENSION_POINTS = "...";

		/**
		 * The icon to be shown. If it is not null, {@link #mLabel} will be
		 * ignored. mIcon 如果不為空,mLabel就被忽略。
		 */
		private Drawable mIcon;

		/**
		 * The label to be shown. It is enabled only if {@link #mIcon} is null.
		 */
		private String mLabel;

		private int mLabeColor = 0xff000000;
		private Paint mPaintLabel;
		private FontMetricsInt mFmi; // 字型整形/尺寸

		/**
		 * The width to show suspension points. 省略號的寬度
		 */
		private float mSuspensionPointsWidth;

		public BalloonView(Context context) {
			super(context);
			mPaintLabel = new Paint();
			mPaintLabel.setColor(mLabeColor);
			mPaintLabel.setAntiAlias(true);
			mPaintLabel.setFakeBoldText(true);
			mFmi = mPaintLabel.getFontMetricsInt();
		}

		public void setIcon(Drawable icon) {
			mIcon = icon;
		}

		public void setTextConfig(String label, float fontSize,
				boolean textBold, int textColor) {
			// Icon should be cleared so that the label will be enabled.
			mIcon = null;
			mLabel = label;
			mPaintLabel.setTextSize(fontSize);
			mPaintLabel.setFakeBoldText(textBold);
			mPaintLabel.setColor(textColor);
			mFmi = mPaintLabel.getFontMetricsInt();
			mSuspensionPointsWidth = mPaintLabel.measureText(SUSPENSION_POINTS);
		}

		@Override
		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
			// 取出測量的模式
			final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
			final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
			// 取出測量的寬度高度
			final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
			final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

			if (widthMode == MeasureSpec.EXACTLY) {
				setMeasuredDimension(widthSize, heightSize);
				return;
			}

			// 計算最少需要的尺寸
			int measuredWidth = getPaddingLeft() + getPaddingRight();
			int measuredHeight = getPaddingTop() + getPaddingBottom();
			if (null != mIcon) {
				measuredWidth += mIcon.getIntrinsicWidth();
				measuredHeight += mIcon.getIntrinsicHeight();
			} else if (null != mLabel) {
				measuredWidth += (int) (mPaintLabel.measureText(mLabel));
				measuredHeight += mFmi.bottom - mFmi.top;
			}

			if (widthSize > measuredWidth || widthMode == MeasureSpec.AT_MOST) {
				measuredWidth = widthSize;
			}

			if (heightSize > measuredHeight
					|| heightMode == MeasureSpec.AT_MOST) {
				measuredHeight = heightSize;
			}

			// TODO
			// measuredWidth不是包含getPaddingLeft()和getPaddingRight()嗎?怎麼螢幕寬度還需要再減去它們?
			int maxWidth = Environment.getInstance().getScreenWidth()
					- getPaddingLeft() - getPaddingRight();
			if (measuredWidth > maxWidth) {
				measuredWidth = maxWidth;
			}

			// 設定尺寸
			setMeasuredDimension(measuredWidth, measuredHeight);
		}

		@Override
		protected void onDraw(Canvas canvas) {
			int width = getWidth();
			int height = getHeight();
			if (null != mIcon) {
				int marginLeft = (width - mIcon.getIntrinsicWidth()) / 2;
				int marginRight = width - mIcon.getIntrinsicWidth()
						- marginLeft;
				int marginTop = (height - mIcon.getIntrinsicHeight()) / 2;
				int marginBottom = height - mIcon.getIntrinsicHeight()
						- marginTop;
				mIcon.setBounds(marginLeft, marginTop, width - marginRight,
						height - marginBottom);
				mIcon.draw(canvas);
			} else if (null != mLabel) {
				float labelMeasuredWidth = mPaintLabel.measureText(mLabel);
				float x = getPaddingLeft();
				x += (width - labelMeasuredWidth - getPaddingLeft() - getPaddingRight()) / 2.0f;
				String labelToDraw = mLabel;
				if (x < getPaddingLeft()) {
					// 區域不夠顯示,顯示短語+省略號
					x = getPaddingLeft();
					labelToDraw = getLimitedLabelForDrawing(mLabel, width
							- getPaddingLeft() - getPaddingRight());
				}

				int fontHeight = mFmi.bottom - mFmi.top;
				float marginY = (height - fontHeight) / 2.0f;
				float y = marginY - mFmi.top;
				canvas.drawText(labelToDraw, x, y, mPaintLabel);
			}
		}

		/**
		 * 顯示的文字過長,擷取適合的短語+省略號
		 * 
		 * @param rawLabel
		 * @param widthToDraw
		 * @return
		 */
		private String getLimitedLabelForDrawing(String rawLabel,
				float widthToDraw) {
			int subLen = rawLabel.length();
			if (subLen <= 1)
				return rawLabel;
			do {
				subLen--;
				float width = mPaintLabel.measureText(rawLabel, 0, subLen);
				if (width + mSuspensionPointsWidth <= widthToDraw
						|| 1 >= subLen) {
					return rawLabel.substring(0, subLen) + SUSPENSION_POINTS;
				}
			} while (true);
		}
	}
}


2、CandidatesContainer.java

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.ViewFlipper;

import com.keanbin.pinyinime.PinyinIME.DecodingInfo;

/**
 * 集裝箱中的箭頭更新監聽器
 * 
 * @ClassName ArrowUpdater
 * @author keanbin
 */
interface ArrowUpdater {
	void updateArrowStatus();
}

/**
 * Container used to host the two candidate views. When user drags on candidate
 * view, animation is used to dismiss the current candidate view and show a new
 * one. These two candidate views and their parent are hosted by this container.
 * <p>
 * Besides the candidate views, there are two arrow views to show the page
 * forward/backward arrows.
 * </p>
 */
/**
 * 候選詞集裝箱
 * 
 * @ClassName CandidatesContainer
 * @author keanbin
 */
public class CandidatesContainer extends RelativeLayout implements
		OnTouchListener, AnimationListener, ArrowUpdater {
	/**
	 * Alpha value to show an enabled arrow. 箭頭圖片顯示時的透明度
	 */
	private static int ARROW_ALPHA_ENABLED = 0xff;

	/**
	 * Alpha value to show an disabled arrow. 箭頭圖片不顯示時的透明度
	 */
	private static int ARROW_ALPHA_DISABLED = 0x40;

	/**
	 * Animation time to show a new candidate view and dismiss the old one.
	 * 顯示或者關閉一個候選詞View的動畫時間
	 */
	private static int ANIMATION_TIME = 200;

	/**
	 * Listener used to notify IME that user clicks a candidate, or navigate
	 * between them. 候選詞檢視監聽器
	 */
	private CandidateViewListener mCvListener;

	/**
	 * The left arrow button used to show previous page. 左邊箭頭按鈕
	 */
	private ImageButton mLeftArrowBtn;

	/**
	 * The right arrow button used to show next page. 右邊箭頭按鈕
	 */
	private ImageButton mRightArrowBtn;

	/**
	 * Decoding result to show. 詞庫解碼物件
	 */
	private DecodingInfo mDecInfo;

	/**
	 * The animation view used to show candidates. It contains two views.
	 * Normally, the candidates are shown one of them. When user navigates to
	 * another page, animation effect will be performed.
	 * ViewFlipper頁面管理,它包含兩個檢視,正常只顯示其中一個,當切換候選詞頁的時候,就啟動另一個檢視裝載接著要顯示的候選詞切入進來。
	 */
	private ViewFlipper mFlipper;

	/**
	 * The x offset of the flipper in this container. ViewFlipper 在集裝箱的偏移位置。
	 */
	private int xOffsetForFlipper;

	/**
	 * Animation used by the incoming view when the user navigates to a left
	 * page. 傳入頁面移動向左邊的動畫
	 */
	private Animation mInAnimPushLeft;

	/**
	 * Animation used by the incoming view when the user navigates to a right
	 * page. 傳入頁面移動向右邊的動畫
	 */
	private Animation mInAnimPushRight;

	/**
	 * Animation used by the incoming view when the user navigates to a page
	 * above. If the page navigation is triggered by DOWN key, this animation is
	 * used. 傳入頁面移動向上的動畫
	 */
	private Animation mInAnimPushUp;

	/**
	 * Animation used by the incoming view when the user navigates to a page
	 * below. If the page navigation is triggered by UP key, this animation is
	 * used. 傳入頁面移動向下的動畫
	 */
	private Animation mInAnimPushDown;

	/**
	 * Animation used by the outgoing view when the user navigates to a left
	 * page. 傳出頁面移動向左邊的動畫
	 */
	private Animation mOutAnimPushLeft;

	/**
	 * Animation used by the outgoing view when the user navigates to a right
	 * page.傳出頁面移動向右邊的動畫
	 */
	private Animation mOutAnimPushRight;

	/**
	 * Animation used by the outgoing view when the user navigates to a page
	 * above. If the page navigation is triggered by DOWN key, this animation is
	 * used.傳出頁面移動向上邊的動畫
	 */
	private Animation mOutAnimPushUp;

	/**
	 * Animation used by the incoming view when the user navigates to a page
	 * below. If the page navigation is triggered by UP key, this animation is
	 * used.傳出頁面移動向下邊的動畫
	 */
	private Animation mOutAnimPushDown;

	/**
	 * Animation object which is used for the incoming view currently.
	 * 傳入頁面當前使用的動畫
	 */
	private Animation mInAnimInUse;

	/**
	 * Animation object which is used for the outgoing view currently.
	 * 傳出頁面當前使用的動畫
	 */
	private Animation mOutAnimInUse;

	/**
	 * Current page number in display. 當前顯示的頁碼
	 */
	private int mCurrentPage = -1;

	public CandidatesContainer(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public void initialize(CandidateViewListener cvListener,
			BalloonHint balloonHint, GestureDetector gestureDetector) {
		mCvListener = cvListener;

		mLeftArrowBtn = (ImageButton) findViewById(R.id.arrow_left_btn);
		mRightArrowBtn = (ImageButton) findViewById(R.id.arrow_right_btn);
		mLeftArrowBtn.setOnTouchListener(this);
		mRightArrowBtn.setOnTouchListener(this);

		mFlipper = (ViewFlipper) findViewById(R.id.candidate_flipper);
		mFlipper.setMeasureAllChildren(true);

		invalidate();
		requestLayout();

		for (int i = 0; i < mFlipper.getChildCount(); i++) {
			CandidateView cv = (CandidateView) mFlipper.getChildAt(i);
			cv.initialize(this, balloonHint, gestureDetector, mCvListener);
		}
	}

	/**
	 * 顯示候選詞
	 * 
	 * @param decInfo
	 * @param enableActiveHighlight
	 */
	public void showCandidates(PinyinIME.DecodingInfo decInfo,
			boolean enableActiveHighlight) {
		if (null == decInfo)
			return;
		mDecInfo = decInfo;
		mCurrentPage = 0;

		if (decInfo.isCandidatesListEmpty()) {
			showArrow(mLeftArrowBtn, false);
			showArrow(mRightArrowBtn, false);
		} else {
			showArrow(mLeftArrowBtn, true);
			showArrow(mRightArrowBtn, true);
		}

		for (int i = 0; i < mFlipper.getChildCount(); i++) {
			CandidateView cv = (CandidateView) mFlipper.getChildAt(i);
			cv.setDecodingInfo(mDecInfo);
		}
		stopAnimation();

		CandidateView cv = (CandidateView) mFlipper.getCurrentView();
		cv.showPage(mCurrentPage, 0, enableActiveHighlight);

		updateArrowStatus();
		invalidate();
	}

	/**
	 * 獲取當前的頁碼
	 * 
	 * @return
	 */
	public int getCurrentPage() {
		return mCurrentPage;
	}

	/**
	 * 設定候選詞是否高亮
	 * 
	 * @param enableActiveHighlight
	 */
	public void enableActiveHighlight(boolean enableActiveHighlight) {
		CandidateView cv = (CandidateView) mFlipper.getCurrentView();
		cv.enableActiveHighlight(enableActiveHighlight);
		invalidate();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		Environment env = Environment.getInstance();
		int measuredWidth = env.getScreenWidth();
		int measuredHeight = getPaddingTop();
		measuredHeight += env.getHeightForCandidates();
		widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,
				MeasureSpec.EXACTLY);
		heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight,
				MeasureSpec.EXACTLY);
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		if (null != mLeftArrowBtn) {
			// 設定候選詞所在的 ViewFlipper 在集裝箱中的偏移位置
			xOffsetForFlipper = mLeftArrowBtn.getMeasuredWidth();
		}
	}

	/**
	 * 高亮位置向上一個候選詞移動或者移動到上一頁的最後一個候選詞的位置。
	 * 
	 * @return
	 */
	public boolean activeCurseBackward() {
		if (mFlipper.isFlipping() || null == mDecInfo) {
			return false;
		}

		CandidateView cv = (CandidateView) mFlipper.getCurrentView();

		if (cv.activeCurseBackward()) {
			cv.invalidate();
			return true;
		} else {
			return pageBackward(true, true);
		}
	}

	/**
	 * 高亮位置向下一個候選詞移動或者移動到下一頁的第一個候選詞的位置。
	 * 
	 * @return
	 */
	public boolean activeCurseForward() {
		if (mFlipper.isFlipping() || null == mDecInfo) {
			return false;
		}

		CandidateView cv = (CandidateView) mFlipper.getCurrentView();

		if (cv.activeCursorForward()) {
			cv.invalidate();
			return true;
		} else {
			return pageForward(true, true);
		}
	}

	/**
	 * 到上一頁候選詞
	 * 
	 * @param animLeftRight
	 *            高亮位置是否到本頁最後一個候選詞位置
	 * @param enableActiveHighlight
	 * @return
	 */
	public boolean pageBackward(boolean animLeftRight,
			boolean enableActiveHighlight) {
		if (null == mDecInfo)
			return false;

		if (mFlipper.isFlipping() || 0 == mCurrentPage)
			return false;

		int child = mFlipper.getDisplayedChild();
		int childNext = (child + 1) % 2;
		CandidateView cv = (CandidateView) mFlipper.getChildAt(child);
		CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext);

		mCurrentPage--;
		int activeCandInPage = cv.getActiveCandiatePosInPage();
		if (animLeftRight)
			activeCandInPage = mDecInfo.mPageStart.elementAt(mCurrentPage + 1)
					- mDecInfo.mPageStart.elementAt(mCurrentPage) - 1;

		cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight);
		loadAnimation(animLeftRight, false);
		startAnimation();

		updateArrowStatus();
		return true;
	}

	/**
	 * 到下一頁候選詞
	 * 
	 * @param animLeftRight
	 *            高亮位置是否到本頁第一個候選詞位置
	 * @param enableActiveHighlight
	 * @return
	 */
	public boolean pageForward(boolean animLeftRight,
			boolean enableActiveHighlight) {
		if (null == mDecInfo)
			return false;

		if (mFlipper.isFlipping() || !mDecInfo.preparePage(mCurrentPage + 1)) {
			return false;
		}

		int child = mFlipper.getDisplayedChild();
		int childNext = (child + 1) % 2;
		CandidateView cv = (CandidateView) mFlipper.getChildAt(child);
		int activeCandInPage = cv.getActiveCandiatePosInPage();
		cv.enableActiveHighlight(enableActiveHighlight);

		CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext);
		mCurrentPage++;
		if (animLeftRight)
			activeCandInPage = 0;

		cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight);
		loadAnimation(animLeftRight, true);
		startAnimation();

		updateArrowStatus();
		return true;
	}

	/**
	 * 獲取活動(高亮)的候選詞在所有候選詞中的位置
	 * 
	 * @return
	 */
	public int getActiveCandiatePos() {
		if (null == mDecInfo)
			return -1;
		CandidateView cv = (CandidateView) mFlipper.getCurrentView();
		return cv.getActiveCandiatePosGlobal();
	}

	/**
	 * 更新箭頭顯示
	 */
	public void updateArrowStatus() {
		if (mCurrentPage < 0)
			return;
		boolean forwardEnabled = mDecInfo.pageForwardable(mCurrentPage);
		boolean backwardEnabled = mDecInfo.pageBackwardable(mCurrentPage);

		if (backwardEnabled) {
			enableArrow(mLeftArrowBtn, true);
		} else {
			enableArrow(mLeftArrowBtn, false);
		}
		if (forwardEnabled) {
			enableArrow(mRightArrowBtn, true);
		} else {
			enableArrow(mRightArrowBtn, false);
		}
	}

	/**
	 * 設定箭頭圖示是否有效,和圖示的透明度。
	 * 
	 * @param arrowBtn
	 * @param enabled
	 */
	private void enableArrow(ImageButton arrowBtn, boolean enabled) {
		arrowBtn.setEnabled(enabled);
		if (enabled)
			arrowBtn.setAlpha(ARROW_ALPHA_ENABLED);
		else
			arrowBtn.setAlpha(ARROW_ALPHA_DISABLED);
	}

	/**
	 * 設定箭頭圖示是否顯示
	 * 
	 * @param arrowBtn
	 * @param show
	 */
	private void showArrow(ImageButton arrowBtn, boolean show) {
		if (show)
			arrowBtn.setVisibility(View.VISIBLE);
		else
			arrowBtn.setVisibility(View.INVISIBLE);
	}

	/**
	 * view的觸控事件監聽器
	 * 
	 * @param v
	 * @param event
	 */
	public boolean onTouch(View v, MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			if (v == mLeftArrowBtn) {
				// 呼叫候選詞檢視監聽器的向右滑動手勢處理函式
				mCvListener.onToRightGesture();
			} else if (v == mRightArrowBtn) {
				// 呼叫候選詞檢視監聽器的向左滑動手勢處理函式
				mCvListener.onToLeftGesture();
			}
		} else if (event.getAction() == MotionEvent.ACTION_UP) {
			// 設定候選詞檢視高亮活動的候選詞。
			CandidateView cv = (CandidateView) mFlipper.getCurrentView();
			cv.enableActiveHighlight(true);
		}

		return false;
	}

	// The reason why we handle candiate view's touch events here is because
	// that the view under the focused view may get touch events instead of the
	// focused one.
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO 觸控事件的座標點是以哪裡為原點?
		// 調整觸控事件座標點的座標
		event.offsetLocation(-xOffsetForFlipper, 0);
		// 呼叫候選詞檢視觸控事件處理函式
		CandidateView cv = (CandidateView) mFlipper.getCurrentView();
		cv.onTouchEventReal(event);
		return true;
	}

	/**
	 * 建立動畫,並設定給ViewFlipper mFlipper。
	 * 
	 * @param animLeftRight
	 * @param forward
	 */
	public void loadAnimation(boolean animLeftRight, boolean forward) {
		if (animLeftRight) {
			if (forward) {
				if (null == mInAnimPushLeft) {
					mInAnimPushLeft = createAnimation(1.0f, 0, 0, 0, 0, 1.0f,
							ANIMATION_TIME);
					mOutAnimPushLeft = createAnimation(0, -1.0f, 0, 0, 1.0f, 0,
							ANIMATION_TIME);
				}
				mInAnimInUse = mInAnimPushLeft;
				mOutAnimInUse = mOutAnimPushLeft;
			} else {
				if (null == mInAnimPushRight) {
					mInAnimPushRight = createAnimation(-1.0f, 0, 0, 0, 0, 1.0f,
							ANIMATION_TIME);
					mOutAnimPushRight = createAnimation(0, 1.0f, 0, 0, 1.0f, 0,
							ANIMATION_TIME);
				}
				mInAnimInUse = mInAnimPushRight;
				mOutAnimInUse = mOutAnimPushRight;
			}
		} else {
			if (forward) {
				if (null == mInAnimPushUp) {
					mInAnimPushUp = createAnimation(0, 0, 1.0f, 0, 0, 1.0f,
							ANIMATION_TIME);
					mOutAnimPushUp = createAnimation(0, 0, 0, -1.0f, 1.0f, 0,
							ANIMATION_TIME);
				}
				mInAnimInUse = mInAnimPushUp;
				mOutAnimInUse = mOutAnimPushUp;
			} else {
				if (null == mInAnimPushDown) {
					mInAnimPushDown = createAnimation(0, 0, -1.0f, 0, 0, 1.0f,
							ANIMATION_TIME);
					mOutAnimPushDown = createAnimation(0, 0, 0, 1.0f, 1.0f, 0,
							ANIMATION_TIME);
				}
				mInAnimInUse = mInAnimPushDown;
				mOutAnimInUse = mOutAnimPushDown;
			}
		}

		// 設定動畫監聽器,當動畫結束的時候,呼叫onAnimationEnd()。
		mInAnimInUse.setAnimationListener(this);

		mFlipper.setInAnimation(mInAnimInUse);
		mFlipper.setOutAnimation(mOutAnimInUse);
	}

	/**
	 * 建立移動動畫
	 * 
	 * @param xFrom
	 * @param xTo
	 * @param yFrom
	 * @param yTo
	 * @param alphaFrom
	 * @param alphaTo
	 * @param duration
	 * @return
	 */
	private Animation createAnimation(float xFrom, float xTo, float yFrom,
			float yTo, float alphaFrom, float alphaTo, long duration) {
		AnimationSet animSet = new AnimationSet(getContext(), null);
		Animation trans = new TranslateAnimation(Animation.RELATIVE_TO_SELF,
				xFrom, Animation.RELATIVE_TO_SELF, xTo,
				Animation.RELATIVE_TO_SELF, yFrom, Animation.RELATIVE_TO_SELF,
				yTo);
		Animation alpha = new AlphaAnimation(alphaFrom, alphaTo);
		animSet.addAnimation(trans);
		animSet.addAnimation(alpha);
		animSet.setDuration(duration);
		return animSet;
	}

	/**
	 * 開始動畫,mFlipper顯示下一個。
	 */
	private void startAnimation() {
		mFlipper.showNext();
	}

	/**
	 * 停止動畫,mFlipper停止切換Flipping。
	 */
	private void stopAnimation() {
		mFlipper.stopFlipping();
	}

	/**
	 * 動畫監聽器:動畫停止的時候的監聽器回撥。
	 */
	public void onAnimationEnd(Animation animation) {
		if (!mLeftArrowBtn.isPressed() && !mRightArrowBtn.isPressed()) {
			CandidateView cv = (CandidateView) mFlipper.getCurrentView();
			cv.enableActiveHighlight(true);
		}
	}

	/**
	 * 動畫監聽器:動畫重複的時候的監聽器回撥。
	 */
	public void onAnimationRepeat(Animation animation) {
	}

	/**
	 * 動畫監聽器:動畫開始的時候的監聽器回撥。
	 */
	public void onAnimationStart(Animation animation) {
	}
}


3、CandidateView.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.util.Vector;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import com.keanbin.pinyinime.PinyinIME.DecodingInfo;

/**
 * View to show candidate list. There two candidate view instances which are
 * used to show animation when user navigates between pages.
 */
/**
 * 候選詞檢視
 * 
 * @ClassName CandidateView
 * @author keanbin
 */
public class CandidateView extends View {
	/**
	 * The minimum width to show a item. 一個item最小的寬度
	 */
	private static final float MIN_ITEM_WIDTH = 22;

	/**
	 * Suspension points used to display long items. 省略號
	 */
	private static final String SUSPENSION_POINTS = "...";

	/**
	 * The width to draw candidates. 候選詞區域的寬度
	 */
	private int mContentWidth;

	/**
	 * The height to draw candidate content. 候選詞區域的高度
	 */
	private int mContentHeight;

	/**
	 * Whether footnotes are displayed. Footnote is shown when hardware keyboard
	 * is available. 是否顯示附註。附註是當硬鍵盤有效的時候顯示的。
	 */
	private boolean mShowFootnote = true;

	/**
	 * Balloon hint for candidate press/release. 當候選詞被按下的時候顯示的氣泡
	 */
	private BalloonHint mBalloonHint;

	/**
	 * Desired position of the balloon to the input view. 氣泡顯示的位置
	 */
	private int mHintPositionToInputView[] = new int[2];

	/**
	 * Decoding result to show. 詞庫解碼物件
	 */
	private DecodingInfo mDecInfo;

	/**
	 * Listener used to notify IME that user clicks a candidate, or navigate
	 * between them. 候選詞監聽器
	 */
	private CandidateViewListener mCvListener;

	/**
	 * Used to notify the container to update the status of forward/backward
	 * arrows. 箭頭更新介面。在onDraw()中,當mUpdateArrowStatusWhenDraw為true,
	 * 該介面的updateArrowStatus()方法被呼叫。因為箭頭是放在候選詞集裝箱中的,不是放在候選詞檢視中。
	 */
	private ArrowUpdater mArrowUpdater;

	/**
	 * If true, update the arrow status when drawing candidates.
	 * 在onDraw()的時候是否更新箭頭
	 */
	private boolean mUpdateArrowStatusWhenDraw = false;

	/**
	 * Page number of the page displayed in this view. 候選詞檢視顯示的頁碼
	 */
	private int mPageNo;

	/**
	 * Active candidate position in this page. 活動(高亮)的候選詞在頁面的位置。
	 */
	private int mActiveCandInPage;

	/**
	 * Used to decided whether the active candidate should be highlighted or
	 * not. If user changes focus to composing view (The view to show Pinyin
	 * string), the highlight in candidate view should be removed. 是否高亮活動的候選詞
	 */
	private boolean mEnableActiveHighlight = true;

	/**
	 * The page which is just calculated. 剛剛計算的頁碼
	 */
	private int mPageNoCalculated = -1;

	/**
	 * The Drawable used to display as the background of the high-lighted item.
	 * 高亮顯示的圖片
	 */
	private Drawable mActiveCellDrawable;

	/**
	 * The Drawable used to display as separators between candidates. 分隔符圖片
	 */
	private Drawable mSeparatorDrawable;

	/**
	 * Color to draw normal candidates generated by IME. 正常候選詞的顏色,來自輸入法詞庫的候選詞。
	 */
	private int mImeCandidateColor;

	/**
	 * Color to draw normal candidates Recommended by application.
	 * 推薦候選詞的顏色,推薦的候選詞是來自APP的。
	 */
	private int mRecommendedCandidateColor;

	/**
	 * Color to draw the normal(not highlighted) candidates, it can be one of
	 * {@link #mImeCandidateColor} or {@link #mRecommendedCandidateColor}.
	 * 候選詞的顏色,它可以是 mImeCandidateColor 和 mRecommendedCandidateColor 其中的一個。
	 */
	private int mNormalCandidateColor;

	/**
	 * Color to draw the active(highlighted) candidates, including candidates
	 * from IME and candidates from application. 高亮候選詞的顏色
	 */
	private int mActiveCandidateColor;

	/**
	 * Text size to draw candidates generated by IME. 正常候選詞的文字大小,來自輸入法詞庫的候選詞。
	 */
	private int mImeCandidateTextSize;

	/**
	 * Text size to draw candidates recommended by application.
	 * 推薦候選詞的文字大小,推薦的候選詞是來自APP的。
	 */
	private int mRecommendedCandidateTextSize;

	/**
	 * The current text size to draw candidates. It can be one of
	 * {@link #mImeCandidateTextSize} or {@link #mRecommendedCandidateTextSize}.
	 * 候選詞的文字大小,它可以是 mImeCandidateTextSize 和 mRecommendedCandidateTextSize
	 * 其中的一個。
	 */
	private int mCandidateTextSize;

	/**
	 * Paint used to draw candidates. 候選詞的畫筆
	 */
	private Paint mCandidatesPaint;

	/**
	 * Used to draw footnote. 附註的畫筆
	 */
	private Paint mFootnotePaint;

	/**
	 * The width to show suspension points. 省略號的寬度
	 */
	private float mSuspensionPointsWidth;

	/**
	 * Rectangle used to draw the active candidate. 活動(高亮)候選詞的區域
	 */
	private RectF mActiveCellRect;

	/**
	 * Left and right margins for a candidate. It is specified in xml, and is
	 * the minimum margin for a candidate. The actual gap between two candidates
	 * is 2 * {@link #mCandidateMargin} + {@link #mSeparatorDrawable}.
	 * getIntrinsicWidth(). Because length of candidate is not fixed, there can
	 * be some extra space after the last candidate in the current page. In
	 * order to achieve best look-and-feel, this extra space will be divided and
	 * allocated to each candidates. 候選詞的左邊和右邊間隔
	 */
	private float mCandidateMargin;

	/**
	 * Left and right extra margins for a candidate. 候選詞的左邊和右邊的額外間隔
	 */
	private float mCandidateMarginExtra;

	/**
	 * Rectangles for the candidates in this page. 在本頁候選詞的區域向量列表
	 **/
	private Vector<RectF> mCandRects;

	/**
	 * FontMetricsInt used to measure the size of candidates. 候選詞的字型測量物件
	 */
	private FontMetricsInt mFmiCandidates;

	/**
	 * FontMetricsInt used to measure the size of footnotes. 附註的字型測量物件
	 */
	private FontMetricsInt mFmiFootnote;

	/**
	 * 按下某個候選詞的定時器。
	 */
	private PressTimer mTimer = new PressTimer();

	/**
	 * 手勢識別物件
	 */
	private GestureDetector mGestureDetector;

	/**
	 * 臨時位置資訊
	 */
	private int mLocationTmp[] = new int[2];

	public CandidateView(Context context, AttributeSet attrs) {
		super(context, attrs);

		Resources r = context.getResources();

		// 判斷是否顯示附註
		Configuration conf = r.getConfiguration();
		if (conf.keyboard == Configuration.KEYBOARD_NOKEYS
				|| conf.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
			mShowFootnote = false;
		}

		mActiveCellDrawable = r.getDrawable(R.drawable.candidate_hl_bg);
		mSeparatorDrawable = r.getDrawable(R.drawable.candidates_vertical_line);
		mCandidateMargin = r.getDimension(R.dimen.candidate_margin_left_right);

		mImeCandidateColor = r.getColor(R.color.candidate_color);
		mRecommendedCandidateColor = r
				.getColor(R.color.recommended_candidate_color);
		mNormalCandidateColor = mImeCandidateColor;
		mActiveCandidateColor = r.getColor(R.color.active_candidate_color);

		mCandidatesPaint = new Paint();
		mCandidatesPaint.setAntiAlias(true);

		mFootnotePaint = new Paint();
		mFootnotePaint.setAntiAlias(true);
		mFootnotePaint.setColor(r.getColor(R.color.footnote_color));
		mActiveCellRect = new RectF();

		mCandRects = new Vector<RectF>();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int mOldWidth = getMeasuredWidth();
		int mOldHeight = getMeasuredHeight();

		setMeasuredDimension(
				getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
				getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

		if (mOldWidth != getMeasuredWidth()
				|| mOldHeight != getMeasuredHeight()) {
			onSizeChanged();
		}
	}

	/**
	 * 初始化。
	 * 
	 * @param arrowUpdater
	 * @param balloonHint
	 * @param gestureDetector
	 * @param cvListener
	 */
	public void initialize(ArrowUpdater arrowUpdater, BalloonHint balloonHint,
			GestureDetector gestureDetector, CandidateViewListener cvListener) {
		mArrowUpdater = arrowUpdater;
		mBalloonHint = balloonHint;
		mGestureDetector = gestureDetector;
		mCvListener = cvListener;
	}

	/**
	 * 根據候選詞的來源設定候選詞使用的顏色和文字大小,並計算省略號的寬度。
	 * 
	 * @param decInfo
	 */
	public void setDecodingInfo(DecodingInfo decInfo) {
		if (null == decInfo)
			return;
		mDecInfo = decInfo;
		mPageNoCalculated = -1;

		// 根據候選詞來源設定候選詞使用的顏色和文字大小
		if (mDecInfo.candidatesFromApp()) {
			mNormalCandidateColor = mRecommendedCandidateColor;
			mCandidateTextSize = mRecommendedCandidateTextSize;
		} else {
			mNormalCandidateColor = mImeCandidateColor;
			mCandidateTextSize = mImeCandidateTextSize;
		}

		if (mCandidatesPaint.getTextSize() != mCandidateTextSize) {
			// 計算省略號寬度
			mCandidatesPaint.setTextSize(mCandidateTextSize);
			mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
			mSuspensionPointsWidth = mCandidatesPaint
					.measureText(SUSPENSION_POINTS);
		}

		// Remove any pending timer for the previous list.
		mTimer.removeTimer();
	}

	/**
	 * 獲取活動(高亮)的候選詞在頁面的位置。
	 * 
	 * @return
	 */
	public int getActiveCandiatePosInPage() {
		return mActiveCandInPage;
	}

	/**
	 * 獲取活動(高亮)的候選詞在所有候選詞中的位置
	 * 
	 * @return
	 */
	public int getActiveCandiatePosGlobal() {
		return mDecInfo.mPageStart.get(mPageNo) + mActiveCandInPage;
	}

	/**
	 * Show a page in the decoding result set previously.
	 * 
	 * @param pageNo
	 *            Which page to show.
	 * @param activeCandInPage
	 *            Which candidate should be set as active item.
	 * @param enableActiveHighlight
	 *            When false, active item will not be highlighted.
	 */
	/**
	 * 顯示指定頁的候選詞
	 * 
	 * @param pageNo
	 * @param activeCandInPage
	 * @param enableActiveHighlight
	 */
	public void showPage(int pageNo, int activeCandInPage,
			boolean enableActiveHighlight) {
		if (null == mDecInfo)
			return;
		mPageNo = pageNo;
		mActiveCandInPage = activeCandInPage;
		if (mEnableActiveHighlight != enableActiveHighlight) {
			mEnableActiveHighlight = enableActiveHighlight;
		}

		if (!calculatePage(mPageNo)) {
			mUpdateArrowStatusWhenDraw = true;
		} else {
			mUpdateArrowStatusWhenDraw = false;
		}

		invalidate();
	}

	/**
	 * 設定是否高亮候選詞
	 * 
	 * @param enableActiveHighlight
	 */
	public void enableActiveHighlight(boolean enableActiveHighlight) {
		if (enableActiveHighlight == mEnableActiveHighlight)
			return;

		mEnableActiveHighlight = enableActiveHighlight;
		invalidate();
	}

	/**
	 * 高亮位置向下一個候選詞移動。
	 * 
	 * @return
	 */
	public boolean activeCursorForward() {
		if (!mDecInfo.pageReady(mPageNo))
			return false;
		int pageSize = mDecInfo.mPageStart.get(mPageNo + 1)
				- mDecInfo.mPageStart.get(mPageNo);
		if (mActiveCandInPage + 1 < pageSize) {
			showPage(mPageNo, mActiveCandInPage + 1, true);
			return true;
		}
		return false;
	}

	/**
	 * 高亮位置向上一個候選詞移動。
	 * 
	 * @return
	 */
	public boolean activeCurseBackward() {
		if (mActiveCandInPage > 0) {
			showPage(mPageNo, mActiveCandInPage - 1, true);
			return true;
		}
		return false;
	}

	/**
	 * 計算候選詞區域的寬度和高度、候選詞文字大小、附註文字大小、省略號寬度。當尺寸發生改變時呼叫。在onMeasure()中呼叫。
	 */
	private void onSizeChanged() {
		// 計算候選詞區域的寬度和高度
		mContentWidth = getMeasuredWidth() - getPaddingLeft()
				- getPaddingRight();
		mContentHeight = (int) ((getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) * 0.95f);

		/**
		 * How to decide the font size if the height for display is given? Now
		 * it is implemented in a stupid way.
		 */
		// 根據候選詞區域高度來計算候選詞應該使用的文字大小
		int textSize = 1;
		mCandidatesPaint.setTextSize(textSize);
		mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
		while (mFmiCandidates.bottom - mFmiCandidates.top < mContentHeight) {
			textSize++;
			mCandidatesPaint.setTextSize(textSize);
			mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
		}

		// 設定計算出的候選詞文字大小
		mImeCandidateTextSize = textSize;
		mRecommendedCandidateTextSize = textSize * 3 / 4;
		if (null == mDecInfo) {
			// 計算省略號的寬度
			mCandidateTextSize = mImeCandidateTextSize;
			mCandidatesPaint.setTextSize(mCandidateTextSize);
			mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
			mSuspensionPointsWidth = mCandidatesPaint
					.measureText(SUSPENSION_POINTS);
		} else {
			// Reset the decoding information to update members for painting.
			setDecodingInfo(mDecInfo);
		}

		// 計算附註文字的大小
		textSize = 1;
		mFootnotePaint.setTextSize(textSize);
		mFmiFootnote = mFootnotePaint.getFontMetricsInt();
		while (mFmiFootnote.bottom - mFmiFootnote.top < mContentHeight / 2) {
			textSize++;
			mFootnotePaint.setTextSize(textSize);
			mFmiFootnote = mFootnotePaint.getFontMetricsInt();
		}
		textSize--;
		mFootnotePaint.setTextSize(textSize);
		mFmiFootnote = mFootnotePaint.getFontMetricsInt();

		// When the size is changed, the first page will be displayed.
		mPageNo = 0;
		mActiveCandInPage = 0;
	}

	/**
	 * 對還沒有分頁的候選詞進行分頁,計算指定頁的候選詞左右的額外間隔。
	 * 
	 * @param pageNo
	 * @return
	 */
	private boolean calculatePage(int pageNo) {
		if (pageNo == mPageNoCalculated)
			return true;

		// 計算候選詞區域寬度和高度
		mContentWidth = getMeasuredWidth() - getPaddingLeft()
				- getPaddingRight();
		mContentHeight = (int) ((getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) * 0.95f);

		if (mContentWidth <= 0 || mContentHeight <= 0)
			return false;

		// 候選詞列表的size,即候選詞的數量。
		int candSize = mDecInfo.mCandidatesList.size();

		// If the size of page exists, only calculate the extra margin.
		boolean onlyExtraMargin = false;
		int fromPage = mDecInfo.mPageStart.size() - 1;
		if (mDecInfo.mPageStart.size() > pageNo + 1) {
			// pageNo是最後一頁之前的頁碼,不包括最後一頁
			onlyExtraMargin = true;
			fromPage = pageNo;
		}

		// If the previous pages have no information, calculate them first.
		for (int p = fromPage; p <= pageNo; p++) {
			int pStart = mDecInfo.mPageStart.get(p);
			int pSize = 0;
			int charNum = 0;
			float lastItemWidth = 0;

			float xPos;
			xPos = 0;
			xPos += mSeparatorDrawable.getIntrinsicWidth();
			while (xPos < mContentWidth && pStart + pSize < candSize) {
				int itemPos = pStart + pSize;
				String itemStr = mDecInfo.mCandidatesList.get(itemPos);
				float itemWidth = mCandidatesPaint.measureText(itemStr);
				if (itemWidth < MIN_ITEM_WIDTH)
					itemWidth = MIN_ITEM_WIDTH;

				itemWidth += mCandidateMargin * 2;
				itemWidth += mSeparatorDrawable.getIntrinsicWidth();
				if (xPos + itemWidth < mContentWidth || 0 == pSize) {
					xPos += itemWidth;
					lastItemWidth = itemWidth;
					pSize++;
					charNum += itemStr.length();
				} else {
					break;
				}
			}
			if (!onlyExtraMargin) {
				// pageNo是最後一頁或者往後的一頁,這裡應該就是對候選詞進行分頁的地方,保證每頁候選詞都能正常顯示。
				mDecInfo.mPageStart.add(pStart + pSize);
				mDecInfo.mCnToPage.add(mDecInfo.mCnToPage.get(p) + charNum);
			}

			// 計算候選詞的左右間隔
			float marginExtra = (mContentWidth - xPos) / pSize / 2;

			if (mContentWidth - xPos > lastItemWidth) {
				// Must be the last page, because if there are more items,
				// the next item's width must be less than lastItemWidth.
				// In this case, if the last margin is less than the current
				// one, the last margin can be used, so that the
				// look-and-feeling will be the same as the previous page.
				if (mCandidateMarginExtra <= marginExtra) {
					marginExtra = mCandidateMarginExtra;
				}
			} else if (pSize == 1) {
				marginExtra = 0;
			}
			mCandidateMarginExtra = marginExtra;
		}
		mPageNoCalculated = pageNo;
		return true;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		// The invisible candidate view(the one which is not in foreground) can
		// also be called to drawn, but its decoding result and candidate list
		// may be empty.
		if (null == mDecInfo || mDecInfo.isCandidatesListEmpty())
			return;

		// Calculate page. If the paging information is ready, the function will
		// return at once.
		calculatePage(mPageNo);

		int pStart = mDecInfo.mPageStart.get(mPageNo);
		int pSize = mDecInfo.mPageStart.get(mPageNo + 1) - pStart;
		float candMargin = mCandidateMargin + mCandidateMarginExtra;
		if (mActiveCandInPage > pSize - 1) {
			mActiveCandInPage = pSize - 1;
		}

		mCandRects.removeAllElements();

		float xPos = getPaddingLeft();
		int yPos = (getMeasuredHeight() - (mFmiCandidates.bottom - mFmiCandidates.top))
				/ 2 - mFmiCandidates.top;
		xPos += drawVerticalSeparator(canvas, xPos);
		for (int i = 0; i < pSize; i++) {
			float footnoteSize = 0;
			String footnote = null;
			if (mShowFootnote) {
				footnote = Integer.toString(i + 1);
				footnoteSize = mFootnotePaint.measureText(footnote);
				assert (footnoteSize < candMargin);
			}
			String cand = mDecInfo.mCandidatesList.get(pStart + i);
			float candidateWidth = mCandidatesPaint.measureText(cand);
			float centerOffset = 0;
			if (candidateWidth < MIN_ITEM_WIDTH) {
				centerOffset = (MIN_ITEM_WIDTH - candidateWidth) / 2;
				candidateWidth = MIN_ITEM_WIDTH;
			}

			float itemTotalWidth = candidateWidth + 2 * candMargin;

			// 畫高亮背景
			if (mActiveCandInPage == i && mEnableActiveHighlight) {
				mActiveCellRect.set(xPos, getPaddingTop() + 1, xPos
						+ itemTotalWidth, getHeight() - getPaddingBottom() - 1);
				mActiveCellDrawable.setBounds((int) mActiveCellRect.left,
						(int) mActiveCellRect.top, (int) mActiveCellRect.right,
						(int) mActiveCellRect.bottom);
				mActiveCellDrawable.draw(canvas);
			}

			if (mCandRects.size() < pSize)
				mCandRects.add(new RectF());
			mCandRects.elementAt(i).set(xPos - 1, yPos + mFmiCandidates.top,
					xPos + itemTotalWidth + 1, yPos + mFmiCandidates.bottom);

			// Draw footnote
			if (mShowFootnote) {
				// 畫附註
				canvas.drawText(footnote, xPos + (candMargin - footnoteSize)
						/ 2, yPos, mFootnotePaint);
			}

			// Left margin
			xPos += candMargin;
			if (candidateWidth > mContentWidth - xPos - centerOffset) {
				cand = getLimitedCandidateForDrawing(cand, mContentWidth - xPos
						- centerOffset);
			}
			if (mActiveCandInPage == i && mEnableActiveHighlight) {
				mCandidatesPaint.setColor(mActiveCandidateColor);
			} else {
				mCandidatesPaint.setColor(mNormalCandidateColor);
			}
			// 畫候選詞
			canvas.drawText(cand, xPos + centerOffset, yPos, mCandidatesPaint);

			// Candidate and right margin
			xPos += candidateWidth + candMargin;

			// Draw the separator between candidates.
			// 畫分隔符
			xPos += drawVerticalSeparator(canvas, xPos);
		}

		// Update the arrow status of the container.
		if (null != mArrowUpdater && mUpdateArrowStatusWhenDraw) {
			mArrowUpdater.updateArrowStatus();
			mUpdateArrowStatusWhenDraw = false;
		}
	}

	/**
	 * 擷取要顯示的候選詞短語+省略號
	 * 
	 * @param rawCandidate
	 * @param widthToDraw
	 * @return
	 */
	private String getLimitedCandidateForDrawing(String rawCandidate,
			float widthToDraw) {
		int subLen = rawCandidate.length();
		if (subLen <= 1)
			return rawCandidate;
		do {
			subLen--;
			float width = mCandidatesPaint.measureText(rawCandidate, 0, subLen);
			if (width + mSuspensionPointsWidth <= widthToDraw || 1 >= subLen) {
				return rawCandidate.substring(0, subLen) + SUSPENSION_POINTS;
			}
		} while (true);
	}

	/**
	 * 畫分隔符
	 * 
	 * @param canvas
	 * @param xPos
	 * @return
	 */
	private float drawVerticalSeparator(Canvas canvas, float xPos) {
		mSeparatorDrawable.setBounds((int) xPos, getPaddingTop(), (int) xPos
				+ mSeparatorDrawable.getIntrinsicWidth(), getMeasuredHeight()
				- getPaddingBottom());
		mSeparatorDrawable.draw(canvas);
		return mSeparatorDrawable.getIntrinsicWidth();
	}

	/**
	 * 返回座標點所在或者離的最近的候選詞區域在mCandRects的索引
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	private int mapToItemInPage(int x, int y) {
		// mCandRects.size() == 0 happens when the page is set, but
		// touch events occur before onDraw(). It usually happens with
		// monkey test.
		if (!mDecInfo.pageReady(mPageNo) || mPageNoCalculated != mPageNo
				|| mCandRects.size() == 0) {
			return -1;
		}

		int pageStart = mDecInfo.mPageStart.get(mPageNo);
		int pageSize = mDecInfo.mPageStart.get(mPageNo + 1) - pageStart;
		if (mCandRects.size() < pageSize) {
			return -1;
		}

		// If not found, try to find the nearest one.
		float nearestDis = Float.MAX_VALUE;
		int nearest = -1;
		for (int i = 0; i < pageSize; i++) {
			RectF r = mCandRects.elementAt(i);
			if (r.left < x && r.right > x && r.top < y && r.bottom > y) {
				return i;
			}
			float disx = (r.left + r.right) / 2 - x;
			float disy = (r.top + r.bottom) / 2 - y;
			float dis = disx * disx + disy * disy;
			if (dis < nearestDis) {
				nearestDis = dis;
				nearest = i;
			}
		}

		return nearest;
	}

	// Because the candidate view under the current focused one may also get
	// touching events. Here we just bypass the event to the container and let
	// it decide which view should handle the event.
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}

	/**
	 * 候選詞檢視觸控事件處理。在候選詞集裝箱 CandidatesContainer 中的觸控事件處理函式中呼叫。
	 * 
	 * @param event
	 * @return
	 */
	public boolean onTouchEventReal(MotionEvent event) {
		// The page in the background can also be touched.
		if (null == mDecInfo || !mDecInfo.pageReady(mPageNo)
				|| mPageNoCalculated != mPageNo)
			return true;

		int x, y;
		x = (int) event.getX();
		y = (int) event.getY();

		// 手勢處理
		if (mGestureDetector.onTouchEvent(event)) {
			mTimer.removeTimer();
			mBalloonHint.delayedDismiss(0);
			return true;
		}

		int clickedItemInPage = -1;

		switch (event.getAction()) {
		case MotionEvent.ACTION_UP:
			// 通知上層選擇了候選詞,並關閉氣泡
			clickedItemInPage = mapToItemInPage(x, y);
			if (clickedItemInPage >= 0) {
				invalidate();
				mCvListener.onClickChoice(clickedItemInPage
						+ mDecInfo.mPageStart.get(mPageNo));
			}
			mBalloonHint.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
			break;

		case MotionEvent.ACTION_DOWN:
			// 顯示氣泡,啟動按下定時器更新按下候選詞高亮效果。
			clickedItemInPage = mapToItemInPage(x, y);
			if (clickedItemInPage >= 0) {
				showBalloon(clickedItemInPage, true);
				mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
						clickedItemInPage);
			}
			break;

		case MotionEvent.ACTION_CANCEL:
			break;

		case MotionEvent.ACTION_MOVE:
			clickedItemInPage = mapToItemInPage(x, y);
			if (clickedItemInPage >= 0
					&& (clickedItemInPage != mTimer.getActiveCandOfPageToShow() || mPageNo != mTimer
							.getPageToShow())) {
				showBalloon(clickedItemInPage, true);
				mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
						clickedItemInPage);
			}
		}
		return true;
	}

	/**
	 * 顯示氣泡
	 * 
	 * @param candPos
	 * @param delayedShow
	 */
	private void showBalloon(int candPos, boolean delayedShow) {
		mBalloonHint.removeTimer();

		RectF r = mCandRects.elementAt(candPos);
		int desired_width = (int) (r.right - r.left);
		int desired_height = (int) (r.bottom - r.top);
		mBalloonHint.setBalloonConfig(
				mDecInfo.mCandidatesList.get(mDecInfo.mPageStart.get(mPageNo)
						+ candPos), 44, true, mImeCandidateColor,
				desired_width, desired_height);

		getLocationOnScreen(mLocationTmp);
		mHintPositionToInputView[0] = mLocationTmp[0]
				+ (int) (r.left - (mBalloonHint.getWidth() - desired_width) / 2);
		mHintPositionToInputView[1] = -mBalloonHint.getHeight();

		long delay = BalloonHint.TIME_DELAY_SHOW;
		if (!delayedShow)
			delay = 0;
		mBalloonHint.dismiss();
		if (!mBalloonHint.isShowing()) {
			mBalloonHint.delayedShow(delay, mHintPositionToInputView);
		} else {
			mBalloonHint.delayedUpdate(0, mHintPositionToInputView, -1, -1);
		}
	}

	/**
	 * 按下某個候選詞的定時器。主要是重新整理頁面,顯示按下的候選詞為高亮狀態。
	 * 
	 * @ClassName PressTimer
	 * @author keanbin
	 */
	private class PressTimer extends Handler implements Runnable {
		private boolean mTimerPending = false; // 是否在定時器執行期間
		private int mPageNoToShow; // 顯示的頁碼
		private int mActiveCandOfPage; // 高亮候選詞在頁面的位置

		public PressTimer() {
			super();
		}

		public void startTimer(long afterMillis, int pageNo, int activeInPage) {
			mTimer.removeTimer();
			postDelayed(this, afterMillis);
			mTimerPending = true;
			mPageNoToShow = pageNo;
			mActiveCandOfPage = activeInPage;
		}

		public int getPageToShow() {
			return mPageNoToShow;
		}

		public int getActiveCandOfPageToShow() {
			return mActiveCandOfPage;
		}

		public boolean removeTimer() {
			if (mTimerPending) {
				mTimerPending = false;
				removeCallbacks(this);
				return true;
			}
			return false;
		}

		public boolean isPending() {
			return mTimerPending;
		}

		public void run() {
			if (mPageNoToShow >= 0 && mActiveCandOfPage >= 0) {
				// Always enable to highlight the clicked one.
				showPage(mPageNoToShow, mActiveCandOfPage, true);
				invalidate();
			}
			mTimerPending = false;
		}
	}
}


4、CandidateViewListener.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

/**
 * Interface to notify the input method when the user clicks a candidate or
 * makes a direction-gesture on candidate view.
 */
/**
 * 候選詞檢視監聽器介面
 * 
 * @ClassName CandidateViewListener
 * @author keanbin
 */
public interface CandidateViewListener {

	/**
	 * 選擇了候選詞的處理函式
	 * 
	 * @param choiceId
	 */
	public void onClickChoice(int choiceId);

	/**
	 * 向左滑動的手勢處理函式
	 */
	public void onToLeftGesture();

	/**
	 * 向右滑動的手勢處理函式
	 */
	public void onToRightGesture();

	/**
	 * 向上滑動的手勢處理函式
	 */
	public void onToTopGesture();

	/**
	 * 向下滑動的手勢處理函式
	 */
	public void onToBottomGesture();
}


5、ComposingView.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;

/**
 * View used to show composing string (The Pinyin string for the unselected
 * syllables and the Chinese string for the selected syllables.)
 */
/**
 * 拼音字串View,用於顯示輸入的拼音。
 * 
 * @ClassName ComposingView
 * @author keanbin
 */
public class ComposingView extends View {
	/**
	 * <p>
	 * There are three statuses for the composing view.
	 * </p>
	 * 
	 * <p>
	 * {@link #SHOW_PINYIN} is used to show the current Pinyin string without
	 * highlighted effect. When user inputs Pinyin characters one by one, the
	 * Pinyin string will be shown in this mode.
	 * </p>
	 * <p>
	 * {@link #SHOW_STRING_LOWERCASE} is used to show the Pinyin string in
	 * lowercase with highlighted effect. When user presses UP key and there is
	 * no fixed Chinese characters, composing view will switch from
	 * {@link #SHOW_PINYIN} to this mode, and in this mode, user can press
	 * confirm key to input the lower-case string, so that user can input
	 * English letter in Chinese mode.
	 * </p>
	 * <p>
	 * {@link #EDIT_PINYIN} is used to edit the Pinyin string (shown with
	 * highlighted effect). When current status is {@link #SHOW_PINYIN} and user
	 * presses UP key, if there are fixed Characters, the input method will
	 * switch to {@link #EDIT_PINYIN} thus user can modify some characters in
	 * the middle of the Pinyin string. If the current status is
	 * {@link #SHOW_STRING_LOWERCASE} and user presses LEFT and RIGHT key, it
	 * will also switch to {@link #EDIT_PINYIN}.
	 * </p>
	 * <p>
	 * Whenever user presses down key, the status switches to
	 * {@link #SHOW_PINYIN}.
	 * </p>
	 * <p>
	 * When composing view's status is {@link #SHOW_PINYIN}, the IME's status is
	 * {@link PinyinIME.ImeState#STATE_INPUT}, otherwise, the IME's status
	 * should be {@link PinyinIME.ImeState#STATE_COMPOSING}.
	 * </p>
	 */
	/**
	 * 拼音字串的狀態
	 */
	public enum ComposingStatus {
		SHOW_PINYIN, SHOW_STRING_LOWERCASE, EDIT_PINYIN,
	}

	private static final int LEFT_RIGHT_MARGIN = 5;

	/**
	 * Used to draw composing string. When drawing the active and idle part of
	 * the spelling(Pinyin) string, the color may be changed.
	 */
	private Paint mPaint;

	/**
	 * Drawable used to draw highlight effect. 高亮
	 */
	private Drawable mHlDrawable;

	/**
	 * Drawable used to draw cursor for editing mode. 游標
	 */
	private Drawable mCursor;

	/**
	 * Used to estimate dimensions to show the string .
	 */
	private FontMetricsInt mFmi;

	private int mStrColor; // 字串普通顏色
	private int mStrColorHl; // 字串高亮顏色
	private int mStrColorIdle; // 字串空閒顏色

	private int mFontSize; // 字型大小

	/**
	 * 獲拼音字串的狀態
	 */
	private ComposingStatus mComposingStatus;

	/**
	 * 解碼操作物件
	 */
	PinyinIME.DecodingInfo mDecInfo;

	public ComposingView(Context context, AttributeSet attrs) {
		super(context, attrs);

		Resources r = context.getResources();
		mHlDrawable = r.getDrawable(R.drawable.composing_hl_bg);
		mCursor = r.getDrawable(R.drawable.composing_area_cursor);

		mStrColor = r.getColor(R.color.composing_color);
		mStrColorHl = r.getColor(R.color.composing_color_hl);
		mStrColorIdle = r.getColor(R.color.composing_color_idle);

		mFontSize = r.getDimensionPixelSize(R.dimen.composing_height);

		mPaint = new Paint();
		mPaint.setColor(mStrColor);
		mPaint.setAntiAlias(true);
		mPaint.setTextSize(mFontSize);

		mFmi = mPaint.getFontMetricsInt();
	}

	/**
	 * 重置拼音字串View狀態
	 */
	public void reset() {
		mComposingStatus = ComposingStatus.SHOW_PINYIN;
	}

	/**
	 * Set the composing string to show. If the IME status is
	 * {@link PinyinIME.ImeState#STATE_INPUT}, the composing view's status will
	 * be set to {@link ComposingStatus#SHOW_PINYIN}, otherwise the composing
	 * view will set its status to {@link ComposingStatus#SHOW_STRING_LOWERCASE}
	 * or {@link ComposingStatus#EDIT_PINYIN} automatically.
	 */
	/**
	 * 設定 解碼操作物件,然後重新整理View。
	 * 
	 * @param decInfo
	 * @param imeStatus
	 */
	public void setDecodingInfo(PinyinIME.DecodingInfo decInfo,
			PinyinIME.ImeState imeStatus) {
		mDecInfo = decInfo;

		if (PinyinIME.ImeState.STATE_INPUT == imeStatus) {
			mComposingStatus = ComposingStatus.SHOW_PINYIN;
			mDecInfo.moveCursorToEdge(false);
		} else {
			if (decInfo.getFixedLen() != 0
					|| ComposingStatus.EDIT_PINYIN == mComposingStatus) {
				mComposingStatus = ComposingStatus.EDIT_PINYIN;
			} else {
				mComposingStatus = ComposingStatus.SHOW_STRING_LOWERCASE;
			}
			mDecInfo.moveCursor(0);
		}

		measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
		requestLayout();
		invalidate();
	}

	/**
	 * 移動游標。其實可以算是對KeyEvent.KEYCODE_DPAD_LEFT和KeyEvent.
	 * KEYCODE_DPAD_RIGHT這兩個鍵的處理函式。
	 * 
	 * @param keyCode
	 * @return
	 */
	public boolean moveCursor(int keyCode) {
		if (keyCode != KeyEvent.KEYCODE_DPAD_LEFT
				&& keyCode != KeyEvent.KEYCODE_DPAD_RIGHT)
			return false;

		if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
			int offset = 0;
			if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT)
				offset = -1;
			else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)
				offset = 1;
			mDecInfo.moveCursor(offset);
		} else if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {
			if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
					|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
				mComposingStatus = ComposingStatus.EDIT_PINYIN;

				measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
				requestLayout();
			}

		}
		invalidate();
		return true;
	}

	/**
	 * 獲取輸入的音字串的狀態
	 * 
	 * @return
	 */
	public ComposingStatus getComposingStatus() {
		return mComposingStatus;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		float width;
		int height;
		height = mFmi.bottom - mFmi.top + getPaddingTop() + getPaddingBottom();

		if (null == mDecInfo) {
			width = 0;
		} else {
			width = getPaddingLeft() + getPaddingRight() + LEFT_RIGHT_MARGIN
					* 2;

			String str;
			if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {
				str = mDecInfo.getOrigianlSplStr().toString();
			} else {
				str = mDecInfo.getComposingStrForDisplay();
			}
			width += mPaint.measureText(str, 0, str.length());
		}
		setMeasuredDimension((int) (width + 0.5f), height);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (ComposingStatus.EDIT_PINYIN == mComposingStatus
				|| ComposingStatus.SHOW_PINYIN == mComposingStatus) {
			drawForPinyin(canvas);
			return;
		}

		// 畫選中的候選詞
		float x, y;
		x = getPaddingLeft() + LEFT_RIGHT_MARGIN;
		y = -mFmi.top + getPaddingTop();

		mPaint.setColor(mStrColorHl);
		mHlDrawable.setBounds(getPaddingLeft(), getPaddingTop(), getWidth()
				- getPaddingRight(), getHeight() - getPaddingBottom());
		mHlDrawable.draw(canvas);

		String splStr = mDecInfo.getOrigianlSplStr().toString();
		canvas.drawText(splStr, 0, splStr.length(), x, y, mPaint);
	}

	/**
	 * 畫游標
	 * 
	 * @param canvas
	 * @param x
	 */
	private void drawCursor(Canvas canvas, float x) {
		mCursor.setBounds((int) x, getPaddingTop(),
				(int) x + mCursor.getIntrinsicWidth(), getHeight()
						- getPaddingBottom());
		mCursor.draw(canvas);
	}

	/**
	 * 畫拼音字串
	 * 
	 * @param canvas
	 */
	private void drawForPinyin(Canvas canvas) {
		float x, y;
		x = getPaddingLeft() + LEFT_RIGHT_MARGIN;
		y = -mFmi.top + getPaddingTop();

		mPaint.setColor(mStrColor);

		int cursorPos = mDecInfo.getCursorPosInCmpsDisplay();
		int cmpsPos = cursorPos;
		String cmpsStr = mDecInfo.getComposingStrForDisplay();
		int activeCmpsLen = mDecInfo.getActiveCmpsDisplayLen();
		if (cursorPos > activeCmpsLen)
			cmpsPos = activeCmpsLen;
		canvas.drawText(cmpsStr, 0, cmpsPos, x, y, mPaint);
		x += mPaint.measureText(cmpsStr, 0, cmpsPos);
		if (cursorPos <= activeCmpsLen) {
			if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
				drawCursor(canvas, x);
			}
			canvas.drawText(cmpsStr, cmpsPos, activeCmpsLen, x, y, mPaint);
		}

		x += mPaint.measureText(cmpsStr, cmpsPos, activeCmpsLen);

		if (cmpsStr.length() > activeCmpsLen) {
			mPaint.setColor(mStrColorIdle);
			int oriPos = activeCmpsLen;
			if (cursorPos > activeCmpsLen) {
				if (cursorPos > cmpsStr.length())
					cursorPos = cmpsStr.length();
				canvas.drawText(cmpsStr, oriPos, cursorPos, x, y, mPaint);
				x += mPaint.measureText(cmpsStr, oriPos, cursorPos);

				if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
					drawCursor(canvas, x);
				}

				oriPos = cursorPos;
			}
			canvas.drawText(cmpsStr, oriPos, cmpsStr.length(), x, y, mPaint);
		}
	}
}


6、EnglishInputProcessor.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.view.KeyEvent;
import android.view.inputmethod.InputConnection;

/**
 * Class to handle English input.
 */
/**
 * 英文輸入法按鍵處理器
 * 
 * @ClassName EnglishInputProcessor
 * @author keanbin
 */
public class EnglishInputProcessor {

	private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;

	/**
	 * 英文輸入法按鍵處理函式
	 * 
	 * @param inputContext
	 * @param event
	 * @param upperCase
	 * @param realAction
	 * @return
	 */
	public boolean processKey(InputConnection inputContext, KeyEvent event,
			boolean upperCase, boolean realAction) {
		if (null == inputContext || null == event)
			return false;

		int keyCode = event.getKeyCode();

		CharSequence prefix = null;
		prefix = inputContext.getTextBeforeCursor(2, 0);

		int keyChar;
		keyChar = 0;
		if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
			keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';
			if (upperCase) {
				keyChar = keyChar + 'A' - 'a';
			}
		} else if (keyCode >= KeyEvent.KEYCODE_0
				&& keyCode <= KeyEvent.KEYCODE_9)
			keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';
		else if (keyCode == KeyEvent.KEYCODE_COMMA)
			keyChar = ',';
		else if (keyCode == KeyEvent.KEYCODE_PERIOD)
			keyChar = '.';
		else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE)
			keyChar = '\'';
		else if (keyCode == KeyEvent.KEYCODE_AT)
			keyChar = '@';
		else if (keyCode == KeyEvent.KEYCODE_SLASH)
			keyChar = '/';

		// 功能鍵處理
		if (0 == keyChar) {
			mLastKeyCode = keyCode;

			String insert = null;
			if (KeyEvent.KEYCODE_DEL == keyCode) {
				if (realAction) {
					inputContext.deleteSurroundingText(1, 0);
				}
			} else if (KeyEvent.KEYCODE_ENTER == keyCode) {
				insert = "\n";
			} else if (KeyEvent.KEYCODE_SPACE == keyCode) {
				insert = " ";
			} else {
				return false;
			}

			if (null != insert && realAction)
				inputContext.commitText(insert, insert.length());

			return true;
		}

		if (!realAction)
			return true;

		// 再一次判斷大小寫,根據mLastKeyCode
		if (KeyEvent.KEYCODE_SHIFT_LEFT == mLastKeyCode
				|| KeyEvent.KEYCODE_SHIFT_LEFT == mLastKeyCode) {
			if (keyChar >= 'a' && keyChar <= 'z')
				keyChar = keyChar - 'a' + 'A';
		} else if (KeyEvent.KEYCODE_ALT_LEFT == mLastKeyCode) {
		}

		String result = String.valueOf((char) keyChar);
		inputContext.commitText(result, result.length());
		mLastKeyCode = keyCode;
		return true;
	}
}


7、Environment.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.Context;
import android.content.res.Configuration;
import android.view.Display;
import android.view.WindowManager;

/**
 * Global environment configurations for showing soft keyboard and candidate
 * view. All original dimension values are defined in float, and the real size
 * is calculated from the float values of and screen size. In this way, this
 * input method can work even when screen size is changed.
 * 該類儲存佈局的一些尺寸。比如:螢幕的寬度、螢幕的高度
 * 、按鍵的高度、候選詞區域的高度、按鍵氣泡寬度比按鍵寬度大的差值、按鍵氣泡高度比按鍵高度大的差值、正常按鍵中文字的大小
 * 、功能按鍵中文字的大小、正常按鍵氣泡中文字的大小、功能按鍵氣泡中文字的大小。
 */
public class Environment {
	/**
	 * The key height for portrait mode. It is relative to the screen height.
	 * 豎屏按鍵高度,值是相對於螢幕高度。
	 */
	private static final float KEY_HEIGHT_RATIO_PORTRAIT = 0.105f;

	/**
	 * The key height for landscape mode. It is relative to the screen height.
	 * 橫屏按鍵高度,值是相對於螢幕高度。
	 */
	private static final float KEY_HEIGHT_RATIO_LANDSCAPE = 0.147f;

	/**
	 * The height of the candidates area for portrait mode. It is relative to
	 * screen height. 豎屏候選詞區域的高度,值是相對於螢幕高度。
	 */
	private static final float CANDIDATES_AREA_HEIGHT_RATIO_PORTRAIT = 0.084f;

	/**
	 * The height of the candidates area for portrait mode. It is relative to
	 * screen height. 橫屏候選詞區域高度,值是相對於螢幕高度。
	 */
	private static final float CANDIDATES_AREA_HEIGHT_RATIO_LANDSCAPE = 0.125f;

	/**
	 * How much should the balloon width be larger than width of the real key.
	 * It is relative to the smaller one of screen width and height.
	 * 猜測:點選軟鍵盤按鈕時彈出來的氣泡大於按鍵的寬度的差值,值是相對於螢幕高度和寬度較小的那一個。
	 */
	private static final float KEY_BALLOON_WIDTH_PLUS_RATIO = 0.08f;

	/**
	 * How much should the balloon height be larger than that of the real key.
	 * It is relative to the smaller one of screen width and height.
	 * 猜測:點選軟鍵盤按鈕時彈出來的氣泡大於按鍵的高度的差值,值是相對於螢幕高度和寬度較小的那一個。
	 */
	private static final float KEY_BALLOON_HEIGHT_PLUS_RATIO = 0.07f;

	/**
	 * The text size for normal keys. It is relative to the smaller one of
	 * screen width and height. 正常按鍵的文字的大小,值是相對於螢幕高度和寬度較小的那一個。
	 */
	private static final float NORMAL_KEY_TEXT_SIZE_RATIO = 0.075f;

	/**
	 * The text size for function keys. It is relative to the smaller one of
	 * screen width and height. 功能按鍵的文字的大小,值是相對於螢幕高度和寬度較小的那一個。
	 */
	private static final float FUNCTION_KEY_TEXT_SIZE_RATIO = 0.055f;

	/**
	 * The text size balloons of normal keys. It is relative to the smaller one
	 * of screen width and height. 正常按鍵彈出的氣泡的文字的大小,值是相對於螢幕高度和寬度較小的那一個。
	 */
	private static final float NORMAL_BALLOON_TEXT_SIZE_RATIO = 0.14f;

	/**
	 * The text size balloons of function keys. It is relative to the smaller
	 * one of screen width and height. 功能按鍵彈出的氣泡的文字的大小,值是相對於螢幕高度和寬度較小的那一個。
	 */
	private static final float FUNCTION_BALLOON_TEXT_SIZE_RATIO = 0.085f;

	/**
	 * The configurations are managed in a singleton. 該類的例項,該類採用設計模式的單例模式。
	 */
	private static Environment mInstance;

	private int mScreenWidth; // 螢幕的寬度
	private int mScreenHeight; // 螢幕的高度
	private int mKeyHeight; // 按鍵的高度
	private int mCandidatesAreaHeight; // 候選詞區域的高度
	private int mKeyBalloonWidthPlus; // 按鍵氣泡寬度比按鍵寬度大的差值
	private int mKeyBalloonHeightPlus; // 按鍵氣泡高度比按鍵高度大的差值
	private int mNormalKeyTextSize; // 正常按鍵中文字的大小
	private int mFunctionKeyTextSize; // 功能按鍵中文字的大小
	private int mNormalBalloonTextSize; // 正常按鍵氣泡中文字的大小
	private int mFunctionBalloonTextSize; // 功能按鍵氣泡中文字的大小
	private Configuration mConfig = new Configuration();
	private boolean mDebug = false;

	private Environment() {
	}

	public static Environment getInstance() {
		if (null == mInstance) {
			mInstance = new Environment();
		}
		return mInstance;
	}

	public void onConfigurationChanged(Configuration newConfig, Context context) {
		if (mConfig.orientation != newConfig.orientation) {
			WindowManager wm = (WindowManager) context
					.getSystemService(Context.WINDOW_SERVICE);
			Display d = wm.getDefaultDisplay();
			mScreenWidth = d.getWidth();
			mScreenHeight = d.getHeight();

			int scale;
			if (mScreenHeight > mScreenWidth) {
				mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_PORTRAIT);
				mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_PORTRAIT);
				scale = mScreenWidth;
			} else {
				mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_LANDSCAPE);
				mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_LANDSCAPE);
				scale = mScreenHeight;
			}
			mNormalKeyTextSize = (int) (scale * NORMAL_KEY_TEXT_SIZE_RATIO);
			mFunctionKeyTextSize = (int) (scale * FUNCTION_KEY_TEXT_SIZE_RATIO);
			mNormalBalloonTextSize = (int) (scale * NORMAL_BALLOON_TEXT_SIZE_RATIO);
			mFunctionBalloonTextSize = (int) (scale * FUNCTION_BALLOON_TEXT_SIZE_RATIO);
			mKeyBalloonWidthPlus = (int) (scale * KEY_BALLOON_WIDTH_PLUS_RATIO);
			mKeyBalloonHeightPlus = (int) (scale * KEY_BALLOON_HEIGHT_PLUS_RATIO);
		}

		mConfig.updateFrom(newConfig);
	}

	public Configuration getConfiguration() {
		return mConfig;
	}

	public int getScreenWidth() {
		return mScreenWidth;
	}

	public int getScreenHeight() {
		return mScreenHeight;
	}

	public int getHeightForCandidates() {
		return mCandidatesAreaHeight;
	}

	public float getKeyXMarginFactor() {
		return 1.0f;
	}

	public float getKeyYMarginFactor() {
		if (Configuration.ORIENTATION_LANDSCAPE == mConfig.orientation) {
			return 0.7f;
		}
		return 1.0f;
	}

	public int getKeyHeight() {
		return mKeyHeight;
	}

	public int getKeyBalloonWidthPlus() {
		return mKeyBalloonWidthPlus;
	}

	public int getKeyBalloonHeightPlus() {
		return mKeyBalloonHeightPlus;
	}

	public int getSkbHeight() {
		if (Configuration.ORIENTATION_PORTRAIT == mConfig.orientation) {
			return mKeyHeight * 4;
		} else if (Configuration.ORIENTATION_LANDSCAPE == mConfig.orientation) {
			return mKeyHeight * 4;
		}
		return 0;
	}

	/**
	 * 獲得按鍵的文字大小
	 * 
	 * @param isFunctionKey
	 *            是否是功能鍵
	 * @return
	 */
	public int getKeyTextSize(boolean isFunctionKey) {
		if (isFunctionKey) {
			return mFunctionKeyTextSize;
		} else {
			return mNormalKeyTextSize;
		}
	}

	public int getBalloonTextSize(boolean isFunctionKey) {
		if (isFunctionKey) {
			return mFunctionBalloonTextSize;
		} else {
			return mNormalBalloonTextSize;
		}
	}

	public boolean hasHardKeyboard() {
		if (mConfig.keyboard == Configuration.KEYBOARD_NOKEYS
				|| mConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
			return false;
		}
		return true;
	}

	public boolean needDebug() {
		return mDebug;
	}
}


8、InputModeSwitcher.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.res.Resources;
import android.view.inputmethod.EditorInfo;

import com.keanbin.pinyinime.SoftKeyboard.KeyRow;

/**
 * 
 * 輸入法模式轉換器。設定輸入法的軟鍵盤。
 * 
 * @author keanbin
 * 
 */
public class InputModeSwitcher {
	/**
	 * User defined key code, used by soft keyboard.
	 * 使用者定義的key的code,用於軟鍵盤。shift鍵的code。
	 */
	private static final int USERDEF_KEYCODE_SHIFT_1 = -1;

	/**
	 * User defined key code, used by soft keyboard. 語言鍵的code
	 */
	private static final int USERDEF_KEYCODE_LANG_2 = -2;

	/**
	 * User defined key code, used by soft keyboard. “?123”鍵的code,作用是與數字鍵盤的切換。
	 */
	private static final int USERDEF_KEYCODE_SYM_3 = -3;

	/**
	 * User defined key code, used by soft keyboard.
	 */
	public static final int USERDEF_KEYCODE_PHONE_SYM_4 = -4;

	/**
	 * User defined key code, used by soft keyboard.
	 */
	private static final int USERDEF_KEYCODE_MORE_SYM_5 = -5;

	/**
	 * User defined key code, used by soft keyboard. 微笑按鍵的code,比如中文時,語言鍵的上面那個按鍵。
	 */
	private static final int USERDEF_KEYCODE_SMILEY_6 = -6;

	/**
	 * Bits used to indicate soft keyboard layout. If none bit is set, the
	 * current input mode does not require a soft keyboard.
	 * 第8位指明軟鍵盤的佈局。如果最8位為0,那麼就表明當前輸入法模式不需要軟鍵盤。
	 **/
	private static final int MASK_SKB_LAYOUT = 0xf0000000;

	/**
	 * A kind of soft keyboard layout. An input mode should be anded with
	 * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明標準的傳統鍵盤
	 */
	private static final int MASK_SKB_LAYOUT_QWERTY = 0x10000000;

	/**
	 * A kind of soft keyboard layout. An input mode should be anded with
	 * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明符號軟鍵盤一
	 */
	private static final int MASK_SKB_LAYOUT_SYMBOL1 = 0x20000000;

	/**
	 * A kind of soft keyboard layout. An input mode should be anded with
	 * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明符號軟鍵盤二
	 */
	private static final int MASK_SKB_LAYOUT_SYMBOL2 = 0x30000000;

	/**
	 * A kind of soft keyboard layout. An input mode should be anded with
	 * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明微笑軟鍵盤
	 */
	private static final int MASK_SKB_LAYOUT_SMILEY = 0x40000000;

	/**
	 * A kind of soft keyboard layout. An input mode should be anded with
	 * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明電話軟鍵盤
	 */
	private static final int MASK_SKB_LAYOUT_PHONE = 0x50000000;

	/**
	 * Used to indicate which language the current input mode is in. If the
	 * current input mode works with a none-QWERTY soft keyboard, these bits are
	 * also used to get language information. For example, a Chinese symbol soft
	 * keyboard and an English one are different in an icon which is used to
	 * tell user the language information. BTW, the smiley soft keyboard mode
	 * should be set with {@link #MASK_LANGUAGE_CN} because it can only be
	 * launched from Chinese QWERTY soft keyboard, and it has Chinese icon on
	 * soft keyboard. 第7位指明語言。
	 */
	private static final int MASK_LANGUAGE = 0x0f000000;

	/**
	 * Used to indicate the current language. An input mode should be anded with
	 * {@link #MASK_LANGUAGE} to get this information. 指明中文語言。
	 */
	private static final int MASK_LANGUAGE_CN = 0x01000000;

	/**
	 * Used to indicate the current language. An input mode should be anded with
	 * {@link #MASK_LANGUAGE} to get this information. 指明英文語言。
	 */
	private static final int MASK_LANGUAGE_EN = 0x02000000;

	/**
	 * Used to indicate which case the current input mode is in. For example,
	 * English QWERTY has lowercase and uppercase. For the Chinese QWERTY, these
	 * bits are ignored. For phone keyboard layout, these bits can be
	 * {@link #MASK_CASE_UPPER} to request symbol page for phone soft keyboard.
	 * 第6位指明軟鍵盤當前的狀態,比如高(大寫),低(小寫)。
	 */
	private static final int MASK_CASE = 0x00f00000;

	/**
	 * Used to indicate the current case information. An input mode should be
	 * anded with {@link #MASK_CASE} to get this information. 指明軟鍵盤狀態為低(小寫)。
	 */
	private static final int MASK_CASE_LOWER = 0x00100000;

	/**
	 * Used to indicate the current case information. An input mode should be
	 * anded with {@link #MASK_CASE} to get this information. 指明軟鍵盤狀態為高(大寫)。
	 */
	private static final int MASK_CASE_UPPER = 0x00200000;

	/**
	 * Mode for inputing Chinese with soft keyboard. 中文標準軟鍵盤模式
	 */
	public static final int MODE_SKB_CHINESE = (MASK_SKB_LAYOUT_QWERTY | MASK_LANGUAGE_CN);

	/**
	 * Mode for inputing basic symbols for Chinese mode with soft keyboard.
	 * 中文符號軟鍵盤一模式
	 */
	public static final int MODE_SKB_SYMBOL1_CN = (MASK_SKB_LAYOUT_SYMBOL1 | MASK_LANGUAGE_CN);

	/**
	 * Mode for inputing more symbols for Chinese mode with soft keyboard.
	 * 中文符號軟鍵盤二模式
	 */
	public static final int MODE_SKB_SYMBOL2_CN = (MASK_SKB_LAYOUT_SYMBOL2 | MASK_LANGUAGE_CN);

	/**
	 * Mode for inputing English lower characters with soft keyboard. 英文小寫軟鍵盤模式
	 */
	public static final int MODE_SKB_ENGLISH_LOWER = (MASK_SKB_LAYOUT_QWERTY
			| MASK_LANGUAGE_EN | MASK_CASE_LOWER);

	/**
	 * Mode for inputing English upper characters with soft keyboard. 英文大寫軟鍵盤模式
	 */
	public static final int MODE_SKB_ENGLISH_UPPER = (MASK_SKB_LAYOUT_QWERTY
			| MASK_LANGUAGE_EN | MASK_CASE_UPPER);

	/**
	 * Mode for inputing basic symbols for English mode with soft keyboard.
	 * 英文符號軟鍵盤一模式
	 */
	public static final int MODE_SKB_SYMBOL1_EN = (MASK_SKB_LAYOUT_SYMBOL1 | MASK_LANGUAGE_EN);

	/**
	 * Mode for inputing more symbols for English mode with soft keyboard.
	 * 英文符號軟鍵盤二模式
	 */
	public static final int MODE_SKB_SYMBOL2_EN = (MASK_SKB_LAYOUT_SYMBOL2 | MASK_LANGUAGE_EN);

	/**
	 * Mode for inputing smileys with soft keyboard. 中文笑臉軟鍵盤模式
	 */
	public static final int MODE_SKB_SMILEY = (MASK_SKB_LAYOUT_SMILEY | MASK_LANGUAGE_CN);

	/**
	 * Mode for inputing phone numbers. 電話號碼軟鍵盤模式
	 */
	public static final int MODE_SKB_PHONE_NUM = (MASK_SKB_LAYOUT_PHONE);

	/**
	 * Mode for inputing phone numbers. 電話號碼大寫軟鍵盤模式
	 */
	public static final int MODE_SKB_PHONE_SYM = (MASK_SKB_LAYOUT_PHONE | MASK_CASE_UPPER);

	/**
	 * Mode for inputing Chinese with a hardware keyboard. 中文硬鍵盤模式。(即不需要軟鍵盤)
	 */
	public static final int MODE_HKB_CHINESE = (MASK_LANGUAGE_CN);

	/**
	 * Mode for inputing English with a hardware keyboard 英文硬鍵盤模式。(即不需要軟鍵盤)
	 */
	public static final int MODE_HKB_ENGLISH = (MASK_LANGUAGE_EN);

	/**
	 * Unset mode. 未設定輸入法模式。
	 */
	public static final int MODE_UNSET = 0;

	/**
	 * Maximum toggle states for a soft keyboard.
	 * 一個軟鍵盤的切換狀態的最大數量。這裡的切換狀態是指要顯示的按鍵的狀態
	 * ,所以這些狀態不可能有兩個同時出現在一個按鍵當中。如果有兩個同時出現在一個按鍵當中,那就 不知道要顯示哪一個了。
	 */
	public static final int MAX_TOGGLE_STATES = 4;

	/**
	 * The input mode for the current edit box. 當前輸入法的模式
	 */
	private int mInputMode = MODE_UNSET;

	/**
	 * Used to remember previous input mode. When user enters an edit field, the
	 * previous input mode will be tried. If the previous mode can not be used
	 * for the current situation (For example, previous mode is a soft keyboard
	 * mode to input symbols, and we have a hardware keyboard for the current
	 * situation), {@link #mRecentLauageInputMode} will be tried. 前一個輸入法的模式
	 **/
	private int mPreviousInputMode = MODE_SKB_CHINESE;

	/**
	 * Used to remember recent mode to input language. 最近的語言輸入法模式
	 */
	private int mRecentLauageInputMode = MODE_SKB_CHINESE;

	/**
	 * Editor information of the current edit box. 當前編輯框的 EditorInfo 。
	 */
	private EditorInfo mEditorInfo;

	/**
	 * Used to indicate required toggling operations.
	 * 控制當前輸入法模式軟鍵盤佈局要顯示的按鍵切換狀態和要顯示的行ID。比如當前軟鍵盤佈局中
	 * ,有一個按鍵有預設狀態、和兩個切換狀態,ToggleStates中的mKeyStates[]儲存的就是當前要顯示的切換狀態
	 * ,如果按鍵的兩個切換狀態都沒有在mKeyStates[]中
	 * ,那麼按鍵就顯示預設狀態,如果兩個切換狀態中有一個在mKeyStates[]中,就顯示那個切換狀態
	 * 。注意:絕對不可能有一個按鍵的兩個或兩個以上的切換狀態同時在mKeyStates
	 * []中。ToggleStates不僅控制按鍵的顯示狀態,還可以直接控制一行按鍵的顯示
	 * 。mRowIdToEnable儲存的就是可顯示的行的ID(每行的ID不是唯一的 ,一個ID同時賦值給多行)。只有ID為
	 * mRowIdToEnable 和 ALWAYS_SHOW_ROW_ID 的行才會被顯示出來。
	 */
	private ToggleStates mToggleStates = new ToggleStates();

	/**
	 * The current field is a short message field? 當前的欄位是否是一個短語欄位? 當
	 * editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION ==
	 * EditorInfo.TYPE_MASK_VARIATION 時,被設定為true,否則為false。
	 */
	private boolean mShortMessageField;

	/**
	 * Is return key in normal state? 是否是正常狀態下的 Enter 鍵?
	 */
	private boolean mEnterKeyNormal = true;

	/**
	 * Current icon. 0 for none icon. 當前輸入法的圖示。顯示在訊號欄的圖示。
	 */
	int mInputIcon = R.drawable.ime_pinyin;

	/**
	 * IME service. 輸入法服務
	 */
	private PinyinIME mImeService;

	/**
	 * Key toggling state for Chinese mode. 軟鍵盤中的按鍵的切換狀態的ID:中文狀態。
	 */
	private int mToggleStateCn;

	/**
	 * Key toggling state for Chinese mode with candidates.
	 * 軟鍵盤中的按鍵的切換狀態的ID:中文候選詞狀態。
	 */
	private int mToggleStateCnCand;

	/**
	 * Key toggling state for English lowwercase mode. 軟鍵盤中的按鍵的切換狀態的ID:英文小寫狀態。
	 */
	private int mToggleStateEnLower;

	/**
	 * Key toggling state for English upppercase mode. 軟鍵盤中的按鍵的切換狀態的ID:英文大寫狀態。
	 */
	private int mToggleStateEnUpper;

	/**
	 * Key toggling state for English symbol mode for the first page.
	 * 軟鍵盤中的按鍵的切換狀態的ID:英文符號一狀態。
	 */
	private int mToggleStateEnSym1;

	/**
	 * Key toggling state for English symbol mode for the second page.
	 * 軟鍵盤中的按鍵的切換狀態的ID:英文符號二狀態。
	 */
	private int mToggleStateEnSym2;

	/**
	 * Key toggling state for smiley mode. 軟鍵盤中的按鍵的切換狀態的ID:笑臉狀態。
	 */
	private int mToggleStateSmiley;

	/**
	 * Key toggling state for phone symbol mode. 軟鍵盤中的按鍵的切換狀態的ID:電話號碼狀態。
	 */
	private int mToggleStatePhoneSym;

	/**
	 * Key toggling state for GO action of ENTER key.
	 * 軟鍵盤中的按鍵的切換狀態的ID:Enter鍵作為go操作狀態。
	 */
	private int mToggleStateGo;

	/**
	 * Key toggling state for SEARCH action of ENTER key.
	 * 軟鍵盤中的按鍵的切換狀態的ID:Enter鍵作為搜尋操作狀態。
	 */
	private int mToggleStateSearch;

	/**
	 * Key toggling state for SEND action of ENTER key.
	 * 軟鍵盤中的按鍵的切換狀態的ID:Enter鍵作為傳送操作狀態。
	 */
	private int mToggleStateSend;

	/**
	 * Key toggling state for NEXT action of ENTER key.
	 * 軟鍵盤中的按鍵的切換狀態的ID:Enter鍵作為下一步操作狀態。
	 */
	private int mToggleStateNext;

	/**
	 * Key toggling state for SEND action of ENTER key.
	 * 軟鍵盤中的按鍵的切換狀態的ID:Enter鍵作為完成操作狀態。
	 */
	private int mToggleStateDone;

	/**
	 * QWERTY row toggling state for Chinese input. 軟鍵盤中的行的ID:中文狀態。
	 */
	private int mToggleRowCn;

	/**
	 * QWERTY row toggling state for English input. 軟鍵盤中的行的ID:英文狀態。
	 */
	private int mToggleRowEn;

	/**
	 * QWERTY row toggling state for URI input. 軟鍵盤中的行的ID:Uri輸入狀態。
	 */
	private int mToggleRowUri;

	/**
	 * QWERTY row toggling state for email address input. 軟鍵盤中的行的ID:郵件地址輸入狀態。
	 */
	private int mToggleRowEmailAddress;

	/**
	 * 控制當前輸入法模式軟鍵盤佈局要顯示的按鍵切換狀態和要顯示的行ID的管理類。比如當前軟鍵盤佈局中
	 * ,有一個按鍵有預設狀態、和兩個切換狀態,ToggleStates中的mKeyStates[]儲存的就是當前要顯示的切換狀態
	 * ,如果按鍵的兩個切換狀態都沒有在mKeyStates[]中
	 * ,那麼按鍵就顯示預設狀態,如果兩個切換狀態中有一個在mKeyStates[]中,就顯示那個切換狀態
	 * 。注意:絕對不可能有一個按鍵的兩個或兩個以上的切換狀態同時在mKeyStates
	 * []中。ToggleStates不僅控制按鍵的顯示狀態,還可以直接控制一行按鍵的顯示
	 * 。mRowIdToEnable儲存的就是可顯示的行的ID(每行的ID不是唯一的 ,一個ID同時賦值給多行)。只有ID為
	 * mRowIdToEnable 和 ALWAYS_SHOW_ROW_ID 的行才會被顯示出來。
	 * 
	 * @ClassName ToggleStates
	 * @author keanbin
	 */
	class ToggleStates {
		/**
		 * If it is true, this soft keyboard is a QWERTY one. 是否是標準鍵盤
		 */
		boolean mQwerty;

		/**
		 * If {@link #mQwerty} is true, this variable is used to decide the
		 * letter case of the QWERTY keyboard. 是否是標準鍵盤大寫模式
		 */
		boolean mQwertyUpperCase;

		/**
		 * The id of enabled row in the soft keyboard. Refer to
		 * {@link com.android.inputmethod.pinyin.SoftKeyboard.KeyRow} for
		 * details. 軟鍵盤中要顯示的行的ID。只有ID為 mRowIdToEnable 和 ALWAYS_SHOW_ROW_ID
		 * 的行才會被顯示出來。
		 */
		public int mRowIdToEnable;

		/**
		 * Used to store all other toggle states for the current input mode.
		 * 這裡保持此刻要顯示的按鍵的狀態,所以這些狀態不可能有兩個同時出現在一個按鍵當中。如果有兩個同時出現在一個按鍵當中,那就
		 * 不知道要顯示哪一個了。
		 */
		public int mKeyStates[] = new int[MAX_TOGGLE_STATES];

		/**
		 * Number of states to toggle. 按鍵切換狀態的數量。mKeyStates[]的有用的長度。
		 */
		public int mKeyStatesNum;
	}

	public InputModeSwitcher(PinyinIME imeService) {
		mImeService = imeService;
		Resources r = mImeService.getResources();

		// 初始化按鍵各種切換狀態的ID 和 行的ID
		mToggleStateCn = Integer.parseInt(r.getString(R.string.toggle_cn));
		mToggleStateCnCand = Integer.parseInt(r
				.getString(R.string.toggle_cn_cand));
		mToggleStateEnLower = Integer.parseInt(r
				.getString(R.string.toggle_en_lower));
		mToggleStateEnUpper = Integer.parseInt(r
				.getString(R.string.toggle_en_upper));
		mToggleStateEnSym1 = Integer.parseInt(r
				.getString(R.string.toggle_en_sym1));
		mToggleStateEnSym2 = Integer.parseInt(r
				.getString(R.string.toggle_en_sym2));
		mToggleStateSmiley = Integer.parseInt(r
				.getString(R.string.toggle_smiley));
		mToggleStatePhoneSym = Integer.parseInt(r
				.getString(R.string.toggle_phone_sym));

		mToggleStateGo = Integer
				.parseInt(r.getString(R.string.toggle_enter_go));
		mToggleStateSearch = Integer.parseInt(r
				.getString(R.string.toggle_enter_search));
		mToggleStateSend = Integer.parseInt(r
				.getString(R.string.toggle_enter_send));
		mToggleStateNext = Integer.parseInt(r
				.getString(R.string.toggle_enter_next));
		mToggleStateDone = Integer.parseInt(r
				.getString(R.string.toggle_enter_done));

		mToggleRowCn = Integer.parseInt(r.getString(R.string.toggle_row_cn));
		mToggleRowEn = Integer.parseInt(r.getString(R.string.toggle_row_en));
		mToggleRowUri = Integer.parseInt(r.getString(R.string.toggle_row_uri));
		mToggleRowEmailAddress = Integer.parseInt(r
				.getString(R.string.toggle_row_emailaddress));
	}

	/**
	 * 獲取當前的輸入法模式
	 * 
	 * @有效值 
	 *      MODE_UNSET(未設定輸入法模式)、MODE_SKB_CHINESE(中文標準軟鍵盤模式)、MODE_SKB_SYMBOL1_CN
	 *      (中文符號軟鍵盤一模式)、MODE_SKB_SYMBOL2_CN(中文符號軟鍵盤二模式)、
	 *      MODE_SKB_ENGLISH_LOWER
	 *      (英文小寫軟鍵盤模式)、MODE_SKB_ENGLISH_UPPER(英文大寫軟鍵盤模式
	 *      )、MODE_SKB_SYMBOL1_EN(英文符號軟鍵盤一模式
	 *      )、MODE_SKB_SYMBOL2_EN(英文符號軟鍵盤二模式)、MODE_SKB_SMILEY
	 *      (中文笑臉軟鍵盤模式)、MODE_SKB_PHONE_NUM
	 *      (電話號碼軟鍵盤模式)、MODE_SKB_PHONE_SYM(電話號碼大寫軟鍵盤模式
	 *      )、MODE_HKB_CHINESE(中文硬鍵盤模式)、MODE_HKB_ENGLISH(英文硬鍵盤模式)。
	 * @return
	 */
	public int getInputMode() {
		return mInputMode;
	}

	/**
	 * 獲取控制當前輸入法模式軟鍵盤佈局的按鍵切換狀態和可顯示行ID的物件。
	 * 
	 * @return
	 */
	public ToggleStates getToggleStates() {
		return mToggleStates;
	}

	/**
	 * 更加軟鍵盤 LAYOUT 獲取軟鍵盤佈局檔案資源ID
	 * 
	 * @return
	 */
	public int getSkbLayout() {
		int layout = (mInputMode & MASK_SKB_LAYOUT);

		switch (layout) {
		case MASK_SKB_LAYOUT_QWERTY:
			return R.xml.skb_qwerty;
		case MASK_SKB_LAYOUT_SYMBOL1:
			return R.xml.skb_sym1;
		case MASK_SKB_LAYOUT_SYMBOL2:
			return R.xml.skb_sym2;
		case MASK_SKB_LAYOUT_SMILEY:
			return R.xml.skb_smiley;
		case MASK_SKB_LAYOUT_PHONE:
			return R.xml.skb_phone;
		}
		return 0;
	}

	/**
	 * 切換硬鍵盤的語言模式,返回切換後的語言模式的圖示。
	 * 先設定新的輸入法語言是中文模式,再判斷當前的輸入法語言模式是否是中文模式,是的話,就改成英文模式。
	 * 
	 * @return the icon to update
	 */
	public int switchLanguageWithHkb() {
		int newInputMode = MODE_HKB_CHINESE;
		mInputIcon = R.drawable.ime_pinyin;

		if (MODE_HKB_CHINESE == mInputMode) {
			newInputMode = MODE_HKB_ENGLISH;
			mInputIcon = R.drawable.ime_en;
		}

		saveInputMode(newInputMode);
		return mInputIcon;
	}

	/**
	 * 通過我們定義的軟鍵盤的按鍵,切換輸入法模式。
	 * 
	 * @param userKey
	 * @return the icon to update.
	 */
	public int switchModeForUserKey(int userKey) {
		int newInputMode = MODE_UNSET;

		if (USERDEF_KEYCODE_LANG_2 == userKey) {
			// 語言鍵:顯示中文或者英文、中符、英符的鍵
			if (MODE_SKB_CHINESE == mInputMode) {
				newInputMode = MODE_SKB_ENGLISH_LOWER;
			} else if (MODE_SKB_ENGLISH_LOWER == mInputMode
					|| MODE_SKB_ENGLISH_UPPER == mInputMode) {
				newInputMode = MODE_SKB_CHINESE;
			} else if (MODE_SKB_SYMBOL1_CN == mInputMode) {
				newInputMode = MODE_SKB_SYMBOL1_EN;
			} else if (MODE_SKB_SYMBOL1_EN == mInputMode) {
				newInputMode = MODE_SKB_SYMBOL1_CN;
			} else if (MODE_SKB_SYMBOL2_CN == mInputMode) {
				newInputMode = MODE_SKB_SYMBOL2_EN;
			} else if (MODE_SKB_SYMBOL2_EN == mInputMode) {
				newInputMode = MODE_SKB_SYMBOL2_CN;
			} else if (MODE_SKB_SMILEY == mInputMode) {
				newInputMode = MODE_SKB_CHINESE;
			}
		} else if (USERDEF_KEYCODE_SYM_3 == userKey) {
			// 系統鍵:顯示“?123”的鍵
			if (MODE_SKB_CHINESE == mInputMode) {
				newInputMode = MODE_SKB_SYMBOL1_CN;
			} else if (MODE_SKB_ENGLISH_UPPER == mInputMode
					|| MODE_SKB_ENGLISH_LOWER == mInputMode) {
				newInputMode = MODE_SKB_SYMBOL1_EN;
			} else if (MODE_SKB_SYMBOL1_EN == mInputMode
					|| MODE_SKB_SYMBOL2_EN == mInputMode) {
				newInputMode = MODE_SKB_ENGLISH_LOWER;
			} else if (MODE_SKB_SYMBOL1_CN == mInputMode
					|| MODE_SKB_SYMBOL2_CN == mInputMode) {
				newInputMode = MODE_SKB_CHINESE;
			} else if (MODE_SKB_SMILEY == mInputMode) {
				newInputMode = MODE_SKB_SYMBOL1_CN;
			}
		} else if (USERDEF_KEYCODE_SHIFT_1 == userKey) {
			// shift鍵:顯示“,” 或者 大小寫圖示的按鍵。
			if (MODE_SKB_ENGLISH_LOWER == mInputMode) {
				newInputMode = MODE_SKB_ENGLISH_UPPER;
			} else if (MODE_SKB_ENGLISH_UPPER == mInputMode) {
				newInputMode = MODE_SKB_ENGLISH_LOWER;
			}
		} else if (USERDEF_KEYCODE_MORE_SYM_5 == userKey) {
			// 更多系統鍵,顯示“ALT”的按鍵
			int sym = (MASK_SKB_LAYOUT & mInputMode);
			if (MASK_SKB_LAYOUT_SYMBOL1 == sym) {
				sym = MASK_SKB_LAYOUT_SYMBOL2;
			} else {
				sym = MASK_SKB_LAYOUT_SYMBOL1;
			}
			newInputMode = ((mInputMode & (~MASK_SKB_LAYOUT)) | sym);
		} else if (USERDEF_KEYCODE_SMILEY_6 == userKey) {
			// 笑臉鍵:顯示機器人笑臉圖示的按鍵
			if (MODE_SKB_CHINESE == mInputMode) {
				newInputMode = MODE_SKB_SMILEY;
			} else {
				newInputMode = MODE_SKB_CHINESE;
			}
		} else if (USERDEF_KEYCODE_PHONE_SYM_4 == userKey) {
			// 電話鍵:顯示“*#{”或者“123”的按鍵
			if (MODE_SKB_PHONE_NUM == mInputMode) {
				newInputMode = MODE_SKB_PHONE_SYM;
			} else {
				newInputMode = MODE_SKB_PHONE_NUM;
			}
		}

		if (newInputMode == mInputMode || MODE_UNSET == newInputMode) {
			return mInputIcon;
		}

		// 儲存新的輸入法模式
		saveInputMode(newInputMode);
		// 準備切換輸入法狀態
		prepareToggleStates(true);
		return mInputIcon;
	}

	/**
	 * 根據編輯框的 EditorInfo 資訊獲取硬鍵盤的輸入法模式。
	 * 
	 * @param editorInfo
	 * @return the icon to update.
	 */
	public int requestInputWithHkb(EditorInfo editorInfo) {
		mShortMessageField = false;
		boolean english = false;
		int newInputMode = MODE_HKB_CHINESE;

		switch (editorInfo.inputType & EditorInfo.TYPE_MASK_CLASS) {
		case EditorInfo.TYPE_CLASS_NUMBER:
		case EditorInfo.TYPE_CLASS_PHONE:
		case EditorInfo.TYPE_CLASS_DATETIME:
			english = true;
			break;
		case EditorInfo.TYPE_CLASS_TEXT:
			int v = editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;
			if (v == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
					|| v == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
					|| v == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
					|| v == EditorInfo.TYPE_TEXT_VARIATION_URI) {
				english = true;
			} else if (v == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
				mShortMessageField = true;
			}
			break;
		default:
		}

		if (english) {
			// If the application request English mode, we switch to it.
			newInputMode = MODE_HKB_ENGLISH;
		} else {
			// If the application do not request English mode, we will
			// try to keep the previous mode to input language text.
			// Because there is not soft keyboard, we need discard all
			// soft keyboard related information from the previous language
			// mode.
			// 如果最近一次的輸入法模式是中文,那麼就改為中文。
			if ((mRecentLauageInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {
				newInputMode = MODE_HKB_CHINESE;
			} else {
				newInputMode = MODE_HKB_ENGLISH;
			}
		}
		mEditorInfo = editorInfo;
		saveInputMode(newInputMode);
		prepareToggleStates(false);
		return mInputIcon;
	}

	/**
	 * 根據編輯框的 EditorInfo 資訊獲取軟鍵盤的輸入法模式。
	 * 
	 * @param editorInfo
	 * @return the icon to update.
	 */
	public int requestInputWithSkb(EditorInfo editorInfo) {
		mShortMessageField = false;

		int newInputMode = MODE_SKB_CHINESE;

		switch (editorInfo.inputType & EditorInfo.TYPE_MASK_CLASS) {
		case EditorInfo.TYPE_CLASS_NUMBER:
		case EditorInfo.TYPE_CLASS_DATETIME:
			newInputMode = MODE_SKB_SYMBOL1_EN;
			break;
		case EditorInfo.TYPE_CLASS_PHONE:
			newInputMode = MODE_SKB_PHONE_NUM;
			break;
		case EditorInfo.TYPE_CLASS_TEXT:
			int v = editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;
			if (v == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
					|| v == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
					|| v == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
					|| v == EditorInfo.TYPE_TEXT_VARIATION_URI) {
				// If the application request English mode, we switch to it.
				newInputMode = MODE_SKB_ENGLISH_LOWER;
			} else {
				if (v == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
					mShortMessageField = true;
				}
				// If the application do not request English mode, we will
				// try to keep the previous mode.
				int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
				newInputMode = mInputMode;
				if (0 == skbLayout) {
					if ((mInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {
						newInputMode = MODE_SKB_CHINESE;
					} else {
						newInputMode = MODE_SKB_ENGLISH_LOWER;
					}
				}
			}
			break;
		default:
			// Try to keep the previous mode.
			int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
			newInputMode = mInputMode;
			if (0 == skbLayout) {
				if ((mInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {
					newInputMode = MODE_SKB_CHINESE;
				} else {
					newInputMode = MODE_SKB_ENGLISH_LOWER;
				}
			}
			break;
		}

		mEditorInfo = editorInfo;
		saveInputMode(newInputMode);
		prepareToggleStates(true);
		return mInputIcon;
	}

	/**
	 * 請求返還上一次輸入法模式
	 * 
	 * @return
	 */
	public int requestBackToPreviousSkb() {
		int layout = (mInputMode & MASK_SKB_LAYOUT);
		int lastLayout = (mPreviousInputMode & MASK_SKB_LAYOUT);
		if (0 != layout && 0 != lastLayout) {
			// TODO 進行以下程式碼後 mInputMode 和 mPreviousInputMode 就一樣了,這樣好嗎?
			mInputMode = mPreviousInputMode;
			saveInputMode(mInputMode);
			prepareToggleStates(true);
			return mInputIcon;
		}
		return 0;
	}

	/**
	 * 獲得中文候選詞模式狀態
	 * 
	 * @return
	 */
	public int getTooggleStateForCnCand() {
		return mToggleStateCnCand;
	}

	/**
	 * 是否是硬鍵盤輸入法模式
	 * 
	 * @return
	 */
	public boolean isEnglishWithHkb() {
		return MODE_HKB_ENGLISH == mInputMode;
	}

	/**
	 * 是否是軟體盤英語模式
	 * 
	 * @return
	 */
	public boolean isEnglishWithSkb() {
		return MODE_SKB_ENGLISH_LOWER == mInputMode
				|| MODE_SKB_ENGLISH_UPPER == mInputMode;
	}

	/**
	 * 是否是軟鍵盤高(大寫)模式
	 * 
	 * @return
	 */
	public boolean isEnglishUpperCaseWithSkb() {
		return MODE_SKB_ENGLISH_UPPER == mInputMode;
	}

	/**
	 * 是否是中文語言(傳統標準軟鍵盤或者硬鍵盤)。
	 * 
	 * @return
	 */
	public boolean isChineseText() {
		int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
		if (MASK_SKB_LAYOUT_QWERTY == skbLayout || 0 == skbLayout) {
			int language = (mInputMode & MASK_LANGUAGE);
			if (MASK_LANGUAGE_CN == language)
				return true;
		}
		return false;
	}

	/**
	 * 是否是硬鍵盤的中文語言
	 * 
	 * @return
	 */
	public boolean isChineseTextWithHkb() {
		int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
		if (0 == skbLayout) {
			int language = (mInputMode & MASK_LANGUAGE);
			if (MASK_LANGUAGE_CN == language)
				return true;
		}
		return false;
	}

	/**
	 * 是否是軟鍵盤的中文語言
	 * 
	 * @return
	 */
	public boolean isChineseTextWithSkb() {
		int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
		if (MASK_SKB_LAYOUT_QWERTY == skbLayout) {
			int language = (mInputMode & MASK_LANGUAGE);
			if (MASK_LANGUAGE_CN == language)
				return true;
		}
		return false;
	}

	/**
	 * 是否是軟鍵盤的符號
	 * 
	 * @return
	 */
	public boolean isSymbolWithSkb() {
		int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
		if (MASK_SKB_LAYOUT_SYMBOL1 == skbLayout
				|| MASK_SKB_LAYOUT_SYMBOL2 == skbLayout) {
			return true;
		}
		return false;
	}

	/**
	 * 是否是正常Enter鍵狀態
	 * 
	 * @return
	 */
	public boolean isEnterNoramlState() {
		return mEnterKeyNormal;
	}

	/**
	 * @param 長按處理
	 * @return
	 */
	public boolean tryHandleLongPressSwitch(int keyCode) {
		if (USERDEF_KEYCODE_LANG_2 == keyCode
				|| USERDEF_KEYCODE_PHONE_SYM_4 == keyCode) {
			mImeService.showOptionsMenu();
			return true;
		}
		return false;
	}

	/**
	 * 儲存新的輸入法模式
	 * 
	 * @param newInputMode
	 */
	private void saveInputMode(int newInputMode) {
		// 儲存當前輸入法模式
		mPreviousInputMode = mInputMode;
		// 設定新的輸入法模式為當前的輸入法模式
		mInputMode = newInputMode;

		// 輸入法模式的佈局屬性(第8位)
		int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
		if (MASK_SKB_LAYOUT_QWERTY == skbLayout || 0 == skbLayout) {
			// 儲存最近的語言輸入法模式
			mRecentLauageInputMode = mInputMode;
		}

		// 設定輸入法圖示
		mInputIcon = R.drawable.ime_pinyin;
		if (isEnglishWithHkb()) {
			mInputIcon = R.drawable.ime_en;
		} else if (isChineseTextWithHkb()) {
			mInputIcon = R.drawable.ime_pinyin;
		}

		// 如果有硬鍵盤,就設定輸入法模式為未設定狀態。
		if (!Environment.getInstance().hasHardKeyboard()) {
			mInputIcon = 0;
		}
	}

	/**
	 * 準備設定控制顯示的按鍵切換狀態和可顯示行ID的物件的資料,封裝mToggleStates的資料。
	 * 
	 * @param needSkb
	 */
	private void prepareToggleStates(boolean needSkb) {
		mEnterKeyNormal = true;
		if (!needSkb)
			return;

		mToggleStates.mQwerty = false;
		mToggleStates.mKeyStatesNum = 0;

		int states[] = mToggleStates.mKeyStates;
		int statesNum = 0;
		// Toggle state for language.
		int language = (mInputMode & MASK_LANGUAGE);
		int layout = (mInputMode & MASK_SKB_LAYOUT);
		int charcase = (mInputMode & MASK_CASE);
		int variation = mEditorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;

		// 更加輸入法模式新增要顯示的按鍵的切換狀態
		if (MASK_SKB_LAYOUT_PHONE != layout) {
			if (MASK_LANGUAGE_CN == language) {
				// Chinese and Chinese symbol are always the default states,
				// do not add a toggling operation.
				if (MASK_SKB_LAYOUT_QWERTY == layout) {
					mToggleStates.mQwerty = true;
					mToggleStates.mQwertyUpperCase = true;
					if (mShortMessageField) {
						states[statesNum] = mToggleStateSmiley;
						statesNum++;
					}
				}
			} else if (MASK_LANGUAGE_EN == language) {
				if (MASK_SKB_LAYOUT_QWERTY == layout) {
					mToggleStates.mQwerty = true;
					mToggleStates.mQwertyUpperCase = false;
					states[statesNum] = mToggleStateEnLower;
					if (MASK_CASE_UPPER == charcase) {
						mToggleStates.mQwertyUpperCase = true;
						states[statesNum] = mToggleStateEnUpper;
					}
					statesNum++;
				} else if (MASK_SKB_LAYOUT_SYMBOL1 == layout) {
					states[statesNum] = mToggleStateEnSym1;
					statesNum++;
				} else if (MASK_SKB_LAYOUT_SYMBOL2 == layout) {
					states[statesNum] = mToggleStateEnSym2;
					statesNum++;
				}
			}

			// Toggle rows for QWERTY.
			mToggleStates.mRowIdToEnable = KeyRow.DEFAULT_ROW_ID;
			if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
				mToggleStates.mRowIdToEnable = mToggleRowEmailAddress;
			} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
				mToggleStates.mRowIdToEnable = mToggleRowUri;
			} else if (MASK_LANGUAGE_CN == language) {
				mToggleStates.mRowIdToEnable = mToggleRowCn;
			} else if (MASK_LANGUAGE_EN == language) {
				mToggleStates.mRowIdToEnable = mToggleRowEn;
			}
		} else {
			if (MASK_CASE_UPPER == charcase) {
				states[statesNum] = mToggleStatePhoneSym;
				statesNum++;
			}
		}

		// Toggle state for enter key.
		// 根據EditorInfo.imeOptions新增 要顯示的按鍵的切換狀態 ,以下只新增 Enter 鍵的切換狀態。
		int action = mEditorInfo.imeOptions
				& (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);

		if (action == EditorInfo.IME_ACTION_GO) {
			states[statesNum] = mToggleStateGo;
			statesNum++;
			mEnterKeyNormal = false;
		} else if (action == EditorInfo.IME_ACTION_SEARCH) {
			states[statesNum] = mToggleStateSearch;
			statesNum++;
			mEnterKeyNormal = false;
		} else if (action == EditorInfo.IME_ACTION_SEND) {
			states[statesNum] = mToggleStateSend;
			statesNum++;
			mEnterKeyNormal = false;
		} else if (action == EditorInfo.IME_ACTION_NEXT) {
			int f = mEditorInfo.inputType & EditorInfo.TYPE_MASK_FLAGS;
			if (f != EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
				states[statesNum] = mToggleStateNext;
				statesNum++;
				mEnterKeyNormal = false;
			}
		} else if (action == EditorInfo.IME_ACTION_DONE) {
			states[statesNum] = mToggleStateDone;
			statesNum++;
			mEnterKeyNormal = false;
		}
		mToggleStates.mKeyStatesNum = statesNum;
	}
}


9、KeyMapDream.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.view.KeyEvent;

/**
 * Class used to map the symbols on Dream's hardware keyboard to corresponding
 * Chinese full-width symbols.
 * 
 * 硬鍵碟符號對應表,對應中文全形符號。
 */
public class KeyMapDream {
	// Number of shift bits to store full-width symbols
	private static final int SHIFT_FWCH = 8;
	private static final int[] mKeyMap = {
			KeyEvent.KEYCODE_UNKNOWN,
			KeyEvent.KEYCODE_SOFT_LEFT,
			KeyEvent.KEYCODE_SOFT_RIGHT,
			KeyEvent.KEYCODE_HOME,
			KeyEvent.KEYCODE_BACK,
			KeyEvent.KEYCODE_CALL,
			KeyEvent.KEYCODE_ENDCALL,
			KeyEvent.KEYCODE_0 | ('\uff09' << SHIFT_FWCH), // )
			KeyEvent.KEYCODE_1 | ('\uff01' << SHIFT_FWCH), // !
			KeyEvent.KEYCODE_2 | ('\uff20' << SHIFT_FWCH), // @
			KeyEvent.KEYCODE_3 | ('\uff03' << SHIFT_FWCH), // #
			KeyEvent.KEYCODE_4 | ('\uffe5' << SHIFT_FWCH), // $ - fullwidth Yuan
			KeyEvent.KEYCODE_5 | ('\uff05' << SHIFT_FWCH), // %
			KeyEvent.KEYCODE_6 | ('\u2026' << SHIFT_FWCH), // ^ - Apostrophe
			KeyEvent.KEYCODE_7 | ('\uff06' << SHIFT_FWCH), // &
			KeyEvent.KEYCODE_8 | ('\uff0a' << SHIFT_FWCH), // *
			KeyEvent.KEYCODE_9 | ('\uff08' << SHIFT_FWCH), // (
			KeyEvent.KEYCODE_STAR,
			KeyEvent.KEYCODE_POUND,
			KeyEvent.KEYCODE_DPAD_UP,
			KeyEvent.KEYCODE_DPAD_DOWN,
			KeyEvent.KEYCODE_DPAD_LEFT,
			KeyEvent.KEYCODE_DPAD_RIGHT,
			KeyEvent.KEYCODE_DPAD_CENTER,
			KeyEvent.KEYCODE_VOLUME_UP,
			KeyEvent.KEYCODE_VOLUME_DOWN,
			KeyEvent.KEYCODE_POWER,
			KeyEvent.KEYCODE_CAMERA,
			KeyEvent.KEYCODE_CLEAR,
			KeyEvent.KEYCODE_A,
			KeyEvent.KEYCODE_B | ('\uff3d' << SHIFT_FWCH), // ]
			KeyEvent.KEYCODE_C | ('\u00a9' << SHIFT_FWCH), // copyright
			KeyEvent.KEYCODE_D | ('\u3001' << SHIFT_FWCH), // \\
			KeyEvent.KEYCODE_E | ('_' << SHIFT_FWCH), // _
			KeyEvent.KEYCODE_F | ('\uff5b' << SHIFT_FWCH), // {
			KeyEvent.KEYCODE_G | ('\uff5d' << SHIFT_FWCH), // }
			KeyEvent.KEYCODE_H | ('\uff1a' << SHIFT_FWCH), // :
			KeyEvent.KEYCODE_I | ('\uff0d' << SHIFT_FWCH), // -
			KeyEvent.KEYCODE_J | ('\uff1b' << SHIFT_FWCH), // ;
			KeyEvent.KEYCODE_K | ('\u201c' << SHIFT_FWCH), // "
			KeyEvent.KEYCODE_L | ('\u2019' << SHIFT_FWCH), // '
			KeyEvent.KEYCODE_M | ('\u300b' << SHIFT_FWCH), // > - French quotes
			KeyEvent.KEYCODE_N | ('\u300a' << SHIFT_FWCH), // < - French quotes
			KeyEvent.KEYCODE_O | ('\uff0b' << SHIFT_FWCH), // +
			KeyEvent.KEYCODE_P | ('\uff1d' << SHIFT_FWCH), // =
			KeyEvent.KEYCODE_Q | ('\t' << SHIFT_FWCH), // \t
			KeyEvent.KEYCODE_R | ('\u00ae' << SHIFT_FWCH), // trademark
			KeyEvent.KEYCODE_S | ('\uff5c' << SHIFT_FWCH), // |
			KeyEvent.KEYCODE_T | ('\u20ac' << SHIFT_FWCH), //
			KeyEvent.KEYCODE_U | ('\u00d7' << SHIFT_FWCH), // multiplier
			KeyEvent.KEYCODE_V | ('\uff3b' << SHIFT_FWCH), // [
			KeyEvent.KEYCODE_W | ('\uff40' << SHIFT_FWCH), // `
			KeyEvent.KEYCODE_X, KeyEvent.KEYCODE_Y | ('\u00f7' << SHIFT_FWCH),
			KeyEvent.KEYCODE_Z,
			KeyEvent.KEYCODE_COMMA | ('\uff1f' << SHIFT_FWCH),
			KeyEvent.KEYCODE_PERIOD | ('\uff0f' << SHIFT_FWCH),
			KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
			KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,
			KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_SYM,
			KeyEvent.KEYCODE_EXPLORER, KeyEvent.KEYCODE_ENVELOPE,
			KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DEL,
			KeyEvent.KEYCODE_GRAVE, KeyEvent.KEYCODE_MINUS,
			KeyEvent.KEYCODE_EQUALS, KeyEvent.KEYCODE_LEFT_BRACKET,
			KeyEvent.KEYCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_BACKSLASH,
			KeyEvent.KEYCODE_SEMICOLON, KeyEvent.KEYCODE_APOSTROPHE,
			KeyEvent.KEYCODE_SLASH,
			KeyEvent.KEYCODE_AT | ('\uff5e' << SHIFT_FWCH),
			KeyEvent.KEYCODE_NUM, KeyEvent.KEYCODE_HEADSETHOOK,
			KeyEvent.KEYCODE_FOCUS, KeyEvent.KEYCODE_PLUS,
			KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_NOTIFICATION,
			KeyEvent.KEYCODE_SEARCH, };

	/**
	 * 獲取中文全形字元
	 * 
	 * @param keyCode
	 * @return
	 */
	static public char getChineseLabel(int keyCode) {
		if (keyCode <= 0 || keyCode >= KeyEvent.getMaxKeyCode())
			return 0;
		assert ((mKeyMap[keyCode] & 0x000000ff) == keyCode);
		return (char) (mKeyMap[keyCode] >> SHIFT_FWCH);
	}
}


10、MyReceiver.java:

package com.keanbin.pinyinime;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * 廣播接收器,接收android.intent.idatachina.RFID.BARCODE.SCANINFO的廣播,取出Intent中欄位
 * "idatachina.SCAN_DATA"儲存的資料,呼叫拼音服務PinyinIME傳送給EditText
 * 
 * @ClassName MyReceiver
 * @author keanbin
 */
public class MyReceiver extends BroadcastReceiver {
	PinyinIME ss = new PinyinIME();

	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		// MainActivity.onrecvintend(intent);
		String tinfo = intent.getStringExtra("idatachina.SCAN_DATA");
		ss.pinyinIME.SetText(tinfo);

	}
}


11、PinyinDecoderService.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Vector;

import android.app.Service;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.os.IBinder;
import android.util.Log;

/**
 * This class is used to separate the input method kernel in an individual
 * service so that both IME and IME-syncer can use it.
 * 
 * 詞庫解碼JNI函式服務
 * 
 * @ClassName PinyinDecoderService
 * @author keanbin
 */
public class PinyinDecoderService extends Service {
	native static boolean nativeImOpenDecoder(byte fn_sys_dict[],
			byte fn_usr_dict[]);

	/**
	 * JNI函式:開啟解碼器
	 * 
	 * @param fd
	 * @param startOffset
	 * @param length
	 * @param fn_usr_dict
	 * @return
	 */
	native static boolean nativeImOpenDecoderFd(FileDescriptor fd,
			long startOffset, long length, byte fn_usr_dict[]);

	/**
	 * JNI函式:設定最大的長度
	 * 
	 * @param maxSpsLen
	 * @param maxHzsLen
	 */
	native static void nativeImSetMaxLens(int maxSpsLen, int maxHzsLen);

	/**
	 * JNI函式:關閉解碼器
	 * 
	 * @return
	 */
	native static boolean nativeImCloseDecoder();

	/**
	 * JNI函式:根據拼音查詢候選詞
	 * 
	 * @param pyBuf
	 * @param pyLen
	 * @return
	 */
	native static int nativeImSearch(byte pyBuf[], int pyLen);

	/**
	 * JNI函式:刪除指定位置的拼音後進行查詢
	 * 
	 * @param pos
	 * @param is_pos_in_splid
	 * @param clear_fixed_this_step
	 * @return
	 */
	native static int nativeImDelSearch(int pos, boolean is_pos_in_splid,
			boolean clear_fixed_this_step);

	/**
	 * JNI函式:重置拼音查詢,應該是清除之前查詢的資料
	 */
	native static void nativeImResetSearch();

	/**
	 * JNI函式:增加字母。
	 * 
	 * @備註 目前沒有使用。
	 * @param ch
	 * @return
	 */
	native static int nativeImAddLetter(byte ch);

	/**
	 * JNI函式:獲取拼音字串
	 * 
	 * @param decoded
	 * @return
	 */
	native static String nativeImGetPyStr(boolean decoded);

	/**
	 * JNI函式:獲取拼音字串的長度
	 * 
	 * @param decoded
	 * @return
	 */
	native static int nativeImGetPyStrLen(boolean decoded);

	/**
	 * JNI函式:獲取每個拼寫的開始位置,猜測:第一個元素是拼寫的總數量?
	 * 
	 * @return
	 */
	native static int[] nativeImGetSplStart();

	/**
	 * JNI函式:獲取指定位置的候選詞
	 * 
	 * @param choiceId
	 * @return
	 */
	native static String nativeImGetChoice(int choiceId);

	/**
	 * JNI函式:獲取候選詞的數量
	 * 
	 * @param choiceId
	 * @return
	 */
	native static int nativeImChoose(int choiceId);

	/**
	 * JNI函式:取消最後的選擇
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static int nativeImCancelLastChoice();

	/**
	 * JNI函式:獲取固定字元的長度
	 * 
	 * @return
	 */
	native static int nativeImGetFixedLen();

	/**
	 * JNI函式:取消輸入
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static boolean nativeImCancelInput();

	/**
	 * JNI函式:重新整理快取
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static boolean nativeImFlushCache();

	/**
	 * JNI函式:根據字串 fixedStr 獲取預報的候選詞
	 * 
	 * @param fixedStr
	 * @return
	 */
	native static int nativeImGetPredictsNum(String fixedStr);

	/**
	 * JNI函式:獲取指定位置的預報候選詞
	 * 
	 * @param predictNo
	 * @return
	 */
	native static String nativeImGetPredictItem(int predictNo);

	// Sync related
	/**
	 * JNI函式:同步到使用者詞典,猜測:是不是記住使用者的常用詞。
	 * 
	 * @備註 目前沒有使用
	 * @param user_dict
	 * @param tomerge
	 * @return
	 */
	native static String nativeSyncUserDict(byte[] user_dict, String tomerge);

	/**
	 * JNI函式:開始使用者詞典同步
	 * 
	 * @備註 目前沒有使用
	 * @param user_dict
	 * @return
	 */
	native static boolean nativeSyncBegin(byte[] user_dict);

	/**
	 * JNI函式:同步結束
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static boolean nativeSyncFinish();

	/**
	 * JNI函式:同步獲取Lemmas
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static String nativeSyncGetLemmas();

	/**
	 * JNI函式:同步存入Lemmas
	 * 
	 * @備註 目前沒有使用
	 * @param tomerge
	 * @return
	 */
	native static int nativeSyncPutLemmas(String tomerge);

	/**
	 * JNI函式:同步獲取最後的數量
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static int nativeSyncGetLastCount();

	/**
	 * JNI函式:同步獲取總數量
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static int nativeSyncGetTotalCount();

	/**
	 * JNI函式:同步清空最後獲取
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static boolean nativeSyncClearLastGot();

	/**
	 * JNI函式:同步獲取容量
	 * 
	 * @備註 目前沒有使用
	 * @return
	 */
	native static int nativeSyncGetCapacity();

	/**
	 * 最大的檔案路徑長度
	 */
	private final static int MAX_PATH_FILE_LENGTH = 100;

	/**
	 * 是否完成初始化
	 */
	private static boolean inited = false;

	/**
	 * 使用者的詞典檔案
	 */
	private String mUsr_dict_file;

	// 匯入本地函式庫
	static {
		try {
			System.loadLibrary("jni_pinyinime");
		} catch (UnsatisfiedLinkError ule) {
			Log.e("PinyinDecoderService",
					"WARNING: Could not load jni_pinyinime natives");
		}
	}

	/**
	 * Get file name of the specified dictionary 獲取使用者詞典的檔名
	 * 
	 * @param usr_dict
	 * @return
	 */
	private boolean getUsrDictFileName(byte usr_dict[]) {
		if (null == usr_dict) {
			return false;
		}

		for (int i = 0; i < mUsr_dict_file.length(); i++)
			usr_dict[i] = (byte) mUsr_dict_file.charAt(i);
		usr_dict[mUsr_dict_file.length()] = 0;

		return true;
	}

	/**
	 * 初始化拼音引擎
	 */
	private void initPinyinEngine() {
		byte usr_dict[];
		usr_dict = new byte[MAX_PATH_FILE_LENGTH];

		// Here is how we open a built-in dictionary for access through
		// a file descriptor...
		// 獲取詞庫 R.raw.dict_pinyin 的檔案描述符
		AssetFileDescriptor afd = getResources().openRawResourceFd(
				R.raw.dict_pinyin);
		if (Environment.getInstance().needDebug()) {
			Log.i("foo", "Dict: start=" + afd.getStartOffset() + ", length="
					+ afd.getLength() + ", fd=" + afd.getParcelFileDescriptor());
		}
		if (getUsrDictFileName(usr_dict)) {
			// JNI函式:開啟解碼器
			inited = nativeImOpenDecoderFd(afd.getFileDescriptor(),
					afd.getStartOffset(), afd.getLength(), usr_dict);
		}
		try {
			afd.close();
		} catch (IOException e) {
		}
	}

	@Override
	public void onCreate() {
		super.onCreate();
		// 獲取使用者詞典"usr_dict.dat"的路徑。"usr_dict.dat"放在file目錄下。
		// 猜測:呼叫getFileStreamPath("usr_dict.dat"),如果"usr_dict.dat"不存在,會呼叫openFileOutput()建立該檔案。
		mUsr_dict_file = getFileStreamPath("usr_dict.dat").getPath();
		// This is a hack to make sure our "files" directory has been
		// created.
		try {
			openFileOutput("dummy", 0).close();
		} catch (FileNotFoundException e) {
		} catch (IOException e) {
		}

		initPinyinEngine();
	}

	@Override
	public void onDestroy() {
		// JNI函式:關閉解碼器
		nativeImCloseDecoder();
		inited = false;
		super.onDestroy();
	}

	/**
	 * 給外部呼叫的介面
	 */
	private final IPinyinDecoderService.Stub mBinder = new IPinyinDecoderService.Stub() {

		/**
		 * 返回12345
		 */
		public int getInt() {
			return 12345;
		}

		/**
		 * 設定最大的長度
		 */
		public void setMaxLens(int maxSpsLen, int maxHzsLen) {
			nativeImSetMaxLens(maxSpsLen, maxHzsLen);
		}

		/**
		 * 根據拼音查詢候選詞
		 */
		public int imSearch(byte[] pyBuf, int pyLen) {
			return nativeImSearch(pyBuf, pyLen);
		}

		/**
		 * 刪除指定位置的拼音後進行查詢
		 */
		public int imDelSearch(int pos, boolean is_pos_in_splid,
				boolean clear_fixed_this_step) {
			return nativeImDelSearch(pos, is_pos_in_splid,
					clear_fixed_this_step);
		}

		/**
		 * 重置拼音查詢,應該是清除之前查詢的資料
		 */
		public void imResetSearch() {
			nativeImResetSearch();
		}

		/**
		 * 增加字母。
		 * 
		 * @備註 目前沒有使用。
		 */
		public int imAddLetter(byte ch) {
			return nativeImAddLetter(ch);
		}

		/**
		 * 獲取拼音字串
		 */
		public String imGetPyStr(boolean decoded) {
			return nativeImGetPyStr(decoded);
		}

		/**
		 * 獲取拼音字串的長度
		 */
		public int imGetPyStrLen(boolean decoded) {
			return nativeImGetPyStrLen(decoded);
		}

		/**
		 * 獲取每個拼寫的開始位置,猜測:第一個元素是拼寫的總數量?
		 */
		public int[] imGetSplStart() {
			return nativeImGetSplStart();
		}

		/**
		 * 獲取指定位置的候選詞
		 */
		public String imGetChoice(int choiceId) {
			return nativeImGetChoice(choiceId);
		}

		/**
		 * 獲取多個候選詞
		 * 
		 * @備註 目前沒有使用。
		 */
		public String imGetChoices(int choicesNum) {
			String retStr = null;
			for (int i = 0; i < choicesNum; i++) {
				if (null == retStr)
					retStr = nativeImGetChoice(i);
				else
					retStr += " " + nativeImGetChoice(i);
			}
			return retStr;
		}

		/**
		 * 獲取候選詞列表。choicesStart位置的候選詞從sentFixedLen開始擷取。
		 */
		public List<String> imGetChoiceList(int choicesStart, int choicesNum,
				int sentFixedLen) {
			Vector<String> choiceList = new Vector<String>();
			for (int i = choicesStart; i < choicesStart + choicesNum; i++) {
				String retStr = nativeImGetChoice(i);
				if (0 == i)
					retStr = retStr.substring(sentFixedLen);
				choiceList.add(retStr);
			}
			return choiceList;
		}

		/**
		 * 獲取候選詞的數量
		 */
		public int imChoose(int choiceId) {
			return nativeImChoose(choiceId);
		}

		/**
		 * 取消最後的選擇
		 * 
		 * @備註 目前沒有使用
		 */
		public int imCancelLastChoice() {
			return nativeImCancelLastChoice();
		}

		/**
		 * 獲取固定字元的長度
		 */
		public int imGetFixedLen() {
			return nativeImGetFixedLen();
		}

		/**
		 * 取消輸入
		 * 
		 * @備註 目前沒有使用
		 */
		public boolean imCancelInput() {
			return nativeImCancelInput();
		}

		/**
		 * 重新整理快取
		 * 
		 * @備註 目前沒有使用
		 */
		public void imFlushCache() {
			nativeImFlushCache();
		}

		/**
		 * 根據字串 fixedStr 獲取預報的候選詞
		 */
		public int imGetPredictsNum(String fixedStr) {
			return nativeImGetPredictsNum(fixedStr);
		}

		/**
		 * 獲取指定位置的預報候選詞
		 */
		public String imGetPredictItem(int predictNo) {
			return nativeImGetPredictItem(predictNo);
		}

		/**
		 * 獲取候選詞列表
		 */
		public List<String> imGetPredictList(int predictsStart, int predictsNum) {
			Vector<String> predictList = new Vector<String>();
			for (int i = predictsStart; i < predictsStart + predictsNum; i++) {
				predictList.add(nativeImGetPredictItem(i));
			}
			return predictList;
		}

		/**
		 * 同步到使用者詞典,猜測:是不是記住使用者的常用詞。
		 * 
		 * @備註 目前沒有使用
		 */
		public String syncUserDict(String tomerge) {
			byte usr_dict[];
			usr_dict = new byte[MAX_PATH_FILE_LENGTH];

			if (getUsrDictFileName(usr_dict)) {
				return nativeSyncUserDict(usr_dict, tomerge);
			}
			return null;
		}

		/**
		 * 開始使用者詞典同步
		 * 
		 * @備註 目前沒有使用
		 */
		public boolean syncBegin() {
			byte usr_dict[];
			usr_dict = new byte[MAX_PATH_FILE_LENGTH];

			if (getUsrDictFileName(usr_dict)) {
				return nativeSyncBegin(usr_dict);
			}
			return false;
		}

		/**
		 * 同步結束
		 * 
		 * @備註 目前沒有使用
		 */
		public void syncFinish() {
			nativeSyncFinish();
		}

		/**
		 * 同步存入Lemmas
		 * 
		 * @備註 目前沒有使用
		 */
		public int syncPutLemmas(String tomerge) {
			return nativeSyncPutLemmas(tomerge);
		}

		/**
		 * 同步獲取Lemmas
		 * 
		 * @備註 目前沒有使用
		 */
		public String syncGetLemmas() {
			return nativeSyncGetLemmas();
		}

		/**
		 * 同步獲取最後的數量
		 * 
		 * @備註 目前沒有使用
		 */
		public int syncGetLastCount() {
			return nativeSyncGetLastCount();
		}

		/**
		 * 同步獲取總數量
		 * 
		 * @備註 目前沒有使用
		 */
		public int syncGetTotalCount() {
			return nativeSyncGetTotalCount();
		}

		/**
		 * 同步清空最後獲取
		 * 
		 * @備註 目前沒有使用
		 */
		public void syncClearLastGot() {
			nativeSyncClearLastGot();
		}

		/**
		 * 同步獲取容量
		 * 
		 * @備註 目前沒有使用
		 */
		public int imSyncGetCapacity() {
			return nativeSyncGetCapacity();
		}
	};

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}
}


12、PinyinIME.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.LinearLayout;
import android.widget.PopupWindow;

/**
 * Main class of the Pinyin input method. 輸入法服務
 */
public class PinyinIME extends InputMethodService {
	/**
	 * TAG for debug.
	 */
	static final String TAG = "PinyinIME";
	static PinyinIME pinyinIME;
	/**
	 * If is is true, IME will simulate key events for delete key, and send the
	 * events back to the application.
	 */
	private static final boolean SIMULATE_KEY_DELETE = true;

	/**
	 * Necessary environment configurations like screen size for this IME.
	 * 該物件儲存了佈局的一些尺寸,它的類是單例模式。
	 */
	private Environment mEnvironment;

	/**
	 * Used to switch input mode. 輸入法狀態變換器
	 */
	private InputModeSwitcher mInputModeSwitcher;

	/**
	 * Soft keyboard container view to host real soft keyboard view. 軟鍵盤集裝箱
	 */
	private SkbContainer mSkbContainer;

	/**
	 * The floating container which contains the composing view. If necessary,
	 * some other view like candiates container can also be put here. 浮動檢視集裝箱
	 */
	private LinearLayout mFloatingContainer;

	/**
	 * View to show the composing string. 組成字串的View,用於顯示輸入的拼音。
	 */
	private ComposingView mComposingView;

	/**
	 * Window to show the composing string. 用於輸入拼音字串的視窗。
	 */
	private PopupWindow mFloatingWindow;

	/**
	 * Used to show the floating window. 顯示輸入的拼音字串PopupWindow 定時器
	 */
	private PopupTimer mFloatingWindowTimer = new PopupTimer();

	/**
	 * View to show candidates list. 候選詞檢視集裝箱
	 */
	private CandidatesContainer mCandidatesContainer;

	/**
	 * Balloon used when user presses a candidate. 候選詞氣泡
	 */
	private BalloonHint mCandidatesBalloon;

	/**
	 * Used to notify the input method when the user touch a candidate.
	 * 當使用者選擇了候選詞或者在候選詞檢視滑動了手勢時的通知輸入法。 實現了候選詞檢視的監聽器CandidateViewListener。
	 */
	private ChoiceNotifier mChoiceNotifier;

	/**
	 * Used to notify gestures from soft keyboard. 軟鍵盤的手勢監聽器
	 */
	private OnGestureListener mGestureListenerSkb;

	/**
	 * Used to notify gestures from candidates view. 候選詞的手勢監聽器
	 */
	private OnGestureListener mGestureListenerCandidates;

	/**
	 * The on-screen movement gesture detector for soft keyboard. 軟鍵盤的手勢檢測器
	 */
	private GestureDetector mGestureDetectorSkb;

	/**
	 * The on-screen movement gesture detector for candidates view. 候選詞的手勢檢測器
	 */
	private GestureDetector mGestureDetectorCandidates;

	/**
	 * Option dialog to choose settings and other IMEs. 功能對話方塊
	 */
	private AlertDialog mOptionsDialog;

	/**
	 * Connection used to bind the decoding service. 連結
	 * 詞庫解碼遠端服務PinyinDecoderService 的監聽器
	 */
	private PinyinDecoderServiceConnection mPinyinDecoderServiceConnection;

	/**
	 * The current IME status. 當前的輸入法狀態
	 * 
	 * @see com.android.inputmethod.pinyin.PinyinIME.ImeState
	 */
	private ImeState mImeState = ImeState.STATE_IDLE;

	/**
	 * The decoding information, include spelling(Pinyin) string, decoding
	 * result, etc. 詞庫解碼操作物件
	 */
	private DecodingInfo mDecInfo = new DecodingInfo();

	/**
	 * For English input. 英文輸入法按鍵處理器
	 * 
	 */
	private EnglishInputProcessor mImEn;

	// receive ringer mode changes
	/**
	 * 聲音模式改變時的廣播接收器
	 */
	private BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			SoundManager.getInstance(context).updateRingerMode();
		}
	};

	@Override
	public void onCreate() {
		mEnvironment = Environment.getInstance();
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onCreate.");
		}
		super.onCreate();
		pinyinIME = this;

		// 繫結詞庫解碼遠端服務PinyinDecoderService
		startPinyinDecoderService();

		mImEn = new EnglishInputProcessor();
		Settings.getInstance(PreferenceManager
				.getDefaultSharedPreferences(getApplicationContext()));

		mInputModeSwitcher = new InputModeSwitcher(this);
		mChoiceNotifier = new ChoiceNotifier(this);
		mGestureListenerSkb = new OnGestureListener(false);
		mGestureListenerCandidates = new OnGestureListener(true);
		mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);
		mGestureDetectorCandidates = new GestureDetector(this,
				mGestureListenerCandidates);

		mEnvironment.onConfigurationChanged(getResources().getConfiguration(),
				this);
	}

	@Override
	public void onDestroy() {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onDestroy.");
		}

		// 解繫結詞庫解碼遠端服務PinyinDecoderService
		unbindService(mPinyinDecoderServiceConnection);

		// 釋放設定類的引用
		Settings.releaseInstance();

		super.onDestroy();
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		Environment env = Environment.getInstance();
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onConfigurationChanged");
			Log.d(TAG, "--last config: " + env.getConfiguration().toString());
			Log.d(TAG, "---new config: " + newConfig.toString());
		}
		// We need to change the local environment first so that UI components
		// can get the environment instance to handle size issues. When
		// super.onConfigurationChanged() is called, onCreateCandidatesView()
		// and onCreateInputView() will be executed if necessary.
		env.onConfigurationChanged(newConfig, this);

		// Clear related UI of the previous configuration.
		if (null != mSkbContainer) {
			mSkbContainer.dismissPopups();
		}
		if (null != mCandidatesBalloon) {
			mCandidatesBalloon.dismiss();
		}
		super.onConfigurationChanged(newConfig);

		// 重置到空閒狀態
		resetToIdleState(false);
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (processKey(event, 0 != event.getRepeatCount()))
			return true;
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		if (processKey(event, true))
			return true;
		return super.onKeyUp(keyCode, event);
	}

	/**
	 * 給EditText傳送文字,在廣播接收器MyReceiver的onReceive()中呼叫。
	 * 
	 * @param text
	 */
	public void SetText(CharSequence text) {
		InputConnection ic = getCurrentInputConnection();
		if (ic == null)
			return;
		ic.beginBatchEdit();

		ic.commitText(text, 0);
		ic.endBatchEdit();

	}

	/**
	 * 按鍵處理函式
	 * 
	 * @param event
	 * @param realAction
	 * @return
	 */
	private boolean processKey(KeyEvent event, boolean realAction) {
		if (ImeState.STATE_BYPASS == mImeState)
			return false;

		int keyCode = event.getKeyCode();
		// SHIFT-SPACE is used to switch between Chinese and English
		// when HKB is on.
		// SHIFT + SPACE 按鍵組合處理
		if (KeyEvent.KEYCODE_SPACE == keyCode && event.isShiftPressed()) {
			if (!realAction)
				return true;

			updateIcon(mInputModeSwitcher.switchLanguageWithHkb());
			resetToIdleState(false);

			// 清除alt shift sym 鍵按住的狀態
			int allMetaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON
					| KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_ON
					| KeyEvent.META_SHIFT_LEFT_ON
					| KeyEvent.META_SHIFT_RIGHT_ON | KeyEvent.META_SYM_ON;
			getCurrentInputConnection().clearMetaKeyStates(allMetaState);
			return true;
		}

		// If HKB is on to input English, by-pass the key event so that
		// default key listener will handle it.
		// 如果是硬鍵盤英文輸入狀態,就忽略掉該按鍵,讓預設的按鍵監聽器去處理它。
		if (mInputModeSwitcher.isEnglishWithHkb()) {
			return false;
		}

		// 功能鍵處理
		if (processFunctionKeys(keyCode, realAction)) {
			return true;
		}

		int keyChar = 0;
		if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
			keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';
		} else if (keyCode >= KeyEvent.KEYCODE_0
				&& keyCode <= KeyEvent.KEYCODE_9) {
			keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';
		} else if (keyCode == KeyEvent.KEYCODE_COMMA) {
			keyChar = ',';
		} else if (keyCode == KeyEvent.KEYCODE_PERIOD) {
			keyChar = '.';
		} else if (keyCode == KeyEvent.KEYCODE_SPACE) {
			keyChar = ' ';
		} else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE) {
			keyChar = '\'';
		}

		if (mInputModeSwitcher.isEnglishWithSkb()) {// 英語軟鍵盤處理
			return mImEn.processKey(getCurrentInputConnection(), event,
					mInputModeSwitcher.isEnglishUpperCaseWithSkb(), realAction);
		} else if (mInputModeSwitcher.isChineseText()) {// 中文輸入法模式
			if (mImeState == ImeState.STATE_IDLE
					|| mImeState == ImeState.STATE_APP_COMPLETION) {
				mImeState = ImeState.STATE_IDLE;
				return processStateIdle(keyChar, keyCode, event, realAction);
			} else if (mImeState == ImeState.STATE_INPUT) {
				return processStateInput(keyChar, keyCode, event, realAction);
			} else if (mImeState == ImeState.STATE_PREDICT) {
				return processStatePredict(keyChar, keyCode, event, realAction);
			} else if (mImeState == ImeState.STATE_COMPOSING) {
				return processStateEditComposing(keyChar, keyCode, event,
						realAction);
			}
		} else {// 符號處理
			if (0 != keyChar && realAction) {
				// 傳送文字給EditText
				commitResultText(String.valueOf((char) keyChar));
			}
		}

		return false;
	}

	// keyCode can be from both hard key or soft key.
	/**
	 * 功能鍵處理函式
	 * 
	 * @param keyCode
	 * @param realAction
	 * @return
	 */
	private boolean processFunctionKeys(int keyCode, boolean realAction) {
		// Back key is used to dismiss all popup UI in a soft keyboard.
		// 後退鍵的處理。副軟鍵盤彈出框顯示的時候,如果realAction為true,那麼就呼叫dismissPopupSkb()隱藏副軟鍵盤彈出框,顯示主軟鍵盤檢視。
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			if (isInputViewShown()) {
				if (mSkbContainer.handleBack(realAction))
					return true;
			}
		}

		// Chinese related input is handle separately.
		// 中文相關輸入是單獨處理的,不在這邊處理。
		if (mInputModeSwitcher.isChineseText()) {
			return false;
		}

		if (null != mCandidatesContainer && mCandidatesContainer.isShown()
				&& !mDecInfo.isCandidatesListEmpty()) {// 候選詞檢視顯示的時候
			if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
				if (!realAction)
					return true;

				// 選擇當前高亮的候選詞
				chooseCandidate(-1);
				return true;
			}

			if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
				if (!realAction)
					return true;

				// 高亮位置向上一個候選詞移動或者移動到上一頁的最後一個候選詞的位置。
				mCandidatesContainer.activeCurseBackward();
				return true;
			}

			if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
				if (!realAction)
					return true;

				// 高亮位置向下一個候選詞移動或者移動到下一頁的第一個候選詞的位置。
				mCandidatesContainer.activeCurseForward();
				return true;
			}

			if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
				if (!realAction)
					return true;

				// 到上一頁候選詞
				mCandidatesContainer.pageBackward(false, true);
				return true;
			}

			if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
				if (!realAction)
					return true;

				// 到下一頁候選詞
				mCandidatesContainer.pageForward(false, true);
				return true;
			}

			// 在預報狀態下的刪除鍵處理
			if (keyCode == KeyEvent.KEYCODE_DEL
					&& ImeState.STATE_PREDICT == mImeState) {
				if (!realAction)
					return true;
				resetToIdleState(false);
				return true;
			}
		} else {// 沒有候選詞顯示的時候

			if (keyCode == KeyEvent.KEYCODE_DEL) {
				if (!realAction)
					return true;
				if (SIMULATE_KEY_DELETE) {
					// 給EditText傳送一個刪除按鍵的按下和彈起事件。
					simulateKeyEventDownUp(keyCode);
				} else {
					// 傳送刪除一個字元的操作給EditText
					getCurrentInputConnection().deleteSurroundingText(1, 0);
				}
				return true;
			}
			if (keyCode == KeyEvent.KEYCODE_ENTER) {
				if (!realAction)
					return true;

				// 傳送Enter鍵給EditText
				sendKeyChar('\n');
				return true;
			}
			if (keyCode == KeyEvent.KEYCODE_SPACE) {
				if (!realAction)
					return true;

				// 傳送' '字元給EditText
				sendKeyChar(' ');
				return true;
			}
		}

		return false;
	}

	/**
	 * 當 mImeState == ImeState.STATE_IDLE 或者 mImeState ==
	 * ImeState.STATE_APP_COMPLETION 時的按鍵處理函式
	 * 
	 * @param keyChar
	 * @param keyCode
	 * @param event
	 * @param realAction
	 * @return
	 */
	private boolean processStateIdle(int keyChar, int keyCode, KeyEvent event,
			boolean realAction) {
		// In this status, when user presses keys in [a..z], the status will
		// change to input state.
		if (keyChar >= 'a' && keyChar <= 'z' && !event.isAltPressed()) {
			if (!realAction)
				return true;
			mDecInfo.addSplChar((char) keyChar, true);

			// 對輸入的拼音進行查詢
			chooseAndUpdate(-1);
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_DEL) {
			if (!realAction)
				return true;
			if (SIMULATE_KEY_DELETE) {
				// 模擬刪除鍵傳送給 EditText
				simulateKeyEventDownUp(keyCode);
			} else {
				// 傳送刪除一個字元的操作給 EditText
				getCurrentInputConnection().deleteSurroundingText(1, 0);
			}
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_ENTER) {
			if (!realAction)
				return true;

			// 傳送 ENTER 鍵給 EditText
			sendKeyChar('\n');
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
				|| keyCode == KeyEvent.KEYCODE_ALT_RIGHT
				|| keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
				|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
			return true;
		} else if (event.isAltPressed()) {
			// 獲取中文全形字元
			char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
			if (0 != fullwidth_char) {
				if (realAction) {
					String result = String.valueOf(fullwidth_char);
					commitResultText(result);
				}
				return true;
			} else {
				if (keyCode >= KeyEvent.KEYCODE_A
						&& keyCode <= KeyEvent.KEYCODE_Z) {
					return true;
				}
			}
		} else if (keyChar != 0 && keyChar != '\t') {
			if (realAction) {
				if (keyChar == ',' || keyChar == '.') {
					// 傳送 '\uff0c' 或者 '\u3002' 給EditText
					inputCommaPeriod("", keyChar, false, ImeState.STATE_IDLE);
				} else {
					if (0 != keyChar) {
						String result = String.valueOf((char) keyChar);
						commitResultText(result);
					}
				}
			}
			return true;
		}
		return false;
	}

	/**
	 * 當 mImeState == ImeState.STATE_INPUT 時的按鍵處理函式
	 * 
	 * @param keyChar
	 * @param keyCode
	 * @param event
	 * @param realAction
	 * @return
	 */
	private boolean processStateInput(int keyChar, int keyCode, KeyEvent event,
			boolean realAction) {
		// If ALT key is pressed, input alternative key. But if the
		// alternative key is quote key, it will be used for input a splitter
		// in Pinyin string.
		// 如果 ALT 被按住
		if (event.isAltPressed()) {
			if ('\'' != event.getUnicodeChar(event.getMetaState())) {
				if (realAction) {
					// 獲取中文全形字元
					char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
					if (0 != fullwidth_char) {
						// 傳送高亮的候選詞 + 中文全形字元 給 EditView
						commitResultText(mDecInfo
								.getCurrentFullSent(mCandidatesContainer
										.getActiveCandiatePos())
								+ String.valueOf(fullwidth_char));
						resetToIdleState(false);
					}
				}
				return true;
			} else {
				keyChar = '\'';
			}
		}

		if (keyChar >= 'a' && keyChar <= 'z' || keyChar == '\''
				&& !mDecInfo.charBeforeCursorIsSeparator()
				|| keyCode == KeyEvent.KEYCODE_DEL) {
			if (!realAction)
				return true;

			// 新增輸入的拼音,然後進行詞庫查詢,或者刪除輸入的拼音指定的字元或字串,然後進行詞庫查詢。
			return processSurfaceChange(keyChar, keyCode);
		} else if (keyChar == ',' || keyChar == '.') {
			if (!realAction)
				return true;

			// 傳送 '\uff0c' 或者 '\u3002' 給EditText
			inputCommaPeriod(mDecInfo.getCurrentFullSent(mCandidatesContainer
					.getActiveCandiatePos()), keyChar, true,
					ImeState.STATE_IDLE);
			return true;

		} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
				|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN
				|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT
				|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			if (!realAction)
				return true;

			if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
				// 高亮位置向上一個候選詞移動或者移動到上一頁的最後一個候選詞的位置。
				mCandidatesContainer.activeCurseBackward();
			} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
				// 高亮位置向下一個候選詞移動或者移動到下一頁的第一個候選詞的位置。
				mCandidatesContainer.activeCurseForward();
			} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
				// If it has been the first page, a up key will shift
				// the state to edit composing string.
				// 到上一頁候選詞
				if (!mCandidatesContainer.pageBackward(false, true)) {
					mCandidatesContainer.enableActiveHighlight(false);
					changeToStateComposing(true);
					updateComposingText(true);
				}
			} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
				// 到下一頁候選詞
				mCandidatesContainer.pageForward(false, true);
			}
			return true;
		} else if (keyCode >= KeyEvent.KEYCODE_1
				&& keyCode <= KeyEvent.KEYCODE_9) {
			if (!realAction)
				return true;

			int activePos = keyCode - KeyEvent.KEYCODE_1;
			int currentPage = mCandidatesContainer.getCurrentPage();
			if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
				activePos = activePos
						+ mDecInfo.getCurrentPageStart(currentPage);
				if (activePos >= 0) {
					// 選擇候選詞,並根據條件是否進行下一步的預報。
					chooseAndUpdate(activePos);
				}
			}
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_ENTER) {
			if (!realAction)
				return true;
			if (mInputModeSwitcher.isEnterNoramlState()) {
				// 把輸入的拼音字串傳送給EditText
				commitResultText(mDecInfo.getOrigianlSplStr().toString());
				resetToIdleState(false);
			} else {
				// 把高亮的候選詞傳送給EditText
				commitResultText(mDecInfo
						.getCurrentFullSent(mCandidatesContainer
								.getActiveCandiatePos()));
				// 把ENTER傳送給EditText
				sendKeyChar('\n');
				resetToIdleState(false);
			}
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
				|| keyCode == KeyEvent.KEYCODE_SPACE) {
			if (!realAction)
				return true;
			// 選擇高亮的候選詞
			chooseCandidate(-1);
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_BACK) {
			if (!realAction)
				return true;
			resetToIdleState(false);
			// 關閉輸入法
			requestHideSelf(0);
			return true;
		}
		return false;
	}

	/**
	 * 當 mImeState == ImeState.STATE_PREDICT 時的按鍵處理函式
	 * 
	 * @param keyChar
	 * @param keyCode
	 * @param event
	 * @param realAction
	 * @return
	 */
	private boolean processStatePredict(int keyChar, int keyCode,
			KeyEvent event, boolean realAction) {
		if (!realAction)
			return true;

		// If ALT key is pressed, input alternative key.
		// 按住Alt鍵
		if (event.isAltPressed()) {
			// 獲取中文全形字元
			char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
			if (0 != fullwidth_char) {
				// 傳送高亮的候選詞 + 中文全形字元 給 EditView
				commitResultText(mDecInfo.getCandidate(mCandidatesContainer
						.getActiveCandiatePos())
						+ String.valueOf(fullwidth_char));
				resetToIdleState(false);
			}
			return true;
		}

		// In this status, when user presses keys in [a..z], the status will
		// change to input state.
		if (keyChar >= 'a' && keyChar <= 'z') {
			changeToStateInput(true);
			// 加一個字元進輸入的拼音字串中
			mDecInfo.addSplChar((char) keyChar, true);
			// 對輸入的拼音進行查詢。
			chooseAndUpdate(-1);
		} else if (keyChar == ',' || keyChar == '.') {
			// 傳送 '\uff0c' 或者 '\u3002' 給EditText
			inputCommaPeriod("", keyChar, true, ImeState.STATE_IDLE);
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
				|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN
				|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT
				|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
				// 高亮位置向上一個候選詞移動或者移動到上一頁的最後一個候選詞的位置。
				mCandidatesContainer.activeCurseBackward();
			}
			if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
				// 高亮位置向下一個候選詞移動或者移動到下一頁的第一個候選詞的位置。
				mCandidatesContainer.activeCurseForward();
			}
			if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
				// 到上一頁候選詞
				mCandidatesContainer.pageBackward(false, true);
			}
			if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
				// 到下一頁候選詞
				mCandidatesContainer.pageForward(false, true);
			}
		} else if (keyCode == KeyEvent.KEYCODE_DEL) {
			resetToIdleState(false);
		} else if (keyCode == KeyEvent.KEYCODE_BACK) {
			resetToIdleState(false);
			// 關閉輸入法
			requestHideSelf(0);
		} else if (keyCode >= KeyEvent.KEYCODE_1
				&& keyCode <= KeyEvent.KEYCODE_9) {
			int activePos = keyCode - KeyEvent.KEYCODE_1;
			int currentPage = mCandidatesContainer.getCurrentPage();
			if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
				activePos = activePos
						+ mDecInfo.getCurrentPageStart(currentPage);
				if (activePos >= 0) {
					// 選擇候選詞
					chooseAndUpdate(activePos);
				}
			}
		} else if (keyCode == KeyEvent.KEYCODE_ENTER) {
			// 發生ENTER鍵給EditText
			sendKeyChar('\n');
			resetToIdleState(false);
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
				|| keyCode == KeyEvent.KEYCODE_SPACE) {
			// 選擇候選詞
			chooseCandidate(-1);
		}

		return true;
	}

	/**
	 * 當 mImeState == ImeState.STATE_COMPOSING 時的按鍵處理函式
	 * 
	 * @param keyChar
	 * @param keyCode
	 * @param event
	 * @param realAction
	 * @return
	 */
	private boolean processStateEditComposing(int keyChar, int keyCode,
			KeyEvent event, boolean realAction) {
		if (!realAction)
			return true;

		// 獲取輸入的音字串的狀態
		ComposingView.ComposingStatus cmpsvStatus = mComposingView
				.getComposingStatus();

		// If ALT key is pressed, input alternative key. But if the
		// alternative key is quote key, it will be used for input a splitter
		// in Pinyin string.
		// 按住 ALT 鍵
		if (event.isAltPressed()) {
			if ('\'' != event.getUnicodeChar(event.getMetaState())) {
				// 獲取中文全形字元
				char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
				if (0 != fullwidth_char) {
					String retStr;
					if (ComposingView.ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {
						// 獲取原始的輸入拼音的字元
						retStr = mDecInfo.getOrigianlSplStr().toString();
					} else {
						// 獲取組合的輸入拼音的字元(有可能存在選中的候選詞)
						retStr = mDecInfo.getComposingStr();
					}
					// 傳送文字給EditText
					commitResultText(retStr + String.valueOf(fullwidth_char));
					resetToIdleState(false);
				}
				return true;
			} else {
				keyChar = '\'';
			}
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			if (!mDecInfo.selectionFinished()) {
				changeToStateInput(true);
			}
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
				|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			// 移動候選詞的游標
			mComposingView.moveCursor(keyCode);
		} else if ((keyCode == KeyEvent.KEYCODE_ENTER && mInputModeSwitcher
				.isEnterNoramlState())
				|| keyCode == KeyEvent.KEYCODE_DPAD_CENTER
				|| keyCode == KeyEvent.KEYCODE_SPACE) {
			if (ComposingView.ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {
				// 獲取原始的輸入拼音的字元
				String str = mDecInfo.getOrigianlSplStr().toString();
				if (!tryInputRawUnicode(str)) {
					// 傳送文字給EditText
					commitResultText(str);
				}
			} else if (ComposingView.ComposingStatus.EDIT_PINYIN == cmpsvStatus) {
				// 獲取組合的輸入拼音的字元(有可能存在選中的候選詞)
				String str = mDecInfo.getComposingStr();
				// 對開頭或者結尾為"unicode"的字串進行轉換
				if (!tryInputRawUnicode(str)) {
					// 傳送文字給EditText
					commitResultText(str);
				}
			} else {
				// 發生 組合的輸入拼音的字元(有可能存在選中的候選詞) 給 EditText
				commitResultText(mDecInfo.getComposingStr());
			}
			resetToIdleState(false);
		} else if (keyCode == KeyEvent.KEYCODE_ENTER
				&& !mInputModeSwitcher.isEnterNoramlState()) {
			String retStr;
			if (!mDecInfo.isCandidatesListEmpty()) {
				// 獲取當前高亮的候選詞
				retStr = mDecInfo.getCurrentFullSent(mCandidatesContainer
						.getActiveCandiatePos());
			} else {
				// 獲取組合的輸入拼音的字元(有可能存在選中的候選詞)
				retStr = mDecInfo.getComposingStr();
			}
			// 傳送文字給EditText
			commitResultText(retStr);
			// 發生ENTER鍵給EditText
			sendKeyChar('\n');
			resetToIdleState(false);
		} else if (keyCode == KeyEvent.KEYCODE_BACK) {
			resetToIdleState(false);
			// 關閉輸入法
			requestHideSelf(0);
			return true;
		} else {
			// 新增輸入的拼音,然後進行詞庫查詢,或者刪除輸入的拼音指定的字元或字串,然後進行詞庫查詢。
			return processSurfaceChange(keyChar, keyCode);
		}
		return true;
	}

	/**
	 * 對開頭或者結尾為"unicode"的字串進行轉換
	 * 
	 * @param str
	 * @return
	 */
	private boolean tryInputRawUnicode(String str) {
		if (str.length() > 7) {
			if (str.substring(0, 7).compareTo("unicode") == 0) {// str是"unicode"開頭
				try {
					// 擷取"unicode"後面的字串
					String digitStr = str.substring(7);
					int startPos = 0;
					int radix = 10;
					if (digitStr.length() > 2 && digitStr.charAt(0) == '0'
							&& digitStr.charAt(1) == 'x') {
						startPos = 2;
						radix = 16;
					}
					digitStr = digitStr.substring(startPos);
					// 取digitStr對應的整數
					int unicode = Integer.parseInt(digitStr, radix);
					if (unicode > 0) {
						char low = (char) (unicode & 0x0000ffff);
						char high = (char) ((unicode & 0xffff0000) >> 16);
						commitResultText(String.valueOf(low));
						if (0 != high) {
							commitResultText(String.valueOf(high));
						}
					}
					return true;
				} catch (NumberFormatException e) {
					return false;
				}
			} else if (str.substring(str.length() - 7, str.length()).compareTo(
					"unicode") == 0) {// str是"unicode"結尾
				String resultStr = "";
				for (int pos = 0; pos < str.length() - 7; pos++) {
					if (pos > 0) {
						resultStr += " ";
					}

					resultStr += "0x" + Integer.toHexString(str.charAt(pos));
				}
				commitResultText(String.valueOf(resultStr));
				return true;
			}
		}
		return false;
	}

	/**
	 * 新增輸入的拼音,然後進行詞庫查詢,或者刪除輸入的拼音指定的字元或字串,然後進行詞庫查詢。
	 * 
	 * @param keyChar
	 * @param keyCode
	 * @return
	 */
	private boolean processSurfaceChange(int keyChar, int keyCode) {
		if (mDecInfo.isSplStrFull() && KeyEvent.KEYCODE_DEL != keyCode) {
			return true;
		}

		if ((keyChar >= 'a' && keyChar <= 'z')
				|| (keyChar == '\'' && !mDecInfo.charBeforeCursorIsSeparator())
				|| (((keyChar >= '0' && keyChar <= '9') || keyChar == ' ') && ImeState.STATE_COMPOSING == mImeState)) {
			mDecInfo.addSplChar((char) keyChar, false);
			chooseAndUpdate(-1);
		} else if (keyCode == KeyEvent.KEYCODE_DEL) {
			mDecInfo.prepareDeleteBeforeCursor();
			chooseAndUpdate(-1);
		}
		return true;
	}

	/**
	 * 設定輸入法狀態為 mImeState = ImeState.STATE_COMPOSING;
	 * 
	 * @param updateUi
	 *            是否更新UI
	 */
	private void changeToStateComposing(boolean updateUi) {
		mImeState = ImeState.STATE_COMPOSING;
		if (!updateUi)
			return;

		if (null != mSkbContainer && mSkbContainer.isShown()) {
			mSkbContainer.toggleCandidateMode(true);
		}
	}

	/**
	 * 設定輸入法狀態為 mImeState = ImeState.STATE_INPUT;
	 * 
	 * @param updateUi
	 *            是否更新UI
	 */
	private void changeToStateInput(boolean updateUi) {
		mImeState = ImeState.STATE_INPUT;
		if (!updateUi)
			return;

		if (null != mSkbContainer && mSkbContainer.isShown()) {
			mSkbContainer.toggleCandidateMode(true);
		}
		showCandidateWindow(true);
	}

	/**
	 * 模擬按下一個按鍵
	 * 
	 * @param keyCode
	 */
	private void simulateKeyEventDownUp(int keyCode) {
		InputConnection ic = getCurrentInputConnection();
		if (null == ic)
			return;

		ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
		ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
	}

	/**
	 * 傳送字串給編輯框
	 * 
	 * @param resultText
	 */
	private void commitResultText(String resultText) {
		InputConnection ic = getCurrentInputConnection();
		if (null != ic)
			ic.commitText(resultText, 1);
		if (null != mComposingView) {
			mComposingView.setVisibility(View.INVISIBLE);
			mComposingView.invalidate();
		}
	}

	/**
	 * 設定是否顯示輸入拼音的view
	 * 
	 * @param visible
	 */
	private void updateComposingText(boolean visible) {
		if (!visible) {
			mComposingView.setVisibility(View.INVISIBLE);
		} else {
			mComposingView.setDecodingInfo(mDecInfo, mImeState);
			mComposingView.setVisibility(View.VISIBLE);
		}
		mComposingView.invalidate();
	}

	/**
	 * 傳送 '\uff0c' 或者 '\u3002' 給EditText
	 * 
	 * @param preEdit
	 * @param keyChar
	 * @param dismissCandWindow
	 *            是否重置候選詞視窗
	 * @param nextState
	 *            mImeState的下一個狀態
	 */
	private void inputCommaPeriod(String preEdit, int keyChar,
			boolean dismissCandWindow, ImeState nextState) {
		if (keyChar == ',')
			preEdit += '\uff0c';
		else if (keyChar == '.')
			preEdit += '\u3002';
		else
			return;
		commitResultText(preEdit);
		if (dismissCandWindow)
			resetCandidateWindow();
		mImeState = nextState;
	}

	/**
	 * 重置到空閒狀態
	 * 
	 * @param resetInlineText
	 */
	private void resetToIdleState(boolean resetInlineText) {
		if (ImeState.STATE_IDLE == mImeState)
			return;

		mImeState = ImeState.STATE_IDLE;
		mDecInfo.reset();

		// 重置顯示輸入拼音字串的 View
		if (null != mComposingView)
			mComposingView.reset();
		if (resetInlineText)
			commitResultText("");

		resetCandidateWindow();
	}

	/**
	 * 選擇候選詞,並根據條件是否進行下一步的預報。
	 * 
	 * @param candId
	 *            如果candId小於0 ,就對輸入的拼音進行查詢。
	 */
	private void chooseAndUpdate(int candId) {

		// 不是中文輸入法狀態
		if (!mInputModeSwitcher.isChineseText()) {
			String choice = mDecInfo.getCandidate(candId);
			if (null != choice) {
				commitResultText(choice);
			}
			resetToIdleState(false);
			return;
		}

		if (ImeState.STATE_PREDICT != mImeState) {
			// Get result candidate list, if choice_id < 0, do a new decoding.
			// If choice_id >=0, select the candidate, and get the new candidate
			// list.
			mDecInfo.chooseDecodingCandidate(candId);
		} else {
			// Choose a prediction item.
			mDecInfo.choosePredictChoice(candId);
		}

		if (mDecInfo.getComposingStr().length() > 0) {
			String resultStr;
			// 獲取選擇了的候選詞
			resultStr = mDecInfo.getComposingStrActivePart();

			// choiceId >= 0 means user finishes a choice selection.
			if (candId >= 0 && mDecInfo.canDoPrediction()) {
				// 發生選擇了的候選詞給EditText
				commitResultText(resultStr);
				// 設定輸入法狀態為預報
				mImeState = ImeState.STATE_PREDICT;
				// TODO 這一步是做什麼?
				if (null != mSkbContainer && mSkbContainer.isShown()) {
					mSkbContainer.toggleCandidateMode(false);
				}

				// Try to get the prediction list.
				// 獲取預報的候選詞列表
				if (Settings.getPrediction()) {
					InputConnection ic = getCurrentInputConnection();
					if (null != ic) {
						CharSequence cs = ic.getTextBeforeCursor(3, 0);
						if (null != cs) {
							mDecInfo.preparePredicts(cs);
						}
					}
				} else {
					mDecInfo.resetCandidates();
				}

				if (mDecInfo.mCandidatesList.size() > 0) {
					showCandidateWindow(false);
				} else {
					resetToIdleState(false);
				}
			} else {
				if (ImeState.STATE_IDLE == mImeState) {
					if (mDecInfo.getSplStrDecodedLen() == 0) {
						changeToStateComposing(true);
					} else {
						changeToStateInput(true);
					}
				} else {
					if (mDecInfo.selectionFinished()) {
						changeToStateComposing(true);
					}
				}
				showCandidateWindow(true);
			}
		} else {
			resetToIdleState(false);
		}
	}

	// If activeCandNo is less than 0, get the current active candidate number
	// from candidate view, otherwise use activeCandNo.
	/**
	 * 選擇候選詞
	 * 
	 * @param activeCandNo
	 *            如果小於0,就選擇當前高亮的候選詞。
	 */
	private void chooseCandidate(int activeCandNo) {
		if (activeCandNo < 0) {
			activeCandNo = mCandidatesContainer.getActiveCandiatePos();
		}
		if (activeCandNo >= 0) {
			chooseAndUpdate(activeCandNo);
		}
	}

	/**
	 * 繫結詞庫解碼遠端服務PinyinDecoderService
	 * 
	 * @return
	 */
	private boolean startPinyinDecoderService() {
		if (null == mDecInfo.mIPinyinDecoderService) {
			Intent serviceIntent = new Intent();
			serviceIntent.setClass(this, PinyinDecoderService.class);

			if (null == mPinyinDecoderServiceConnection) {
				mPinyinDecoderServiceConnection = new PinyinDecoderServiceConnection();
			}

			// Bind service
			if (bindService(serviceIntent, mPinyinDecoderServiceConnection,
					Context.BIND_AUTO_CREATE)) {
				return true;
			} else {
				return false;
			}
		}
		return true;
	}

	@Override
	public View onCreateCandidatesView() {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onCreateCandidatesView.");
		}

		LayoutInflater inflater = getLayoutInflater();

		// 設定顯示輸入拼音字串View的集裝箱
		// Inflate the floating container view
		mFloatingContainer = (LinearLayout) inflater.inflate(
				R.layout.floating_container, null);

		// The first child is the composing view.
		mComposingView = (ComposingView) mFloatingContainer.getChildAt(0);

		// 設定候選詞集裝箱
		mCandidatesContainer = (CandidatesContainer) inflater.inflate(
				R.layout.candidates_container, null);

		// Create balloon hint for candidates view. 建立候選詞氣泡
		mCandidatesBalloon = new BalloonHint(this, mCandidatesContainer,
				MeasureSpec.UNSPECIFIED);
		mCandidatesBalloon.setBalloonBackground(getResources().getDrawable(
				R.drawable.candidate_balloon_bg));
		mCandidatesContainer.initialize(mChoiceNotifier, mCandidatesBalloon,
				mGestureDetectorCandidates);

		// The floating window
		if (null != mFloatingWindow && mFloatingWindow.isShowing()) {
			mFloatingWindowTimer.cancelShowing();
			mFloatingWindow.dismiss();
		}
		mFloatingWindow = new PopupWindow(this);
		mFloatingWindow.setClippingEnabled(false);
		mFloatingWindow.setBackgroundDrawable(null);
		mFloatingWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
		mFloatingWindow.setContentView(mFloatingContainer);

		setCandidatesViewShown(true);
		return mCandidatesContainer;
	}

	/**
	 * 響應軟鍵盤按鍵的處理函式。在軟鍵盤集裝箱SkbContainer中responseKeyEvent()的呼叫。
	 * 軟鍵盤集裝箱SkbContainer的responseKeyEvent()在自身類中呼叫。
	 * 
	 * @param sKey
	 */
	public void responseSoftKeyEvent(SoftKey sKey) {
		if (null == sKey)
			return;

		InputConnection ic = getCurrentInputConnection();
		if (ic == null)
			return;

		int keyCode = sKey.getKeyCode();
		// Process some general keys, including KEYCODE_DEL, KEYCODE_SPACE,
		// KEYCODE_ENTER and KEYCODE_DPAD_CENTER.
		if (sKey.isKeyCodeKey()) {// 是系統的keycode
			// 功能鍵處理函式
			if (processFunctionKeys(keyCode, true))
				return;
		}

		if (sKey.isUserDefKey()) {// 是使用者定義的keycode
			// 通過我們定義的軟鍵盤的按鍵,切換輸入法模式。
			updateIcon(mInputModeSwitcher.switchModeForUserKey(keyCode));
			resetToIdleState(false);
			mSkbContainer.updateInputMode();
		} else {
			if (sKey.isKeyCodeKey()) {// 是系統的keycode
				KeyEvent eDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
						keyCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
				KeyEvent eUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode,
						0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);

				onKeyDown(keyCode, eDown);
				onKeyUp(keyCode, eUp);
			} else if (sKey.isUniStrKey()) {// 是字元按鍵
				boolean kUsed = false;
				// 獲取按鍵的字元
				String keyLabel = sKey.getKeyLabel();
				if (mInputModeSwitcher.isChineseTextWithSkb()
						&& (ImeState.STATE_INPUT == mImeState || ImeState.STATE_COMPOSING == mImeState)) {
					if (mDecInfo.length() > 0 && keyLabel.length() == 1
							&& keyLabel.charAt(0) == '\'') {
						// 加入拼音分隔符,然後進行詞庫查詢
						processSurfaceChange('\'', 0);
						kUsed = true;
					}
				}
				if (!kUsed) {
					if (ImeState.STATE_INPUT == mImeState) {
						// 傳送高亮候選詞給EditText
						commitResultText(mDecInfo
								.getCurrentFullSent(mCandidatesContainer
										.getActiveCandiatePos()));
					} else if (ImeState.STATE_COMPOSING == mImeState) {
						// 傳送 拼音字串(有可能存在選中的候選詞) 給EditText
						commitResultText(mDecInfo.getComposingStr());
					}

					// 傳送 按鍵的字元 給EditText
					commitResultText(keyLabel);
					resetToIdleState(false);
				}
			}

			// If the current soft keyboard is not sticky, IME needs to go
			// back to the previous soft keyboard automatically.
			// 如果當前的軟鍵盤不是粘性的,那麼輸入法需要返回上一個輸入法模式。
			if (!mSkbContainer.isCurrentSkbSticky()) {
				updateIcon(mInputModeSwitcher.requestBackToPreviousSkb());
				resetToIdleState(false);
				mSkbContainer.updateInputMode();
			}
		}
	}

	/**
	 * 顯示候選詞檢視
	 * 
	 * @param showComposingView
	 *            是否顯示輸入的拼音View
	 */
	private void showCandidateWindow(boolean showComposingView) {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "Candidates window is shown. Parent = "
					+ mCandidatesContainer);
		}

		setCandidatesViewShown(true);

		if (null != mSkbContainer)
			mSkbContainer.requestLayout();

		if (null == mCandidatesContainer) {
			resetToIdleState(false);
			return;
		}

		updateComposingText(showComposingView);
		mCandidatesContainer.showCandidates(mDecInfo,
				ImeState.STATE_COMPOSING != mImeState);
		mFloatingWindowTimer.postShowFloatingWindow();
	}

	/**
	 * 關閉候選詞視窗,並且關閉用於輸入拼音字串的視窗
	 */
	private void dismissCandidateWindow() {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "Candidates window is to be dismissed");
		}
		if (null == mCandidatesContainer)
			return;
		try {
			mFloatingWindowTimer.cancelShowing();
			mFloatingWindow.dismiss();
		} catch (Exception e) {
			Log.e(TAG, "Fail to show the PopupWindow.");
		}
		setCandidatesViewShown(false);

		if (null != mSkbContainer && mSkbContainer.isShown()) {
			mSkbContainer.toggleCandidateMode(false);
		}
	}

	/**
	 * 重置候選詞區域
	 */
	private void resetCandidateWindow() {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "Candidates window is to be reset");
		}
		if (null == mCandidatesContainer)
			return;
		try {
			mFloatingWindowTimer.cancelShowing();
			mFloatingWindow.dismiss();
		} catch (Exception e) {
			Log.e(TAG, "Fail to show the PopupWindow.");
		}

		if (null != mSkbContainer && mSkbContainer.isShown()) {
			mSkbContainer.toggleCandidateMode(false);
		}

		mDecInfo.resetCandidates();

		if (null != mCandidatesContainer && mCandidatesContainer.isShown()) {
			showCandidateWindow(false);
		}
	}

	/**
	 * 更新輸入法服務的圖示
	 * 
	 * @param iconId
	 */
	private void updateIcon(int iconId) {
		if (iconId > 0) {
			showStatusIcon(iconId);
		} else {
			hideStatusIcon();
		}
	}

	@Override
	public View onCreateInputView() {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onCreateInputView.");
		}
		LayoutInflater inflater = getLayoutInflater();
		mSkbContainer = (SkbContainer) inflater.inflate(R.layout.skb_container,
				null);
		mSkbContainer.setService(this);
		mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);
		mSkbContainer.setGestureDetector(mGestureDetectorSkb);
		return mSkbContainer;
	}

	@Override
	public void onStartInput(EditorInfo editorInfo, boolean restarting) {
		if (mEnvironment.needDebug()) {
			Log.d(TAG,
					"onStartInput " + " ccontentType: "
							+ String.valueOf(editorInfo.inputType)
							+ " Restarting:" + String.valueOf(restarting));
		}
		updateIcon(mInputModeSwitcher.requestInputWithHkb(editorInfo));
		resetToIdleState(false);
	}

	@Override
	public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
		if (mEnvironment.needDebug()) {
			Log.d(TAG,
					"onStartInputView " + " contentType: "
							+ String.valueOf(editorInfo.inputType)
							+ " Restarting:" + String.valueOf(restarting));
		}
		updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo));
		resetToIdleState(false);
		mSkbContainer.updateInputMode();
		setCandidatesViewShown(false);
	}

	@Override
	public void onFinishInputView(boolean finishingInput) {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onFinishInputView.");
		}
		resetToIdleState(false);
		super.onFinishInputView(finishingInput);
	}

	@Override
	public void onFinishInput() {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onFinishInput.");
		}
		resetToIdleState(false);
		super.onFinishInput();
	}

	@Override
	public void onFinishCandidatesView(boolean finishingInput) {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "onFinishCandidateView.");
		}
		resetToIdleState(false);
		super.onFinishCandidatesView(finishingInput);
	}

	@Override
	public void onDisplayCompletions(CompletionInfo[] completions) {
		// TODO 該函式什麼情況下被呼叫?
		if (!isFullscreenMode())
			return;
		if (null == completions || completions.length <= 0)
			return;
		if (null == mSkbContainer || !mSkbContainer.isShown())
			return;

		if (!mInputModeSwitcher.isChineseText()
				|| ImeState.STATE_IDLE == mImeState
				|| ImeState.STATE_PREDICT == mImeState) {
			mImeState = ImeState.STATE_APP_COMPLETION;
			// 準備從app獲取候選詞
			mDecInfo.prepareAppCompletions(completions);
			showCandidateWindow(false);
		}
	}

	/**
	 * 選擇候選詞後的處理函式。在ChoiceNotifier中實現CandidateViewListener監聽器的onClickChoice()中呼叫
	 * 。
	 * 
	 * @param activeCandNo
	 */
	private void onChoiceTouched(int activeCandNo) {
		if (mImeState == ImeState.STATE_COMPOSING) {
			changeToStateInput(true);
		} else if (mImeState == ImeState.STATE_INPUT
				|| mImeState == ImeState.STATE_PREDICT) {
			// 選擇候選詞
			chooseCandidate(activeCandNo);
		} else if (mImeState == ImeState.STATE_APP_COMPLETION) {
			if (null != mDecInfo.mAppCompletions && activeCandNo >= 0
					&& activeCandNo < mDecInfo.mAppCompletions.length) {
				CompletionInfo ci = mDecInfo.mAppCompletions[activeCandNo];
				if (null != ci) {
					InputConnection ic = getCurrentInputConnection();
					// 傳送從APP中獲取的候選詞給EditText
					ic.commitCompletion(ci);
				}
			}
			resetToIdleState(false);
		}
	}

	@Override
	public void requestHideSelf(int flags) {
		if (mEnvironment.needDebug()) {
			Log.d(TAG, "DimissSoftInput.");
		}
		dismissCandidateWindow();
		if (null != mSkbContainer && mSkbContainer.isShown()) {
			mSkbContainer.dismissPopups();
		}
		super.requestHideSelf(flags);
	}

	/**
	 * 選項選單對話方塊
	 */
	public void showOptionsMenu() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setCancelable(true);
		builder.setIcon(R.drawable.app_icon);
		builder.setNegativeButton(android.R.string.cancel, null);
		CharSequence itemSettings = getString(R.string.ime_settings_activity_name);
		CharSequence itemInputMethod = "";// =
											// getString(com.android.internal.R.string.inputMethod);
		builder.setItems(new CharSequence[] { itemSettings, itemInputMethod },
				new DialogInterface.OnClickListener() {

					public void onClick(DialogInterface di, int position) {
						di.dismiss();
						switch (position) {
						case 0:
							launchSettings();

							break;
						case 1:
							// InputMethodManager.getInstance(PinyinIME.this)
							// .showInputMethodPicker();
							break;
						}
					}
				});
		builder.setTitle(getString(R.string.ime_name));
		mOptionsDialog = builder.create();
		Window window = mOptionsDialog.getWindow();
		WindowManager.LayoutParams lp = window.getAttributes();
		lp.token = mSkbContainer.getWindowToken();
		lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
		window.setAttributes(lp);
		window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
		mOptionsDialog.show();
	}

	/**
	 * 啟動系統的設定頁面
	 */
	private void launchSettings() {
		Intent intent = new Intent();
		intent.setClass(PinyinIME.this, SettingsActivity.class);
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		startActivity(intent);
	}

	/**
	 * 顯示輸入的拼音字串PopupWindow 定時器
	 * 
	 * @ClassName PopupTimer
	 * @author keanbin
	 */
	private class PopupTimer extends Handler implements Runnable {
		private int mParentLocation[] = new int[2];

		void postShowFloatingWindow() {
			mFloatingContainer.measure(LayoutParams.WRAP_CONTENT,
					LayoutParams.WRAP_CONTENT);
			mFloatingWindow.setWidth(mFloatingContainer.getMeasuredWidth());
			mFloatingWindow.setHeight(mFloatingContainer.getMeasuredHeight());
			post(this);
		}

		void cancelShowing() {
			if (mFloatingWindow.isShowing()) {
				mFloatingWindow.dismiss();
			}
			removeCallbacks(this);
		}

		public void run() {
			// 獲取候選集裝箱的位置
			mCandidatesContainer.getLocationInWindow(mParentLocation);

			if (!mFloatingWindow.isShowing()) {
				// 顯示候選詞PopupWindow
				mFloatingWindow.showAtLocation(mCandidatesContainer,
						Gravity.LEFT | Gravity.TOP, mParentLocation[0],
						mParentLocation[1] - mFloatingWindow.getHeight());
			} else {
				// 更新候選詞PopupWindow
				mFloatingWindow
						.update(mParentLocation[0], mParentLocation[1]
								- mFloatingWindow.getHeight(),
								mFloatingWindow.getWidth(),
								mFloatingWindow.getHeight());
			}
		}
	}

	/**
	 * Used to notify IME that the user selects a candidate or performs an
	 * gesture. 當使用者選擇了候選詞或者在候選詞檢視滑動了手勢時的通知輸入法。實現了候選詞檢視的監聽器CandidateViewListener,
	 * 有選擇候選詞的處理函式、手勢向右滑動的處理函式、手勢向左滑動的處理函式 、手勢向上滑動的處理函式、手勢向下滑動的處理函式。
	 */
	public class ChoiceNotifier extends Handler implements
			CandidateViewListener {
		PinyinIME mIme;

		ChoiceNotifier(PinyinIME ime) {
			mIme = ime;
		}

		public void onClickChoice(int choiceId) {
			if (choiceId >= 0) {
				mIme.onChoiceTouched(choiceId);
			}
		}

		public void onToLeftGesture() {
			if (ImeState.STATE_COMPOSING == mImeState) {
				changeToStateInput(true);
			}
			mCandidatesContainer.pageForward(true, false);
		}

		public void onToRightGesture() {
			if (ImeState.STATE_COMPOSING == mImeState) {
				changeToStateInput(true);
			}
			mCandidatesContainer.pageBackward(true, false);
		}

		public void onToTopGesture() {
		}

		public void onToBottomGesture() {
		}
	}

	/**
	 * 手勢監聽器
	 * 
	 * @ClassName OnGestureListener
	 * @author keanbin
	 */
	public class OnGestureListener extends
			GestureDetector.SimpleOnGestureListener {
		/**
		 * When user presses and drags, the minimum x-distance to make a
		 * response to the drag event. 當使用者拖拽的時候,x軸上最小的差值才可以產生拖拽事件。
		 */
		private static final int MIN_X_FOR_DRAG = 60;

		/**
		 * When user presses and drags, the minimum y-distance to make a
		 * response to the drag event.當使用者拖拽的時候,y軸上最小的差值才可以產生拖拽事件。
		 */
		private static final int MIN_Y_FOR_DRAG = 40;

		/**
		 * Velocity threshold for a screen-move gesture. If the minimum
		 * x-velocity is less than it, no
		 * gesture.x軸上的手勢的最小速率閥值,小於這個閥值,就不是手勢。只要在滑動的期間
		 * ,有任意一段的速率小於這個值,就判斷這次的滑動不是手勢mNotGesture = true,就算接下去滑動的速率變高也是沒用。
		 */
		static private final float VELOCITY_THRESHOLD_X1 = 0.3f;

		/**
		 * Velocity threshold for a screen-move gesture. If the maximum
		 * x-velocity is less than it, no
		 * gesture.x軸上的手勢的最大速率閥值,大於這個閥值,就一定是手勢,mGestureRecognized = true。
		 */
		static private final float VELOCITY_THRESHOLD_X2 = 0.7f;

		/**
		 * Velocity threshold for a screen-move gesture. If the minimum
		 * y-velocity is less than it, no
		 * gesture.y軸上的手勢的最小速率閥值,小於這個閥值,就不是手勢。只要在滑動的期間
		 * ,有任意一段的速率小於這個值,就判斷這次的滑動不是手勢mNotGesture =
		 * true,就算接下去滑動的速率變高也是沒用,mGestureRecognized = true。
		 */
		static private final float VELOCITY_THRESHOLD_Y1 = 0.2f;

		/**
		 * Velocity threshold for a screen-move gesture. If the maximum
		 * y-velocity is less than it, no gesture.y軸上的手勢的最大速率閥值,大於這個閥值,就一定是手勢。
		 */
		static private final float VELOCITY_THRESHOLD_Y2 = 0.45f;

		/** If it false, we will not response detected gestures. 是否響應檢測到的手勢 */
		private boolean mReponseGestures;

		/** The minimum X velocity observed in the gesture. 能檢測到的x最小速率的手勢 */
		private float mMinVelocityX = Float.MAX_VALUE;

		/** The minimum Y velocity observed in the gesture. 能檢測到y最小速率的手勢 */
		private float mMinVelocityY = Float.MAX_VALUE;

		/**
		 * The first down time for the series of touch events for an
		 * action.第一次觸控事件的時間
		 */
		private long mTimeDown;

		/** The last time when onScroll() is called.最後一次 onScroll()被呼叫的時間 */
		private long mTimeLastOnScroll;

		/**
		 * This flag used to indicate that this gesture is not a gesture.
		 * 是否不是一個手勢?
		 */
		private boolean mNotGesture;

		/**
		 * This flag used to indicate that this gesture has been recognized.
		 * 是否是一個公認的手勢?
		 */
		private boolean mGestureRecognized;

		public OnGestureListener(boolean reponseGestures) {
			mReponseGestures = reponseGestures;
		}

		@Override
		public boolean onDown(MotionEvent e) {
			mMinVelocityX = Integer.MAX_VALUE;
			mMinVelocityY = Integer.MAX_VALUE;
			mTimeDown = e.getEventTime();
			mTimeLastOnScroll = mTimeDown;
			mNotGesture = false;
			mGestureRecognized = false;
			return false;
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
			if (mNotGesture)
				return false;
			if (mGestureRecognized)
				return true;

			if (Math.abs(e1.getX() - e2.getX()) < MIN_X_FOR_DRAG
					&& Math.abs(e1.getY() - e2.getY()) < MIN_Y_FOR_DRAG)
				return false;

			long timeNow = e2.getEventTime();
			long spanTotal = timeNow - mTimeDown;
			long spanThis = timeNow - mTimeLastOnScroll;
			if (0 == spanTotal)
				spanTotal = 1;
			if (0 == spanThis)
				spanThis = 1;

			// 計算總速率
			float vXTotal = (e2.getX() - e1.getX()) / spanTotal;
			float vYTotal = (e2.getY() - e1.getY()) / spanTotal;

			// The distances are from the current point to the previous one.
			// 計算這次 onScroll 的速率
			float vXThis = -distanceX / spanThis;
			float vYThis = -distanceY / spanThis;

			float kX = vXTotal * vXThis;
			float kY = vYTotal * vYThis;
			float k1 = kX + kY;
			float k2 = Math.abs(kX) + Math.abs(kY);

			// TODO 這個是什麼計算公式?
			if (k1 / k2 < 0.8) {
				mNotGesture = true;
				return false;
			}
			float absVXTotal = Math.abs(vXTotal);
			float absVYTotal = Math.abs(vYTotal);
			if (absVXTotal < mMinVelocityX) {
				mMinVelocityX = absVXTotal;
			}
			if (absVYTotal < mMinVelocityY) {
				mMinVelocityY = absVYTotal;
			}

			// 如果最小的速率比規定的小,那麼就不是手勢。
			if (mMinVelocityX < VELOCITY_THRESHOLD_X1
					&& mMinVelocityY < VELOCITY_THRESHOLD_Y1) {
				mNotGesture = true;
				return false;
			}

			// 判斷是什麼手勢?並呼叫手勢處理函式。
			if (vXTotal > VELOCITY_THRESHOLD_X2
					&& absVYTotal < VELOCITY_THRESHOLD_Y2) {
				if (mReponseGestures)
					onDirectionGesture(Gravity.RIGHT);
				mGestureRecognized = true;
			} else if (vXTotal < -VELOCITY_THRESHOLD_X2
					&& absVYTotal < VELOCITY_THRESHOLD_Y2) {
				if (mReponseGestures)
					onDirectionGesture(Gravity.LEFT);
				mGestureRecognized = true;
			} else if (vYTotal > VELOCITY_THRESHOLD_Y2
					&& absVXTotal < VELOCITY_THRESHOLD_X2) {
				if (mReponseGestures)
					onDirectionGesture(Gravity.BOTTOM);
				mGestureRecognized = true;
			} else if (vYTotal < -VELOCITY_THRESHOLD_Y2
					&& absVXTotal < VELOCITY_THRESHOLD_X2) {
				if (mReponseGestures)
					onDirectionGesture(Gravity.TOP);
				mGestureRecognized = true;
			}

			mTimeLastOnScroll = timeNow;
			return mGestureRecognized;
		}

		@Override
		public boolean onFling(MotionEvent me1, MotionEvent me2,
				float velocityX, float velocityY) {
			return mGestureRecognized;
		}

		/**
		 * 手勢的處理函式
		 * 
		 * @param gravity
		 *            手勢的類別
		 */
		public void onDirectionGesture(int gravity) {
			if (Gravity.NO_GRAVITY == gravity) {
				return;
			}

			if (Gravity.LEFT == gravity || Gravity.RIGHT == gravity) {
				if (mCandidatesContainer.isShown()) {
					if (Gravity.LEFT == gravity) {
						mCandidatesContainer.pageForward(true, true);
					} else {
						mCandidatesContainer.pageBackward(true, true);
					}
					return;
				}
			}
		}
	}

	/**
	 * Connection used for binding to the Pinyin decoding service.
	 * 詞庫解碼遠端服務PinyinDecoderService 的監聽器
	 */
	public class PinyinDecoderServiceConnection implements ServiceConnection {
		public void onServiceConnected(ComponentName name, IBinder service) {
			mDecInfo.mIPinyinDecoderService = IPinyinDecoderService.Stub
					.asInterface(service);
		}

		public void onServiceDisconnected(ComponentName name) {
		}
	}

	/**
	 * 輸入法狀態
	 */
	public enum ImeState {
		STATE_BYPASS, STATE_IDLE, STATE_INPUT, STATE_COMPOSING, STATE_PREDICT, STATE_APP_COMPLETION
	}

	/**
	 * 詞庫解碼操作物件
	 * 
	 * @ClassName DecodingInfo
	 * @author keanbin
	 */
	public class DecodingInfo {
		/**
		 * Maximum length of the Pinyin string
		 * 最大的字串的長度,其實只有27,因為最後一位為0,是mPyBuf[]的長度
		 */
		private static final int PY_STRING_MAX = 28;

		/**
		 * Maximum number of candidates to display in one page. 一頁顯示候選詞的最大個數
		 */
		private static final int MAX_PAGE_SIZE_DISPLAY = 10;

		/**
		 * Spelling (Pinyin) string. 拼音字串
		 */
		private StringBuffer mSurface;

		/**
		 * Byte buffer used as the Pinyin string parameter for native function
		 * call. 字元緩衝區作為拼音字串引數給本地函式呼叫,它的長度為PY_STRING_MAX,最後一位為0
		 */
		private byte mPyBuf[];

		/**
		 * The length of surface string successfully decoded by engine.
		 * 成功解碼的字串長度
		 */
		private int mSurfaceDecodedLen;

		/**
		 * Composing string. 拼音字串
		 */
		private String mComposingStr;

		/**
		 * Length of the active composing string. 活動的拼音字串長度
		 */
		private int mActiveCmpsLen;

		/**
		 * Composing string for display, it is copied from mComposingStr, and
		 * add spaces between spellings.
		 * 顯示的拼音字串,是從mComposingStr複製過來的,並且在拼寫之間加上了空格。
		 **/
		private String mComposingStrDisplay;

		/**
		 * Length of the active composing string for display. 顯示的拼音字串的長度
		 */
		private int mActiveCmpsDisplayLen;

		/**
		 * The first full sentence choice. 第一個完整句子,第一個候選詞。
		 */
		private String mFullSent;

		/**
		 * Number of characters which have been fixed. 固定的字元的數量
		 */
		private int mFixedLen;

		/**
		 * If this flag is true, selection is finished. 是否選擇完成了?
		 */
		private boolean mFinishSelection;

		/**
		 * The starting position for each spelling. The first one is the number
		 * of the real starting position elements. 每個拼寫的開始位置,猜測:第一個元素是拼寫的總數量?
		 */
		private int mSplStart[];

		/**
		 * Editing cursor in mSurface. 游標的位置
		 */
		private int mCursorPos;

		/**
		 * Remote Pinyin-to-Hanzi decoding engine service. 解碼引擎遠端服務
		 */
		private IPinyinDecoderService mIPinyinDecoderService;

		/**
		 * The complication information suggested by application. 應用的併發建議資訊
		 */
		private CompletionInfo[] mAppCompletions;

		/**
		 * The total number of choices for display. The list may only contains
		 * the first part. If user tries to navigate to next page which is not
		 * in the result list, we need to get these items. 顯示的可選擇的總數
		 **/
		public int mTotalChoicesNum;

		/**
		 * Candidate list. The first one is the full-sentence candidate. 候選詞列表
		 */
		public List<String> mCandidatesList = new Vector<String>();

		/**
		 * Element i stores the starting position of page i. 頁的開始位置
		 */
		public Vector<Integer> mPageStart = new Vector<Integer>();

		/**
		 * Element i stores the number of characters to page i. 每一頁的數量
		 */
		public Vector<Integer> mCnToPage = new Vector<Integer>();

		/**
		 * The position to delete in Pinyin string. If it is less than 0, IME
		 * will do an incremental search, otherwise IME will do a deletion
		 * operation. if {@link #mIsPosInSpl} is true, IME will delete the whole
		 * string for mPosDelSpl-th spelling, otherwise it will only delete
		 * mPosDelSpl-th character in the Pinyin string. 在拼音字串中的刪除位置
		 */
		public int mPosDelSpl = -1;

		/**
		 * If {@link #mPosDelSpl} is big than or equal to 0, this member is used
		 * to indicate that whether the postion is counted in spelling id or
		 * character. 如果 mPosDelSpl 大於等於 0,那麼這個引數就用於表明是否是 拼寫的id 或者 字元。
		 */
		public boolean mIsPosInSpl;

		public DecodingInfo() {
			mSurface = new StringBuffer();
			mSurfaceDecodedLen = 0;
		}

		/**
		 * 重置
		 */
		public void reset() {
			mSurface.delete(0, mSurface.length());
			mSurfaceDecodedLen = 0;
			mCursorPos = 0;
			mFullSent = "";
			mFixedLen = 0;
			mFinishSelection = false;
			mComposingStr = "";
			mComposingStrDisplay = "";
			mActiveCmpsLen = 0;
			mActiveCmpsDisplayLen = 0;

			resetCandidates();
		}

		/**
		 * 候選詞列表是否為空
		 * 
		 * @return
		 */
		public boolean isCandidatesListEmpty() {
			return mCandidatesList.size() == 0;
		}

		/**
		 * 拼寫的字串是否已滿
		 * 
		 * @return
		 */
		public boolean isSplStrFull() {
			if (mSurface.length() >= PY_STRING_MAX - 1)
				return true;
			return false;
		}

		/**
		 * 增加拼寫字元
		 * 
		 * @param ch
		 * @param reset
		 *            拼寫字元是否重置
		 */
		public void addSplChar(char ch, boolean reset) {
			if (reset) {
				mSurface.delete(0, mSurface.length());
				mSurfaceDecodedLen = 0;
				mCursorPos = 0;
				try {
					mIPinyinDecoderService.imResetSearch();
				} catch (RemoteException e) {
				}
			}
			mSurface.insert(mCursorPos, ch);
			mCursorPos++;
		}

		// Prepare to delete before cursor. We may delete a spelling char if
		// the cursor is in the range of unfixed part, delete a whole spelling
		// if the cursor in inside the range of the fixed part.
		// This function only marks the position used to delete.
		/**
		 * 刪除前的準備。該函式只是標記要刪除的位置。
		 */
		public void prepareDeleteBeforeCursor() {
			if (mCursorPos > 0) {
				int pos;

				for (pos = 0; pos < mFixedLen; pos++) {
					if (mSplStart[pos + 2] >= mCursorPos
							&& mSplStart[pos + 1] < mCursorPos) {
						// 刪除一個拼寫字串
						mPosDelSpl = pos;
						mCursorPos = mSplStart[pos + 1];
						mIsPosInSpl = true;
						break;
					}
				}

				if (mPosDelSpl < 0) {
					// 刪除一個字元
					mPosDelSpl = mCursorPos - 1;
					mCursorPos--;
					mIsPosInSpl = false;
				}
			}
		}

		/**
		 * 獲取拼音字串長度
		 * 
		 * @return
		 */
		public int length() {
			return mSurface.length();
		}

		/**
		 * 獲得拼音字串中指定位置的字元
		 * 
		 * @param index
		 * @return
		 */
		public char charAt(int index) {
			return mSurface.charAt(index);
		}

		/**
		 * 獲得拼音字串
		 * 
		 * @return
		 */
		public StringBuffer getOrigianlSplStr() {
			return mSurface;
		}

		/**
		 * 獲得成功解碼的字串長度
		 * 
		 * @return
		 */
		public int getSplStrDecodedLen() {
			return mSurfaceDecodedLen;
		}

		/**
		 * 獲得每個拼寫字串的開始位置
		 * 
		 * @return
		 */
		public int[] getSplStart() {
			return mSplStart;
		}

		/**
		 * 獲取拼音字串,有可能存在選中的候選詞
		 * 
		 * @return
		 */
		public String getComposingStr() {
			return mComposingStr;
		}

		/**
		 * 獲取活動的拼音字串,就是選擇了的候選詞。
		 * 
		 * @return
		 */
		public String getComposingStrActivePart() {
			assert (mActiveCmpsLen <= mComposingStr.length());
			return mComposingStr.substring(0, mActiveCmpsLen);
		}

		/**
		 * 獲得活動的拼音字串長度
		 * 
		 * @return
		 */
		public int getActiveCmpsLen() {
			return mActiveCmpsLen;
		}

		/**
		 * 獲取顯示的拼音字串
		 * 
		 * @return
		 */
		public String getComposingStrForDisplay() {
			return mComposingStrDisplay;
		}

		/**
		 * 顯示的拼音字串的長度
		 * 
		 * @return
		 */
		public int getActiveCmpsDisplayLen() {
			return mActiveCmpsDisplayLen;
		}

		/**
		 * 第一個完整句子
		 * 
		 * @return
		 */
		public String getFullSent() {
			return mFullSent;
		}

		/**
		 * 獲取當前完整句子
		 * 
		 * @param activeCandPos
		 * @return
		 */
		public String getCurrentFullSent(int activeCandPos) {
			try {
				String retStr = mFullSent.substring(0, mFixedLen);
				retStr += mCandidatesList.get(activeCandPos);
				return retStr;
			} catch (Exception e) {
				return "";
			}
		}

		/**
		 * 重置候選詞列表
		 */
		public void resetCandidates() {
			mCandidatesList.clear();
			mTotalChoicesNum = 0;

			mPageStart.clear();
			mPageStart.add(0);
			mCnToPage.clear();
			mCnToPage.add(0);
		}

		/**
		 * 候選詞來自app,判斷輸入法狀態 mImeState == ImeState.STATE_APP_COMPLETION。
		 * 
		 * @return
		 */
		public boolean candidatesFromApp() {
			return ImeState.STATE_APP_COMPLETION == mImeState;
		}

		/**
		 * 判斷 mComposingStr.length() == mFixedLen ?
		 * 
		 * @return
		 */
		public boolean canDoPrediction() {
			return mComposingStr.length() == mFixedLen;
		}

		/**
		 * 選擇是否完成
		 * 
		 * @return
		 */
		public boolean selectionFinished() {
			return mFinishSelection;
		}

		// After the user chooses a candidate, input method will do a
		// re-decoding and give the new candidate list.
		// If candidate id is less than 0, means user is inputting Pinyin,
		// not selecting any choice.
		/**
		 * 如果candId〉0,就選擇一個候選詞,並且重新獲取一個候選詞列表,選擇的候選詞存放在mComposingStr中,通過mDecInfo.
		 * getComposingStrActivePart()取出來。如果candId小於0 ,就對輸入的拼音進行查詢。
		 * 
		 * @param candId
		 */
		private void chooseDecodingCandidate(int candId) {
			if (mImeState != ImeState.STATE_PREDICT) {
				resetCandidates();
				int totalChoicesNum = 0;
				try {
					if (candId < 0) {
						if (length() == 0) {
							totalChoicesNum = 0;
						} else {
							if (mPyBuf == null)
								mPyBuf = new byte[PY_STRING_MAX];
							for (int i = 0; i < length(); i++)
								mPyBuf[i] = (byte) charAt(i);
							mPyBuf[length()] = 0;

							if (mPosDelSpl < 0) {
								totalChoicesNum = mIPinyinDecoderService
										.imSearch(mPyBuf, length());
							} else {
								boolean clear_fixed_this_step = true;
								if (ImeState.STATE_COMPOSING == mImeState) {
									clear_fixed_this_step = false;
								}
								totalChoicesNum = mIPinyinDecoderService
										.imDelSearch(mPosDelSpl, mIsPosInSpl,
												clear_fixed_this_step);
								mPosDelSpl = -1;
							}
						}
					} else {
						totalChoicesNum = mIPinyinDecoderService
								.imChoose(candId);
					}
				} catch (RemoteException e) {
				}
				updateDecInfoForSearch(totalChoicesNum);
			}
		}

		/**
		 * 更新查詢詞庫後的資訊
		 * 
		 * @param totalChoicesNum
		 */
		private void updateDecInfoForSearch(int totalChoicesNum) {
			mTotalChoicesNum = totalChoicesNum;
			if (mTotalChoicesNum < 0) {
				mTotalChoicesNum = 0;
				return;
			}

			try {
				String pyStr;

				mSplStart = mIPinyinDecoderService.imGetSplStart();
				pyStr = mIPinyinDecoderService.imGetPyStr(false);
				mSurfaceDecodedLen = mIPinyinDecoderService.imGetPyStrLen(true);
				assert (mSurfaceDecodedLen <= pyStr.length());

				mFullSent = mIPinyinDecoderService.imGetChoice(0);
				mFixedLen = mIPinyinDecoderService.imGetFixedLen();

				// Update the surface string to the one kept by engine.
				mSurface.replace(0, mSurface.length(), pyStr);

				if (mCursorPos > mSurface.length())
					mCursorPos = mSurface.length();
				mComposingStr = mFullSent.substring(0, mFixedLen)
						+ mSurface.substring(mSplStart[mFixedLen + 1]);

				mActiveCmpsLen = mComposingStr.length();
				if (mSurfaceDecodedLen > 0) {
					mActiveCmpsLen = mActiveCmpsLen
							- (mSurface.length() - mSurfaceDecodedLen);
				}

				// Prepare the display string.
				if (0 == mSurfaceDecodedLen) {
					mComposingStrDisplay = mComposingStr;
					mActiveCmpsDisplayLen = mComposingStr.length();
				} else {
					mComposingStrDisplay = mFullSent.substring(0, mFixedLen);
					for (int pos = mFixedLen + 1; pos < mSplStart.length - 1; pos++) {
						mComposingStrDisplay += mSurface.substring(
								mSplStart[pos], mSplStart[pos + 1]);
						if (mSplStart[pos + 1] < mSurfaceDecodedLen) {
							mComposingStrDisplay += " ";
						}
					}
					mActiveCmpsDisplayLen = mComposingStrDisplay.length();
					if (mSurfaceDecodedLen < mSurface.length()) {
						mComposingStrDisplay += mSurface
								.substring(mSurfaceDecodedLen);
					}
				}

				if (mSplStart.length == mFixedLen + 2) {
					mFinishSelection = true;
				} else {
					mFinishSelection = false;
				}
			} catch (RemoteException e) {
				Log.w(TAG, "PinyinDecoderService died", e);
			} catch (Exception e) {
				mTotalChoicesNum = 0;
				mComposingStr = "";
			}
			// Prepare page 0.
			if (!mFinishSelection) {
				preparePage(0);
			}
		}

		/**
		 * 選擇預報候選詞
		 * 
		 * @param choiceId
		 */
		private void choosePredictChoice(int choiceId) {
			if (ImeState.STATE_PREDICT != mImeState || choiceId < 0
					|| choiceId >= mTotalChoicesNum) {
				return;
			}

			String tmp = mCandidatesList.get(choiceId);

			resetCandidates();

			mCandidatesList.add(tmp);
			mTotalChoicesNum = 1;

			mSurface.replace(0, mSurface.length(), "");
			mCursorPos = 0;
			mFullSent = tmp;
			mFixedLen = tmp.length();
			mComposingStr = mFullSent;
			mActiveCmpsLen = mFixedLen;

			mFinishSelection = true;
		}

		/**
		 * 獲得指定的候選詞
		 * 
		 * @param candId
		 * @return
		 */
		public String getCandidate(int candId) {
			// Only loaded items can be gotten, so we use mCandidatesList.size()
			// instead mTotalChoiceNum.
			if (candId < 0 || candId > mCandidatesList.size()) {
				return null;
			}
			return mCandidatesList.get(candId);
		}

		/**
		 * 從快取中獲取一頁的候選詞,然後放進mCandidatesList中。三種不同的獲取方式:1、mIPinyinDecoderService.
		 * imGetChoiceList
		 * ();2、mIPinyinDecoderService.imGetPredictList;3、從mAppCompletions[]取。
		 */
		private void getCandiagtesForCache() {
			int fetchStart = mCandidatesList.size();
			int fetchSize = mTotalChoicesNum - fetchStart;
			if (fetchSize > MAX_PAGE_SIZE_DISPLAY) {
				fetchSize = MAX_PAGE_SIZE_DISPLAY;
			}
			try {
				List<String> newList = null;
				if (ImeState.STATE_INPUT == mImeState
						|| ImeState.STATE_IDLE == mImeState
						|| ImeState.STATE_COMPOSING == mImeState) {
					newList = mIPinyinDecoderService.imGetChoiceList(
							fetchStart, fetchSize, mFixedLen);
				} else if (ImeState.STATE_PREDICT == mImeState) {
					newList = mIPinyinDecoderService.imGetPredictList(
							fetchStart, fetchSize);
				} else if (ImeState.STATE_APP_COMPLETION == mImeState) {
					newList = new ArrayList<String>();
					if (null != mAppCompletions) {
						for (int pos = fetchStart; pos < fetchSize; pos++) {
							CompletionInfo ci = mAppCompletions[pos];
							if (null != ci) {
								CharSequence s = ci.getText();
								if (null != s)
									newList.add(s.toString());
							}
						}
					}
				}
				mCandidatesList.addAll(newList);
			} catch (RemoteException e) {
				Log.w(TAG, "PinyinDecoderService died", e);
			}
		}

		/**
		 * 判斷指定頁是否準備好了?
		 * 
		 * @param pageNo
		 * @return
		 */
		public boolean pageReady(int pageNo) {
			// If the page number is less than 0, return false
			if (pageNo < 0)
				return false;

			// Page pageNo's ending information is not ready.
			if (mPageStart.size() <= pageNo + 1) {
				return false;
			}

			return true;
		}

		/**
		 * 準備指定頁,從快取中取出指定頁的候選詞。
		 * 
		 * @param pageNo
		 * @return
		 */
		public boolean preparePage(int pageNo) {
			// If the page number is less than 0, return false
			if (pageNo < 0)
				return false;

			// Make sure the starting information for page pageNo is ready.
			if (mPageStart.size() <= pageNo) {
				return false;
			}

			// Page pageNo's ending information is also ready.
			if (mPageStart.size() > pageNo + 1) {
				return true;
			}

			// If cached items is enough for page pageNo.
			if (mCandidatesList.size() - mPageStart.elementAt(pageNo) >= MAX_PAGE_SIZE_DISPLAY) {
				return true;
			}

			// Try to get more items from engine
			getCandiagtesForCache();

			// Try to find if there are available new items to display.
			// If no new item, return false;
			if (mPageStart.elementAt(pageNo) >= mCandidatesList.size()) {
				return false;
			}

			// If there are new items, return true;
			return true;
		}

		/**
		 * 準備預報候選詞
		 * 
		 * @param history
		 */
		public void preparePredicts(CharSequence history) {
			if (null == history)
				return;

			resetCandidates();

			if (Settings.getPrediction()) {
				String preEdit = history.toString();
				int predictNum = 0;
				if (null != preEdit) {
					try {
						mTotalChoicesNum = mIPinyinDecoderService
								.imGetPredictsNum(preEdit);
					} catch (RemoteException e) {
						return;
					}
				}
			}

			preparePage(0);
			mFinishSelection = false;
		}

		/**
		 * 準備從app獲取候選詞
		 * 
		 * @param completions
		 */
		private void prepareAppCompletions(CompletionInfo completions[]) {
			resetCandidates();
			mAppCompletions = completions;
			mTotalChoicesNum = completions.length;
			preparePage(0);
			mFinishSelection = false;
			return;
		}

		/**
		 * 獲取當前頁的長度
		 * 
		 * @param currentPage
		 * @return
		 */
		public int getCurrentPageSize(int currentPage) {
			if (mPageStart.size() <= currentPage + 1)
				return 0;
			return mPageStart.elementAt(currentPage + 1)
					- mPageStart.elementAt(currentPage);
		}

		/**
		 * 獲取當前頁的開始位置
		 * 
		 * @param currentPage
		 * @return
		 */
		public int getCurrentPageStart(int currentPage) {
			if (mPageStart.size() < currentPage + 1)
				return mTotalChoicesNum;
			return mPageStart.elementAt(currentPage);
		}

		/**
		 * 是否還有下一頁?
		 * 
		 * @param currentPage
		 * @return
		 */
		public boolean pageForwardable(int currentPage) {
			if (mPageStart.size() <= currentPage + 1)
				return false;
			if (mPageStart.elementAt(currentPage + 1) >= mTotalChoicesNum) {
				return false;
			}
			return true;
		}

		/**
		 * 是否有上一頁
		 * 
		 * @param currentPage
		 * @return
		 */
		public boolean pageBackwardable(int currentPage) {
			if (currentPage > 0)
				return true;
			return false;
		}

		/**
		 * 游標前面的字元是否是分隔符“'”
		 * 
		 * @return
		 */
		public boolean charBeforeCursorIsSeparator() {
			int len = mSurface.length();
			if (mCursorPos > len)
				return false;
			if (mCursorPos > 0 && mSurface.charAt(mCursorPos - 1) == '\'') {
				return true;
			}
			return false;
		}

		/**
		 * 獲取游標位置
		 * 
		 * @return
		 */
		public int getCursorPos() {
			return mCursorPos;
		}

		/**
		 * 獲取游標在拼音字串中的位置
		 * 
		 * @return
		 */
		public int getCursorPosInCmps() {
			int cursorPos = mCursorPos;
			int fixedLen = 0;

			for (int hzPos = 0; hzPos < mFixedLen; hzPos++) {
				if (mCursorPos >= mSplStart[hzPos + 2]) {
					cursorPos -= mSplStart[hzPos + 2] - mSplStart[hzPos + 1];
					cursorPos += 1;
				}
			}
			return cursorPos;
		}

		/**
		 * 獲取游標在顯示的拼音字串中的位置
		 * 
		 * @return
		 */
		public int getCursorPosInCmpsDisplay() {
			int cursorPos = getCursorPosInCmps();
			// +2 is because: one for mSplStart[0], which is used for other
			// purpose(The length of the segmentation string), and another
			// for the first spelling which does not need a space before it.
			for (int pos = mFixedLen + 2; pos < mSplStart.length - 1; pos++) {
				if (mCursorPos <= mSplStart[pos]) {
					break;
				} else {
					cursorPos++;
				}
			}
			return cursorPos;
		}

		/**
		 * 移動游標到末尾
		 * 
		 * @param left
		 */
		public void moveCursorToEdge(boolean left) {
			if (left)
				mCursorPos = 0;
			else
				mCursorPos = mSurface.length();
		}

		// Move cursor. If offset is 0, this function can be used to adjust
		// the cursor into the bounds of the string.
		/**
		 * 移動游標
		 * 
		 * @param offset
		 */
		public void moveCursor(int offset) {
			if (offset > 1 || offset < -1)
				return;

			if (offset != 0) {
				int hzPos = 0;
				for (hzPos = 0; hzPos <= mFixedLen; hzPos++) {
					if (mCursorPos == mSplStart[hzPos + 1]) {
						if (offset < 0) {
							if (hzPos > 0) {
								offset = mSplStart[hzPos]
										- mSplStart[hzPos + 1];
							}
						} else {
							if (hzPos < mFixedLen) {
								offset = mSplStart[hzPos + 2]
										- mSplStart[hzPos + 1];
							}
						}
						break;
					}
				}
			}
			mCursorPos += offset;
			if (mCursorPos < 0) {
				mCursorPos = 0;
			} else if (mCursorPos > mSurface.length()) {
				mCursorPos = mSurface.length();
			}
		}

		/**
		 * 獲取拼寫字串的數量
		 * 
		 * @return
		 */
		public int getSplNum() {
			return mSplStart[0];
		}

		/**
		 * 獲取固定的字元的數量
		 * 
		 * @return
		 */
		public int getFixedLen() {
			return mFixedLen;
		}
	}
}

13、Settings.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

/**
 * Class used to maintain settings. 設定類,對配置檔案進行讀取寫入操作。該類採用了單例模式。
 * 
 * @ClassName Settings
 * @author keanbin
 */
public class Settings {
	private static final String ANDPY_CONFS_KEYSOUND_KEY = "Sound";
	private static final String ANDPY_CONFS_VIBRATE_KEY = "Vibrate";
	private static final String ANDPY_CONFS_PREDICTION_KEY = "Prediction";

	private static boolean mKeySound;
	private static boolean mVibrate;
	private static boolean mPrediction;

	private static Settings mInstance = null;

	/**
	 * 引用計數
	 */
	private static int mRefCount = 0;

	private static SharedPreferences mSharedPref = null;

	protected Settings(SharedPreferences pref) {
		mSharedPref = pref;
		initConfs();
	}

	/**
	 * 獲得該例項
	 * 
	 * @param pref
	 * @return
	 */
	public static Settings getInstance(SharedPreferences pref) {
		if (mInstance == null) {
			mInstance = new Settings(pref);
		}
		assert (pref == mSharedPref);
		mRefCount++;
		return mInstance;
	}

	/**
	 * 設定震動、聲音、預報開關標記進入配置檔案
	 */
	public static void writeBack() {
		Editor editor = mSharedPref.edit();
		editor.putBoolean(ANDPY_CONFS_VIBRATE_KEY, mVibrate);
		editor.putBoolean(ANDPY_CONFS_KEYSOUND_KEY, mKeySound);
		editor.putBoolean(ANDPY_CONFS_PREDICTION_KEY, mPrediction);
		editor.commit();
	}

	/**
	 * 釋放對該例項的使用。
	 */
	public static void releaseInstance() {
		mRefCount--;
		if (mRefCount == 0) {
			mInstance = null;
		}
	}

	/**
	 * 初始化,從配置檔案中取出震動、聲音、預報開關標記。
	 */
	private void initConfs() {
		mKeySound = mSharedPref.getBoolean(ANDPY_CONFS_KEYSOUND_KEY, true);
		mVibrate = mSharedPref.getBoolean(ANDPY_CONFS_VIBRATE_KEY, false);
		mPrediction = mSharedPref.getBoolean(ANDPY_CONFS_PREDICTION_KEY, true);
	}

	/**
	 * 獲得按鍵聲音的開關
	 * 
	 * @return
	 */
	public static boolean getKeySound() {
		return mKeySound;
	}

	/**
	 * 設定按鍵聲音的開關
	 * 
	 * @param v
	 */
	public static void setKeySound(boolean v) {
		if (mKeySound == v)
			return;
		mKeySound = v;
	}

	/**
	 * 獲得震動開關
	 * 
	 * @return
	 */
	public static boolean getVibrate() {
		return mVibrate;
	}

	/**
	 * 設定震動開關
	 * 
	 * @param v
	 */
	public static void setVibrate(boolean v) {
		if (mVibrate == v)
			return;
		mVibrate = v;
	}

	/**
	 * 獲得預報開關
	 * 
	 * @return
	 */
	public static boolean getPrediction() {
		return mPrediction;
	}

	/**
	 * 設定預報開關
	 * 
	 * @param v
	 */
	public static void setPrediction(boolean v) {
		if (mPrediction == v)
			return;
		mPrediction = v;
	}
}


14、SettingsActivity.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.util.List;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;

/**
 * Setting activity of Pinyin IME. 設定頁面
 * 
 * @ClassName SettingsActivity
 * @author keanbin
 */
public class SettingsActivity extends PreferenceActivity implements
		Preference.OnPreferenceChangeListener {

	private static String TAG = "SettingsActivity";

	private CheckBoxPreference mKeySoundPref;
	private CheckBoxPreference mVibratePref;
	private CheckBoxPreference mPredictionPref;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.xml.settings);

		PreferenceScreen prefSet = getPreferenceScreen();

		mKeySoundPref = (CheckBoxPreference) prefSet
				.findPreference(getString(R.string.setting_sound_key));
		mVibratePref = (CheckBoxPreference) prefSet
				.findPreference(getString(R.string.setting_vibrate_key));
		mPredictionPref = (CheckBoxPreference) prefSet
				.findPreference(getString(R.string.setting_prediction_key));

		prefSet.setOnPreferenceChangeListener(this);

		Settings.getInstance(PreferenceManager
				.getDefaultSharedPreferences(getApplicationContext()));

		updatePreference(prefSet, getString(R.string.setting_advanced_key));

		updateWidgets();
	}

	@Override
	protected void onResume() {
		super.onResume();
		updateWidgets();
	}

	@Override
	protected void onDestroy() {
		Settings.releaseInstance();
		super.onDestroy();
	}

	@Override
	protected void onPause() {
		super.onPause();

		// 把使用者的設定存入配置檔案中
		Settings.setKeySound(mKeySoundPref.isChecked());
		Settings.setVibrate(mVibratePref.isChecked());
		Settings.setPrediction(mPredictionPref.isChecked());

		Settings.writeBack();
	}

	public boolean onPreferenceChange(Preference preference, Object newValue) {
		return true;
	}

	/**
	 * 從配置檔案獲取之前的設定,更像UI
	 */
	private void updateWidgets() {
		mKeySoundPref.setChecked(Settings.getKeySound());
		mVibratePref.setChecked(Settings.getVibrate());
		mPredictionPref.setChecked(Settings.getPrediction());
	}

	/**
	 * 設定PreferenceScreen
	 * 
	 * @param parentPref
	 * @param prefKey
	 */
	public void updatePreference(PreferenceGroup parentPref, String prefKey) {
		Preference preference = parentPref.findPreference(prefKey);
		if (preference == null) {
			return;
		}
		Intent intent = preference.getIntent();
		if (intent != null) {
			PackageManager pm = getPackageManager();
			List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
			int listSize = list.size();
			if (listSize == 0)
				parentPref.removePreference(preference);
		}
	}
}


15、SkbContainer.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.Context;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.ViewFlipper;

/**
 * The top container to host soft keyboard view(s). 軟鍵盤View的集裝箱,主持一個軟體盤View。
 */
public class SkbContainer extends RelativeLayout implements OnTouchListener {
	/**
	 * For finger touch, user tends to press the bottom part of the target key,
	 * or he/she even presses the area out of it, so it is necessary to make a
	 * simple bias correction. If the input method runs on emulator, no bias
	 * correction will be used. 對於手指觸控點的Y座標進行的偏好 。
	 */
	private static final int Y_BIAS_CORRECTION = -10;

	/**
	 * Used to skip these move events whose position is too close to the
	 * previous touch events. 觸控移動的x座標和y座標的差值如果在MOVE_TOLERANCE之內,觸控的移動事件就被拋棄。
	 */
	private static final int MOVE_TOLERANCE = 6;

	/**
	 * If this member is true, PopupWindow is used to show on-key highlight
	 * effect. 彈出框是否用於重點顯示效果。
	 */
	private static boolean POPUPWINDOW_FOR_PRESSED_UI = false;

	/**
	 * The current soft keyboard layout. 當前的軟鍵盤佈局檔案資源ID。
	 * 
	 * @see com.android.inputmethod.pinyin.InputModeSwitcher for detailed layout
	 *      definitions.
	 * 
	 * 
	 */
	private int mSkbLayout = 0;

	/**
	 * The input method service. 輸入法服務
	 */
	private InputMethodService mService;

	/**
	 * Input mode switcher used to switch between different modes like Chinese,
	 * English, etc. 輸入法變換器
	 */
	private InputModeSwitcher mInputModeSwitcher;

	/**
	 * The gesture detector. 手勢識別
	 */
	private GestureDetector mGestureDetector;

	private Environment mEnvironment;

	/**
	 * view切換管理
	 */
	private ViewFlipper mSkbFlipper;

	/**
	 * The popup balloon hint for key press/release. 氣泡
	 */
	private BalloonHint mBalloonPopup;

	/**
	 * The on-key balloon hint for key press/release. 氣泡
	 */
	private BalloonHint mBalloonOnKey = null;

	/** The major sub soft keyboard. 主要檢視:軟鍵盤檢視。 */
	private SoftKeyboardView mMajorView;

	/**
	 * The last parameter when function {@link #toggleCandidateMode(boolean)}
	 * was called. 最後的候選詞顯示。
	 */
	private boolean mLastCandidatesShowing;

	/**
	 * Used to indicate whether a popup soft keyboard is shown. 一個彈出副的軟體盤是否在顯示 ?
	 */
	private boolean mPopupSkbShow = false;

	/**
	 * Used to indicate whether a popup soft keyboard is just shown, and waits
	 * for the touch event to release. After the release, the popup window can
	 * response to touch events.
	 * 是否一個副軟鍵盤彈出框正在顯示,並且等待觸控事件釋放,觸控事件釋放之後,副軟鍵盤可以響應觸控事件?
	 **/
	private boolean mPopupSkbNoResponse = false;

	/** Popup sub keyboard. 副軟鍵盤彈出框 */
	private PopupWindow mPopupSkb;

	/** The view of the popup sub soft keyboard. 副軟鍵盤彈出框中的軟鍵盤檢視 */
	private SoftKeyboardView mPopupSkbView;

	private int mPopupX;

	private int mPopupY;

	/**
	 * When user presses a key, a timer is started, when it times out, it is
	 * necessary to detect whether user still holds the key.
	 * 當使用者按下一個按鍵,一個定時器啟動,定時器時間到的時候,需要檢查使用者是否還按住這個鍵。
	 */
	private volatile boolean mWaitForTouchUp = false;

	/**
	 * When user drags on the soft keyboard and the distance is enough, this
	 * drag will be recognized as a gesture and a gesture-based action will be
	 * taken, in this situation, ignore the consequent events.
	 * 當使用者在鍵盤上拖拽足夠的距離後,是否忽略隨之而生的事件?
	 */
	private volatile boolean mDiscardEvent = false;

	/**
	 * For finger touch, user tends to press the bottom part of the target key,
	 * or he/she even presses the area out of it, so it is necessary to make a
	 * simple bias correction in Y. 對使用者點選的觸控點進行Y座標的糾正。
	 */
	private int mYBiasCorrection = 0;

	/**
	 * The x coordination of the last touch event. 最後觸控事件的x座標。
	 */
	private int mXLast;

	/**
	 * The y coordination of the last touch event. 最後觸控事件的 y座標。
	 */
	private int mYLast;

	/**
	 * The soft keyboard view. 軟鍵盤檢視
	 */
	private SoftKeyboardView mSkv;

	/**
	 * The position of the soft keyboard view in the container. 軟鍵盤檢視的集裝箱的位置
	 */
	private int mSkvPosInContainer[] = new int[2];

	/**
	 * The key pressed by user.使用者按下的按鍵
	 */
	private SoftKey mSoftKeyDown = null;

	/**
	 * Used to timeout a press if user holds the key for a long time. 長按定時器
	 */
	private LongPressTimer mLongPressTimer;
	Context mContext;
	/**
	 * For temporary use. 臨時使用
	 */
	private int mXyPosTmp[] = new int[2];

	public SkbContainer(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		mEnvironment = Environment.getInstance();

		mLongPressTimer = new LongPressTimer(this);

		// If it runs on an emulator, no bias correction
		// if ("1".equals(SystemProperties.get("ro.kernel.qemu"))) {
		// mYBiasCorrection = 0;
		// } else {
		mYBiasCorrection = Y_BIAS_CORRECTION;
		// }

		// 建立彈出氣泡
		mBalloonPopup = new BalloonHint(context, this, MeasureSpec.AT_MOST);
		if (POPUPWINDOW_FOR_PRESSED_UI) {
			mBalloonOnKey = new BalloonHint(context, this, MeasureSpec.AT_MOST);
		}

		// 常見彈出軟鍵盤
		mPopupSkb = new PopupWindow(mContext);
		mPopupSkb.setBackgroundDrawable(null);
		mPopupSkb.setClippingEnabled(false);
	}

	public void setService(InputMethodService service) {
		mService = service;
	}

	public void setInputModeSwitcher(InputModeSwitcher inputModeSwitcher) {
		mInputModeSwitcher = inputModeSwitcher;
	}

	public void setGestureDetector(GestureDetector gestureDetector) {
		mGestureDetector = gestureDetector;
	}

	// TODO 這個函式用來做什麼?
	public boolean isCurrentSkbSticky() {
		if (null == mMajorView)
			return true;
		SoftKeyboard skb = mMajorView.getSoftKeyboard();
		if (null != skb) {
			return skb.getStickyFlag();
		}
		return true;
	}

	/**
	 * 切換候選詞模式。邏輯簡介:先從mInputModeSwitcher輸入法模式交換器中獲得中文候選詞模式狀態,然後判斷是否是要切入候選詞模式,
	 * 如果是鍵盤就變為中文候選詞模式狀態
	 * ,如果不是,鍵盤就消除中文候選詞模式狀態,變為mInputModeSwitcher中的mToggleStates設定鍵盤的狀態。
	 * 
	 * @param candidatesShowing
	 */
	public void toggleCandidateMode(boolean candidatesShowing) {
		if (null == mMajorView || !mInputModeSwitcher.isChineseText()
				|| mLastCandidatesShowing == candidatesShowing)
			return;
		mLastCandidatesShowing = candidatesShowing;

		SoftKeyboard skb = mMajorView.getSoftKeyboard();
		if (null == skb)
			return;

		int state = mInputModeSwitcher.getTooggleStateForCnCand();
		if (!candidatesShowing) {
			skb.disableToggleState(state, false);
			skb.enableToggleStates(mInputModeSwitcher.getToggleStates());
		} else {
			skb.enableToggleState(state, false);
		}

		mMajorView.invalidate();
	}

	/**
	 * 更新輸入法模式。邏輯簡介:先獲取軟鍵盤xml佈局檔案,然後更新軟鍵盤佈局,設定軟鍵盤狀態。
	 */
	public void updateInputMode() {
		int skbLayout = mInputModeSwitcher.getSkbLayout();
		if (mSkbLayout != skbLayout) {
			mSkbLayout = skbLayout;
			updateSkbLayout();
		}

		mLastCandidatesShowing = false;

		if (null == mMajorView)
			return;

		SoftKeyboard skb = mMajorView.getSoftKeyboard();
		if (null == skb)
			return;
		skb.enableToggleStates(mInputModeSwitcher.getToggleStates());
		invalidate();
		return;
	}

	/**
	 * 更新軟鍵盤佈局
	 */
	private void updateSkbLayout() {
		int screenWidth = mEnvironment.getScreenWidth();
		int keyHeight = mEnvironment.getKeyHeight();
		int skbHeight = mEnvironment.getSkbHeight();

		Resources r = mContext.getResources();
		if (null == mSkbFlipper) {
			mSkbFlipper = (ViewFlipper) findViewById(R.id.alpha_floatable);
		}
		mMajorView = (SoftKeyboardView) mSkbFlipper.getChildAt(0);

		SoftKeyboard majorSkb = null;
		SkbPool skbPool = SkbPool.getInstance();

		switch (mSkbLayout) {
		case R.xml.skb_qwerty:
			majorSkb = skbPool.getSoftKeyboard(R.xml.skb_qwerty,
					R.xml.skb_qwerty, screenWidth, skbHeight, mContext);
			break;

		case R.xml.skb_sym1:
			majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym1, R.xml.skb_sym1,
					screenWidth, skbHeight, mContext);
			break;

		case R.xml.skb_sym2:
			majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym2, R.xml.skb_sym2,
					screenWidth, skbHeight, mContext);
			break;

		case R.xml.skb_smiley:
			majorSkb = skbPool.getSoftKeyboard(R.xml.skb_smiley,
					R.xml.skb_smiley, screenWidth, skbHeight, mContext);
			break;

		case R.xml.skb_phone:
			majorSkb = skbPool.getSoftKeyboard(R.xml.skb_phone,
					R.xml.skb_phone, screenWidth, skbHeight, mContext);
			break;
		default:
		}

		if (null == majorSkb || !mMajorView.setSoftKeyboard(majorSkb)) {
			return;
		}
		mMajorView.setBalloonHint(mBalloonOnKey, mBalloonPopup, false);
		mMajorView.invalidate();
	}

	/**
	 * 響應按鍵事件。呼叫輸入法服務的響應按鍵事件方法,把按鍵事件流到輸入法服務裡面去處理。
	 * 
	 * @param sKey
	 */
	private void responseKeyEvent(SoftKey sKey) {
		if (null == sKey)
			return;
		((PinyinIME) mService).responseSoftKeyEvent(sKey);
		return;
	}

	/**
	 * 返回軟鍵盤檢視。邏輯簡介:先判斷副軟鍵盤彈出框是否在顯示,是的話,就判斷座標點是否在副軟鍵盤的區域內,如果是,就返回副軟鍵盤彈出框,
	 * 否則返回null。如果副軟鍵盤彈出框沒有顯示,就直接返回主軟鍵盤檢視mMajorView。
	 * 
	 * @param x
	 * @param y
	 * @param positionInParent
	 * @return
	 */
	private SoftKeyboardView inKeyboardView(int x, int y,
			int positionInParent[]) {
		if (mPopupSkbShow) {
			if (mPopupX <= x && mPopupX + mPopupSkb.getWidth() > x
					&& mPopupY <= y && mPopupY + mPopupSkb.getHeight() > y) {
				positionInParent[0] = mPopupX;
				positionInParent[1] = mPopupY;
				mPopupSkbView.setOffsetToSkbContainer(positionInParent);
				return mPopupSkbView;
			}
			return null;
		}

		return mMajorView;
	}

	/**
	 * 彈出副軟鍵盤彈出框。副軟鍵盤彈出框的軟鍵盤xml資源ID是存放在按下的按鍵mSoftKeyDown的屬性mPopupSkbId中的。
	 * 彈出副軟鍵盤彈出框後,主軟鍵盤檢視mMajorView會被隱藏。
	 */
	private void popupSymbols() {
		int popupResId = mSoftKeyDown.getPopupResId();
		if (popupResId > 0) {
			int skbContainerWidth = getWidth();
			int skbContainerHeight = getHeight();
			// The paddings of the background are not included.
			int miniSkbWidth = (int) (skbContainerWidth * 0.8);
			int miniSkbHeight = (int) (skbContainerHeight * 0.23);

			SkbPool skbPool = SkbPool.getInstance();
			SoftKeyboard skb = skbPool.getSoftKeyboard(popupResId, popupResId,
					miniSkbWidth, miniSkbHeight, mContext);
			if (null == skb)
				return;

			mPopupX = (skbContainerWidth - skb.getSkbTotalWidth()) / 2;
			mPopupY = (skbContainerHeight - skb.getSkbTotalHeight()) / 2;

			if (null == mPopupSkbView) {
				mPopupSkbView = new SoftKeyboardView(mContext, null);
				mPopupSkbView.onMeasure(LayoutParams.WRAP_CONTENT,
						LayoutParams.WRAP_CONTENT);
			}
			mPopupSkbView.setOnTouchListener(this);
			mPopupSkbView.setSoftKeyboard(skb);
			mPopupSkbView.setBalloonHint(mBalloonOnKey, mBalloonPopup, true);

			mPopupSkb.setContentView(mPopupSkbView);
			mPopupSkb.setWidth(skb.getSkbCoreWidth()
					+ mPopupSkbView.getPaddingLeft()
					+ mPopupSkbView.getPaddingRight());
			mPopupSkb.setHeight(skb.getSkbCoreHeight()
					+ mPopupSkbView.getPaddingTop()
					+ mPopupSkbView.getPaddingBottom());

			getLocationInWindow(mXyPosTmp);
			mPopupSkb.showAtLocation(this, Gravity.NO_GRAVITY, mPopupX, mPopupY
					+ mXyPosTmp[1]);
			mPopupSkbShow = true;
			mPopupSkbNoResponse = true;
			// Invalidate itself to dim the current soft keyboards.
			dimSoftKeyboard(true);
			resetKeyPress(0);
		}
	}

	/**
	 * 隱藏主軟鍵盤檢視
	 * 
	 * @param dimSkb
	 */
	private void dimSoftKeyboard(boolean dimSkb) {
		mMajorView.dimSoftKeyboard(dimSkb);
	}

	/**
	 * 隱藏副軟鍵盤彈出框,顯示主軟鍵盤檢視。
	 */
	private void dismissPopupSkb() {
		mPopupSkb.dismiss();
		mPopupSkbShow = false;
		dimSoftKeyboard(false);
		resetKeyPress(0);
	}

	/**
	 * 重置按下按鍵
	 * 
	 * @param delay
	 */
	private void resetKeyPress(long delay) {
		mLongPressTimer.removeTimer();

		if (null != mSkv) {
			mSkv.resetKeyPress(delay);
		}
	}

	/**
	 * 副軟鍵盤彈出框顯示的時候,如果realAction為true,那麼就呼叫dismissPopupSkb()隱藏副軟鍵盤彈出框,顯示主軟鍵盤檢視。
	 * 
	 * @param realAction
	 * @return
	 */
	public boolean handleBack(boolean realAction) {
		if (mPopupSkbShow) {
			if (!realAction)
				return true;

			dismissPopupSkb();
			mDiscardEvent = true;
			return true;
		}
		return false;
	}

	/**
	 * 隱藏副軟鍵盤彈出框
	 */
	public void dismissPopups() {
		handleBack(true);
		resetKeyPress(0);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		Environment env = Environment.getInstance();
		int measuredWidth = env.getScreenWidth();
		int measuredHeight = getPaddingTop();
		measuredHeight += env.getSkbHeight();
		widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,
				MeasureSpec.EXACTLY);
		heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight,
				MeasureSpec.EXACTLY);
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		super.onTouchEvent(event);

		if (mSkbFlipper.isFlipping()) {
			resetKeyPress(0);
			return true;
		}

		int x = (int) event.getX();
		int y = (int) event.getY();
		// Bias correction
		y = y + mYBiasCorrection;

		// Ignore short-distance movement event to get better performance.
		if (event.getAction() == MotionEvent.ACTION_MOVE) {
			if (Math.abs(x - mXLast) <= MOVE_TOLERANCE
					&& Math.abs(y - mYLast) <= MOVE_TOLERANCE) {
				return true;
			}
		}

		mXLast = x;
		mYLast = y;

		if (!mPopupSkbShow) {
			// mGestureDetector的監聽器在輸入法服務PinyinIME中。
			if (mGestureDetector.onTouchEvent(event)) {
				resetKeyPress(0);
				mDiscardEvent = true;
				return true;
			}
		}

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			resetKeyPress(0);

			mWaitForTouchUp = true;
			mDiscardEvent = false;

			mSkv = null;
			mSoftKeyDown = null;
			mSkv = inKeyboardView(x, y, mSkvPosInContainer);
			if (null != mSkv) {
				mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
						- mSkvPosInContainer[1], mLongPressTimer, false);
			}
			break;

		case MotionEvent.ACTION_MOVE:
			if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
				break;
			}
			if (mDiscardEvent) {
				resetKeyPress(0);
				break;
			}

			if (mPopupSkbShow && mPopupSkbNoResponse) {
				break;
			}

			SoftKeyboardView skv = inKeyboardView(x, y, mSkvPosInContainer);
			if (null != skv) {
				if (skv != mSkv) {
					mSkv = skv;
					mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
							- mSkvPosInContainer[1], mLongPressTimer, true);
				} else if (null != skv) {
					if (null != mSkv) {
						mSoftKeyDown = mSkv.onKeyMove(
								x - mSkvPosInContainer[0], y
										- mSkvPosInContainer[1]);
						if (null == mSoftKeyDown) {
							mDiscardEvent = true;
						}
					}
				}
			}
			break;

		case MotionEvent.ACTION_UP:
			if (mDiscardEvent) {
				resetKeyPress(0);
				break;
			}

			mWaitForTouchUp = false;

			// The view which got the {@link MotionEvent#ACTION_DOWN} event is
			// always used to handle this event.
			if (null != mSkv) {
				mSkv.onKeyRelease(x - mSkvPosInContainer[0], y
						- mSkvPosInContainer[1]);
			}

			if (!mPopupSkbShow || !mPopupSkbNoResponse) {
				responseKeyEvent(mSoftKeyDown);
			}

			if (mSkv == mPopupSkbView && !mPopupSkbNoResponse) {
				dismissPopupSkb();
			}
			mPopupSkbNoResponse = false;
			break;

		case MotionEvent.ACTION_CANCEL:
			break;
		}

		if (null == mSkv) {
			return false;
		}

		return true;
	}

	// Function for interface OnTouchListener, it is used to handle touch events
	// which will be delivered to the popup soft keyboard view.
	public boolean onTouch(View v, MotionEvent event) {
		// Translate the event to fit to the container.
		MotionEvent newEv = MotionEvent.obtain(event.getDownTime(),
				event.getEventTime(), event.getAction(),
				event.getX() + mPopupX, event.getY() + mPopupY,
				event.getPressure(), event.getSize(), event.getMetaState(),
				event.getXPrecision(), event.getYPrecision(),
				event.getDeviceId(), event.getEdgeFlags());
		boolean ret = onTouchEvent(newEv);
		return ret;
	}

	/**
	 * 長按定時器
	 * 
	 * @ClassName LongPressTimer
	 * @author keanbin
	 */
	class LongPressTimer extends Handler implements Runnable {
		/**
		 * When user presses a key for a long time, the timeout interval to
		 * generate first {@link #LONG_PRESS_KEYNUM1} key events. 長按時間一
		 */
		public static final int LONG_PRESS_TIMEOUT1 = 500;

		/**
		 * When user presses a key for a long time, after the first
		 * {@link #LONG_PRESS_KEYNUM1} key events, this timeout interval will be
		 * used. 長按時間二
		 */
		private static final int LONG_PRESS_TIMEOUT2 = 100;

		/**
		 * When user presses a key for a long time, after the first
		 * {@link #LONG_PRESS_KEYNUM2} key events, this timeout interval will be
		 * used. 長按時間三
		 */
		private static final int LONG_PRESS_TIMEOUT3 = 100;

		/**
		 * When user presses a key for a long time, after the first
		 * {@link #LONG_PRESS_KEYNUM1} key events, timeout interval
		 * {@link #LONG_PRESS_TIMEOUT2} will be used instead.
		 * 
		 */
		public static final int LONG_PRESS_KEYNUM1 = 1;

		/**
		 * When user presses a key for a long time, after the first
		 * {@link #LONG_PRESS_KEYNUM2} key events, timeout interval
		 * {@link #LONG_PRESS_TIMEOUT3} will be used instead.
		 */
		public static final int LONG_PRESS_KEYNUM2 = 3;

		SkbContainer mSkbContainer;

		private int mResponseTimes = 0;

		public LongPressTimer(SkbContainer skbContainer) {
			mSkbContainer = skbContainer;
		}

		public void startTimer() {
			postAtTime(this, SystemClock.uptimeMillis() + LONG_PRESS_TIMEOUT1);
			mResponseTimes = 0;
		}

		public boolean removeTimer() {
			removeCallbacks(this);
			return true;
		}

		public void run() {
			if (mWaitForTouchUp) {
				mResponseTimes++;
				if (mSoftKeyDown.repeatable()) {
					if (mSoftKeyDown.isUserDefKey()) {
						// 使用者定義的按鍵
						if (1 == mResponseTimes) {
							if (mInputModeSwitcher
									.tryHandleLongPressSwitch(mSoftKeyDown.mKeyCode)) {
								mDiscardEvent = true;
								resetKeyPress(0);
							}
						}
					} else {
						// 系統定義的按鍵,長按相當於執行重複按鍵功能,mResponseTimes是按的次數
						responseKeyEvent(mSoftKeyDown);
						long timeout;
						if (mResponseTimes < LONG_PRESS_KEYNUM1) {
							timeout = LONG_PRESS_TIMEOUT1;
						} else if (mResponseTimes < LONG_PRESS_KEYNUM2) {
							timeout = LONG_PRESS_TIMEOUT2;
						} else {
							timeout = LONG_PRESS_TIMEOUT3;
						}
						postAtTime(this, SystemClock.uptimeMillis() + timeout);
					}
				} else {
					if (1 == mResponseTimes) {
						popupSymbols();
					}
				}
			}
		}
	}
}


16、SkbPool.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.util.Vector;

import android.content.Context;

/**
 * Class used to cache previously loaded soft keyboard layouts.
 */
/**
 * 軟鍵盤記憶體池,該類採用單例模式,它有兩個向量列表:軟鍵盤模版列表、軟鍵盤列表。
 * 
 * @ClassName SkbPool
 * @author keanbin
 */
public class SkbPool {
	private static SkbPool mInstance = null;

	private Vector<SkbTemplate> mSkbTemplates = new Vector<SkbTemplate>();
	private Vector<SoftKeyboard> mSoftKeyboards = new Vector<SoftKeyboard>();

	private SkbPool() {
	}

	public static SkbPool getInstance() {
		if (null == mInstance)
			mInstance = new SkbPool();
		return mInstance;
	}

	public void resetCachedSkb() {
		mSoftKeyboards.clear();
	}

	/**
	 * 獲取軟體盤模版。邏輯簡介:首先先從mSkbTemplates列表中獲取,如果沒有獲取到,
	 * 就呼叫XmlKeyboardLoader解析資原始檔ID為skbTemplateId的軟鍵盤模版xml檔案
	 * ,生成一個模版,並加入mSkbTemplates列表中。
	 * 
	 * @param skbTemplateId
	 * @param context
	 * @return
	 */
	public SkbTemplate getSkbTemplate(int skbTemplateId, Context context) {
		for (int i = 0; i < mSkbTemplates.size(); i++) {
			SkbTemplate t = mSkbTemplates.elementAt(i);
			if (t.getSkbTemplateId() == skbTemplateId) {
				return t;
			}
		}

		if (null != context) {
			XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);
			SkbTemplate t = xkbl.loadSkbTemplate(skbTemplateId);
			if (null != t) {
				mSkbTemplates.add(t);
				return t;
			}
		}
		return null;
	}

	// Try to find the keyboard in the pool with the cache id. If there is no
	// keyboard found, try to load it with the given xml id.
	/**
	 * 獲取軟體盤。邏輯簡介:首先先從mSoftKeyboards列表中獲取,如果沒有獲取到,
	 * 就呼叫XmlKeyboardLoader解析資原始檔ID為skbXmlId的軟鍵盤xml檔案
	 * ,生成一個軟鍵盤,並加入mSoftKeyboards列表中。
	 * 
	 * @param skbCacheId
	 * @param skbXmlId
	 * @param skbWidth
	 * @param skbHeight
	 * @param context
	 * @return
	 */
	public SoftKeyboard getSoftKeyboard(int skbCacheId, int skbXmlId,
			int skbWidth, int skbHeight, Context context) {
		for (int i = 0; i < mSoftKeyboards.size(); i++) {
			SoftKeyboard skb = mSoftKeyboards.elementAt(i);
			if (skb.getCacheId() == skbCacheId && skb.getSkbXmlId() == skbXmlId) {
				skb.setSkbCoreSize(skbWidth, skbHeight);
				skb.setNewlyLoadedFlag(false);
				return skb;
			}
		}
		if (null != context) {
			XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);
			SoftKeyboard skb = xkbl.loadKeyboard(skbXmlId, skbWidth, skbHeight);
			if (skb != null) {
				if (skb.getCacheFlag()) {
					skb.setCacheId(skbCacheId);
					mSoftKeyboards.add(skb);
				}
			}
			return skb;
		}
		return null;
	}
}


17、SkbTemplate.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.util.Vector;

import android.graphics.drawable.Drawable;

/**
 * Key icon definition. It is defined in soft keyboard template. A soft keyboard
 * can refer to such an icon in its xml file directly to improve performance.
 * 按鍵圖示,包含按鍵code、按鍵圖示、按鍵彈出圖示
 */
class KeyIconRecord {
	int keyCode;
	Drawable icon;
	Drawable iconPopup;
}

/**
 * Default definition for a certain key. It is defined in soft keyboard
 * template. A soft keyboard can refer to a default key in its xml file. Nothing
 * of the key can be overwritten, including the size.
 */
class KeyRecord {
	int keyId;
	SoftKey softKey;
}

/**
 * Soft keyboard template used by soft keyboards to share common resources. In
 * this way, memory cost is reduced.
 */
public class SkbTemplate {
	private int mSkbTemplateId; // 模版的xml檔案資源ID,也是模版在軟鍵盤池中的ID
	private Drawable mSkbBg;
	private Drawable mBalloonBg;
	private Drawable mPopupBg;
	private float mXMargin = 0;
	private float mYMargin = 0;
	/** Key type list. */
	private Vector<SoftKeyType> mKeyTypeList = new Vector<SoftKeyType>();

	/**
	 * Default key icon list. It is only for keys which do not have popup icons.
	 */
	private Vector<KeyIconRecord> mKeyIconRecords = new Vector<KeyIconRecord>();

	/**
	 * Default key list.
	 */
	private Vector<KeyRecord> mKeyRecords = new Vector<KeyRecord>();

	public SkbTemplate(int skbTemplateId) {
		mSkbTemplateId = skbTemplateId;
	}

	public int getSkbTemplateId() {
		return mSkbTemplateId;
	}

	public void setBackgrounds(Drawable skbBg, Drawable balloonBg,
			Drawable popupBg) {
		mSkbBg = skbBg;
		mBalloonBg = balloonBg;
		mPopupBg = popupBg;
	}

	public Drawable getSkbBackground() {
		return mSkbBg;
	}

	public Drawable getBalloonBackground() {
		return mBalloonBg;
	}

	public Drawable getPopupBackground() {
		return mPopupBg;
	}

	public void setMargins(float xMargin, float yMargin) {
		mXMargin = xMargin;
		mYMargin = yMargin;
	}

	public float getXMargin() {
		return mXMargin;
	}

	public float getYMargin() {
		return mYMargin;
	}

	public SoftKeyType createKeyType(int id, Drawable bg, Drawable hlBg) {
		return new SoftKeyType(id, bg, hlBg);
	}

	public boolean addKeyType(SoftKeyType keyType) {
		// The newly added item should have the right id.
		if (mKeyTypeList.size() != keyType.mKeyTypeId)
			return false;
		mKeyTypeList.add(keyType);
		return true;
	}

	public SoftKeyType getKeyType(int typeId) {
		if (typeId < 0 || typeId > mKeyTypeList.size())
			return null;
		return mKeyTypeList.elementAt(typeId);
	}

	public void addDefaultKeyIcons(int keyCode, Drawable icon,
			Drawable iconPopup) {
		if (null == icon || null == iconPopup)
			return;

		KeyIconRecord iconRecord = new KeyIconRecord();
		iconRecord.icon = icon;
		iconRecord.iconPopup = iconPopup;
		iconRecord.keyCode = keyCode;

		int size = mKeyIconRecords.size();
		int pos = 0;
		while (pos < size) {
			if (mKeyIconRecords.get(pos).keyCode >= keyCode)
				break;
			pos++;
		}
		mKeyIconRecords.add(pos, iconRecord);
	}

	public Drawable getDefaultKeyIcon(int keyCode) {
		int size = mKeyIconRecords.size();
		int pos = 0;
		while (pos < size) {
			KeyIconRecord iconRecord = mKeyIconRecords.get(pos);
			if (iconRecord.keyCode < keyCode) {
				pos++;
				continue;
			}
			if (iconRecord.keyCode == keyCode) {
				return iconRecord.icon;
			}
			return null;
		}
		return null;
	}

	public Drawable getDefaultKeyIconPopup(int keyCode) {
		int size = mKeyIconRecords.size();
		int pos = 0;
		while (pos < size) {
			KeyIconRecord iconRecord = mKeyIconRecords.get(pos);
			if (iconRecord.keyCode < keyCode) {
				pos++;
				continue;
			}
			if (iconRecord.keyCode == keyCode) {
				return iconRecord.iconPopup;
			}
			return null;
		}
		return null;
	}

	public void addDefaultKey(int keyId, SoftKey softKey) {
		if (null == softKey)
			return;

		KeyRecord keyRecord = new KeyRecord();
		keyRecord.keyId = keyId;
		keyRecord.softKey = softKey;

		int size = mKeyRecords.size();
		int pos = 0;
		while (pos < size) {
			if (mKeyRecords.get(pos).keyId >= keyId)
				break;
			pos++;
		}
		mKeyRecords.add(pos, keyRecord);
	}

	public SoftKey getDefaultKey(int keyId) {
		int size = mKeyRecords.size();
		int pos = 0;
		while (pos < size) {
			KeyRecord keyRecord = mKeyRecords.get(pos);
			if (keyRecord.keyId < keyId) {
				pos++;
				continue;
			}
			if (keyRecord.keyId == keyId) {
				return keyRecord.softKey;
			}
			return null;
		}
		return null;
	}
}

class SoftKeyType {
	public static final int KEYTYPE_ID_NORMAL_KEY = 0;

	public int mKeyTypeId;
	public Drawable mKeyBg;
	public Drawable mKeyHlBg;
	public int mColor;
	public int mColorHl;
	public int mColorBalloon;

	SoftKeyType(int id, Drawable bg, Drawable hlBg) {
		mKeyTypeId = id;
		mKeyBg = bg;
		mKeyHlBg = hlBg;
	}

	public void setColors(int color, int colorHl, int colorBalloon) {
		mColor = color;
		mColorHl = colorHl;
		mColorBalloon = colorBalloon;
	}
}


18、SoftKey.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.graphics.drawable.Drawable;

/**
 * Class for soft keys which defined in the keyboard xml file. A soft key can be
 * a basic key or a toggling key. 按鍵
 * 
 * @see com.android.inputmethod.pinyin.SoftKeyToggle
 */
public class SoftKey {
	protected static final int KEYMASK_REPEAT = 0x10000000;
	protected static final int KEYMASK_BALLOON = 0x20000000;

	/**
	 * For a finger touch device, after user presses a key, there will be some
	 * consequent moving events because of the changing in touching pressure. If
	 * the moving distance in x is within this threshold, the moving events will
	 * be ignored. 觸控移動事件有效的x座標差值,移動的x座標差值小於有效的x座標差值,移動事件被拋棄。
	 */
	public static final int MAX_MOVE_TOLERANCE_X = 0;

	/**
	 * For a finger touch device, after user presses a key, there will be some
	 * consequent moving events because of the changing in touching pressure. If
	 * the moving distance in y is within this threshold, the moving events will
	 * be ignored. 觸控移動事件有效的y座標差值,移動的x座標差值小於有效的y座標差值,移動事件被拋棄。
	 */
	public static final int MAX_MOVE_TOLERANCE_Y = 0;

	/**
	 * Used to indicate the type and attributes of this key. the lowest 8 bits
	 * should be reserved for SoftkeyToggle. 按鍵的屬性和型別,最低的8位留給軟鍵盤變換狀態。
	 */
	protected int mKeyMask;

	/** key的型別 */
	protected SoftKeyType mKeyType;

	/** key的圖示 */
	protected Drawable mKeyIcon;

	/** key的彈出圖示 */
	protected Drawable mKeyIconPopup;

	/** key的文字 */
	protected String mKeyLabel;

	/** key的code */
	protected int mKeyCode;

	/**
	 * If this value is not 0, this key can be used to popup a sub soft keyboard
	 * when user presses it for some time.
	 * 軟體盤彈出對話方塊的id。如果這個值不為空,那麼當它被長按的時候,彈出一個副軟鍵盤。
	 */
	public int mPopupSkbId;

	/** 鍵盤寬度的百分比 ,mLeft = (int) (mLeftF * skbWidth); */
	public float mLeftF;
	public float mRightF;
	/** 鍵盤高度的百分比 */
	public float mTopF;
	public float mBottomF;
	// TODO 以下的 區域座標是相對於什麼的?是全域性還是相對於父檢視的?
	public int mLeft;
	public int mRight;
	public int mTop;
	public int mBottom;

	/**
	 * 設定按鍵的型別、圖示、彈出圖示
	 * 
	 * @param keyType
	 * @param keyIcon
	 * @param keyIconPopup
	 */
	public void setKeyType(SoftKeyType keyType, Drawable keyIcon,
			Drawable keyIconPopup) {
		mKeyType = keyType;
		mKeyIcon = keyIcon;
		mKeyIconPopup = keyIconPopup;
	}

	// The caller guarantees that all parameters are in [0, 1]
	public void setKeyDimensions(float left, float top, float right,
			float bottom) {
		mLeftF = left;
		mTopF = top;
		mRightF = right;
		mBottomF = bottom;
	}

	public void setKeyAttribute(int keyCode, String label, boolean repeat,
			boolean balloon) {
		mKeyCode = keyCode;
		mKeyLabel = label;

		if (repeat) {
			mKeyMask |= KEYMASK_REPEAT;
		} else {
			mKeyMask &= (~KEYMASK_REPEAT);
		}

		if (balloon) {
			mKeyMask |= KEYMASK_BALLOON;
		} else {
			mKeyMask &= (~KEYMASK_BALLOON);
		}
	}

	/**
	 * 設定副軟鍵盤彈出框
	 * 
	 * @param popupSkbId
	 */
	public void setPopupSkbId(int popupSkbId) {
		mPopupSkbId = popupSkbId;
	}

	// Call after setKeyDimensions(). The caller guarantees that the
	// keyboard with and height are valid.
	/**
	 * 設定按鍵的區域
	 * 
	 * @param skbWidth
	 *            鍵盤的寬度
	 * @param skbHeight
	 *            鍵盤的高度
	 */
	public void setSkbCoreSize(int skbWidth, int skbHeight) {
		mLeft = (int) (mLeftF * skbWidth);
		mRight = (int) (mRightF * skbWidth);
		mTop = (int) (mTopF * skbHeight);
		mBottom = (int) (mBottomF * skbHeight);
	}

	public Drawable getKeyIcon() {
		return mKeyIcon;
	}

	public Drawable getKeyIconPopup() {
		if (null != mKeyIconPopup) {
			return mKeyIconPopup;
		}
		return mKeyIcon;
	}

	/**
	 * 獲取按鍵的key code
	 * 
	 * @return
	 */
	public int getKeyCode() {
		return mKeyCode;
	}

	/**
	 * 獲取按鍵的字元
	 * 
	 * @return
	 */
	public String getKeyLabel() {
		return mKeyLabel;
	}

	/**
	 * 大小寫轉換
	 * 
	 * @param upperCase
	 */
	public void changeCase(boolean upperCase) {
		if (null != mKeyLabel) {
			if (upperCase)
				mKeyLabel = mKeyLabel.toUpperCase();
			else
				mKeyLabel = mKeyLabel.toLowerCase();
		}
	}

	public Drawable getKeyBg() {
		return mKeyType.mKeyBg;
	}

	public Drawable getKeyHlBg() {
		return mKeyType.mKeyHlBg;
	}

	public int getColor() {
		return mKeyType.mColor;
	}

	public int getColorHl() {
		return mKeyType.mColorHl;
	}

	public int getColorBalloon() {
		return mKeyType.mColorBalloon;
	}

	/**
	 * 是否是系統的keycode
	 * 
	 * @return
	 */
	public boolean isKeyCodeKey() {
		if (mKeyCode > 0)
			return true;
		return false;
	}

	/**
	 * 是否是使用者定義的keycode
	 * 
	 * @return
	 */
	public boolean isUserDefKey() {
		if (mKeyCode < 0)
			return true;
		return false;
	}

	/**
	 * 是否是字元按鍵
	 * 
	 * @return
	 */
	public boolean isUniStrKey() {
		if (null != mKeyLabel && mKeyCode == 0)
			return true;
		return false;
	}

	/**
	 * 是否需要彈出氣泡
	 * 
	 * @return
	 */
	public boolean needBalloon() {
		return (mKeyMask & KEYMASK_BALLOON) != 0;
	}

	/**
	 * 是否有重複按下功能,即連續按這個按鍵是否執行不同的操作。
	 * 
	 * @return
	 */
	public boolean repeatable() {
		return (mKeyMask & KEYMASK_REPEAT) != 0;
	}

	public int getPopupResId() {
		return mPopupSkbId;
	}

	public int width() {
		return mRight - mLeft;
	}

	public int height() {
		return mBottom - mTop;
	}

	/**
	 * 判斷座標是否在該按鍵的區域內
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public boolean moveWithinKey(int x, int y) {
		if (mLeft - MAX_MOVE_TOLERANCE_X <= x
				&& mTop - MAX_MOVE_TOLERANCE_Y <= y
				&& mRight + MAX_MOVE_TOLERANCE_X > x
				&& mBottom + MAX_MOVE_TOLERANCE_Y > y) {
			return true;
		}
		return false;
	}

	@Override
	public String toString() {
		String str = "\n";
		str += "  keyCode: " + String.valueOf(mKeyCode) + "\n";
		str += "  keyMask: " + String.valueOf(mKeyMask) + "\n";
		str += "  keyLabel: " + (mKeyLabel == null ? "null" : mKeyLabel) + "\n";
		str += "  popupResId: " + String.valueOf(mPopupSkbId) + "\n";
		str += "  Position: " + String.valueOf(mLeftF) + ", "
				+ String.valueOf(mTopF) + ", " + String.valueOf(mRightF) + ", "
				+ String.valueOf(mBottomF) + "\n";
		return str;
	}
}


19、SoftKeyboard.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.util.ArrayList;
import java.util.List;

import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.KeyEvent;

import com.keanbin.pinyinime.InputModeSwitcher.ToggleStates;

/**
 * Class used to represent a soft keyboard definition, including the height, the
 * background image, the image for high light, the keys, etc.
 * 一個軟體盤的定義,包括按鍵的排列布局,寬度高度。
 */
public class SoftKeyboard {
	/**
	 * The XML resource id for this soft keyboard. 軟鍵盤的xml檔案ID
	 * */
	private int mSkbXmlId;

	/**
	 * Do we need to cache this soft keyboard? 是否快取這個軟體盤?
	 */
	private boolean mCacheFlag;

	/**
	 * After user switches to this soft keyboard, if this flag is true, this
	 * soft keyboard will be kept unless explicit switching operation is
	 * performed, otherwise IME will switch back to the previous keyboard layout
	 * whenever user clicks on any none-function key.
	 **/
	private boolean mStickyFlag;

	/**
	 * The cache id for this soft keyboard. It is used to identify it in the
	 * soft keyboard pool. 快取ID
	 */
	private int mCacheId;

	/**
	 * Used to indicate whether this soft keyboard is newly loaded from an XML
	 * file or is just gotten from the soft keyboard pool.
	 * 是否是最近從xml檔案匯入的軟鍵盤,或者剛剛從軟鍵盤池中得到的軟鍵盤?
	 */
	private boolean mNewlyLoadedFlag = true;

	/**
	 * The width of the soft keyboard. 鍵盤的寬度
	 */
	private int mSkbCoreWidth;

	/**
	 * The height of the soft keyboard. 鍵盤的高度
	 */
	private int mSkbCoreHeight;

	/**
	 * The soft keyboard template for this soft keyboard.軟鍵盤的模版
	 */
	private SkbTemplate mSkbTemplate;

	/**
	 * Used to indicate whether this soft keyboard is a QWERTY keyboard.
	 * 是否使用標準的軟鍵盤
	 */
	private boolean mIsQwerty;

	/**
	 * When {@link #mIsQwerty} is true, this member is Used to indicate that the
	 * soft keyboard should be displayed in uppercase. 是否是標準鍵盤的大寫
	 */
	private boolean mIsQwertyUpperCase;

	/**
	 * The id of the rows which are enabled. Rows with id
	 * {@link KeyRow#ALWAYS_SHOW_ROW_ID} are always enabled. 可用行的id
	 */
	private int mEnabledRowId;

	/**
	 * Rows in this soft keyboard. Each row has a id. Only matched rows will be
	 * enabled. 按鍵排列的行的連結串列,每個元素都是一行。
	 */
	private List<KeyRow> mKeyRows;

	/**
	 * Background of the soft keyboard. If it is null, the one in the soft
	 * keyboard template will be used. 軟鍵盤的背景,如果為空,就會使用軟鍵盤模版中的背景。
	 **/
	public Drawable mSkbBg;

	/**
	 * Background for key balloon. If it is null, the one in the soft keyboard
	 * template will be used. 氣泡的背景,如果為空,就會使用軟鍵盤模版中的氣泡背景。
	 **/
	private Drawable mBalloonBg;

	/**
	 * Background for popup mini soft keyboard. If it is null, the one in the
	 * soft keyboard template will be used. 彈出框的背景,如果為空,就會使用軟鍵盤模版中的彈出框背景。
	 **/
	private Drawable mPopupBg;

	/** The left and right margin of a key. 一個按鍵的左右間隔 */
	private float mKeyXMargin = 0;

	/** The top and bottom margin of a key. 一個按鍵的上下間隔 */
	private float mKeyYMargin = 0;

	private Rect mTmpRect = new Rect();

	public SoftKeyboard(int skbXmlId, SkbTemplate skbTemplate, int skbWidth,
			int skbHeight) {
		mSkbXmlId = skbXmlId;
		mSkbTemplate = skbTemplate;
		mSkbCoreWidth = skbWidth;
		mSkbCoreHeight = skbHeight;
	}

	public void setFlags(boolean cacheFlag, boolean stickyFlag,
			boolean isQwerty, boolean isQwertyUpperCase) {
		mCacheFlag = cacheFlag;
		mStickyFlag = stickyFlag;
		mIsQwerty = isQwerty;
		mIsQwertyUpperCase = isQwertyUpperCase;
	}

	public boolean getCacheFlag() {
		return mCacheFlag;
	}

	public void setCacheId(int cacheId) {
		mCacheId = cacheId;
	}

	public boolean getStickyFlag() {
		return mStickyFlag;
	}

	public void setSkbBackground(Drawable skbBg) {
		mSkbBg = skbBg;
	}

	public void setPopupBackground(Drawable popupBg) {
		mPopupBg = popupBg;
	}

	public void setKeyBalloonBackground(Drawable balloonBg) {
		mBalloonBg = balloonBg;
	}

	public void setKeyMargins(float xMargin, float yMargin) {
		mKeyXMargin = xMargin;
		mKeyYMargin = yMargin;
	}

	public int getCacheId() {
		return mCacheId;
	}

	/**
	 * 重置軟鍵盤,只做了清除mKeyRows列表的操作。
	 */
	public void reset() {
		if (null != mKeyRows)
			mKeyRows.clear();
	}

	public void setNewlyLoadedFlag(boolean newlyLoadedFlag) {
		mNewlyLoadedFlag = newlyLoadedFlag;
	}

	public boolean getNewlyLoadedFlag() {
		return mNewlyLoadedFlag;
	}

	/**
	 * 開始新的一行
	 * 
	 * @param rowId
	 * @param yStartingPos
	 */
	public void beginNewRow(int rowId, float yStartingPos) {
		if (null == mKeyRows)
			mKeyRows = new ArrayList<KeyRow>();
		KeyRow keyRow = new KeyRow();
		keyRow.mRowId = rowId;
		keyRow.mTopF = yStartingPos;
		keyRow.mBottomF = yStartingPos;
		keyRow.mSoftKeys = new ArrayList<SoftKey>();
		mKeyRows.add(keyRow);
	}

	/**
	 * 新增一個按鍵,按鍵是新增在最後一行中。
	 * 
	 * @param softKey
	 * @return
	 */
	public boolean addSoftKey(SoftKey softKey) {
		if (mKeyRows.size() == 0)
			return false;
		KeyRow keyRow = mKeyRows.get(mKeyRows.size() - 1);
		if (null == keyRow)
			return false;
		List<SoftKey> softKeys = keyRow.mSoftKeys;

		softKey.setSkbCoreSize(mSkbCoreWidth, mSkbCoreHeight);
		softKeys.add(softKey);

		// 根據加入的按鍵的top和bottom,調整行的top和bottom
		if (softKey.mTopF < keyRow.mTopF) {
			keyRow.mTopF = softKey.mTopF;
		}
		if (softKey.mBottomF > keyRow.mBottomF) {
			keyRow.mBottomF = softKey.mBottomF;
		}
		return true;
	}

	public int getSkbXmlId() {
		return mSkbXmlId;
	}

	// Set the size of the soft keyboard core. In other words, the background's
	// padding are not counted.
	/**
	 * 設定鍵盤核心的寬度和高度(不包括padding),並根據新的寬度和高度,調整鍵盤中各行的top和bottom,調整行中的按鍵的尺寸。
	 * 
	 * @param skbCoreWidth
	 * @param skbCoreHeight
	 */
	public void setSkbCoreSize(int skbCoreWidth, int skbCoreHeight) {
		if (null == mKeyRows
				|| (skbCoreWidth == mSkbCoreWidth && skbCoreHeight == mSkbCoreHeight)) {
			return;
		}
		for (int row = 0; row < mKeyRows.size(); row++) {
			KeyRow keyRow = mKeyRows.get(row);
			keyRow.mBottom = (int) (skbCoreHeight * keyRow.mBottomF);
			keyRow.mTop = (int) (skbCoreHeight * keyRow.mTopF);

			List<SoftKey> softKeys = keyRow.mSoftKeys;
			for (int i = 0; i < softKeys.size(); i++) {
				SoftKey softKey = softKeys.get(i);
				softKey.setSkbCoreSize(skbCoreWidth, skbCoreHeight);
			}
		}
		mSkbCoreWidth = skbCoreWidth;
		mSkbCoreHeight = skbCoreHeight;
	}

	public int getSkbCoreWidth() {
		return mSkbCoreWidth;
	}

	public int getSkbCoreHeight() {
		return mSkbCoreHeight;
	}

	public int getSkbTotalWidth() {
		Rect padding = getPadding();
		return mSkbCoreWidth + padding.left + padding.right;
	}

	public int getSkbTotalHeight() {
		Rect padding = getPadding();
		return mSkbCoreHeight + padding.top + padding.bottom;
	}

	// TODO 不明白這個函式的作用
	public int getKeyXMargin() {
		Environment env = Environment.getInstance();
		return (int) (mKeyXMargin * mSkbCoreWidth * env.getKeyXMarginFactor());
	}

	// TODO 不明白這個函式的作用
	public int getKeyYMargin() {
		Environment env = Environment.getInstance();
		return (int) (mKeyYMargin * mSkbCoreHeight * env.getKeyYMarginFactor());
	}

	public Drawable getSkbBackground() {
		if (null != mSkbBg)
			return mSkbBg;
		return mSkbTemplate.getSkbBackground();
	}

	public Drawable getBalloonBackground() {
		if (null != mBalloonBg)
			return mBalloonBg;
		return mSkbTemplate.getBalloonBackground();
	}

	public Drawable getPopupBackground() {
		if (null != mPopupBg)
			return mPopupBg;
		return mSkbTemplate.getPopupBackground();
	}

	public int getRowNum() {
		if (null != mKeyRows) {
			return mKeyRows.size();
		}
		return 0;
	}

	public KeyRow getKeyRowForDisplay(int row) {
		if (null != mKeyRows && mKeyRows.size() > row) {
			KeyRow keyRow = mKeyRows.get(row);
			if (KeyRow.ALWAYS_SHOW_ROW_ID == keyRow.mRowId
					|| keyRow.mRowId == mEnabledRowId) {
				return keyRow;
			}
		}
		return null;
	}

	public SoftKey getKey(int row, int location) {
		if (null != mKeyRows && mKeyRows.size() > row) {
			List<SoftKey> softKeys = mKeyRows.get(row).mSoftKeys;
			if (softKeys.size() > location) {
				return softKeys.get(location);
			}
		}
		return null;
	}

	/**
	 * 根據座標查詢按鍵,如果座標在某個按鍵區域內,就返回這個按鍵,如果座標不在所有的按鍵區域內,返回離它最近的按鍵。
	 * 
	 * @優化 可以在判斷座標在某個按鍵區域內的時候,並且加上判斷離它最近的按鍵,這樣就只需要一次遍歷就行了。
	 * @param x
	 * @param y
	 * @return
	 */
	public SoftKey mapToKey(int x, int y) {
		if (null == mKeyRows) {
			return null;
		}
		// If the position is inside the rectangle of a certain key, return that
		// key.
		int rowNum = mKeyRows.size();
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mKeyRows.get(row);
			if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
					&& keyRow.mRowId != mEnabledRowId)
				continue;
			if (keyRow.mTop > y && keyRow.mBottom <= y)
				continue;

			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int i = 0; i < keyNum; i++) {
				SoftKey sKey = softKeys.get(i);
				if (sKey.mLeft <= x && sKey.mTop <= y && sKey.mRight > x
						&& sKey.mBottom > y) {
					return sKey;
				}
			}
		}

		// If the position is outside the rectangles of all keys, find the
		// nearest one.
		SoftKey nearestKey = null;
		float nearestDis = Float.MAX_VALUE;
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mKeyRows.get(row);
			if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
					&& keyRow.mRowId != mEnabledRowId)
				continue;
			if (keyRow.mTop > y && keyRow.mBottom <= y)
				continue;

			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int i = 0; i < keyNum; i++) {
				SoftKey sKey = softKeys.get(i);
				int disx = (sKey.mLeft + sKey.mRight) / 2 - x; // 按鍵x座標的中心點到x之間的距離
				int disy = (sKey.mTop + sKey.mBottom) / 2 - y; // 按鍵y座標的中心點到y之間的距離
				float dis = disx * disx + disy * disy;
				if (dis < nearestDis) {
					nearestDis = dis;
					nearestKey = sKey;
				}
			}
		}
		return nearestKey;
	}

	/**
	 * 改變Qwerty鍵盤中每個按鍵的狀態
	 * 
	 * @param toggle_state_id
	 *            按鍵的狀態屬性值
	 * @param upperCase
	 *            大小寫
	 */
	public void switchQwertyMode(int toggle_state_id, boolean upperCase) {
		if (!mIsQwerty)
			return;

		int rowNum = mKeyRows.size();
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mKeyRows.get(row);
			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int i = 0; i < keyNum; i++) {
				SoftKey sKey = softKeys.get(i);
				if (sKey instanceof SoftKeyToggle) {
					((SoftKeyToggle) sKey).enableToggleState(toggle_state_id,
							true);
				}
				if (sKey.mKeyCode >= KeyEvent.KEYCODE_A
						&& sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {
					sKey.changeCase(upperCase);
				}
			}
		}
	}

	/**
	 * 改變鍵盤中每個按鍵的狀態
	 * 
	 * @param toggleStateId
	 * @param resetIfNotFound
	 */
	public void enableToggleState(int toggleStateId, boolean resetIfNotFound) {
		int rowNum = mKeyRows.size();
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mKeyRows.get(row);
			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int i = 0; i < keyNum; i++) {
				SoftKey sKey = softKeys.get(i);
				if (sKey instanceof SoftKeyToggle) {
					((SoftKeyToggle) sKey).enableToggleState(toggleStateId,
							resetIfNotFound);
				}
			}
		}
	}

	/**
	 * 消除鍵盤中按鍵的某一個狀態
	 * 
	 * @param toggleStateId
	 * @param resetIfNotFound
	 */
	public void disableToggleState(int toggleStateId, boolean resetIfNotFound) {
		int rowNum = mKeyRows.size();
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mKeyRows.get(row);
			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int i = 0; i < keyNum; i++) {
				SoftKey sKey = softKeys.get(i);
				if (sKey instanceof SoftKeyToggle) {
					((SoftKeyToggle) sKey).disableToggleState(toggleStateId,
							resetIfNotFound);
				}
			}
		}
	}

	/**
	 * 改變鍵盤的狀態,並且根據鍵盤狀態中的mKeyStates[]來設定每個按鍵。
	 * 
	 * @param toggleStates
	 *            整個鍵盤的狀態
	 */
	public void enableToggleStates(ToggleStates toggleStates) {
		if (null == toggleStates)
			return;

		enableRow(toggleStates.mRowIdToEnable);

		boolean isQwerty = toggleStates.mQwerty;
		boolean isQwertyUpperCase = toggleStates.mQwertyUpperCase;
		boolean needUpdateQwerty = (isQwerty && mIsQwerty && (mIsQwertyUpperCase != isQwertyUpperCase));
		int states[] = toggleStates.mKeyStates;
		int statesNum = toggleStates.mKeyStatesNum;

		int rowNum = mKeyRows.size();
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mKeyRows.get(row);
			if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
					&& keyRow.mRowId != mEnabledRowId) {
				continue;
			}
			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int keyPos = 0; keyPos < keyNum; keyPos++) {
				SoftKey sKey = softKeys.get(keyPos);
				if (sKey instanceof SoftKeyToggle) {
					for (int statePos = 0; statePos < statesNum; statePos++) {
						((SoftKeyToggle) sKey).enableToggleState(
								states[statePos], statePos == 0);
					}
					if (0 == statesNum) {
						((SoftKeyToggle) sKey).disableAllToggleStates();
					}
				}
				if (needUpdateQwerty) {
					if (sKey.mKeyCode >= KeyEvent.KEYCODE_A
							&& sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {
						sKey.changeCase(isQwertyUpperCase);
					}
				}
			}
		}
		mIsQwertyUpperCase = isQwertyUpperCase;
	}

	private Rect getPadding() {
		mTmpRect.set(0, 0, 0, 0);
		Drawable skbBg = getSkbBackground();
		if (null == skbBg)
			return mTmpRect;
		skbBg.getPadding(mTmpRect);
		return mTmpRect;
	}

	/**
	 * Enable a row with the give toggle Id. Rows with other toggle ids (except
	 * the id {@link KeyRow#ALWAYS_SHOW_ROW_ID}) will be disabled.
	 * 
	 * @param rowId
	 *            The row id to enable.
	 * @return True if the soft keyboard requires redrawing.
	 */
	/**
	 * 先查詢與rowId相等的行的id,如果有,就設定mEnabledRowId = rowId。
	 * 
	 * @param rowId
	 * @return
	 */
	private boolean enableRow(int rowId) {
		if (KeyRow.ALWAYS_SHOW_ROW_ID == rowId)
			return false;

		boolean enabled = false;
		int rowNum = mKeyRows.size();
		for (int row = rowNum - 1; row >= 0; row--) {
			if (mKeyRows.get(row).mRowId == rowId) {
				enabled = true;
				break;
			}
		}
		if (enabled) {
			mEnabledRowId = rowId;
		}
		return enabled;
	}

	@Override
	public String toString() {
		String str = "------------------SkbInfo----------------------\n";
		String endStr = "-----------------------------------------------\n";
		str += "Width: " + String.valueOf(mSkbCoreWidth) + "\n";
		str += "Height: " + String.valueOf(mSkbCoreHeight) + "\n";
		str += "KeyRowNum: " + mKeyRows == null ? "0" : String.valueOf(mKeyRows
				.size()) + "\n";
		if (null == mKeyRows)
			return str + endStr;
		int rowNum = mKeyRows.size();
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mKeyRows.get(row);
			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int i = 0; i < softKeys.size(); i++) {
				str += "-key " + String.valueOf(i) + ":"
						+ softKeys.get(i).toString();
			}
		}
		return str + endStr;
	}

	public String toShortString() {
		return super.toString();
	}

	/**
	 * 鍵盤的行
	 * 
	 * @ClassName KeyRow
	 * @author keanbin
	 */
	class KeyRow {
		static final int ALWAYS_SHOW_ROW_ID = -1;
		static final int DEFAULT_ROW_ID = 0;

		List<SoftKey> mSoftKeys;
		/**
		 * If the row id is {@link #ALWAYS_SHOW_ROW_ID}, this row will always be
		 * enabled.
		 */
		int mRowId;
		float mTopF;
		float mBottomF;
		int mTop;
		int mBottom;
	}
}


20、SoftKeyboardView.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.view.View;

import com.keanbin.pinyinime.SoftKeyboard.KeyRow;

/**
 * Class used to show a soft keyboard.
 * 
 * A soft keyboard view should not handle touch event itself, because we do bias
 * correction, need a global strategy to map an event into a proper view to
 * achieve better user experience. 軟體盤檢視
 */
public class SoftKeyboardView extends View {
	/**
	 * The definition of the soft keyboard for the current this soft keyboard
	 * view. 軟體盤佈局
	 */
	private SoftKeyboard mSoftKeyboard;

	/**
	 * The popup balloon hint for key press/release.
	 */
	private BalloonHint mBalloonPopup;

	/**
	 * The on-key balloon hint for key press/release. If it is null, on-key
	 * highlight will be drawn on th soft keyboard view directly.
	 */
	private BalloonHint mBalloonOnKey;

	/** Used to play key sounds. 聲音管理 */
	private SoundManager mSoundManager;

	/** The last key pressed. 最後按下的按鍵 */
	private SoftKey mSoftKeyDown;

	/** Used to indicate whether the user is holding on a key. 是否正在按住按鍵? */
	private boolean mKeyPressed = false;

	/**
	 * The location offset of the view to the keyboard container.
	 * 檢視到鍵盤集裝箱之間的位置偏移
	 */
	private int mOffsetToSkbContainer[] = new int[2];

	/**
	 * The location of the desired hint view to the keyboard container.
	 * 所需提示檢視到鍵盤集裝箱之間的位置偏移
	 */
	private int mHintLocationToSkbContainer[] = new int[2];

	/**
	 * Text size for normal key. 正常按鍵的文字大小
	 */
	private int mNormalKeyTextSize;

	/**
	 * Text size for function key. 功能按鍵的文字大小
	 */
	private int mFunctionKeyTextSize;

	/**
	 * Long press timer used to response long-press. 長按的定時器
	 */
	private SkbContainer.LongPressTimer mLongPressTimer;

	/**
	 * Repeated events for long press 長按是否為重複?
	 */
	private boolean mRepeatForLongPress = false;

	/**
	 * If this parameter is true, the balloon will never be dismissed even if
	 * user moves a lot from the pressed point. 這個引數如果為true,當使用者移開點選點後,氣泡不會被銷燬。
	 */
	private boolean mMovingNeverHidePopupBalloon = false;

	/** Vibration for key press. 震動操作物件 */
	private Vibrator mVibrator;

	/** Vibration pattern for key press. 震動的引數 */
	protected long[] mVibratePattern = new long[] { 1, 20 };

	/**
	 * The dirty rectangle used to mark the area to re-draw during key press and
	 * release. Currently, whenever we can invalidate(Rect), view will call
	 * onDraw() and we MUST draw the whole view. This dirty information is for
	 * future use. 該區域用於標記按鍵按下和釋放後需要重繪的區域,目前沒有作用,是為了以後保留的。
	 */
	private Rect mDirtyRect = new Rect();

	private Paint mPaint;
	private FontMetricsInt mFmi;
	private boolean mDimSkb;
	Context mContext;

	public SoftKeyboardView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		mSoundManager = SoundManager.getInstance(mContext);

		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mFmi = mPaint.getFontMetricsInt();
	}

	public boolean setSoftKeyboard(SoftKeyboard softSkb) {
		if (null == softSkb) {
			return false;
		}
		mSoftKeyboard = softSkb;
		Drawable bg = softSkb.getSkbBackground();
		if (null != bg)
			setBackgroundDrawable(bg);
		return true;
	}

	public SoftKeyboard getSoftKeyboard() {
		return mSoftKeyboard;
	}

	/**
	 * 設定mSoftKeyboard的尺寸
	 * 
	 * @param skbWidth
	 * @param skbHeight
	 */
	public void resizeKeyboard(int skbWidth, int skbHeight) {
		mSoftKeyboard.setSkbCoreSize(skbWidth, skbHeight);
	}

	/**
	 * 設定mBalloonOnKey氣泡、mBalloonPopup氣泡。
	 * 
	 * @param balloonOnKey
	 * @param balloonPopup
	 * @param movingNeverHidePopup
	 */
	public void setBalloonHint(BalloonHint balloonOnKey,
			BalloonHint balloonPopup, boolean movingNeverHidePopup) {
		mBalloonOnKey = balloonOnKey;
		mBalloonPopup = balloonPopup;
		mMovingNeverHidePopupBalloon = movingNeverHidePopup;
	}

	/**
	 * 設定檢視到鍵盤集裝箱之間的位置偏移
	 * 
	 * @param offsetToSkbContainer
	 */
	public void setOffsetToSkbContainer(int offsetToSkbContainer[]) {
		mOffsetToSkbContainer[0] = offsetToSkbContainer[0];
		mOffsetToSkbContainer[1] = offsetToSkbContainer[1];
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int measuredWidth = 0;
		int measuredHeight = 0;
		if (null != mSoftKeyboard) {
			measuredWidth = mSoftKeyboard.getSkbCoreWidth();
			measuredHeight = mSoftKeyboard.getSkbCoreHeight();
			measuredWidth += getPaddingLeft() + getPaddingRight();
			measuredHeight += getPaddingTop() + getPaddingBottom();
		}

		// TODO 如果 measuredWidth 大於父檢視可給予的最大寬度,會出現什麼樣的情況?
		// 設定view的尺寸
		setMeasuredDimension(measuredWidth, measuredHeight);
	}

	/**
	 * 顯示氣泡。邏輯簡介:如果該氣泡需要強行銷燬,那麼就馬上銷燬。如果該氣泡正在顯示,就更新它,否則顯示該氣泡。
	 * 
	 * @param balloon
	 * @param balloonLocationToSkb
	 * @param movePress
	 */
	private void showBalloon(BalloonHint balloon, int balloonLocationToSkb[],
			boolean movePress) {
		long delay = BalloonHint.TIME_DELAY_SHOW;
		if (movePress)
			delay = 0;
		if (balloon.needForceDismiss()) {
			balloon.delayedDismiss(0);
		}
		if (!balloon.isShowing()) {
			balloon.delayedShow(delay, balloonLocationToSkb);
		} else {
			balloon.delayedUpdate(delay, balloonLocationToSkb,
					balloon.getWidth(), balloon.getHeight());
		}
		long b = System.currentTimeMillis();
	}

	/**
	 * 重置mKeyPressed為false,並關閉mBalloonOnKey氣泡。
	 * 
	 * @param balloonDelay
	 *            關閉氣泡的延時時間
	 */
	public void resetKeyPress(long balloonDelay) {
		if (!mKeyPressed)
			return;
		mKeyPressed = false;
		if (null != mBalloonOnKey) {
			mBalloonOnKey.delayedDismiss(balloonDelay);
		} else {
			if (null != mSoftKeyDown) {
				if (mDirtyRect.isEmpty()) {
					mDirtyRect.set(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
							mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
				}
				invalidate(mDirtyRect);
			} else {
				invalidate();
			}
		}
		mBalloonPopup.delayedDismiss(balloonDelay);
	}

	// If movePress is true, means that this function is called because user
	// moves his finger to this button. If movePress is false, means that this
	// function is called when user just presses this key.
	/**
	 * 按鍵按下處理函式
	 * 
	 * @param x
	 * @param y
	 * @param longPressTimer
	 * @param movePress
	 *            如果為true,意味著是因為手指觸控移動到該按鍵。如果為false,意味著是因為手指按下給按鍵。
	 * @return
	 */
	public SoftKey onKeyPress(int x, int y,
			SkbContainer.LongPressTimer longPressTimer, boolean movePress) {
		mKeyPressed = false;
		boolean moveWithinPreviousKey = false;
		if (movePress) {
			SoftKey newKey = mSoftKeyboard.mapToKey(x, y);
			if (newKey == mSoftKeyDown)
				moveWithinPreviousKey = true;
			mSoftKeyDown = newKey;
		} else {
			mSoftKeyDown = mSoftKeyboard.mapToKey(x, y);
		}
		if (moveWithinPreviousKey || null == mSoftKeyDown)
			return mSoftKeyDown;

		// TODO
		// 這句程式碼放在這裡,那如果moveWithinPreviousKey是true的情況下,mKeyPressed不是還是false嗎?
		mKeyPressed = true;

		// 播放按鍵聲音和震動
		if (!movePress) {
			tryPlayKeyDown();
			tryVibrate();
		}

		mLongPressTimer = longPressTimer;

		// 判斷是否是按下操作,如果是,就判讀條件,啟動長按定時器
		if (!movePress) {
			if (mSoftKeyDown.getPopupResId() > 0 || mSoftKeyDown.repeatable()) {
				mLongPressTimer.startTimer();
			}
		} else {
			mLongPressTimer.removeTimer();
		}

		int desired_width;
		int desired_height;
		float textSize;
		Environment env = Environment.getInstance();

		if (null != mBalloonOnKey) {
			// 設定氣泡背景
			Drawable keyHlBg = mSoftKeyDown.getKeyHlBg();
			mBalloonOnKey.setBalloonBackground(keyHlBg);

			// Prepare the on-key balloon
			// 設定氣泡內容和尺寸
			int keyXMargin = mSoftKeyboard.getKeyXMargin();
			int keyYMargin = mSoftKeyboard.getKeyYMargin();
			desired_width = mSoftKeyDown.width() - 2 * keyXMargin;
			desired_height = mSoftKeyDown.height() - 2 * keyYMargin;
			textSize = env
					.getKeyTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);
			Drawable icon = mSoftKeyDown.getKeyIcon();
			if (null != icon) {
				mBalloonOnKey.setBalloonConfig(icon, desired_width,
						desired_height);
			} else {
				mBalloonOnKey.setBalloonConfig(mSoftKeyDown.getKeyLabel(),
						textSize, true, mSoftKeyDown.getColorHl(),
						desired_width, desired_height);
			}

			// 設定氣泡顯示的位置
			mHintLocationToSkbContainer[0] = getPaddingLeft()
					+ mSoftKeyDown.mLeft
					- (mBalloonOnKey.getWidth() - mSoftKeyDown.width()) / 2;
			mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];
			mHintLocationToSkbContainer[1] = getPaddingTop()
					+ (mSoftKeyDown.mBottom - keyYMargin)
					- mBalloonOnKey.getHeight();
			mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];

			// 顯示氣泡
			showBalloon(mBalloonOnKey, mHintLocationToSkbContainer, movePress);
		} else {
			// 設定介面區域性重新整理,只重新整理按鍵區域
			mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
					mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
			invalidate(mDirtyRect);
		}

		// TODO 下面這個氣泡和上面的氣泡有什麼區別?
		// Prepare the popup balloon
		if (mSoftKeyDown.needBalloon()) {
			Drawable balloonBg = mSoftKeyboard.getBalloonBackground();
			mBalloonPopup.setBalloonBackground(balloonBg);

			desired_width = mSoftKeyDown.width() + env.getKeyBalloonWidthPlus();
			desired_height = mSoftKeyDown.height()
					+ env.getKeyBalloonHeightPlus();
			textSize = env
					.getBalloonTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);
			Drawable iconPopup = mSoftKeyDown.getKeyIconPopup();
			if (null != iconPopup) {
				mBalloonPopup.setBalloonConfig(iconPopup, desired_width,
						desired_height);
			} else {
				mBalloonPopup.setBalloonConfig(mSoftKeyDown.getKeyLabel(),
						textSize, mSoftKeyDown.needBalloon(),
						mSoftKeyDown.getColorBalloon(), desired_width,
						desired_height);
			}

			// The position to show.
			mHintLocationToSkbContainer[0] = getPaddingLeft()
					+ mSoftKeyDown.mLeft
					+ -(mBalloonPopup.getWidth() - mSoftKeyDown.width()) / 2;
			mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];
			mHintLocationToSkbContainer[1] = getPaddingTop()
					+ mSoftKeyDown.mTop - mBalloonPopup.getHeight();
			mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];
			showBalloon(mBalloonPopup, mHintLocationToSkbContainer, movePress);
		} else {
			mBalloonPopup.delayedDismiss(0);
		}

		// TODO 怎麼這裡還有一個長按定時器啟動? 上面不是已經設定了嗎?
		if (mRepeatForLongPress)
			longPressTimer.startTimer();
		return mSoftKeyDown;
	}

	/**
	 * 按鍵釋放的處理函式
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public SoftKey onKeyRelease(int x, int y) {
		mKeyPressed = false;
		if (null == mSoftKeyDown)
			return null;

		mLongPressTimer.removeTimer();

		if (null != mBalloonOnKey) {
			mBalloonOnKey.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
		} else {
			mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
					mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
			invalidate(mDirtyRect);
		}

		if (mSoftKeyDown.needBalloon()) {
			mBalloonPopup.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
		}

		// TODO 為什麼在按下的處理函式中判斷座標點屬於哪個按鍵的時候不需要減去padding,而在這裡需要呢?
		if (mSoftKeyDown.moveWithinKey(x - getPaddingLeft(), y
				- getPaddingTop())) {
			return mSoftKeyDown;
		}
		return null;
	}

	/**
	 * 按鍵的移動處理函式
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public SoftKey onKeyMove(int x, int y) {
		if (null == mSoftKeyDown)
			return null;

		if (mSoftKeyDown.moveWithinKey(x - getPaddingLeft(), y
				- getPaddingTop())) {
			return mSoftKeyDown;
		}

		// The current key needs to be updated.
		mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
				mSoftKeyDown.mRight, mSoftKeyDown.mBottom);

		if (mRepeatForLongPress) {
			// 如果mMovingNeverHidePopupBalloon為true,那麼就不銷燬氣泡,否則,先銷燬在進入按鍵處理函式。
			if (mMovingNeverHidePopupBalloon) {
				return onKeyPress(x, y, mLongPressTimer, true);
			}

			if (null != mBalloonOnKey) {
				mBalloonOnKey.delayedDismiss(0);
			} else {
				invalidate(mDirtyRect);
			}

			if (mSoftKeyDown.needBalloon()) {
				mBalloonPopup.delayedDismiss(0);
			}

			if (null != mLongPressTimer) {
				mLongPressTimer.removeTimer();
			}
			return onKeyPress(x, y, mLongPressTimer, true);
		} else {
			// When user moves between keys, repeated response is disabled.
			return onKeyPress(x, y, mLongPressTimer, true);
		}
	}

	/**
	 * 震動
	 */
	private void tryVibrate() {
		if (!Settings.getVibrate()) {
			return;
		}
		if (mVibrator == null) {
			mVibrator = (Vibrator) mContext
					.getSystemService(Context.VIBRATOR_SERVICE);

			// = new Vibrator();
		}
		mVibrator.vibrate(mVibratePattern, -1);
	}

	/**
	 * 播放按鍵按下的聲音
	 */
	private void tryPlayKeyDown() {
		if (Settings.getKeySound()) {
			mSoundManager.playKeyDown();
		}
	}

	/**
	 * 是否銷燬SoftKeyboard檢視
	 * 
	 * @param dimSkb
	 *            該標誌會在onDraw()中使用。
	 */
	public void dimSoftKeyboard(boolean dimSkb) {
		mDimSkb = dimSkb;
		invalidate();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (null == mSoftKeyboard)
			return;

		// 畫布水平移動padding,使得畫布的原點(0,0)相對於父檢視移動了(getPaddingLeft(),
		// getPaddingTop())。
		canvas.translate(getPaddingLeft(), getPaddingTop());

		Environment env = Environment.getInstance();
		mNormalKeyTextSize = env.getKeyTextSize(false);
		mFunctionKeyTextSize = env.getKeyTextSize(true);
		// Draw the last soft keyboard
		int rowNum = mSoftKeyboard.getRowNum();
		int keyXMargin = mSoftKeyboard.getKeyXMargin();
		int keyYMargin = mSoftKeyboard.getKeyYMargin();
		for (int row = 0; row < rowNum; row++) {
			KeyRow keyRow = mSoftKeyboard.getKeyRowForDisplay(row);
			if (null == keyRow)
				continue;
			List<SoftKey> softKeys = keyRow.mSoftKeys;
			int keyNum = softKeys.size();
			for (int i = 0; i < keyNum; i++) {
				SoftKey softKey = softKeys.get(i);
				if (SoftKeyType.KEYTYPE_ID_NORMAL_KEY == softKey.mKeyType.mKeyTypeId) {
					mPaint.setTextSize(mNormalKeyTextSize);
				} else {
					mPaint.setTextSize(mFunctionKeyTextSize);
				}
				drawSoftKey(canvas, softKey, keyXMargin, keyYMargin);
			}
		}

		// 清空畫布
		if (mDimSkb) {
			mPaint.setColor(0xa0000000);
			canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
		}

		mDirtyRect.setEmpty();
	}

	/**
	 * 在畫布上畫一個按鍵
	 * 
	 * @param canvas
	 * @param softKey
	 * @param keyXMargin
	 * @param keyYMargin
	 */
	private void drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin,
			int keyYMargin) {
		Drawable bg;
		int textColor;
		if (mKeyPressed && softKey == mSoftKeyDown) {
			bg = softKey.getKeyHlBg();
			textColor = softKey.getColorHl();
		} else {
			bg = softKey.getKeyBg();
			textColor = softKey.getColor();
		}

		if (null != bg) {
			bg.setBounds(softKey.mLeft + keyXMargin, softKey.mTop + keyYMargin,
					softKey.mRight - keyXMargin, softKey.mBottom - keyYMargin);
			bg.draw(canvas);
		}

		String keyLabel = softKey.getKeyLabel();
		Drawable keyIcon = softKey.getKeyIcon();
		if (null != keyIcon) {
			Drawable icon = keyIcon;
			int marginLeft = (softKey.width() - icon.getIntrinsicWidth()) / 2;
			int marginRight = softKey.width() - icon.getIntrinsicWidth()
					- marginLeft;
			int marginTop = (softKey.height() - icon.getIntrinsicHeight()) / 2;
			int marginBottom = softKey.height() - icon.getIntrinsicHeight()
					- marginTop;
			icon.setBounds(softKey.mLeft + marginLeft,
					softKey.mTop + marginTop, softKey.mRight - marginRight,
					softKey.mBottom - marginBottom);
			icon.draw(canvas);
		} else if (null != keyLabel) {
			mPaint.setColor(textColor);
			float x = softKey.mLeft
					+ (softKey.width() - mPaint.measureText(keyLabel)) / 2.0f;
			int fontHeight = mFmi.bottom - mFmi.top;
			float marginY = (softKey.height() - fontHeight) / 2.0f;
			float y = softKey.mTop + marginY - mFmi.top + mFmi.bottom / 1.5f;
			canvas.drawText(keyLabel, x, y + 1, mPaint);
		}
	}
}


21、SoftKeyToggle.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.graphics.drawable.Drawable;

/**
 * Class for soft keys which defined in the keyboard xml file. A soft key can be
 * a basic key or a toggling key.
 * 
 * @see com.android.inputmethod.pinyin.SoftKey
 */
/**
 * 可以變換狀態的按鍵
 * 
 * @ClassName SoftKeyToggle
 * @author keanbin
 */
public class SoftKeyToggle extends SoftKey {
	/**
	 * The current state number is stored in the lowest 8 bits of mKeyMask, this
	 * mask is used to get the state number. If the current state is 0, the
	 * normal state is enabled; if the current state is more than 0, a toggle
	 * state in the toggle state chain will be enabled.
	 */
	private static final int KEYMASK_TOGGLE_STATE = 0x000000ff;

	private ToggleState mToggleState;

	public int getToggleStateId() {
		return (mKeyMask & KEYMASK_TOGGLE_STATE);
	}

	// The state id should be valid, and less than 255.
	// If resetIfNotFound is true and there is no such toggle state with the
	// given id, the key state will be reset.
	// If the key state is newly changed (enabled to the given state, or
	// reseted) and needs re-draw, return true.
	/**
	 * 查詢該按鍵是否有stateId的狀態,如果有就切換到這個狀態。
	 * 
	 * @param stateId
	 * @param resetIfNotFound
	 *            如果沒有找到與stateId的狀態,是否重置mKeyMask的KEYMASK_TOGGLE_STATE位為0。
	 * @return
	 */
	public boolean enableToggleState(int stateId, boolean resetIfNotFound) {
		int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
		if (oldStateId == stateId)
			return false;

		mKeyMask &= (~KEYMASK_TOGGLE_STATE);
		if (stateId > 0) {
			/*
			 * 先設定判斷的stateId為當前mKeyMask,然後在getToggleState()中根據mKeyMask進行查詢,如果找到了,
			 * 就返回true,如果沒有找到
			 * ,就重置mKeyMask中的KEYMASK_TOGGLE_STATE位為0,再判斷resetIfNotFound是否為true,
			 * 如果不是就還原以前的oldStateId。
			 */
			mKeyMask |= (KEYMASK_TOGGLE_STATE & stateId);
			if (getToggleState() == null) {
				mKeyMask &= (~KEYMASK_TOGGLE_STATE);
				if (!resetIfNotFound && oldStateId > 0) {
					mKeyMask |= (KEYMASK_TOGGLE_STATE & oldStateId);
				}
				return resetIfNotFound;
			} else {
				return true;
			}
		} else {
			return true;
		}
	}

	// The state id should be valid, and less than 255.
	// If resetIfNotFound is true and there is no such toggle state with the
	// given id, the key state will be reset.
	// If the key state is newly changed and needs re-draw, return true.
	/**
	 * 判斷當前的狀態是否是stateId,如果是,就重置mKeyMask的KEYMASK_TOGGLE_STATE位為0。
	 * 
	 * @param stateId
	 * @param resetIfNotFound
	 *            如果沒有找到與stateId的狀態,是否重置mKeyMask的KEYMASK_TOGGLE_STATE位為0。
	 * @return
	 */
	public boolean disableToggleState(int stateId, boolean resetIfNotFound) {
		int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
		if (oldStateId == stateId) {
			mKeyMask &= (~KEYMASK_TOGGLE_STATE);
			return stateId != 0;
		}

		if (resetIfNotFound) {
			mKeyMask &= (~KEYMASK_TOGGLE_STATE);
			return oldStateId != 0;
		}
		return false;
	}

	// Clear any toggle state. If the key needs re-draw, return true.
	/**
	 * 重置mKeyMask的KEYMASK_TOGGLE_STATE位為0。
	 * 
	 * @return
	 */
	public boolean disableAllToggleStates() {
		int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
		mKeyMask &= (~KEYMASK_TOGGLE_STATE);
		return oldStateId != 0;
	}

	@Override
	public Drawable getKeyIcon() {
		ToggleState state = getToggleState();
		if (null != state)
			return state.mKeyIcon;
		return super.getKeyIcon();
	}

	@Override
	public Drawable getKeyIconPopup() {
		ToggleState state = getToggleState();
		if (null != state) {
			if (null != state.mKeyIconPopup) {
				return state.mKeyIconPopup;
			} else {
				return state.mKeyIcon;
			}
		}
		return super.getKeyIconPopup();
	}

	@Override
	public int getKeyCode() {
		ToggleState state = getToggleState();
		if (null != state)
			return state.mKeyCode;
		return mKeyCode;
	}

	@Override
	public String getKeyLabel() {
		ToggleState state = getToggleState();
		if (null != state)
			return state.mKeyLabel;
		return mKeyLabel;
	}

	@Override
	public Drawable getKeyBg() {
		ToggleState state = getToggleState();
		if (null != state && null != state.mKeyType) {
			return state.mKeyType.mKeyBg;
		}
		return mKeyType.mKeyBg;
	}

	@Override
	public Drawable getKeyHlBg() {
		ToggleState state = getToggleState();
		if (null != state && null != state.mKeyType) {
			return state.mKeyType.mKeyHlBg;
		}
		return mKeyType.mKeyHlBg;
	}

	@Override
	public int getColor() {
		ToggleState state = getToggleState();
		if (null != state && null != state.mKeyType) {
			return state.mKeyType.mColor;
		}
		return mKeyType.mColor;
	}

	@Override
	public int getColorHl() {
		ToggleState state = getToggleState();
		if (null != state && null != state.mKeyType) {
			return state.mKeyType.mColorHl;
		}
		return mKeyType.mColorHl;
	}

	@Override
	public int getColorBalloon() {
		ToggleState state = getToggleState();
		if (null != state && null != state.mKeyType) {
			return state.mKeyType.mColorBalloon;
		}
		return mKeyType.mColorBalloon;
	}

	@Override
	public boolean isKeyCodeKey() {
		ToggleState state = getToggleState();
		if (null != state) {
			if (state.mKeyCode > 0)
				return true;
			return false;
		}
		return super.isKeyCodeKey();
	}

	@Override
	public boolean isUserDefKey() {
		ToggleState state = getToggleState();
		if (null != state) {
			if (state.mKeyCode < 0)
				return true;
			return false;
		}
		return super.isUserDefKey();
	}

	@Override
	public boolean isUniStrKey() {
		ToggleState state = getToggleState();
		if (null != state) {
			if (null != state.mKeyLabel && state.mKeyCode == 0) {
				return true;
			}
			return false;
		}
		return super.isUniStrKey();
	}

	@Override
	public boolean needBalloon() {
		ToggleState state = getToggleState();
		if (null != state) {
			return (state.mIdAndFlags & KEYMASK_BALLOON) != 0;
		}
		return super.needBalloon();
	}

	@Override
	public boolean repeatable() {
		ToggleState state = getToggleState();
		if (null != state) {
			return (state.mIdAndFlags & KEYMASK_REPEAT) != 0;
		}
		return super.repeatable();
	}

	@Override
	public void changeCase(boolean lowerCase) {
		ToggleState state = getToggleState();
		if (null != state && null != state.mKeyLabel) {
			if (lowerCase)
				state.mKeyLabel = state.mKeyLabel.toLowerCase();
			else
				state.mKeyLabel = state.mKeyLabel.toUpperCase();
		}
	}

	public ToggleState createToggleState() {
		return new ToggleState();
	}

	public boolean setToggleStates(ToggleState rootState) {
		if (null == rootState)
			return false;
		mToggleState = rootState;
		return true;
	}

	/**
	 * 判斷當前的ToggleState的mIdAndFlags &
	 * KEYMASK_TOGGLE_STATE是否與stateId時相等的,如果不是就移動到下一個ToggleState再找
	 * ,直到與stateId時相等或者沒有下一個ToggleState為止。
	 * 
	 * @return
	 */
	private ToggleState getToggleState() {
		int stateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
		if (0 == stateId)
			return null;

		ToggleState state = mToggleState;
		while ((null != state)
				&& (state.mIdAndFlags & KEYMASK_TOGGLE_STATE) != stateId) {
			state = state.mNextState;
		}
		return state;
	}

	/**
	 * 按鍵變化的狀態,每一個按鍵的變化狀態都是一個單向列表,mNextState指向一下個變化的狀態。
	 * 
	 * @ClassName ToggleState
	 * @author keanbin
	 */
	public class ToggleState {
		// The id should be bigger than 0;
		private int mIdAndFlags;
		public SoftKeyType mKeyType;
		public int mKeyCode;
		public Drawable mKeyIcon;
		public Drawable mKeyIconPopup;
		public String mKeyLabel;
		public ToggleState mNextState;

		public void setStateId(int stateId) {
			mIdAndFlags |= (stateId & KEYMASK_TOGGLE_STATE);
		}

		public void setStateFlags(boolean repeat, boolean balloon) {
			if (repeat) {
				mIdAndFlags |= KEYMASK_REPEAT;
			} else {
				mIdAndFlags &= (~KEYMASK_REPEAT);
			}

			if (balloon) {
				mIdAndFlags |= KEYMASK_BALLOON;
			} else {
				mIdAndFlags &= (~KEYMASK_BALLOON);
			}
		}
	}
}


22、SoundManager.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import android.content.Context;
import android.media.AudioManager;

/**
 * Class used to manage related sound resources.
 */
/**
 * 聲音管理類,該類採用單例模式。
 * 
 * @ClassName SoundManager
 * @author keanbin
 */
public class SoundManager {
	private static SoundManager mInstance = null;
	private Context mContext;
	private AudioManager mAudioManager;
	// Align sound effect volume on music volume
	private final float FX_VOLUME = -1.0f;
	private boolean mSilentMode;

	private SoundManager(Context context) {
		mContext = context;
		updateRingerMode();
	}

	public void updateRingerMode() {
		if (mAudioManager == null) {
			mAudioManager = (AudioManager) mContext
					.getSystemService(Context.AUDIO_SERVICE);
		}
		mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
	}

	public static SoundManager getInstance(Context context) {
		if (null == mInstance) {
			if (null != context) {
				mInstance = new SoundManager(context);
			}
		}
		return mInstance;
	}

	/**
	 * 如果mSilentMode為false,就播放按下按鍵的聲音。
	 */
	public void playKeyDown() {
		if (mAudioManager == null) {
			updateRingerMode();
		}
		if (!mSilentMode) {
			int sound = AudioManager.FX_KEYPRESS_STANDARD;
			mAudioManager.playSoundEffect(sound, FX_VOLUME);
		}
	}
}


23、XmlKeyboardLoader.java:

/*
 * Copyright (C) 2009 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 com.keanbin.pinyinime;

import java.io.IOException;
import java.util.regex.Pattern;

import org.xmlpull.v1.XmlPullParserException;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;

import com.keanbin.pinyinime.SoftKeyboard.KeyRow;

/**
 * Class used to load a soft keyboard or a soft keyboard template from xml
 * files.
 */
/**
 * 解析軟鍵盤模版和軟鍵盤xml檔案。
 * 
 * @ClassName XmlKeyboardLoader
 * @author keanbin
 */
public class XmlKeyboardLoader {
	/**
	 * The tag used to define an xml-based soft keyboard template. xml標籤
	 */
	private static final String XMLTAG_SKB_TEMPLATE = "skb_template";

	/**
	 * The tag used to indicate the soft key type which is defined inside the
	 * {@link #XMLTAG_SKB_TEMPLATE} element in the xml file. file. xml標籤
	 */
	private static final String XMLTAG_KEYTYPE = "key_type";

	/**
	 * The tag used to define a default key icon for enter/delete/space keys. It
	 * is defined inside the {@link #XMLTAG_SKB_TEMPLATE} element in the xml
	 * xml標籤 file.
	 */
	private static final String XMLTAG_KEYICON = "key_icon";

	/**
	 * Attribute tag of the left and right margin for a key. A key's width
	 * should be larger than double of this value. Defined inside
	 * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}. xml標籤中的屬性
	 */
	private static final String XMLATTR_KEY_XMARGIN = "key_xmargin";

	/**
	 * Attribute tag of the top and bottom margin for a key. A key's height
	 * should be larger than double of this value. Defined inside
	 * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}. xml標籤中的屬性
	 */
	private static final String XMLATTR_KEY_YMARGIN = "key_ymargin";

	/**
	 * Attribute tag of the keyboard background image. Defined inside
	 * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}. xml標籤中的屬性
	 */
	private static final String XMLATTR_SKB_BG = "skb_bg";

	/**
	 * Attribute tag of the balloon background image for key press. Defined
	 * inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
	 * xml標籤中的屬性
	 */
	private static final String XMLATTR_BALLOON_BG = "balloon_bg";

	/**
	 * Attribute tag of the popup balloon background image for key press or
	 * popup mini keyboard. Defined inside {@link #XMLTAG_SKB_TEMPLATE} and
	 * {@link #XMLTAG_KEYBOARD}. xml標籤中的屬性
	 */
	private static final String XMLATTR_POPUP_BG = "popup_bg";

	/**
	 * Attribute tag of the color to draw key label. Defined inside
	 * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}. xml標籤中的屬性
	 */
	private static final String XMLATTR_COLOR = "color";

	/**
	 * Attribute tag of the color to draw key's highlighted label. Defined
	 * inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
	 * xml標籤中的屬性
	 */
	private static final String XMLATTR_COLOR_HIGHLIGHT = "color_highlight";

	/**
	 * Attribute tag of the color to draw key's label in the popup balloon.
	 * Defined inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
	 * xml標籤中的屬性
	 */
	private static final String XMLATTR_COLOR_BALLOON = "color_balloon";

	/**
	 * Attribute tag of the id of {@link #XMLTAG_KEYTYPE} and
	 * {@link #XMLTAG_KEY}. Key types and keys defined in a soft keyboard
	 * template should have id, because a soft keyboard needs the id to refer to
	 * these default definitions. If a key defined in {@link #XMLTAG_KEYBOARD}
	 * does not id, that means the key is newly defined; if it has id (and only
	 * has id), the id is used to find the default definition from the soft
	 * keyboard template. xml標籤中的屬性
	 */
	private static final String XMLATTR_ID = "id";

	/**
	 * Attribute tag of the key background for a specified key type. Defined
	 * inside {@link #XMLTAG_KEYTYPE}. xml標籤中的屬性
	 */
	private static final String XMLATTR_KEYTYPE_BG = "bg";

	/**
	 * Attribute tag of the key high-light background for a specified key type.
	 * Defined inside {@link #XMLTAG_KEYTYPE}. xml標籤中的屬性
	 */
	private static final String XMLATTR_KEYTYPE_HLBG = "hlbg";

	/**
	 * Attribute tag of the starting x-position of an element. It can be defined
	 * in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.
	 * If not defined, 0 will be used. For a key defined in
	 * {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to
	 * calculate its own position. xml標籤中的屬性
	 */
	private static final String XMLATTR_START_POS_X = "start_pos_x";

	/**
	 * Attribute tag of the starting y-position of an element. It can be defined
	 * in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.
	 * If not defined, 0 will be used. For a key defined in
	 * {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to
	 * calculate its own position. xml標籤中的屬性
	 */
	private static final String XMLATTR_START_POS_Y = "start_pos_y";

	/**
	 * Attribute tag of a row's id. Defined {@link #XMLTAG_ROW}. If not defined,
	 * -1 will be used. Rows with id -1 will be enabled always, rows with same
	 * row id will be enabled when the id is the same to the activated id of the
	 * soft keyboard. xml標籤中的屬性
	 */
	private static final String XMLATTR_ROW_ID = "row_id";

	/** The tag used to indicate the keyboard element in the xml file. xml標籤 */
	private static final String XMLTAG_KEYBOARD = "keyboard";

	/** The tag used to indicate the row element in the xml file. xml標籤 */
	private static final String XMLTAG_ROW = "row";

	/** The tag used to indicate key-array element in the xml file. xml標籤 */
	private static final String XMLTAG_KEYS = "keys";

	/**
	 * The tag used to indicate a key element in the xml file. If the element is
	 * defined in a soft keyboard template, it should have an id. If it is
	 * defined in a soft keyboard, id is not required. xml標籤
	 */
	private static final String XMLTAG_KEY = "key";

	/** The tag used to indicate a key's toggle element in the xml file. xml標籤 */
	private static final String XMLTAG_TOGGLE_STATE = "toggle_state";

	/**
	 * Attribute tag of the toggle state id for toggle key. Defined inside
	 * {@link #XMLTAG_TOGGLE_STATE} xml標籤中的屬性
	 */
	private static final String XMLATTR_TOGGLE_STATE_ID = "state_id";

	/** Attribute tag of key template for the soft keyboard. xml標籤中的屬性 */
	private static final String XMLATTR_SKB_TEMPLATE = "skb_template";

	/**
	 * Attribute tag used to indicate whether this soft keyboard needs to be
	 * cached in memory for future use. {@link #DEFAULT_SKB_CACHE_FLAG}
	 * specifies the default value. xml標籤中的屬性
	 */
	private static final String XMLATTR_SKB_CACHE_FLAG = "skb_cache_flag";

	/**
	 * Attribute tag used to indicate whether this soft keyboard is sticky. A
	 * sticky soft keyboard will keep the current layout unless user makes a
	 * switch explicitly. A none sticky soft keyboard will automatically goes
	 * back to the previous keyboard after click a none-function key.
	 * {@link #DEFAULT_SKB_STICKY_FLAG} specifies the default value. xml標籤中的屬性
	 */
	private static final String XMLATTR_SKB_STICKY_FLAG = "skb_sticky_flag";

	/**
	 * Attribute tag to indicate whether it is a QWERTY soft keyboard. xml標籤中的屬性
	 */
	private static final String XMLATTR_QWERTY = "qwerty";

	/**
	 * When the soft keyboard is a QWERTY one, this attribute tag to get the
	 * information that whether it is defined in upper case. xml標籤中的屬性
	 */
	private static final String XMLATTR_QWERTY_UPPERCASE = "qwerty_uppercase";

	/** Attribute tag of key type. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_TYPE = "key_type";

	/** Attribute tag of key width. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_WIDTH = "width";

	/** Attribute tag of key height. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_HEIGHT = "height";

	/** Attribute tag of the key's repeating ability. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_REPEAT = "repeat";

	/** Attribute tag of the key's behavior for balloon. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_BALLOON = "balloon";

	/** Attribute tag of the key splitter in a key array. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_SPLITTER = "splitter";

	/** Attribute tag of the key labels in a key array. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_LABELS = "labels";

	/** Attribute tag of the key codes in a key array. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_CODES = "codes";

	/** Attribute tag of the key label in a key. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_LABEL = "label";

	/** Attribute tag of the key code in a key. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_CODE = "code";

	/** Attribute tag of the key icon in a key. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_ICON = "icon";

	/** Attribute tag of the key's popup icon in a key. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_ICON_POPUP = "icon_popup";

	/** The id for a mini popup soft keyboard. xml標籤中的屬性 */
	private static final String XMLATTR_KEY_POPUP_SKBID = "popup_skb";

	private static boolean DEFAULT_SKB_CACHE_FLAG = true;

	private static boolean DEFAULT_SKB_STICKY_FLAG = true;

	/**
	 * The key type id for invalid key type. It is also used to generate next
	 * valid key type id by adding 1. 無效key type id。最後的key type id。
	 */
	private static final int KEYTYPE_ID_LAST = -1;

	private Context mContext;

	private Resources mResources;

	/** The event type in parsing the xml file. */
	private int mXmlEventType;

	/**
	 * The current soft keyboard template used by the current soft keyboard
	 * under loading.
	 **/
	private SkbTemplate mSkbTemplate;

	/** The x position for the next key. */
	float mKeyXPos;

	/** The y position for the next key. */
	float mKeyYPos;

	/** The width of the keyboard to load. */
	int mSkbWidth;

	/** The height of the keyboard to load. */
	int mSkbHeight;

	/** Key margin in x-way. */
	float mKeyXMargin = 0;

	/** Key margin in y-way. */
	float mKeyYMargin = 0;

	/**
	 * Used to indicate whether next event has been fetched during processing
	 * the the current event. 處理當前事件的時候,下一個事件是否已經到達。
	 */
	boolean mNextEventFetched = false;

	String mAttrTmp;

	/**
	 * 標籤的公共屬性
	 * 
	 * @ClassName KeyCommonAttributes
	 * @author keanbin
	 */
	class KeyCommonAttributes {
		XmlResourceParser mXrp;
		int keyType;
		float keyWidth;
		float keyHeight;
		boolean repeat;
		boolean balloon;

		KeyCommonAttributes(XmlResourceParser xrp) {
			mXrp = xrp;
			balloon = true;
		}

		// Make sure the default object is not null.
		/**
		 * 確保預設的物件不是空。
		 * 
		 * @param defAttr
		 * @return
		 */
		boolean getAttributes(KeyCommonAttributes defAttr) {
			keyType = getInteger(mXrp, XMLATTR_KEY_TYPE, defAttr.keyType);
			keyWidth = getFloat(mXrp, XMLATTR_KEY_WIDTH, defAttr.keyWidth);
			keyHeight = getFloat(mXrp, XMLATTR_KEY_HEIGHT, defAttr.keyHeight);
			repeat = getBoolean(mXrp, XMLATTR_KEY_REPEAT, defAttr.repeat);
			balloon = getBoolean(mXrp, XMLATTR_KEY_BALLOON, defAttr.balloon);
			if (keyType < 0 || keyWidth <= 0 || keyHeight <= 0) {
				return false;
			}
			return true;
		}
	}

	public XmlKeyboardLoader(Context context) {
		mContext = context;
		mResources = mContext.getResources();
	}

	/**
	 * 解析軟鍵盤模版xml檔案,生成一個軟鍵盤模版物件。
	 * 
	 * @param resourceId
	 * @return
	 */
	public SkbTemplate loadSkbTemplate(int resourceId) {
		if (null == mContext || 0 == resourceId) {
			return null;
		}
		Resources r = mResources;
		XmlResourceParser xrp = r.getXml(resourceId);

		KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);
		KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);

		mSkbTemplate = new SkbTemplate(resourceId);
		int lastKeyTypeId = KEYTYPE_ID_LAST;
		int globalColor = 0;
		int globalColorHl = 0;
		int globalColorBalloon = 0;
		try {
			mXmlEventType = xrp.next();
			while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {
				mNextEventFetched = false;
				if (mXmlEventType == XmlResourceParser.START_TAG) {
					String attribute = xrp.getName();
					if (XMLTAG_SKB_TEMPLATE.compareTo(attribute) == 0) {
						Drawable skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);
						Drawable balloonBg = getDrawable(xrp,
								XMLATTR_BALLOON_BG, null);
						Drawable popupBg = getDrawable(xrp, XMLATTR_POPUP_BG,
								null);
						if (null == skbBg || null == balloonBg
								|| null == popupBg) {
							return null;
						}
						mSkbTemplate.setBackgrounds(skbBg, balloonBg, popupBg);

						float xMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN, 0);
						float yMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN, 0);
						mSkbTemplate.setMargins(xMargin, yMargin);

						// Get default global colors.
						globalColor = getColor(xrp, XMLATTR_COLOR, 0);
						globalColorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,
								0xffffffff);
						globalColorBalloon = getColor(xrp,
								XMLATTR_COLOR_BALLOON, 0xffffffff);
					} else if (XMLTAG_KEYTYPE.compareTo(attribute) == 0) {
						int id = getInteger(xrp, XMLATTR_ID, KEYTYPE_ID_LAST);
						Drawable bg = getDrawable(xrp, XMLATTR_KEYTYPE_BG, null);
						Drawable hlBg = getDrawable(xrp, XMLATTR_KEYTYPE_HLBG,
								null);
						int color = getColor(xrp, XMLATTR_COLOR, globalColor);
						int colorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,
								globalColorHl);
						int colorBalloon = getColor(xrp, XMLATTR_COLOR_BALLOON,
								globalColorBalloon);
						if (id != lastKeyTypeId + 1) {
							return null;
						}
						SoftKeyType keyType = mSkbTemplate.createKeyType(id,
								bg, hlBg);
						keyType.setColors(color, colorHl, colorBalloon);
						if (!mSkbTemplate.addKeyType(keyType)) {
							return null;
						}
						lastKeyTypeId = id;
					} else if (XMLTAG_KEYICON.compareTo(attribute) == 0) {
						int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
						Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
						Drawable iconPopup = getDrawable(xrp,
								XMLATTR_KEY_ICON_POPUP, null);
						if (null != icon && null != iconPopup) {
							mSkbTemplate.addDefaultKeyIcons(keyCode, icon,
									iconPopup);
						}
					} else if (XMLTAG_KEY.compareTo(attribute) == 0) {
						int keyId = this.getInteger(xrp, XMLATTR_ID, -1);
						if (-1 == keyId)
							return null;

						if (!attrKey.getAttributes(attrDef)) {
							return null;
						}

						// Update the key position for the key.
						mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);
						mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, 0);

						SoftKey softKey = getSoftKey(xrp, attrKey);
						if (null == softKey)
							return null;
						mSkbTemplate.addDefaultKey(keyId, softKey);
					}
				}
				// Get the next tag.
				if (!mNextEventFetched)
					mXmlEventType = xrp.next();
			}
			xrp.close();
			return mSkbTemplate;
		} catch (XmlPullParserException e) {
			// Log.e(TAG, "Ill-formatted keyboard template resource file");
		} catch (IOException e) {
			// Log.e(TAG, "Unable to keyboard template resource file");
		}
		return null;
	}

	/**
	 * 解析軟鍵盤xml檔案,生成一個軟鍵盤。
	 * 
	 * @param resourceId
	 * @param skbWidth
	 * @param skbHeight
	 * @return
	 */
	public SoftKeyboard loadKeyboard(int resourceId, int skbWidth, int skbHeight) {
		if (null == mContext)
			return null;
		Resources r = mResources;
		SkbPool skbPool = SkbPool.getInstance();
		XmlResourceParser xrp = mContext.getResources().getXml(resourceId);
		mSkbTemplate = null;
		SoftKeyboard softKeyboard = null;
		Drawable skbBg;
		Drawable popupBg;
		Drawable balloonBg;
		SoftKey softKey = null;

		KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);
		KeyCommonAttributes attrSkb = new KeyCommonAttributes(xrp);
		KeyCommonAttributes attrRow = new KeyCommonAttributes(xrp);
		KeyCommonAttributes attrKeys = new KeyCommonAttributes(xrp);
		KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);

		mKeyXPos = 0;
		mKeyYPos = 0;
		mSkbWidth = skbWidth;
		mSkbHeight = skbHeight;

		try {
			mKeyXMargin = 0;
			mKeyYMargin = 0;
			mXmlEventType = xrp.next();
			while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {
				mNextEventFetched = false;
				if (mXmlEventType == XmlResourceParser.START_TAG) {
					String attr = xrp.getName();
					// 1. Is it the root element, "keyboard"?
					if (XMLTAG_KEYBOARD.compareTo(attr) == 0) {
						// 1.1 Get the keyboard template id.
						int skbTemplateId = xrp.getAttributeResourceValue(null,
								XMLATTR_SKB_TEMPLATE, 0);

						// 1.2 Try to get the template from pool. If it is not
						// in, the pool will try to load it.
						mSkbTemplate = skbPool.getSkbTemplate(skbTemplateId,
								mContext);

						if (null == mSkbTemplate
								|| !attrSkb.getAttributes(attrDef)) {
							return null;
						}

						boolean cacheFlag = getBoolean(xrp,
								XMLATTR_SKB_CACHE_FLAG, DEFAULT_SKB_CACHE_FLAG);
						boolean stickyFlag = getBoolean(xrp,
								XMLATTR_SKB_STICKY_FLAG,
								DEFAULT_SKB_STICKY_FLAG);
						boolean isQwerty = getBoolean(xrp, XMLATTR_QWERTY,
								false);
						boolean isQwertyUpperCase = getBoolean(xrp,
								XMLATTR_QWERTY_UPPERCASE, false);

						softKeyboard = new SoftKeyboard(resourceId,
								mSkbTemplate, mSkbWidth, mSkbHeight);
						softKeyboard.setFlags(cacheFlag, stickyFlag, isQwerty,
								isQwertyUpperCase);

						mKeyXMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN,
								mSkbTemplate.getXMargin());
						mKeyYMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN,
								mSkbTemplate.getYMargin());
						skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);
						popupBg = getDrawable(xrp, XMLATTR_POPUP_BG, null);
						balloonBg = getDrawable(xrp, XMLATTR_BALLOON_BG, null);
						if (null != skbBg) {
							softKeyboard.setSkbBackground(skbBg);
						}
						if (null != popupBg) {
							softKeyboard.setPopupBackground(popupBg);
						}
						if (null != balloonBg) {
							softKeyboard.setKeyBalloonBackground(balloonBg);
						}
						softKeyboard.setKeyMargins(mKeyXMargin, mKeyYMargin);
					} else if (XMLTAG_ROW.compareTo(attr) == 0) {
						if (!attrRow.getAttributes(attrSkb)) {
							return null;
						}
						// Get the starting positions for the row.
						mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);
						mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, mKeyYPos);
						int rowId = getInteger(xrp, XMLATTR_ROW_ID,
								KeyRow.ALWAYS_SHOW_ROW_ID);
						softKeyboard.beginNewRow(rowId, mKeyYPos);
					} else if (XMLTAG_KEYS.compareTo(attr) == 0) {
						if (null == softKeyboard)
							return null;
						if (!attrKeys.getAttributes(attrRow)) {
							return null;
						}

						String splitter = xrp.getAttributeValue(null,
								XMLATTR_KEY_SPLITTER);
						splitter = Pattern.quote(splitter);
						String labels = xrp.getAttributeValue(null,
								XMLATTR_KEY_LABELS);
						String codes = xrp.getAttributeValue(null,
								XMLATTR_KEY_CODES);
						if (null == splitter || null == labels) {
							return null;
						}
						String labelArr[] = labels.split(splitter);
						String codeArr[] = null;
						if (null != codes) {
							codeArr = codes.split(splitter);
							if (labelArr.length != codeArr.length) {
								return null;
							}
						}

						for (int i = 0; i < labelArr.length; i++) {
							softKey = new SoftKey();
							int keyCode = 0;
							if (null != codeArr) {
								keyCode = Integer.valueOf(codeArr[i]);
							}
							softKey.setKeyAttribute(keyCode, labelArr[i],
									attrKeys.repeat, attrKeys.balloon);

							softKey.setKeyType(
									mSkbTemplate.getKeyType(attrKeys.keyType),
									null, null);

							float left, right, top, bottom;
							left = mKeyXPos;

							right = left + attrKeys.keyWidth;
							top = mKeyYPos;
							bottom = top + attrKeys.keyHeight;

							if (right - left < 2 * mKeyXMargin)
								return null;
							if (bottom - top < 2 * mKeyYMargin)
								return null;

							softKey.setKeyDimensions(left, top, right, bottom);
							softKeyboard.addSoftKey(softKey);
							mKeyXPos = right;
							if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
								return null;
							}
						}
					} else if (XMLTAG_KEY.compareTo(attr) == 0) {
						if (null == softKeyboard) {
							return null;
						}
						if (!attrKey.getAttributes(attrRow)) {
							return null;
						}

						int keyId = this.getInteger(xrp, XMLATTR_ID, -1);
						if (keyId >= 0) {
							softKey = mSkbTemplate.getDefaultKey(keyId);
						} else {
							softKey = getSoftKey(xrp, attrKey);
						}
						if (null == softKey)
							return null;

						// Update the position for next key.
						mKeyXPos = softKey.mRightF;
						if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
							return null;
						}
						// If the current xml event type becomes a starting tag,
						// it indicates that we have parsed too much to get
						// toggling states, and we started a new row. In this
						// case, the row starting position information should
						// be updated.
						if (mXmlEventType == XmlResourceParser.START_TAG) {
							attr = xrp.getName();
							if (XMLTAG_ROW.compareTo(attr) == 0) {
								mKeyYPos += attrRow.keyHeight;
								if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {
									return null;
								}
							}
						}
						softKeyboard.addSoftKey(softKey);
					}
				} else if (mXmlEventType == XmlResourceParser.END_TAG) {
					String attr = xrp.getName();
					if (XMLTAG_ROW.compareTo(attr) == 0) {
						mKeyYPos += attrRow.keyHeight;
						if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {
							return null;
						}
					}
				}

				// Get the next tag.
				if (!mNextEventFetched)
					mXmlEventType = xrp.next();
			}
			xrp.close();
			softKeyboard.setSkbCoreSize(mSkbWidth, mSkbHeight);
			return softKeyboard;
		} catch (XmlPullParserException e) {
			// Log.e(TAG, "Ill-formatted keybaord resource file");
		} catch (IOException e) {
			// Log.e(TAG, "Unable to read keyboard resource file");
		}
		return null;
	}

	// Caller makes sure xrp and r are valid.
	/**
	 * 解析一個按鍵,其中呼叫getToggleStates()解析ToggleState按鍵變換狀態。
	 * 
	 * @param xrp
	 * @param attrKey
	 * @return
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private SoftKey getSoftKey(XmlResourceParser xrp,
			KeyCommonAttributes attrKey) throws XmlPullParserException,
			IOException {
		int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
		String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);
		Drawable keyIcon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
		Drawable keyIconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);
		int popupSkbId = xrp.getAttributeResourceValue(null,
				XMLATTR_KEY_POPUP_SKBID, 0);

		if (null == keyLabel && null == keyIcon) {
			keyIcon = mSkbTemplate.getDefaultKeyIcon(keyCode);
			keyIconPopup = mSkbTemplate.getDefaultKeyIconPopup(keyCode);
			if (null == keyIcon || null == keyIconPopup)
				return null;
		}

		// Dimension information must been initialized before
		// getting toggle state, because mKeyYPos may be changed
		// to next row when trying to get toggle state.
		float left, right, top, bottom;
		left = mKeyXPos;
		right = left + attrKey.keyWidth;
		top = mKeyYPos;
		bottom = top + attrKey.keyHeight;

		if (right - left < 2 * mKeyXMargin)
			return null;
		if (bottom - top < 2 * mKeyYMargin)
			return null;

		// Try to find if the next tag is
		// {@link #XMLTAG_TOGGLE_STATE_OF_KEY}, if yes, try to
		// create a toggle key.
		boolean toggleKey = false;
		mXmlEventType = xrp.next();
		mNextEventFetched = true;

		SoftKey softKey;
		if (mXmlEventType == XmlResourceParser.START_TAG) {
			mAttrTmp = xrp.getName();
			if (mAttrTmp.compareTo(XMLTAG_TOGGLE_STATE) == 0) {
				toggleKey = true;
			}
		}
		if (toggleKey) {
			softKey = new SoftKeyToggle();
			if (!((SoftKeyToggle) softKey).setToggleStates(getToggleStates(
					attrKey, (SoftKeyToggle) softKey, keyCode))) {
				return null;
			}
		} else {
			softKey = new SoftKey();
		}

		// Set the normal state
		softKey.setKeyAttribute(keyCode, keyLabel, attrKey.repeat,
				attrKey.balloon);
		softKey.setPopupSkbId(popupSkbId);
		softKey.setKeyType(mSkbTemplate.getKeyType(attrKey.keyType), keyIcon,
				keyIconPopup);

		softKey.setKeyDimensions(left, top, right, bottom);
		return softKey;
	}

	/**
	 * 解析按鍵變換狀態ToggleState。該函式是遞迴函式。
	 * 
	 * @param attrKey
	 * @param softKey
	 * @param defKeyCode
	 * @return
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private SoftKeyToggle.ToggleState getToggleStates(
			KeyCommonAttributes attrKey, SoftKeyToggle softKey, int defKeyCode)
			throws XmlPullParserException, IOException {
		XmlResourceParser xrp = attrKey.mXrp;
		int stateId = getInteger(xrp, XMLATTR_TOGGLE_STATE_ID, 0);
		if (0 == stateId)
			return null;

		String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);
		int keyTypeId = getInteger(xrp, XMLATTR_KEY_TYPE, KEYTYPE_ID_LAST);
		int keyCode;
		if (null == keyLabel) {
			keyCode = getInteger(xrp, XMLATTR_KEY_CODE, defKeyCode);
		} else {
			keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
		}
		Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
		Drawable iconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);
		if (null == icon && null == keyLabel) {
			return null;
		}
		SoftKeyToggle.ToggleState rootState = softKey.createToggleState();
		rootState.setStateId(stateId);
		rootState.mKeyType = null;
		if (KEYTYPE_ID_LAST != keyTypeId) {
			rootState.mKeyType = mSkbTemplate.getKeyType(keyTypeId);
		}
		rootState.mKeyCode = keyCode;
		rootState.mKeyIcon = icon;
		rootState.mKeyIconPopup = iconPopup;
		rootState.mKeyLabel = keyLabel;

		boolean repeat = getBoolean(xrp, XMLATTR_KEY_REPEAT, attrKey.repeat);
		boolean balloon = getBoolean(xrp, XMLATTR_KEY_BALLOON, attrKey.balloon);
		rootState.setStateFlags(repeat, balloon);

		rootState.mNextState = null;

		// If there is another toggle state.
		mXmlEventType = xrp.next();
		while (mXmlEventType != XmlResourceParser.START_TAG
				&& mXmlEventType != XmlResourceParser.END_DOCUMENT) {
			mXmlEventType = xrp.next();
		}
		if (mXmlEventType == XmlResourceParser.START_TAG) {
			String attr = xrp.getName();
			if (attr.compareTo(XMLTAG_TOGGLE_STATE) == 0) {
				SoftKeyToggle.ToggleState nextState = getToggleStates(attrKey,
						softKey, defKeyCode);
				if (null == nextState)
					return null;
				rootState.mNextState = nextState;
			}
		}

		return rootState;
	}

	private int getInteger(XmlResourceParser xrp, String name, int defValue) {
		int resId = xrp.getAttributeResourceValue(null, name, 0);
		String s;
		if (resId == 0) {
			s = xrp.getAttributeValue(null, name);
			if (null == s)
				return defValue;
			try {
				int ret = Integer.valueOf(s);
				return ret;
			} catch (NumberFormatException e) {
				return defValue;
			}
		} else {
			return Integer.parseInt(mContext.getResources().getString(resId));
		}
	}

	private int getColor(XmlResourceParser xrp, String name, int defValue) {
		int resId = xrp.getAttributeResourceValue(null, name, 0);
		String s;
		if (resId == 0) {
			s = xrp.getAttributeValue(null, name);
			if (null == s)
				return defValue;
			try {
				int ret = Integer.valueOf(s);
				return ret;
			} catch (NumberFormatException e) {
				return defValue;
			}
		} else {
			return mContext.getResources().getColor(resId);
		}
	}

	private String getString(XmlResourceParser xrp, String name, String defValue) {
		int resId = xrp.getAttributeResourceValue(null, name, 0);
		if (resId == 0) {
			return xrp.getAttributeValue(null, name);
		} else {
			return mContext.getResources().getString(resId);
		}
	}

	private float getFloat(XmlResourceParser xrp, String name, float defValue) {
		int resId = xrp.getAttributeResourceValue(null, name, 0);
		if (resId == 0) {
			String s = xrp.getAttributeValue(null, name);
			if (null == s)
				return defValue;
			try {
				float ret;
				if (s.endsWith("%p")) {
					ret = Float.parseFloat(s.substring(0, s.length() - 2)) / 100;
				} else {
					ret = Float.parseFloat(s);
				}
				return ret;
			} catch (NumberFormatException e) {
				return defValue;
			}
		} else {
			return mContext.getResources().getDimension(resId);
		}
	}

	private boolean getBoolean(XmlResourceParser xrp, String name,
			boolean defValue) {
		String s = xrp.getAttributeValue(null, name);
		if (null == s)
			return defValue;
		try {
			boolean ret = Boolean.parseBoolean(s);
			return ret;
		} catch (NumberFormatException e) {
			return defValue;
		}
	}

	private Drawable getDrawable(XmlResourceParser xrp, String name,
			Drawable defValue) {
		int resId = xrp.getAttributeResourceValue(null, name, 0);
		if (0 == resId)
			return defValue;
		return mResources.getDrawable(resId);
	}
}


相關文章