1 /* 2 * Copyright (C) 2013 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.text.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertTrue; 22 23 import android.content.Context; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.Paint; 27 import android.graphics.Picture; 28 import android.support.test.InstrumentationRegistry; 29 import android.support.test.annotation.UiThreadTest; 30 import android.support.test.filters.LargeTest; 31 import android.support.test.filters.MediumTest; 32 import android.support.test.rule.ActivityTestRule; 33 import android.support.test.runner.AndroidJUnit4; 34 import android.util.TypedValue; 35 import android.view.KeyEvent; 36 import android.view.View; 37 import android.webkit.cts.WebViewOnUiThread; 38 import android.widget.EditText; 39 import android.widget.TextView; 40 41 import com.android.compatibility.common.util.NullWebViewUtils; 42 43 import org.junit.Before; 44 import org.junit.Rule; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 48 @MediumTest 49 @RunWith(AndroidJUnit4.class) 50 public class EmojiTest { 51 private Context mContext; 52 private EditText mEditText; 53 54 @Rule 55 public ActivityTestRule<EmojiCtsActivity> mActivityRule = 56 new ActivityTestRule<>(EmojiCtsActivity.class); 57 58 @Before 59 public void setup() { 60 mContext = mActivityRule.getActivity(); 61 } 62 63 /** 64 * Tests all Emoji are defined in Character class 65 */ 66 @Test 67 public void testEmojiCodePoints() { 68 for (int i = 0; i < EmojiConstants.ALL_EMOJI.length; i++) { 69 assertTrue(Character.isDefined(EmojiConstants.ALL_EMOJI[i])); 70 } 71 } 72 73 private String describeBitmap(final Bitmap bmp) { 74 StringBuilder sb = new StringBuilder(); 75 sb.append("[ID:0x" + Integer.toHexString(System.identityHashCode(bmp))); 76 sb.append(" " + Integer.toString(bmp.getWidth()) + "x" + Integer.toString(bmp.getHeight())); 77 sb.append(" Config:"); 78 if (bmp.getConfig() == Bitmap.Config.ALPHA_8) { 79 sb.append("ALPHA_8"); 80 } else if (bmp.getConfig() == Bitmap.Config.RGB_565) { 81 sb.append("RGB_565"); 82 } else if (bmp.getConfig() == Bitmap.Config.ARGB_4444) { 83 sb.append("ARGB_4444"); 84 } else if (bmp.getConfig() == Bitmap.Config.ARGB_8888) { 85 sb.append("ARGB_8888"); 86 } else { 87 sb.append("UNKNOWN"); 88 } 89 sb.append("]"); 90 return sb.toString(); 91 } 92 93 /** 94 * Tests Emoji has different glyph for different meaning characters. 95 * Test on Canvas, TextView, EditText and WebView 96 */ 97 @UiThreadTest 98 @Test 99 public void testEmojiGlyph() { 100 CaptureCanvas ccanvas = new CaptureCanvas(mContext); 101 102 Bitmap mBitmapA, mBitmapB; // Emoji displayed Bitmaps to compare 103 104 int comparedCodePoints[][] = { // Emojis should have different characters 105 {0x1F436, 0x1F435}, // Dog(U+1F436) and Monkey(U+1F435) 106 {0x26BD, 0x26BE}, // Soccer ball(U+26BD) and Baseball(U+26BE) 107 {0x1F47B, 0x1F381}, // Ghost(U+1F47B) and wrapped present(U+1F381) 108 {0x2764, 0x1F494}, // Heavy black heart(U+2764) and broken heart(U+1F494) 109 {0x1F603, 0x1F33B} // Smiling face with open mouth(U+1F603) and sunflower(U+1F33B) 110 }; 111 112 for (int i = 0; i < comparedCodePoints.length; i++) { 113 String baseMessage = "Glyph for U+" + Integer.toHexString(comparedCodePoints[i][0]) + 114 " should be different from glyph for U+" + 115 Integer.toHexString(comparedCodePoints[i][1]) + ". "; 116 117 mBitmapA = ccanvas.capture(Character.toChars(comparedCodePoints[i][0])); 118 mBitmapB = ccanvas.capture(Character.toChars(comparedCodePoints[i][1])); 119 120 String bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB); 121 assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB)); 122 123 // cannot reuse CaptureTextView as 2nd setText call throws NullPointerException 124 CaptureTextView cviewA = new CaptureTextView(mContext); 125 mBitmapA = cviewA.capture(Character.toChars(comparedCodePoints[i][0])); 126 CaptureTextView cviewB = new CaptureTextView(mContext); 127 mBitmapB = cviewB.capture(Character.toChars(comparedCodePoints[i][1])); 128 129 bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB); 130 assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB)); 131 132 CaptureEditText cedittextA = new CaptureEditText(mContext); 133 mBitmapA = cedittextA.capture(Character.toChars(comparedCodePoints[i][0])); 134 CaptureEditText cedittextB = new CaptureEditText(mContext); 135 mBitmapB = cedittextB.capture(Character.toChars(comparedCodePoints[i][1])); 136 137 bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB); 138 assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB)); 139 140 // Trigger activity bringup so we can determine if a WebView is available on this 141 // device. 142 if (NullWebViewUtils.isWebViewAvailable()) { 143 CaptureWebView cwebview = new CaptureWebView(); 144 mBitmapA = cwebview.capture(Character.toChars(comparedCodePoints[i][0])); 145 mBitmapB = cwebview.capture(Character.toChars(comparedCodePoints[i][1])); 146 bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB); 147 assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB)); 148 } 149 } 150 } 151 152 /** 153 * Tests EditText handles Emoji 154 */ 155 @LargeTest 156 @Test 157 public void testEmojiEditable() throws Throwable { 158 int testedCodePoints[] = { 159 0xAE, // registered mark 160 0x2764, // heavy black heart 161 0x1F353 // strawberry - surrogate pair sample. Count as two characters. 162 }; 163 164 String origStr, newStr; 165 166 // delete Emoji by sending KEYCODE_DEL 167 for (int i = 0; i < testedCodePoints.length; i++) { 168 origStr = "Test character "; 169 // cannot reuse CaptureTextView as 2nd setText call throws NullPointerException 170 mActivityRule.runOnUiThread(() -> mEditText = new EditText(mContext)); 171 mEditText.setText(origStr + String.valueOf(Character.toChars(testedCodePoints[i]))); 172 173 // confirm the emoji is added. 174 newStr = mEditText.getText().toString(); 175 assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1); 176 177 // Delete added character by sending KEYCODE_DEL event 178 mActivityRule.runOnUiThread(() -> mEditText.dispatchKeyEvent( 179 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))); 180 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 181 182 newStr = mEditText.getText().toString(); 183 assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1); 184 } 185 } 186 187 private static class CaptureCanvas extends View { 188 189 String mTestStr; 190 Paint paint = new Paint(); 191 192 CaptureCanvas(Context context) { 193 super(context); 194 } 195 196 public void onDraw(Canvas canvas) { 197 if (mTestStr != null) { 198 canvas.drawText(mTestStr, 50, 50, paint); 199 } 200 return; 201 } 202 203 Bitmap capture(char c[]) { 204 mTestStr = String.valueOf(c); 205 invalidate(); 206 207 setDrawingCacheEnabled(true); 208 measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 209 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 210 layout(0, 0, 200,200); 211 212 Bitmap bitmap = Bitmap.createBitmap(getDrawingCache()); 213 setDrawingCacheEnabled(false); 214 return bitmap; 215 } 216 217 } 218 219 private static class CaptureTextView extends TextView { 220 221 CaptureTextView(Context context) { 222 super(context); 223 setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); 224 } 225 226 Bitmap capture(char c[]) { 227 setText(String.valueOf(c)); 228 229 invalidate(); 230 231 setDrawingCacheEnabled(true); 232 measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 233 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 234 layout(0, 0, 200,200); 235 236 Bitmap bitmap = Bitmap.createBitmap(getDrawingCache()); 237 setDrawingCacheEnabled(false); 238 return bitmap; 239 } 240 241 } 242 243 private static class CaptureEditText extends EditText { 244 245 CaptureEditText(Context context) { 246 super(context); 247 setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); 248 } 249 250 Bitmap capture(char c[]) { 251 setText(String.valueOf(c)); 252 253 invalidate(); 254 255 setDrawingCacheEnabled(true); 256 measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 257 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 258 layout(0, 0, 200,200); 259 260 Bitmap bitmap = Bitmap.createBitmap(getDrawingCache()); 261 setDrawingCacheEnabled(false); 262 return bitmap; 263 } 264 265 } 266 267 268 private class CaptureWebView { 269 270 WebViewOnUiThread webViewOnUiThread; 271 Bitmap bitmap; 272 CaptureWebView() { 273 webViewOnUiThread = new WebViewOnUiThread(mActivityRule, 274 mActivityRule.getActivity().getWebView()); 275 } 276 277 Bitmap capture(char c[]) { 278 279 webViewOnUiThread.loadDataAndWaitForCompletion( 280 "<html><body>" + String.valueOf(c) + "</body></html>", 281 "text/html; charset=utf-8", "utf-8"); 282 // The Chromium-powered WebView renders asynchronously and there's nothing reliable 283 // we can easily wait for to be sure that capturePicture will return a fresh frame. 284 // So, just sleep for a sufficient time. 285 try { 286 Thread.sleep(250); 287 } catch (InterruptedException e) { 288 return null; 289 } 290 291 Picture picture = webViewOnUiThread.capturePicture(); 292 if (picture == null || picture.getHeight() <= 0 || picture.getWidth() <= 0) { 293 return null; 294 } else { 295 bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), 296 Bitmap.Config.ARGB_8888); 297 Canvas canvas = new Canvas(bitmap); 298 picture.draw(canvas); 299 } 300 301 return bitmap; 302 } 303 304 } 305 306 } 307 308