12c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu/*
22c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * Copyright (C) 2015 The Android Open Source Project
32c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu *
42c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * Licensed under the Apache License, Version 2.0 (the "License");
52c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * you may not use this file except in compliance with the License.
62c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * You may obtain a copy of the License at
72c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu *
82c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu *      http://www.apache.org/licenses/LICENSE-2.0
92c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu *
102c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * Unless required by applicable law or agreed to in writing, software
112c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * distributed under the License is distributed on an "AS IS" BASIS,
122c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * See the License for the specific language governing permissions and
142c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu * limitations under the License.
152c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu */
162c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.vectordrawable.graphics.drawable.tests;
182c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
19754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport static org.junit.Assert.assertEquals;
20754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport static org.junit.Assert.assertNotNull;
21754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport static org.junit.Assert.assertTrue;
22754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport static org.junit.Assert.fail;
23754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikas
24d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikovimport android.content.Context;
252c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport android.content.res.Resources;
262c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport android.content.res.Resources.Theme;
271f8664abb6cdd6962d58433e06dd9cc7ea856782Teng-Hui Zhuimport android.graphics.Bitmap;
281f8664abb6cdd6962d58433e06dd9cc7ea856782Teng-Hui Zhuimport android.graphics.BitmapFactory;
291f8664abb6cdd6962d58433e06dd9cc7ea856782Teng-Hui Zhuimport android.graphics.Canvas;
301f8664abb6cdd6962d58433e06dd9cc7ea856782Teng-Hui Zhuimport android.graphics.Color;
315f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhuimport android.graphics.Rect;
322c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport android.graphics.drawable.Drawable;
33d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikovimport android.support.test.InstrumentationRegistry;
34754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.MediumTest;
35d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikovimport android.support.test.runner.AndroidJUnit4;
362c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport android.util.Log;
37754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikas
381f1b5540916924b36bc81cc3edb4f02245116ddeAurimas Liutikasimport androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
391f1b5540916924b36bc81cc3edb4f02245116ddeAurimas Liutikasimport androidx.vectordrawable.test.R;
401f1b5540916924b36bc81cc3edb4f02245116ddeAurimas Liutikas
41d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikovimport org.junit.Before;
42d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikovimport org.junit.Test;
43d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikovimport org.junit.runner.RunWith;
442c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport org.xmlpull.v1.XmlPullParserException;
452c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
462c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport java.io.File;
472c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport java.io.FileOutputStream;
482c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhuimport java.io.IOException;
492c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
50d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov@RunWith(AndroidJUnit4.class)
51daea069fe33cc750bcb733ebcb6206d2dcedae76Chris Banes@MediumTest
52d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikovpublic class VectorDrawableTest {
532c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final String LOGTAG = "VectorDrawableTest";
542c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
552c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final int[] ICON_RES_IDS = new int[]{
562c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_create,
572c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_delete,
582c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_heart,
592c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_schedule,
602c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_settings,
612c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_random_path_1,
622c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_random_path_2,
632c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_cq,
642c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_st,
652c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_a_1,
662c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_a_2,
672c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_clip_path_1,
682c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_1,
692c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_4,
702c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_5,
712c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_6,
722c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_render_order_1,
732c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_render_order_2,
742c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_stroke_1,
752c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_stroke_2,
762c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_stroke_3,
772c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_scale_1,
7804027486ce99dc54f3ba88e517d76695c3ae90e8Teng-Hui Zhu            R.drawable.vector_icon_group_clip,
79c59ac731f7d38ee41d0aba567a9d3b77b40df628Teng-Hui Zhu            R.drawable.vector_icon_share,
80c59ac731f7d38ee41d0aba567a9d3b77b40df628Teng-Hui Zhu            R.drawable.vector_icon_wishlist,
816a1b3c0484827f986524666cc017561616c4b1dbTeng-Hui Zhu            R.drawable.vector_icon_five_bars,
823802860a1aa62c4a86c6c6e491ba38fb17e59b02ztenghui            R.drawable.vector_icon_filltype_evenodd,
833802860a1aa62c4a86c6c6e491ba38fb17e59b02ztenghui            R.drawable.vector_icon_filltype_nonzero,
842c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    };
852c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
862c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final int[] GOLDEN_IMAGES = new int[]{
872c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_create_golden,
882c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_delete_golden,
892c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_heart_golden,
902c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_schedule_golden,
912c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_settings_golden,
922c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_random_path_1_golden,
932c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_random_path_2_golden,
942c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_cq_golden,
952c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_st_golden,
962c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_a_1_golden,
972c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_repeated_a_2_golden,
982c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_clip_path_1_golden,
992c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_1_golden,
1002c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_4_golden,
1012c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_5_golden,
1022c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_transformation_6_golden,
1032c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_render_order_1_golden,
1042c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_render_order_2_golden,
1052c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_stroke_1_golden,
1062c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_stroke_2_golden,
1072c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_stroke_3_golden,
1082c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            R.drawable.vector_icon_scale_1_golden,
10904027486ce99dc54f3ba88e517d76695c3ae90e8Teng-Hui Zhu            R.drawable.vector_icon_group_clip_golden,
110c59ac731f7d38ee41d0aba567a9d3b77b40df628Teng-Hui Zhu            R.drawable.vector_icon_share_golden,
111c59ac731f7d38ee41d0aba567a9d3b77b40df628Teng-Hui Zhu            R.drawable.vector_icon_wishlist_golden,
1126a1b3c0484827f986524666cc017561616c4b1dbTeng-Hui Zhu            R.drawable.vector_icon_five_bars_golden,
1133802860a1aa62c4a86c6c6e491ba38fb17e59b02ztenghui            R.drawable.vector_icon_filltype_evenodd_golden,
1143802860a1aa62c4a86c6c6e491ba38fb17e59b02ztenghui            R.drawable.vector_icon_filltype_nonzero_golden,
1152c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    };
1162c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1172c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final int TEST_ICON = R.drawable.vector_icon_create;
1182c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1192c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final int IMAGE_WIDTH = 64;
1202c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final int IMAGE_HEIGHT = 64;
1212c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    // A small value is actually making sure that the values are matching
1222c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    // exactly with the golden image.
1232c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    // We can increase the threshold if the Skia is drawing with some variance
1242c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    // on different devices. So far, the tests show they are matching correctly.
1254724da3c3c0f8892333855229082edfd2bf1e934ztenghui    private static final float PIXEL_ERROR_THRESHOLD = 0.33f;
12622eca99cc198a41db5c72ad6fb981f2bd4d59313Teng-Hui Zhu    private static final float PIXEL_DIFF_COUNT_THRESHOLD = 0.1f;
1272c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final float PIXEL_DIFF_THRESHOLD = 0.025f;
1282c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1292c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private static final boolean DBG_DUMP_PNG = false;
1302c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
131d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    private Context mContext;
1322c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private Resources mResources;
1332c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private VectorDrawableCompat mVectorDrawable;
1342c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private Bitmap mBitmap;
1352c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private Canvas mCanvas;
1362c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private Theme mTheme;
1372c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
138d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    @Before
139d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    public void setup() {
1402c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        final int width = IMAGE_WIDTH;
1412c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        final int height = IMAGE_HEIGHT;
1422c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1432c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1442c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        mCanvas = new Canvas(mBitmap);
1452c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
146d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov        mContext = InstrumentationRegistry.getContext();
1472c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        mResources = mContext.getResources();
1482c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        mTheme = mContext.getTheme();
1492c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
1502c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
151d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    @Test
152d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    public void testSimpleVectorDrawables() throws Exception {
1532c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        verifyVectorDrawables(ICON_RES_IDS, GOLDEN_IMAGES, null);
1542c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
1552c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1562c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private void verifyVectorDrawables(int[] resIds, int[] goldenImages, int[] stateSet)
1572c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            throws XmlPullParserException, IOException {
1582c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        for (int i = 0; i < resIds.length; i++) {
1592c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            // Setup VectorDrawable from xml file and draw into the bitmap.
1602c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            mVectorDrawable = VectorDrawableCompat.create(mResources, resIds[i], mTheme);
1612c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            mVectorDrawable.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
1622c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            if (stateSet != null) {
1632c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                mVectorDrawable.setState(stateSet);
1642c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            }
1652c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1662c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            mBitmap.eraseColor(0);
1672c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            mVectorDrawable.draw(mCanvas);
1682c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1692c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            if (DBG_DUMP_PNG) {
1702c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                saveVectorDrawableIntoPNG(mBitmap, resIds, i, stateSet);
1712c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            } else {
1722c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                // Start to compare
1732c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                Bitmap golden = BitmapFactory.decodeResource(mResources, goldenImages[i]);
1742c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                compareImages(mBitmap, golden, mResources.getString(resIds[i]));
1752c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            }
1762c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        }
1772c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
1782c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
1792c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    // This is only for debugging or golden image (re)generation purpose.
1802c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private void saveVectorDrawableIntoPNG(Bitmap bitmap, int[] resIds, int index, int[] stateSet)
1812c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            throws IOException {
1822c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        // Save the image to the disk.
1832c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        FileOutputStream out = null;
1842c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        try {
1852c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            String outputFolder = "/sdcard/temp/";
1862c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            File folder = new File(outputFolder);
1872c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            if (!folder.exists()) {
1882c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                folder.mkdir();
1892c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            }
1902c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            String originalFilePath = mResources.getString(resIds[index]);
1912c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            File originalFile = new File(originalFilePath);
1922c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            String fileFullName = originalFile.getName();
1932c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            String fileTitle = fileFullName.substring(0, fileFullName.lastIndexOf("."));
1942c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            String stateSetTitle = getTitleForStateSet(stateSet);
1952c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            String outputFilename = outputFolder + fileTitle + "_golden" + stateSetTitle + ".png";
1962c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            File outputFile = new File(outputFilename);
1972c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            if (!outputFile.exists()) {
1982c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                outputFile.createNewFile();
1992c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            }
2002c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2012c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            out = new FileOutputStream(outputFile, false);
2022c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
2032c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            Log.v(LOGTAG, "Write test No." + index + " to file successfully.");
2042c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        } catch (Exception e) {
2052c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            e.printStackTrace();
2062c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        } finally {
2072c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            if (out != null) {
2082c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                out.close();
2092c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            }
2102c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        }
2112c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
2122c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2132c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    /**
2142c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     * Generates an underline-delimited list of states in a given state set.
2152c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     * <p/>
2162c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     * For example, the array {@code {R.attr.state_pressed}} would return
2172c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     * {@code "_pressed"}.
2182c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     *
2192c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     * @param stateSet a state set
2202c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     * @return a string representing the state set, or the empty string if the
2212c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     * state set is empty or {@code null}
2222c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu     */
2232c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private String getTitleForStateSet(int[] stateSet) {
2242c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        if (stateSet == null || stateSet.length == 0) {
2252c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            return "";
2262c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        }
2272c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2282c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        final StringBuilder builder = new StringBuilder();
2292c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        for (int i = 0; i < stateSet.length; i++) {
2302c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            builder.append('_');
2312c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
232d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov            final String state = mResources.getResourceName(stateSet[i]);
2332c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            final int stateIndex = state.indexOf("state_");
2342c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            if (stateIndex >= 0) {
2352c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                builder.append(state.substring(stateIndex + 6));
2362c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            } else {
2372c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                builder.append(stateSet[i]);
2382c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            }
2392c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        }
2402c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2412c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        return builder.toString();
2422c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
2432c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2442c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    private void compareImages(Bitmap ideal, Bitmap given, String filename) {
2452c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        int idealWidth = ideal.getWidth();
2462c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        int idealHeight = ideal.getHeight();
2472c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2482c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertTrue(idealWidth == given.getWidth());
2492c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertTrue(idealHeight == given.getHeight());
2502c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2512c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        int totalDiffPixelCount = 0;
2522c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        float totalPixelCount = idealWidth * idealHeight;
2532c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        for (int x = 0; x < idealWidth; x++) {
2542c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            for (int y = 0; y < idealHeight; y++) {
2552c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                int idealColor = ideal.getPixel(x, y);
2562c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                int givenColor = given.getPixel(x, y);
2572c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                if (idealColor == givenColor)
2582c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                    continue;
2592c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2602c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                float totalError = 0;
2612c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                totalError += Math.abs(Color.red(idealColor) - Color.red(givenColor));
2622c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                totalError += Math.abs(Color.green(idealColor) - Color.green(givenColor));
2632c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                totalError += Math.abs(Color.blue(idealColor) - Color.blue(givenColor));
2642c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                totalError += Math.abs(Color.alpha(idealColor) - Color.alpha(givenColor));
2652c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2662c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                if ((totalError / 1024.0f) >= PIXEL_ERROR_THRESHOLD) {
2672c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                    fail((filename + ": totalError is " + totalError));
2682c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                }
2692c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2702c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                if ((totalError / 1024.0f) >= PIXEL_DIFF_THRESHOLD) {
2712c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                    totalDiffPixelCount++;
2722c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                }
2732c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            }
2742c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        }
27522eca99cc198a41db5c72ad6fb981f2bd4d59313Teng-Hui Zhu        if ((totalDiffPixelCount / totalPixelCount) >= PIXEL_DIFF_COUNT_THRESHOLD) {
2762c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu            fail((filename + ": totalDiffPixelCount is " + totalDiffPixelCount));
2772c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        }
2782c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2792c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
2802c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
281d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    @Test
2822c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    public void testGetChangingConfigurations() {
2832c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        VectorDrawableCompat vectorDrawable =
2842c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
2852c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        Drawable.ConstantState constantState = vectorDrawable.getConstantState();
2862c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2872c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        // default
2882c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0, constantState.getChangingConfigurations());
2892c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0, vectorDrawable.getChangingConfigurations());
2902c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2912c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        // change the drawable's configuration does not affect the state's configuration
2922c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        vectorDrawable.setChangingConfigurations(0xff);
2932c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0xff, vectorDrawable.getChangingConfigurations());
2942c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0, constantState.getChangingConfigurations());
2952c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
2962c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        // the state's configuration get refreshed
2972c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        constantState = vectorDrawable.getConstantState();
2982c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0xff, constantState.getChangingConfigurations());
2992c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
3002c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        // set a new configuration to drawable
3012c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        vectorDrawable.setChangingConfigurations(0xff00);
3022c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0xff, constantState.getChangingConfigurations());
3032c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0xffff, vectorDrawable.getChangingConfigurations());
3042c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
3052c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
306d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    @Test
3072c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    public void testGetConstantState() {
3082c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        VectorDrawableCompat vectorDrawable =
30941d51ba40d85609cc9abe45922279da5d700a654Teng-Hui Zhu                VectorDrawableCompat.create(mResources, R.drawable.vector_icon_delete, mTheme);
3102c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        Drawable.ConstantState constantState = vectorDrawable.getConstantState();
3112c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertNotNull(constantState);
3122c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0, constantState.getChangingConfigurations());
3132c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
3142c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        vectorDrawable.setChangingConfigurations(1);
3152c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        constantState = vectorDrawable.getConstantState();
3162c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertNotNull(constantState);
3172c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(1, constantState.getChangingConfigurations());
3182c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
3192c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
320d5bac09cd05be314f4c38435572a3fc01c2c8d4fKirill Grouchnikov    @Test
3212c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    public void testMutate() {
3222c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        VectorDrawableCompat d1 =
3232c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
3242c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        VectorDrawableCompat d2 =
3252c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                (VectorDrawableCompat) d1.getConstantState().newDrawable(mResources);
3262c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        VectorDrawableCompat d3 =
3272c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu                (VectorDrawableCompat) d1.getConstantState().newDrawable(mResources);
3282c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
3292c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        // d1 will be mutated, while d2 / d3 will not.
3302c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        int originalAlpha = d2.getAlpha();
3312c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
3322c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        d1.setAlpha(0x80);
3332c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x80, d1.getAlpha());
3342c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x80, d2.getAlpha());
3352c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x80, d3.getAlpha());
3362c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
3372c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        d1.mutate();
3382c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        d1.setAlpha(0x40);
3392c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x40, d1.getAlpha());
3402c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x80, d2.getAlpha());
3412c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x80, d3.getAlpha());
3422c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
3432c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        d2.setAlpha(0x20);
3442c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x40, d1.getAlpha());
3452c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x20, d2.getAlpha());
3462c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        assertEquals(0x20, d3.getAlpha());
3472c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu
3482c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu        d2.setAlpha(originalAlpha);
3492c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu    }
3505f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu
3515f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu    public void testBounds() {
3525f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        VectorDrawableCompat vectorDrawable =
3535f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu                VectorDrawableCompat.create(mResources, R.drawable.vector_icon_delete, mTheme);
3545f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        Rect expectedRect = new Rect(0, 0, 100, 100);
3555f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        vectorDrawable.setBounds(0, 0, 100, 100);
3565f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        Rect rect = vectorDrawable.getBounds();
3575f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        assertEquals("Bounds should be same value for setBound(int ...)", rect, expectedRect);
3585f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu
3595f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        vectorDrawable.setBounds(expectedRect);
3605f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        rect = vectorDrawable.getBounds();
3615f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        assertEquals("Bounds should be same value for setBound(Rect)", rect, expectedRect);
3625f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu
3635f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        vectorDrawable.copyBounds(rect);
3645f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu        assertEquals("Bounds should be same value for copyBounds", rect, expectedRect);
3655f21d671a4afb439a61257b393d0b5e40879528aTeng-Hui Zhu    }
3662c3c8bff4c669316cdc2db24b72d9ac3f9b33725Teng-Hui Zhu}
367