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.mms.util;
18
19import android.content.Context;
20import android.text.Spannable;
21import android.text.SpannableStringBuilder;
22import android.text.style.ImageSpan;
23
24import com.android.mms.R;
25
26import java.util.HashMap;
27import java.util.regex.Matcher;
28import java.util.regex.Pattern;
29
30/**
31 * A class for annotating a CharSequence with spans to convert textual emoticons
32 * to graphical ones.
33 */
34public class SmileyParser {
35    // Singleton stuff
36    private static SmileyParser sInstance;
37    public static SmileyParser getInstance() { return sInstance; }
38    public static void init(Context context) {
39        sInstance = new SmileyParser(context);
40    }
41
42    private final Context mContext;
43    private final String[] mSmileyTexts;
44    private final Pattern mPattern;
45    private final HashMap<String, Integer> mSmileyToRes;
46
47    private SmileyParser(Context context) {
48        mContext = context;
49        mSmileyTexts = mContext.getResources().getStringArray(DEFAULT_SMILEY_TEXTS);
50        mSmileyToRes = buildSmileyToRes();
51        mPattern = buildPattern();
52    }
53
54    static class Smileys {
55        private static final int[] sIconIds = {
56            R.drawable.emo_im_happy,
57            R.drawable.emo_im_sad,
58            R.drawable.emo_im_winking,
59            R.drawable.emo_im_tongue_sticking_out,
60            R.drawable.emo_im_surprised,
61            R.drawable.emo_im_kissing,
62            R.drawable.emo_im_yelling,
63            R.drawable.emo_im_cool,
64            R.drawable.emo_im_money_mouth,
65            R.drawable.emo_im_foot_in_mouth,
66            R.drawable.emo_im_embarrassed,
67            R.drawable.emo_im_angel,
68            R.drawable.emo_im_undecided,
69            R.drawable.emo_im_crying,
70            R.drawable.emo_im_lips_are_sealed,
71            R.drawable.emo_im_laughing,
72            R.drawable.emo_im_wtf,
73            R.drawable.emo_im_heart,
74            R.drawable.emo_im_mad,
75            R.drawable.emo_im_smirk,
76            R.drawable.emo_im_pokerface
77        };
78
79        public static int HAPPY = 0;
80        public static int SAD = 1;
81        public static int WINKING = 2;
82        public static int TONGUE_STICKING_OUT = 3;
83        public static int SURPRISED = 4;
84        public static int KISSING = 5;
85        public static int YELLING = 6;
86        public static int COOL = 7;
87        public static int MONEY_MOUTH = 8;
88        public static int FOOT_IN_MOUTH = 9;
89        public static int EMBARRASSED = 10;
90        public static int ANGEL = 11;
91        public static int UNDECIDED = 12;
92        public static int CRYING = 13;
93        public static int LIPS_ARE_SEALED = 14;
94        public static int LAUGHING = 15;
95        public static int WTF = 16;
96        public static int MAD = 17;
97        public static int HEART = 18;
98        public static int SMIRK = 19;
99        public static int POKERFACE = 20;
100
101        public static int getSmileyResource(int which) {
102            return sIconIds[which];
103        }
104    }
105
106    // NOTE: if you change anything about this array, you must make the corresponding change
107    // to the string arrays: default_smiley_texts and default_smiley_names in res/values/arrays.xml
108    public static final int[] DEFAULT_SMILEY_RES_IDS = {
109        Smileys.getSmileyResource(Smileys.HAPPY),                //  0
110        Smileys.getSmileyResource(Smileys.SAD),                  //  1
111        Smileys.getSmileyResource(Smileys.WINKING),              //  2
112        Smileys.getSmileyResource(Smileys.TONGUE_STICKING_OUT),  //  3
113        Smileys.getSmileyResource(Smileys.SURPRISED),            //  4
114        Smileys.getSmileyResource(Smileys.KISSING),              //  5
115        Smileys.getSmileyResource(Smileys.YELLING),              //  6
116        Smileys.getSmileyResource(Smileys.COOL),                 //  7
117        Smileys.getSmileyResource(Smileys.MONEY_MOUTH),          //  8
118        Smileys.getSmileyResource(Smileys.FOOT_IN_MOUTH),        //  9
119        Smileys.getSmileyResource(Smileys.EMBARRASSED),          //  10
120        Smileys.getSmileyResource(Smileys.ANGEL),                //  11
121        Smileys.getSmileyResource(Smileys.UNDECIDED),            //  12
122        Smileys.getSmileyResource(Smileys.CRYING),               //  13
123        Smileys.getSmileyResource(Smileys.LIPS_ARE_SEALED),      //  14
124        Smileys.getSmileyResource(Smileys.LAUGHING),             //  15
125        Smileys.getSmileyResource(Smileys.WTF),                  //  16
126        Smileys.getSmileyResource(Smileys.MAD),                  //  17
127        Smileys.getSmileyResource(Smileys.HEART),                //  18
128        Smileys.getSmileyResource(Smileys.SMIRK),                //  19
129        Smileys.getSmileyResource(Smileys.POKERFACE),            //  20
130    };
131
132    public static final int DEFAULT_SMILEY_TEXTS = R.array.default_smiley_texts;
133    public static final int DEFAULT_SMILEY_NAMES = R.array.default_smiley_names;
134
135    /**
136     * Builds the hashtable we use for mapping the string version
137     * of a smiley (e.g. ":-)") to a resource ID for the icon version.
138     */
139    private HashMap<String, Integer> buildSmileyToRes() {
140        if (DEFAULT_SMILEY_RES_IDS.length != mSmileyTexts.length) {
141            // Throw an exception if someone updated DEFAULT_SMILEY_RES_IDS
142            // and failed to update arrays.xml
143            throw new IllegalStateException("Smiley resource ID/text mismatch");
144        }
145
146        HashMap<String, Integer> smileyToRes =
147                            new HashMap<String, Integer>(mSmileyTexts.length);
148        for (int i = 0; i < mSmileyTexts.length; i++) {
149            smileyToRes.put(mSmileyTexts[i], DEFAULT_SMILEY_RES_IDS[i]);
150        }
151
152        return smileyToRes;
153    }
154
155    /**
156     * Builds the regular expression we use to find smileys in {@link #addSmileySpans}.
157     */
158    private Pattern buildPattern() {
159        // Set the StringBuilder capacity with the assumption that the average
160        // smiley is 3 characters long.
161        StringBuilder patternString = new StringBuilder(mSmileyTexts.length * 3);
162
163        // Build a regex that looks like (:-)|:-(|...), but escaping the smilies
164        // properly so they will be interpreted literally by the regex matcher.
165        patternString.append('(');
166        for (String s : mSmileyTexts) {
167            patternString.append(Pattern.quote(s));
168            patternString.append('|');
169        }
170        // Replace the extra '|' with a ')'
171        patternString.replace(patternString.length() - 1, patternString.length(), ")");
172
173        return Pattern.compile(patternString.toString());
174    }
175
176
177    /**
178     * Adds ImageSpans to a CharSequence that replace textual emoticons such
179     * as :-) with a graphical version.
180     *
181     * @param text A CharSequence possibly containing emoticons
182     * @return A CharSequence annotated with ImageSpans covering any
183     *         recognized emoticons.
184     */
185    public CharSequence addSmileySpans(CharSequence text) {
186        SpannableStringBuilder builder = new SpannableStringBuilder(text);
187
188        Matcher matcher = mPattern.matcher(text);
189        while (matcher.find()) {
190            int resId = mSmileyToRes.get(matcher.group());
191            builder.setSpan(new ImageSpan(mContext, resId),
192                            matcher.start(), matcher.end(),
193                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
194        }
195
196        return builder;
197    }
198}
199
200
201