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 
17 package android.emoji;
18 
19 import android.graphics.Bitmap;
20 
21 import java.lang.ref.WeakReference;
22 import java.util.LinkedHashMap;
23 import 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  */
31 public 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 a cache object
37     // blow up, we use LinkedHashMap with size limit.
38     private class CustomLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
CustomLinkedHashMap()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
removeEldestEntry(Map.Entry<K, V> eldest)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 long 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      */
EmojiFactory(long nativeEmojiFactory, String name)71     private EmojiFactory(long nativeEmojiFactory, String name) {
72         mNativeEmojiFactory = nativeEmojiFactory;
73         mName = name;
74         mCache = new CustomLinkedHashMap<Integer, WeakReference<Bitmap>>();
75     }
76 
77     @Override
finalize()78     protected void finalize() throws Throwable {
79         try {
80             nativeDestructor(mNativeEmojiFactory);
81         } finally {
82             super.finalize();
83         }
84     }
85 
name()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      */
getBitmapFromAndroidPua(int pua)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      */
getBitmapFromVendorSpecificSjis(char sjis)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      */
getBitmapFromVendorSpecificPua(int vsp)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      */
getAndroidPuaFromVendorSpecificSjis(char sjis)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      */
getVendorSpecificSjisFromAndroidPua(int pua)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      */
getAndroidPuaFromVendorSpecificPua(int vsp)177     public int getAndroidPuaFromVendorSpecificPua(int vsp) {
178         return nativeGetAndroidPuaFromVendorSpecificPua(mNativeEmojiFactory, vsp);
179     }
180 
getAndroidPuaFromVendorSpecificPua(String vspString)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      */
getVendorSpecificPuaFromAndroidPua(int pua)211     public int getVendorSpecificPuaFromAndroidPua(int pua) {
212         return nativeGetVendorSpecificPuaFromAndroidPua(mNativeEmojiFactory, pua);
213     }
214 
getVendorSpecificPuaFromAndroidPua(String puaString)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      */
newInstance(String class_name)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      */
newAvailableInstance()255     public static native EmojiFactory newAvailableInstance();
256 
257     /**
258      * Returns the lowest code point corresponding to an Android
259      * emoji character.
260      */
getMinimumAndroidPua()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      */
getMaximumAndroidPua()269     public int getMaximumAndroidPua() {
270         return nativeGetMaximumAndroidPua(mNativeEmojiFactory);
271     }
272 
273     // native methods
274 
nativeDestructor(long nativeEmojiFactory)275     private native void nativeDestructor(long nativeEmojiFactory);
nativeGetBitmapFromAndroidPua(long nativeEmojiFactory, int AndroidPua)276     private native Bitmap nativeGetBitmapFromAndroidPua(long nativeEmojiFactory, int AndroidPua);
nativeGetAndroidPuaFromVendorSpecificSjis(long nativeEmojiFactory, char sjis)277     private native int nativeGetAndroidPuaFromVendorSpecificSjis(long nativeEmojiFactory,
278             char sjis);
nativeGetVendorSpecificSjisFromAndroidPua(long nativeEmojiFactory, int pua)279     private native int nativeGetVendorSpecificSjisFromAndroidPua(long nativeEmojiFactory,
280             int pua);
nativeGetAndroidPuaFromVendorSpecificPua(long nativeEmojiFactory, int vsp)281     private native int nativeGetAndroidPuaFromVendorSpecificPua(long nativeEmojiFactory,
282             int vsp);
nativeGetVendorSpecificPuaFromAndroidPua(long nativeEmojiFactory, int pua)283     private native int nativeGetVendorSpecificPuaFromAndroidPua(long nativeEmojiFactory,
284             int pua);
nativeGetMaximumVendorSpecificPua(long nativeEmojiFactory)285     private native int nativeGetMaximumVendorSpecificPua(long nativeEmojiFactory);
nativeGetMinimumVendorSpecificPua(long nativeEmojiFactory)286     private native int nativeGetMinimumVendorSpecificPua(long nativeEmojiFactory);
nativeGetMaximumAndroidPua(long nativeEmojiFactory)287     private native int nativeGetMaximumAndroidPua(long nativeEmojiFactory);
nativeGetMinimumAndroidPua(long nativeEmojiFactory)288     private native int nativeGetMinimumAndroidPua(long nativeEmojiFactory);
289 }
290