1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.pinyin;
18
19import com.android.inputmethod.pinyin.SoftKeyboard.KeyRow;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.content.res.XmlResourceParser;
24import android.graphics.drawable.Drawable;
25
26import java.io.IOException;
27import java.util.regex.Pattern;
28
29import org.xmlpull.v1.XmlPullParserException;
30
31/**
32 * Class used to load a soft keyboard or a soft keyboard template from xml
33 * files.
34 */
35public class XmlKeyboardLoader {
36    /**
37     * The tag used to define an xml-based soft keyboard template.
38     */
39    private static final String XMLTAG_SKB_TEMPLATE = "skb_template";
40
41    /**
42     * The tag used to indicate the soft key type which is defined inside the
43     * {@link #XMLTAG_SKB_TEMPLATE} element in the xml file. file.
44     */
45    private static final String XMLTAG_KEYTYPE = "key_type";
46
47    /**
48     * The tag used to define a default key icon for enter/delete/space keys. It
49     * is defined inside the {@link #XMLTAG_SKB_TEMPLATE} element in the xml
50     * file.
51     */
52    private static final String XMLTAG_KEYICON = "key_icon";
53
54    /**
55     * Attribute tag of the left and right margin for a key. A key's width
56     * should be larger than double of this value. Defined inside
57     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
58     */
59    private static final String XMLATTR_KEY_XMARGIN = "key_xmargin";
60
61    /**
62     * Attribute tag of the top and bottom margin for a key. A key's height
63     * should be larger than double of this value. Defined inside
64     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
65     */
66    private static final String XMLATTR_KEY_YMARGIN = "key_ymargin";
67
68    /**
69     * Attribute tag of the keyboard background image. Defined inside
70     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
71     */
72    private static final String XMLATTR_SKB_BG = "skb_bg";
73
74    /**
75     * Attribute tag of the balloon background image for key press. Defined
76     * inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
77     */
78    private static final String XMLATTR_BALLOON_BG = "balloon_bg";
79
80    /**
81     * Attribute tag of the popup balloon background image for key press or
82     * popup mini keyboard. Defined inside {@link #XMLTAG_SKB_TEMPLATE} and
83     * {@link #XMLTAG_KEYBOARD}.
84     */
85    private static final String XMLATTR_POPUP_BG = "popup_bg";
86
87    /**
88     * Attribute tag of the color to draw key label. Defined inside
89     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
90     */
91    private static final String XMLATTR_COLOR = "color";
92
93    /**
94     * Attribute tag of the color to draw key's highlighted label. Defined
95     * inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
96     */
97    private static final String XMLATTR_COLOR_HIGHLIGHT = "color_highlight";
98
99    /**
100     * Attribute tag of the color to draw key's label in the popup balloon.
101     * Defined inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
102     */
103    private static final String XMLATTR_COLOR_BALLOON = "color_balloon";
104
105    /**
106     * Attribute tag of the id of {@link #XMLTAG_KEYTYPE} and
107     * {@link #XMLTAG_KEY}. Key types and keys defined in a soft keyboard
108     * template should have id, because a soft keyboard needs the id to refer to
109     * these default definitions. If a key defined in {@link #XMLTAG_KEYBOARD}
110     * does not id, that means the key is newly defined; if it has id (and only
111     * has id), the id is used to find the default definition from the soft
112     * keyboard template.
113     */
114    private static final String XMLATTR_ID = "id";
115
116    /**
117     * Attribute tag of the key background for a specified key type. Defined
118     * inside {@link #XMLTAG_KEYTYPE}.
119     */
120    private static final String XMLATTR_KEYTYPE_BG = "bg";
121
122    /**
123     * Attribute tag of the key high-light background for a specified key type.
124     * Defined inside {@link #XMLTAG_KEYTYPE}.
125     */
126    private static final String XMLATTR_KEYTYPE_HLBG = "hlbg";
127
128    /**
129     * Attribute tag of the starting x-position of an element. It can be defined
130     * in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.
131     * If not defined, 0 will be used. For a key defined in
132     * {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to
133     * calculate its own position.
134     */
135    private static final String XMLATTR_START_POS_X = "start_pos_x";
136
137    /**
138     * Attribute tag of the starting y-position of an element. It can be defined
139     * in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.
140     * If not defined, 0 will be used. For a key defined in
141     * {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to
142     * calculate its own position.
143     */
144    private static final String XMLATTR_START_POS_Y = "start_pos_y";
145
146    /**
147     * Attribute tag of a row's id. Defined {@link #XMLTAG_ROW}. If not defined,
148     * -1 will be used. Rows with id -1 will be enabled always, rows with same
149     * row id will be enabled when the id is the same to the activated id of the
150     * soft keyboard.
151     */
152    private static final String XMLATTR_ROW_ID = "row_id";
153
154    /** The tag used to indicate the keyboard element in the xml file. */
155    private static final String XMLTAG_KEYBOARD = "keyboard";
156
157    /** The tag used to indicate the row element in the xml file. */
158    private static final String XMLTAG_ROW = "row";
159
160    /** The tag used to indicate key-array element in the xml file. */
161    private static final String XMLTAG_KEYS = "keys";
162
163    /**
164     * The tag used to indicate a key element in the xml file. If the element is
165     * defined in a soft keyboard template, it should have an id. If it is
166     * defined in a soft keyboard, id is not required.
167     */
168    private static final String XMLTAG_KEY = "key";
169
170    /** The tag used to indicate a key's toggle element in the xml file. */
171    private static final String XMLTAG_TOGGLE_STATE = "toggle_state";
172
173    /**
174     * Attribute tag of the toggle state id for toggle key. Defined inside
175     * {@link #XMLTAG_TOGGLE_STATE}
176     */
177    private static final String XMLATTR_TOGGLE_STATE_ID = "state_id";
178
179    /** Attribute tag of key template for the soft keyboard. */
180    private static final String XMLATTR_SKB_TEMPLATE = "skb_template";
181
182    /**
183     * Attribute tag used to indicate whether this soft keyboard needs to be
184     * cached in memory for future use. {@link #DEFAULT_SKB_CACHE_FLAG}
185     * specifies the default value.
186     */
187    private static final String XMLATTR_SKB_CACHE_FLAG = "skb_cache_flag";
188
189    /**
190     * Attribute tag used to indicate whether this soft keyboard is sticky. A
191     * sticky soft keyboard will keep the current layout unless user makes a
192     * switch explicitly. A none sticky soft keyboard will automatically goes
193     * back to the previous keyboard after click a none-function key.
194     * {@link #DEFAULT_SKB_STICKY_FLAG} specifies the default value.
195     */
196    private static final String XMLATTR_SKB_STICKY_FLAG = "skb_sticky_flag";
197
198    /** Attribute tag to indicate whether it is a QWERTY soft keyboard. */
199    private static final String XMLATTR_QWERTY = "qwerty";
200
201    /**
202     * When the soft keyboard is a QWERTY one, this attribute tag to get the
203     * information that whether it is defined in upper case.
204     */
205    private static final String XMLATTR_QWERTY_UPPERCASE = "qwerty_uppercase";
206
207    /** Attribute tag of key type. */
208    private static final String XMLATTR_KEY_TYPE = "key_type";
209
210    /** Attribute tag of key width. */
211    private static final String XMLATTR_KEY_WIDTH = "width";
212
213    /** Attribute tag of key height. */
214    private static final String XMLATTR_KEY_HEIGHT = "height";
215
216    /** Attribute tag of the key's repeating ability. */
217    private static final String XMLATTR_KEY_REPEAT = "repeat";
218
219    /** Attribute tag of the key's behavior for balloon. */
220    private static final String XMLATTR_KEY_BALLOON = "balloon";
221
222    /** Attribute tag of the key splitter in a key array. */
223    private static final String XMLATTR_KEY_SPLITTER = "splitter";
224
225    /** Attribute tag of the key labels in a key array. */
226    private static final String XMLATTR_KEY_LABELS = "labels";
227
228    /** Attribute tag of the key codes in a key array. */
229    private static final String XMLATTR_KEY_CODES = "codes";
230
231    /** Attribute tag of the key label in a key. */
232    private static final String XMLATTR_KEY_LABEL = "label";
233
234    /** Attribute tag of the key code in a key. */
235    private static final String XMLATTR_KEY_CODE = "code";
236
237    /** Attribute tag of the key icon in a key. */
238    private static final String XMLATTR_KEY_ICON = "icon";
239
240    /** Attribute tag of the key's popup icon in a key. */
241    private static final String XMLATTR_KEY_ICON_POPUP = "icon_popup";
242
243    /** The id for a mini popup soft keyboard. */
244    private static final String XMLATTR_KEY_POPUP_SKBID = "popup_skb";
245
246    private static boolean DEFAULT_SKB_CACHE_FLAG = true;
247
248    private static boolean DEFAULT_SKB_STICKY_FLAG = true;
249
250    /**
251     * The key type id for invalid key type. It is also used to generate next
252     * valid key type id by adding 1.
253     */
254    private static final int KEYTYPE_ID_LAST = -1;
255
256    private Context mContext;
257
258    private Resources mResources;
259
260    /** The event type in parsing the xml file. */
261    private int mXmlEventType;
262
263    /**
264     * The current soft keyboard template used by the current soft keyboard
265     * under loading.
266     **/
267    private SkbTemplate mSkbTemplate;
268
269    /** The x position for the next key. */
270    float mKeyXPos;
271
272    /** The y position for the next key. */
273    float mKeyYPos;
274
275    /** The width of the keyboard to load. */
276    int mSkbWidth;
277
278    /** The height of the keyboard to load. */
279    int mSkbHeight;
280
281    /** Key margin in x-way. */
282    float mKeyXMargin = 0;
283
284    /** Key margin in y-way. */
285    float mKeyYMargin = 0;
286
287    /**
288     * Used to indicate whether next event has been fetched during processing
289     * the the current event.
290     */
291    boolean mNextEventFetched = false;
292
293    String mAttrTmp;
294
295    class KeyCommonAttributes {
296        XmlResourceParser mXrp;
297        int keyType;
298        float keyWidth;
299        float keyHeight;
300        boolean repeat;
301        boolean balloon;
302
303        KeyCommonAttributes(XmlResourceParser xrp) {
304            mXrp = xrp;
305            balloon = true;
306        }
307
308        // Make sure the default object is not null.
309        boolean getAttributes(KeyCommonAttributes defAttr) {
310            keyType = getInteger(mXrp, XMLATTR_KEY_TYPE, defAttr.keyType);
311            keyWidth = getFloat(mXrp, XMLATTR_KEY_WIDTH, defAttr.keyWidth);
312            keyHeight = getFloat(mXrp, XMLATTR_KEY_HEIGHT, defAttr.keyHeight);
313            repeat = getBoolean(mXrp, XMLATTR_KEY_REPEAT, defAttr.repeat);
314            balloon = getBoolean(mXrp, XMLATTR_KEY_BALLOON, defAttr.balloon);
315            if (keyType < 0 || keyWidth <= 0 || keyHeight <= 0) {
316                return false;
317            }
318            return true;
319        }
320    }
321
322    public XmlKeyboardLoader(Context context) {
323        mContext = context;
324        mResources = mContext.getResources();
325    }
326
327    public SkbTemplate loadSkbTemplate(int resourceId) {
328        if (null == mContext || 0 == resourceId) {
329            return null;
330        }
331        Resources r = mResources;
332        XmlResourceParser xrp = r.getXml(resourceId);
333
334        KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);
335        KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);
336
337        mSkbTemplate = new SkbTemplate(resourceId);
338        int lastKeyTypeId = KEYTYPE_ID_LAST;
339        int globalColor = 0;
340        int globalColorHl = 0;
341        int globalColorBalloon = 0;
342        try {
343            mXmlEventType = xrp.next();
344            while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {
345                mNextEventFetched = false;
346                if (mXmlEventType == XmlResourceParser.START_TAG) {
347                    String attribute = xrp.getName();
348                    if (XMLTAG_SKB_TEMPLATE.compareTo(attribute) == 0) {
349                        Drawable skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);
350                        Drawable balloonBg = getDrawable(xrp,
351                                XMLATTR_BALLOON_BG, null);
352                        Drawable popupBg = getDrawable(xrp, XMLATTR_POPUP_BG,
353                                null);
354                        if (null == skbBg || null == balloonBg
355                                || null == popupBg) {
356                            return null;
357                        }
358                        mSkbTemplate.setBackgrounds(skbBg, balloonBg, popupBg);
359
360                        float xMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN, 0);
361                        float yMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN, 0);
362                        mSkbTemplate.setMargins(xMargin, yMargin);
363
364                        // Get default global colors.
365                        globalColor = getColor(xrp, XMLATTR_COLOR, 0);
366                        globalColorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,
367                                0xffffffff);
368                        globalColorBalloon = getColor(xrp,
369                                XMLATTR_COLOR_BALLOON, 0xffffffff);
370                    } else if (XMLTAG_KEYTYPE.compareTo(attribute) == 0) {
371                        int id = getInteger(xrp, XMLATTR_ID, KEYTYPE_ID_LAST);
372                        Drawable bg = getDrawable(xrp, XMLATTR_KEYTYPE_BG, null);
373                        Drawable hlBg = getDrawable(xrp, XMLATTR_KEYTYPE_HLBG,
374                                null);
375                        int color = getColor(xrp, XMLATTR_COLOR, globalColor);
376                        int colorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,
377                                globalColorHl);
378                        int colorBalloon = getColor(xrp, XMLATTR_COLOR_BALLOON,
379                                globalColorBalloon);
380                        if (id != lastKeyTypeId + 1) {
381                            return null;
382                        }
383                        SoftKeyType keyType = mSkbTemplate.createKeyType(id,
384                                bg, hlBg);
385                        keyType.setColors(color, colorHl, colorBalloon);
386                        if (!mSkbTemplate.addKeyType(keyType)) {
387                            return null;
388                        }
389                        lastKeyTypeId = id;
390                    } else if (XMLTAG_KEYICON.compareTo(attribute) == 0) {
391                        int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
392                        Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
393                        Drawable iconPopup = getDrawable(xrp,
394                                XMLATTR_KEY_ICON_POPUP, null);
395                        if (null != icon && null != iconPopup) {
396                            mSkbTemplate.addDefaultKeyIcons(keyCode, icon,
397                                    iconPopup);
398                        }
399                    } else if (XMLTAG_KEY.compareTo(attribute) == 0) {
400                        int keyId = this.getInteger(xrp, XMLATTR_ID, -1);
401                        if (-1 == keyId) return null;
402
403                        if (!attrKey.getAttributes(attrDef)) {
404                            return null;
405                        }
406
407                        // Update the key position for the key.
408                        mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);
409                        mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, 0);
410
411                        SoftKey softKey = getSoftKey(xrp, attrKey);
412                        if (null == softKey) return null;
413                        mSkbTemplate.addDefaultKey(keyId, softKey);
414                    }
415                }
416                // Get the next tag.
417                if (!mNextEventFetched) mXmlEventType = xrp.next();
418            }
419            xrp.close();
420            return mSkbTemplate;
421        } catch (XmlPullParserException e) {
422            // Log.e(TAG, "Ill-formatted keyboard template resource file");
423        } catch (IOException e) {
424            // Log.e(TAG, "Unable to keyboard template resource file");
425        }
426        return null;
427    }
428
429    public SoftKeyboard loadKeyboard(int resourceId, int skbWidth, int skbHeight) {
430        if (null == mContext) return null;
431        Resources r = mResources;
432        SkbPool skbPool = SkbPool.getInstance();
433        XmlResourceParser xrp = mContext.getResources().getXml(resourceId);
434        mSkbTemplate = null;
435        SoftKeyboard softKeyboard = null;
436        Drawable skbBg;
437        Drawable popupBg;
438        Drawable balloonBg;
439        SoftKey softKey = null;
440
441        KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);
442        KeyCommonAttributes attrSkb = new KeyCommonAttributes(xrp);
443        KeyCommonAttributes attrRow = new KeyCommonAttributes(xrp);
444        KeyCommonAttributes attrKeys = new KeyCommonAttributes(xrp);
445        KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);
446
447        mKeyXPos = 0;
448        mKeyYPos = 0;
449        mSkbWidth = skbWidth;
450        mSkbHeight = skbHeight;
451
452        try {
453            mKeyXMargin = 0;
454            mKeyYMargin = 0;
455            mXmlEventType = xrp.next();
456            while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {
457                mNextEventFetched = false;
458                if (mXmlEventType == XmlResourceParser.START_TAG) {
459                    String attr = xrp.getName();
460                    // 1. Is it the root element, "keyboard"?
461                    if (XMLTAG_KEYBOARD.compareTo(attr) == 0) {
462                        // 1.1 Get the keyboard template id.
463                        int skbTemplateId = xrp.getAttributeResourceValue(null,
464                                XMLATTR_SKB_TEMPLATE, 0);
465
466                        // 1.2 Try to get the template from pool. If it is not
467                        // in, the pool will try to load it.
468                        mSkbTemplate = skbPool.getSkbTemplate(skbTemplateId,
469                                mContext);
470
471                        if (null == mSkbTemplate
472                                || !attrSkb.getAttributes(attrDef)) {
473                            return null;
474                        }
475
476                        boolean cacheFlag = getBoolean(xrp,
477                                XMLATTR_SKB_CACHE_FLAG, DEFAULT_SKB_CACHE_FLAG);
478                        boolean stickyFlag = getBoolean(xrp,
479                                XMLATTR_SKB_STICKY_FLAG,
480                                DEFAULT_SKB_STICKY_FLAG);
481                        boolean isQwerty = getBoolean(xrp, XMLATTR_QWERTY,
482                                false);
483                        boolean isQwertyUpperCase = getBoolean(xrp,
484                                XMLATTR_QWERTY_UPPERCASE, false);
485
486                        softKeyboard = new SoftKeyboard(resourceId,
487                                mSkbTemplate, mSkbWidth, mSkbHeight);
488                        softKeyboard.setFlags(cacheFlag, stickyFlag, isQwerty,
489                                isQwertyUpperCase);
490
491                        mKeyXMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN,
492                                mSkbTemplate.getXMargin());
493                        mKeyYMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN,
494                                mSkbTemplate.getYMargin());
495                        skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);
496                        popupBg = getDrawable(xrp, XMLATTR_POPUP_BG, null);
497                        balloonBg = getDrawable(xrp, XMLATTR_BALLOON_BG, null);
498                        if (null != skbBg) {
499                            softKeyboard.setSkbBackground(skbBg);
500                        }
501                        if (null != popupBg) {
502                            softKeyboard.setPopupBackground(popupBg);
503                        }
504                        if (null != balloonBg) {
505                            softKeyboard.setKeyBalloonBackground(balloonBg);
506                        }
507                        softKeyboard.setKeyMargins(mKeyXMargin, mKeyYMargin);
508                    } else if (XMLTAG_ROW.compareTo(attr) == 0) {
509                        if (!attrRow.getAttributes(attrSkb)) {
510                            return null;
511                        }
512                        // Get the starting positions for the row.
513                        mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);
514                        mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, mKeyYPos);
515                        int rowId = getInteger(xrp, XMLATTR_ROW_ID,
516                                KeyRow.ALWAYS_SHOW_ROW_ID);
517                        softKeyboard.beginNewRow(rowId, mKeyYPos);
518                    } else if (XMLTAG_KEYS.compareTo(attr) == 0) {
519                        if (null == softKeyboard) return null;
520                        if (!attrKeys.getAttributes(attrRow)) {
521                            return null;
522                        }
523
524                        String splitter = xrp.getAttributeValue(null,
525                                XMLATTR_KEY_SPLITTER);
526                        splitter = Pattern.quote(splitter);
527                        String labels = xrp.getAttributeValue(null,
528                                XMLATTR_KEY_LABELS);
529                        String codes = xrp.getAttributeValue(null,
530                                XMLATTR_KEY_CODES);
531                        if (null == splitter || null == labels) {
532                            return null;
533                        }
534                        String labelArr[] = labels.split(splitter);
535                        String codeArr[] = null;
536                        if (null != codes) {
537                            codeArr = codes.split(splitter);
538                            if (labelArr.length != codeArr.length) {
539                                return null;
540                            }
541                        }
542
543                        for (int i = 0; i < labelArr.length; i++) {
544                            softKey = new SoftKey();
545                            int keyCode = 0;
546                            if (null != codeArr) {
547                                keyCode = Integer.valueOf(codeArr[i]);
548                            }
549                            softKey.setKeyAttribute(keyCode, labelArr[i],
550                                    attrKeys.repeat, attrKeys.balloon);
551
552                            softKey.setKeyType(mSkbTemplate
553                                    .getKeyType(attrKeys.keyType), null, null);
554
555                            float left, right, top, bottom;
556                            left = mKeyXPos;
557
558                            right = left + attrKeys.keyWidth;
559                            top = mKeyYPos;
560                            bottom = top + attrKeys.keyHeight;
561
562                            if (right - left < 2 * mKeyXMargin) return null;
563                            if (bottom - top < 2 * mKeyYMargin) return null;
564
565                            softKey.setKeyDimensions(left, top, right, bottom);
566                            softKeyboard.addSoftKey(softKey);
567                            mKeyXPos = right;
568                            if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
569                                return null;
570                            }
571                        }
572                    } else if (XMLTAG_KEY.compareTo(attr) == 0) {
573                        if (null == softKeyboard) {
574                            return null;
575                        }
576                        if (!attrKey.getAttributes(attrRow)) {
577                            return null;
578                        }
579
580                        int keyId = this.getInteger(xrp, XMLATTR_ID, -1);
581                        if (keyId >= 0) {
582                            softKey = mSkbTemplate.getDefaultKey(keyId);
583                        } else {
584                            softKey = getSoftKey(xrp, attrKey);
585                        }
586                        if (null == softKey) return null;
587
588                        // Update the position for next key.
589                        mKeyXPos = softKey.mRightF;
590                        if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
591                            return null;
592                        }
593                        // If the current xml event type becomes a starting tag,
594                        // it indicates that we have parsed too much to get
595                        // toggling states, and we started a new row. In this
596                        // case, the row starting position information should
597                        // be updated.
598                        if (mXmlEventType == XmlResourceParser.START_TAG) {
599                            attr = xrp.getName();
600                            if (XMLTAG_ROW.compareTo(attr) == 0) {
601                                mKeyYPos += attrRow.keyHeight;
602                                if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {
603                                    return null;
604                                }
605                            }
606                        }
607                        softKeyboard.addSoftKey(softKey);
608                    }
609                } else if (mXmlEventType == XmlResourceParser.END_TAG) {
610                    String attr = xrp.getName();
611                    if (XMLTAG_ROW.compareTo(attr) == 0) {
612                        mKeyYPos += attrRow.keyHeight;
613                        if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {
614                            return null;
615                        }
616                    }
617                }
618
619                // Get the next tag.
620                if (!mNextEventFetched) mXmlEventType = xrp.next();
621            }
622            xrp.close();
623            softKeyboard.setSkbCoreSize(mSkbWidth, mSkbHeight);
624            return softKeyboard;
625        } catch (XmlPullParserException e) {
626            // Log.e(TAG, "Ill-formatted keybaord resource file");
627        } catch (IOException e) {
628            // Log.e(TAG, "Unable to read keyboard resource file");
629        }
630        return null;
631    }
632
633    // Caller makes sure xrp and r are valid.
634    private SoftKey getSoftKey(XmlResourceParser xrp,
635            KeyCommonAttributes attrKey) throws XmlPullParserException,
636            IOException {
637        int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
638        String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);
639        Drawable keyIcon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
640        Drawable keyIconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);
641        int popupSkbId = xrp.getAttributeResourceValue(null,
642                XMLATTR_KEY_POPUP_SKBID, 0);
643
644        if (null == keyLabel && null == keyIcon) {
645            keyIcon = mSkbTemplate.getDefaultKeyIcon(keyCode);
646            keyIconPopup = mSkbTemplate.getDefaultKeyIconPopup(keyCode);
647            if (null == keyIcon || null == keyIconPopup) return null;
648        }
649
650        // Dimension information must been initialized before
651        // getting toggle state, because mKeyYPos may be changed
652        // to next row when trying to get toggle state.
653        float left, right, top, bottom;
654        left = mKeyXPos;
655        right = left + attrKey.keyWidth;
656        top = mKeyYPos;
657        bottom = top + attrKey.keyHeight;
658
659        if (right - left < 2 * mKeyXMargin) return null;
660        if (bottom - top < 2 * mKeyYMargin) return null;
661
662        // Try to find if the next tag is
663        // {@link #XMLTAG_TOGGLE_STATE_OF_KEY}, if yes, try to
664        // create a toggle key.
665        boolean toggleKey = false;
666        mXmlEventType = xrp.next();
667        mNextEventFetched = true;
668
669        SoftKey softKey;
670        if (mXmlEventType == XmlResourceParser.START_TAG) {
671            mAttrTmp = xrp.getName();
672            if (mAttrTmp.compareTo(XMLTAG_TOGGLE_STATE) == 0) {
673                toggleKey = true;
674            }
675        }
676        if (toggleKey) {
677            softKey = new SoftKeyToggle();
678            if (!((SoftKeyToggle) softKey).setToggleStates(getToggleStates(
679                    attrKey, (SoftKeyToggle) softKey, keyCode))) {
680                return null;
681            }
682        } else {
683            softKey = new SoftKey();
684        }
685
686        // Set the normal state
687        softKey.setKeyAttribute(keyCode, keyLabel, attrKey.repeat,
688                attrKey.balloon);
689        softKey.setPopupSkbId(popupSkbId);
690        softKey.setKeyType(mSkbTemplate.getKeyType(attrKey.keyType), keyIcon,
691                keyIconPopup);
692
693        softKey.setKeyDimensions(left, top, right, bottom);
694        return softKey;
695    }
696
697    private SoftKeyToggle.ToggleState getToggleStates(
698            KeyCommonAttributes attrKey, SoftKeyToggle softKey, int defKeyCode)
699            throws XmlPullParserException, IOException {
700        XmlResourceParser xrp = attrKey.mXrp;
701        int stateId = getInteger(xrp, XMLATTR_TOGGLE_STATE_ID, 0);
702        if (0 == stateId) return null;
703
704        String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);
705        int keyTypeId = getInteger(xrp, XMLATTR_KEY_TYPE, KEYTYPE_ID_LAST);
706        int keyCode;
707        if (null == keyLabel) {
708            keyCode = getInteger(xrp, XMLATTR_KEY_CODE, defKeyCode);
709        } else {
710            keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
711        }
712        Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
713        Drawable iconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);
714        if (null == icon && null == keyLabel) {
715            return null;
716        }
717        SoftKeyToggle.ToggleState rootState = softKey.createToggleState();
718        rootState.setStateId(stateId);
719        rootState.mKeyType = null;
720        if (KEYTYPE_ID_LAST != keyTypeId) {
721            rootState.mKeyType = mSkbTemplate.getKeyType(keyTypeId);
722        }
723        rootState.mKeyCode = keyCode;
724        rootState.mKeyIcon = icon;
725        rootState.mKeyIconPopup = iconPopup;
726        rootState.mKeyLabel = keyLabel;
727
728        boolean repeat = getBoolean(xrp, XMLATTR_KEY_REPEAT, attrKey.repeat);
729        boolean balloon = getBoolean(xrp, XMLATTR_KEY_BALLOON, attrKey.balloon);
730        rootState.setStateFlags(repeat, balloon);
731
732        rootState.mNextState = null;
733
734        // If there is another toggle state.
735        mXmlEventType = xrp.next();
736        while (mXmlEventType != XmlResourceParser.START_TAG
737                && mXmlEventType != XmlResourceParser.END_DOCUMENT) {
738            mXmlEventType = xrp.next();
739        }
740        if (mXmlEventType == XmlResourceParser.START_TAG) {
741            String attr = xrp.getName();
742            if (attr.compareTo(XMLTAG_TOGGLE_STATE) == 0) {
743                SoftKeyToggle.ToggleState nextState = getToggleStates(attrKey,
744                        softKey, defKeyCode);
745                if (null == nextState) return null;
746                rootState.mNextState = nextState;
747            }
748        }
749
750        return rootState;
751    }
752
753    private int getInteger(XmlResourceParser xrp, String name, int defValue) {
754        int resId = xrp.getAttributeResourceValue(null, name, 0);
755        String s;
756        if (resId == 0) {
757            s = xrp.getAttributeValue(null, name);
758            if (null == s) return defValue;
759            try {
760                int ret = Integer.valueOf(s);
761                return ret;
762            } catch (NumberFormatException e) {
763                return defValue;
764            }
765        } else {
766            return Integer.parseInt(mContext.getResources().getString(resId));
767        }
768    }
769
770    private int getColor(XmlResourceParser xrp, String name, int defValue) {
771        int resId = xrp.getAttributeResourceValue(null, name, 0);
772        String s;
773        if (resId == 0) {
774            s = xrp.getAttributeValue(null, name);
775            if (null == s) return defValue;
776            try {
777                int ret = Integer.valueOf(s);
778                return ret;
779            } catch (NumberFormatException e) {
780                return defValue;
781            }
782        } else {
783            return mContext.getResources().getColor(resId);
784        }
785    }
786
787    private String getString(XmlResourceParser xrp, String name, String defValue) {
788        int resId = xrp.getAttributeResourceValue(null, name, 0);
789        if (resId == 0) {
790            return xrp.getAttributeValue(null, name);
791        } else {
792            return mContext.getResources().getString(resId);
793        }
794    }
795
796    private float getFloat(XmlResourceParser xrp, String name, float defValue) {
797        int resId = xrp.getAttributeResourceValue(null, name, 0);
798        if (resId == 0) {
799            String s = xrp.getAttributeValue(null, name);
800            if (null == s) return defValue;
801            try {
802                float ret;
803                if (s.endsWith("%p")) {
804                    ret = Float.parseFloat(s.substring(0, s.length() - 2)) / 100;
805                } else {
806                    ret = Float.parseFloat(s);
807                }
808                return ret;
809            } catch (NumberFormatException e) {
810                return defValue;
811            }
812        } else {
813            return mContext.getResources().getDimension(resId);
814        }
815    }
816
817    private boolean getBoolean(XmlResourceParser xrp, String name,
818            boolean defValue) {
819        String s = xrp.getAttributeValue(null, name);
820        if (null == s) return defValue;
821        try {
822            boolean ret = Boolean.parseBoolean(s);
823            return ret;
824        } catch (NumberFormatException e) {
825            return defValue;
826        }
827    }
828
829    private Drawable getDrawable(XmlResourceParser xrp, String name,
830            Drawable defValue) {
831        int resId = xrp.getAttributeResourceValue(null, name, 0);
832        if (0 == resId) return defValue;
833        return mResources.getDrawable(resId);
834    }
835}
836