blob: cb719e98ecce9faaa873c834978945d0f9535e6b [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.text.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Picture;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.cts.WebViewOnUiThread;
import android.widget.EditText;
import android.widget.TextView;
import com.android.compatibility.common.util.NullWebViewUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class EmojiTest {
private Context mContext;
private EditText mEditText;
@Rule
public ActivityTestRule<EmojiCtsActivity> mActivityRule =
new ActivityTestRule<>(EmojiCtsActivity.class);
@Before
public void setup() {
mContext = mActivityRule.getActivity();
}
/**
* Tests all Emoji are defined in Character class
*/
@Test
public void testEmojiCodePoints() {
for (int i = 0; i < EmojiConstants.ALL_EMOJI.length; i++) {
assertTrue(Character.isDefined(EmojiConstants.ALL_EMOJI[i]));
}
}
private String describeBitmap(final Bitmap bmp) {
StringBuilder sb = new StringBuilder();
sb.append("[ID:0x" + Integer.toHexString(System.identityHashCode(bmp)));
sb.append(" " + Integer.toString(bmp.getWidth()) + "x" + Integer.toString(bmp.getHeight()));
sb.append(" Config:");
if (bmp.getConfig() == Bitmap.Config.ALPHA_8) {
sb.append("ALPHA_8");
} else if (bmp.getConfig() == Bitmap.Config.RGB_565) {
sb.append("RGB_565");
} else if (bmp.getConfig() == Bitmap.Config.ARGB_4444) {
sb.append("ARGB_4444");
} else if (bmp.getConfig() == Bitmap.Config.ARGB_8888) {
sb.append("ARGB_8888");
} else {
sb.append("UNKNOWN");
}
sb.append("]");
return sb.toString();
}
/**
* Tests Emoji has different glyph for different meaning characters.
* Test on Canvas, TextView, EditText and WebView
*/
@UiThreadTest
@Test
public void testEmojiGlyph() {
CaptureCanvas ccanvas = new CaptureCanvas(mContext);
Bitmap mBitmapA, mBitmapB; // Emoji displayed Bitmaps to compare
int comparedCodePoints[][] = { // Emojis should have different characters
{0x1F436, 0x1F435}, // Dog(U+1F436) and Monkey(U+1F435)
{0x26BD, 0x26BE}, // Soccer ball(U+26BD) and Baseball(U+26BE)
{0x1F47B, 0x1F381}, // Ghost(U+1F47B) and wrapped present(U+1F381)
{0x2764, 0x1F494}, // Heavy black heart(U+2764) and broken heart(U+1F494)
{0x1F603, 0x1F33B} // Smiling face with open mouth(U+1F603) and sunflower(U+1F33B)
};
for (int i = 0; i < comparedCodePoints.length; i++) {
String baseMessage = "Glyph for U+" + Integer.toHexString(comparedCodePoints[i][0]) +
" should be different from glyph for U+" +
Integer.toHexString(comparedCodePoints[i][1]) + ". ";
mBitmapA = ccanvas.capture(Character.toChars(comparedCodePoints[i][0]));
mBitmapB = ccanvas.capture(Character.toChars(comparedCodePoints[i][1]));
String bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
// cannot reuse CaptureTextView as 2nd setText call throws NullPointerException
CaptureTextView cviewA = new CaptureTextView(mContext);
mBitmapA = cviewA.capture(Character.toChars(comparedCodePoints[i][0]));
CaptureTextView cviewB = new CaptureTextView(mContext);
mBitmapB = cviewB.capture(Character.toChars(comparedCodePoints[i][1]));
bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
CaptureEditText cedittextA = new CaptureEditText(mContext);
mBitmapA = cedittextA.capture(Character.toChars(comparedCodePoints[i][0]));
CaptureEditText cedittextB = new CaptureEditText(mContext);
mBitmapB = cedittextB.capture(Character.toChars(comparedCodePoints[i][1]));
bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
// Trigger activity bringup so we can determine if a WebView is available on this
// device.
if (NullWebViewUtils.isWebViewAvailable()) {
CaptureWebView cwebview = new CaptureWebView();
mBitmapA = cwebview.capture(Character.toChars(comparedCodePoints[i][0]));
mBitmapB = cwebview.capture(Character.toChars(comparedCodePoints[i][1]));
bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
}
}
}
/**
* Tests EditText handles Emoji
*/
@LargeTest
@Test
public void testEmojiEditable() throws Throwable {
int testedCodePoints[] = {
0xAE, // registered mark
0x2764, // heavy black heart
0x1F353 // strawberry - surrogate pair sample. Count as two characters.
};
String origStr, newStr;
// delete Emoji by sending KEYCODE_DEL
for (int i = 0; i < testedCodePoints.length; i++) {
origStr = "Test character ";
// cannot reuse CaptureTextView as 2nd setText call throws NullPointerException
mActivityRule.runOnUiThread(() -> mEditText = new EditText(mContext));
mEditText.setText(origStr + String.valueOf(Character.toChars(testedCodePoints[i])));
// confirm the emoji is added.
newStr = mEditText.getText().toString();
assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1);
// Delete added character by sending KEYCODE_DEL event
mActivityRule.runOnUiThread(() -> mEditText.dispatchKeyEvent(
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)));
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
newStr = mEditText.getText().toString();
assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1);
}
}
private static class CaptureCanvas extends View {
String mTestStr;
Paint paint = new Paint();
CaptureCanvas(Context context) {
super(context);
}
public void onDraw(Canvas canvas) {
if (mTestStr != null) {
canvas.drawText(mTestStr, 50, 50, paint);
}
return;
}
Bitmap capture(char c[]) {
mTestStr = String.valueOf(c);
invalidate();
setDrawingCacheEnabled(true);
measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
layout(0, 0, 200,200);
Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
setDrawingCacheEnabled(false);
return bitmap;
}
}
private static class CaptureTextView extends TextView {
CaptureTextView(Context context) {
super(context);
setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
}
Bitmap capture(char c[]) {
setText(String.valueOf(c));
invalidate();
setDrawingCacheEnabled(true);
measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
layout(0, 0, 200,200);
Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
setDrawingCacheEnabled(false);
return bitmap;
}
}
private static class CaptureEditText extends EditText {
CaptureEditText(Context context) {
super(context);
setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
}
Bitmap capture(char c[]) {
setText(String.valueOf(c));
invalidate();
setDrawingCacheEnabled(true);
measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
layout(0, 0, 200,200);
Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
setDrawingCacheEnabled(false);
return bitmap;
}
}
private class CaptureWebView {
WebViewOnUiThread webViewOnUiThread;
Bitmap bitmap;
CaptureWebView() {
webViewOnUiThread = new WebViewOnUiThread(mActivityRule,
mActivityRule.getActivity().getWebView());
}
Bitmap capture(char c[]) {
webViewOnUiThread.loadDataAndWaitForCompletion(
"<html><body>" + String.valueOf(c) + "</body></html>",
"text/html; charset=utf-8", "utf-8");
// The Chromium-powered WebView renders asynchronously and there's nothing reliable
// we can easily wait for to be sure that capturePicture will return a fresh frame.
// So, just sleep for a sufficient time.
try {
Thread.sleep(250);
} catch (InterruptedException e) {
return null;
}
Picture picture = webViewOnUiThread.capturePicture();
if (picture == null || picture.getHeight() <= 0 || picture.getWidth() <= 0) {
return null;
} else {
bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
picture.draw(canvas);
}
return bitmap;
}
}
}