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 android.emoji;
18
19import android.graphics.Bitmap;
20
21import java.lang.ref.WeakReference;
22import java.util.LinkedHashMap;
23import java.util.Map;
24
25/**
26 * A class for the factories which produce Emoji (pictgram) images.
27 * This is intended to be used by IME, Email app, etc.
28 * There's no plan to make this public for now.
29 * @hide
30 */
31public final class EmojiFactory {
32    // private static final String LOG_TAG = "EmojiFactory";
33
34    private int sCacheSize = 100;
35
36    // HashMap for caching Bitmap object. In order not to make an cache object
37    // blow up, we use LinkedHashMap with size limit.
38    private class CustomLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
39        public CustomLinkedHashMap() {
40            // These magic numbers are gotten from the source code of
41            // LinkedHashMap.java and HashMap.java.
42            super(16, 0.75f, true);
43        }
44
45        /*
46         * If size() becomes more than sCacheSize, least recently used cache
47         * is erased.
48         * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
49         */
50        @Override
51        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
52            return size() > sCacheSize;
53        }
54    }
55
56    // A pointer to native EmojiFactory object.
57    private int mNativeEmojiFactory;
58    private String mName;
59    // Cache.
60    private Map<Integer, WeakReference<Bitmap>> mCache;
61
62    /**
63     * @noinspection UnusedDeclaration
64     */
65    /*
66     * Private constructor that must received an already allocated native
67     * EmojiFactory int (pointer).
68     *
69     * This can be called from JNI code.
70     */
71    private EmojiFactory(int nativeEmojiFactory, String name) {
72        mNativeEmojiFactory = nativeEmojiFactory;
73        mName = name;
74        mCache = new CustomLinkedHashMap<Integer, WeakReference<Bitmap>>();
75    }
76
77    @Override
78    protected void finalize() throws Throwable {
79        try {
80            nativeDestructor(mNativeEmojiFactory);
81        } finally {
82            super.finalize();
83        }
84    }
85
86    public String name() {
87        return mName;
88    }
89
90    /**
91     * Returns Bitmap object corresponding to the AndroidPua.
92     *
93     * Note that each Bitmap is cached by this class, which means that, if you modify a
94     * Bitmap object (using setPos() method), all same emoji Bitmap will be modified.
95     * If it is unacceptable, please copy the object before modifying it.
96     *
97     * @param pua A unicode codepoint.
98     * @return Bitmap object when this factory knows the Bitmap relevant to the codepoint.
99     * Otherwise null is returned.
100     */
101    public synchronized Bitmap getBitmapFromAndroidPua(int pua) {
102        WeakReference<Bitmap> cache = mCache.get(pua);
103        if (cache == null) {
104            Bitmap ret = nativeGetBitmapFromAndroidPua(mNativeEmojiFactory, pua);
105            // There is no need to cache returned null, since in most cases it means there
106            // is no map from the AndroidPua to a specific image. In other words, it usually does
107            // not include the cost of creating Bitmap object.
108            if (ret != null) {
109               mCache.put(pua, new WeakReference<Bitmap>(ret));
110            }
111            return ret;
112        } else {
113            Bitmap tmp = cache.get();
114            if (tmp == null) {
115                Bitmap ret = nativeGetBitmapFromAndroidPua(mNativeEmojiFactory, pua);
116                mCache.put(pua, new WeakReference<Bitmap>(ret));
117                return ret;
118            } else {
119                return tmp;
120            }
121        }
122    }
123
124    /**
125     * Returns Bitmap object corresponding to the vendor specified sjis.
126     *
127     * See comments in getBitmapFromAndroidPua().
128     *
129     * @param sjis sjis code specific to each career(vendor)
130     * @return Bitmap object when this factory knows the Bitmap relevant to the code. Otherwise
131     * null is returned.
132     */
133    public synchronized Bitmap getBitmapFromVendorSpecificSjis(char sjis) {
134        return getBitmapFromAndroidPua(getAndroidPuaFromVendorSpecificSjis(sjis));
135    }
136
137    /**
138     * Returns Bitmap object corresponding to the vendor specific Unicode.
139     *
140     * See comments in getBitmapFromAndroidPua().
141     *
142     * @param vsp vendor specific PUA.
143     * @return Bitmap object when this factory knows the Bitmap relevant to the code. Otherwise
144     * null is returned.
145     */
146    public synchronized Bitmap getBitmapFromVendorSpecificPua(int vsp) {
147        return getBitmapFromAndroidPua(getAndroidPuaFromVendorSpecificPua(vsp));
148    }
149
150    /**
151     * Returns Unicode PUA for Android corresponding to the vendor specific sjis.
152     *
153     * @param sjis vendor specific sjis
154     * @return Unicode PUA for Android, or -1 if there's no map for the sjis.
155     */
156    public int getAndroidPuaFromVendorSpecificSjis(char sjis) {
157        return nativeGetAndroidPuaFromVendorSpecificSjis(mNativeEmojiFactory, sjis);
158    }
159
160    /**
161     * Returns vendor specific sjis corresponding to the Unicode AndroidPua.
162     *
163     * @param pua Unicode PUA for Android,
164     * @return vendor specific sjis, or -1 if there's no map for the AndroidPua.
165     */
166    public int getVendorSpecificSjisFromAndroidPua(int pua) {
167        return nativeGetVendorSpecificSjisFromAndroidPua(mNativeEmojiFactory, pua);
168    }
169
170    /**
171     * Returns Unicode PUA for Android corresponding to the vendor specific Unicode.
172     *
173     * @param vsp vendor specific PUA.
174     * @return Unicode PUA for Android, or -1 if there's no map for the
175     * Unicode.
176     */
177    public int getAndroidPuaFromVendorSpecificPua(int vsp) {
178        return nativeGetAndroidPuaFromVendorSpecificPua(mNativeEmojiFactory, vsp);
179    }
180
181    public String getAndroidPuaFromVendorSpecificPua(String vspString) {
182        if (vspString == null) {
183            return null;
184        }
185        int minVsp = nativeGetMinimumVendorSpecificPua(mNativeEmojiFactory);
186        int maxVsp = nativeGetMaximumVendorSpecificPua(mNativeEmojiFactory);
187        int len = vspString.length();
188        int[] codePoints = new int[vspString.codePointCount(0, len)];
189
190        int new_len = 0;
191        for (int i = 0; i < len; i = vspString.offsetByCodePoints(i, 1), new_len++) {
192            int codePoint = vspString.codePointAt(i);
193            if (minVsp <= codePoint && codePoint <= maxVsp) {
194                int newCodePoint = getAndroidPuaFromVendorSpecificPua(codePoint);
195                if (newCodePoint > 0) {
196                    codePoints[new_len] = newCodePoint;
197                    continue;
198                }
199            }
200            codePoints[new_len] = codePoint;
201        }
202        return new String(codePoints, 0, new_len);
203    }
204
205    /**
206     * Returns vendor specific Unicode corresponding to the Unicode AndroidPua.
207     *
208     * @param pua Unicode PUA for Android,
209     * @return vendor specific sjis, or -1 if there's no map for the AndroidPua.
210     */
211    public int getVendorSpecificPuaFromAndroidPua(int pua) {
212        return nativeGetVendorSpecificPuaFromAndroidPua(mNativeEmojiFactory, pua);
213    }
214
215    public String getVendorSpecificPuaFromAndroidPua(String puaString) {
216        if (puaString == null) {
217            return null;
218        }
219        int minVsp = nativeGetMinimumAndroidPua(mNativeEmojiFactory);
220        int maxVsp = nativeGetMaximumAndroidPua(mNativeEmojiFactory);
221        int len = puaString.length();
222        int[] codePoints = new int[puaString.codePointCount(0, len)];
223
224        int new_len = 0;
225        for (int i = 0; i < len; i = puaString.offsetByCodePoints(i, 1), new_len++) {
226            int codePoint = puaString.codePointAt(i);
227            if (minVsp <= codePoint && codePoint <= maxVsp) {
228                int newCodePoint = getVendorSpecificPuaFromAndroidPua(codePoint);
229                if (newCodePoint > 0) {
230                    codePoints[new_len] = newCodePoint;
231                    continue;
232                }
233            }
234            codePoints[new_len] = codePoint;
235        }
236        return new String(codePoints, 0, new_len);
237    }
238
239    /**
240     * Constructs an instance of EmojiFactory corresponding to the name.
241     *
242     * @param class_name Name of the factory. This must include complete package name.
243     * @return A concrete EmojiFactory instance corresponding to factory_name.
244     * If factory_name is invalid, null is returned.
245     */
246    public static native EmojiFactory newInstance(String class_name);
247
248    /**
249     * Constructs an instance of available EmojiFactory.
250     *
251     * @return A concrete EmojiFactory instance. If there are several available
252     * EmojiFactory class, preferred one is chosen by the system. If there isn't, null
253     * is returned.
254     */
255    public static native EmojiFactory newAvailableInstance();
256
257    /**
258     * Returns the lowest code point corresponding to an Android
259     * emoji character.
260     */
261    public int getMinimumAndroidPua() {
262        return nativeGetMinimumAndroidPua(mNativeEmojiFactory);
263    }
264
265    /**
266     * Returns the highest code point corresponding to an Android
267     * emoji character.
268     */
269    public int getMaximumAndroidPua() {
270        return nativeGetMaximumAndroidPua(mNativeEmojiFactory);
271    }
272
273    // native methods
274
275    private native void nativeDestructor(int factory);
276    private native Bitmap nativeGetBitmapFromAndroidPua(int nativeEmojiFactory, int AndroidPua);
277    private native int nativeGetAndroidPuaFromVendorSpecificSjis(int nativeEmojiFactory,
278            char sjis);
279    private native int nativeGetVendorSpecificSjisFromAndroidPua(int nativeEmojiFactory,
280            int pua);
281    private native int nativeGetAndroidPuaFromVendorSpecificPua(int nativeEmojiFactory,
282            int vsp);
283    private native int nativeGetVendorSpecificPuaFromAndroidPua(int nativeEmojiFactory,
284            int pua);
285    private native int nativeGetMaximumVendorSpecificPua(int nativeEmojiFactory);
286    private native int nativeGetMinimumVendorSpecificPua(int nativeEmojiFactory);
287    private native int nativeGetMaximumAndroidPua(int nativeEmojiFactory);
288    private native int nativeGetMinimumAndroidPua(int nativeEmojiFactory);
289}
290