Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2008 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 com.android.compatibility.common.util;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import androidx.annotation.NonNull;
     24 import androidx.annotation.Nullable;
     25 import android.support.test.rule.ActivityTestRule;
     26 import android.text.Editable;
     27 import android.text.TextUtils;
     28 import android.view.View;
     29 import android.view.ViewTreeObserver;
     30 
     31 import junit.framework.Assert;
     32 
     33 import org.hamcrest.BaseMatcher;
     34 import org.hamcrest.Description;
     35 import org.xmlpull.v1.XmlPullParser;
     36 import org.xmlpull.v1.XmlPullParserException;
     37 
     38 import java.io.IOException;
     39 import java.util.concurrent.CountDownLatch;
     40 import java.util.concurrent.TimeUnit;
     41 
     42 import static android.view.ViewTreeObserver.*;
     43 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
     44 
     45 /**
     46  * The useful methods for widget test.
     47  */
     48 public class WidgetTestUtils {
     49     /**
     50      * Assert that two bitmaps have identical content (same dimensions, same configuration,
     51      * same pixel content).
     52      *
     53      * @param b1 the first bitmap which needs to compare.
     54      * @param b2 the second bitmap which needs to compare.
     55      */
     56     public static void assertEquals(Bitmap b1, Bitmap b2) {
     57         if (b1 == b2) {
     58             return;
     59         }
     60 
     61         if (b1 == null || b2 == null) {
     62             Assert.fail("the bitmaps are not equal");
     63         }
     64 
     65         // b1 and b2 are all not null.
     66         if (b1.getWidth() != b2.getWidth() || b1.getHeight() != b2.getHeight()
     67             || b1.getConfig() != b2.getConfig()) {
     68             Assert.fail("the bitmaps are not equal");
     69         }
     70 
     71         int w = b1.getWidth();
     72         int h = b1.getHeight();
     73         int s = w * h;
     74         int[] pixels1 = new int[s];
     75         int[] pixels2 = new int[s];
     76 
     77         b1.getPixels(pixels1, 0, w, 0, 0, w, h);
     78         b2.getPixels(pixels2, 0, w, 0, 0, w, h);
     79 
     80         for (int i = 0; i < s; i++) {
     81             if (pixels1[i] != pixels2[i]) {
     82                 Assert.fail("the bitmaps are not equal");
     83             }
     84         }
     85     }
     86 
     87     /**
     88      * Find beginning of the special element.
     89      * @param parser XmlPullParser will be parsed.
     90      * @param firstElementName the target element name.
     91      *
     92      * @throws XmlPullParserException if XML Pull Parser related faults occur.
     93      * @throws IOException if I/O-related error occur when parsing.
     94      */
     95     public static final void beginDocument(XmlPullParser parser, String firstElementName)
     96             throws XmlPullParserException, IOException {
     97         Assert.assertNotNull(parser);
     98         Assert.assertNotNull(firstElementName);
     99 
    100         int type;
    101         while ((type = parser.next()) != XmlPullParser.START_TAG
    102                 && type != XmlPullParser.END_DOCUMENT) {
    103             ;
    104         }
    105 
    106         if (!parser.getName().equals(firstElementName)) {
    107             throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
    108                     + ", expected " + firstElementName);
    109         }
    110     }
    111 
    112     /**
    113      * Compare the expected pixels with actual, scaling for the target context density
    114      *
    115      * @throws AssertionFailedError
    116      */
    117     public static void assertScaledPixels(int expected, int actual, Context context) {
    118         Assert.assertEquals(expected * context.getResources().getDisplayMetrics().density,
    119                 actual, 3);
    120     }
    121 
    122     /** Converts dips into pixels using the {@link Context}'s density. */
    123     public static int convertDipToPixels(Context context, int dip) {
    124       float density = context.getResources().getDisplayMetrics().density;
    125       return Math.round(density * dip);
    126     }
    127 
    128     /**
    129      * Retrieve a bitmap that can be used for comparison on any density
    130      * @param resources
    131      * @return the {@link Bitmap} or <code>null</code>
    132      */
    133     public static Bitmap getUnscaledBitmap(Resources resources, int resId) {
    134         BitmapFactory.Options options = new BitmapFactory.Options();
    135         options.inScaled = false;
    136         return BitmapFactory.decodeResource(resources, resId, options);
    137     }
    138 
    139     /**
    140      * Retrieve a dithered bitmap that can be used for comparison on any density
    141      * @param resources
    142      * @param config the preferred config for the returning bitmap
    143      * @return the {@link Bitmap} or <code>null</code>
    144      */
    145     public static Bitmap getUnscaledAndDitheredBitmap(Resources resources,
    146             int resId, Bitmap.Config config) {
    147         BitmapFactory.Options options = new BitmapFactory.Options();
    148         options.inDither = true;
    149         options.inScaled = false;
    150         options.inPreferredConfig = config;
    151         return BitmapFactory.decodeResource(resources, resId, options);
    152     }
    153 
    154     /**
    155      * Argument matcher for equality check of a CharSequence.
    156      *
    157      * @param expected expected CharSequence
    158      *
    159      * @return
    160      */
    161     public static CharSequence sameCharSequence(final CharSequence expected) {
    162         return argThat(new BaseMatcher<CharSequence>() {
    163             @Override
    164             public boolean matches(Object o) {
    165                 if (o instanceof CharSequence) {
    166                     return TextUtils.equals(expected, (CharSequence) o);
    167                 }
    168                 return false;
    169             }
    170 
    171             @Override
    172             public void describeTo(Description description) {
    173                 description.appendText("doesn't match " + expected);
    174             }
    175         });
    176     }
    177 
    178     /**
    179      * Argument matcher for equality check of an Editable.
    180      *
    181      * @param expected expected Editable
    182      *
    183      * @return
    184      */
    185     public static Editable sameEditable(final Editable expected) {
    186         return argThat(new BaseMatcher<Editable>() {
    187             @Override
    188             public boolean matches(Object o) {
    189                 if (o instanceof Editable) {
    190                     return TextUtils.equals(expected, (Editable) o);
    191                 }
    192                 return false;
    193             }
    194 
    195             @Override
    196             public void describeTo(Description description) {
    197                 description.appendText("doesn't match " + expected);
    198             }
    199         });
    200     }
    201 
    202     /**
    203      * Runs the specified Runnable on the main thread and ensures that the specified View's tree is
    204      * drawn before returning.
    205      *
    206      * @param activityTestRule the activity test rule used to run the test
    207      * @param view the view whose tree should be drawn before returning
    208      * @param runner the runnable to run on the main thread, or {@code null} to
    209      *               simply force invalidation and a draw pass
    210      */
    211     public static void runOnMainAndDrawSync(@NonNull final ActivityTestRule activityTestRule,
    212             @NonNull final View view, @Nullable final Runnable runner) throws Throwable {
    213         final CountDownLatch latch = new CountDownLatch(1);
    214 
    215         activityTestRule.runOnUiThread(() -> {
    216             final OnDrawListener listener = new OnDrawListener() {
    217                 @Override
    218                 public void onDraw() {
    219                     // posting so that the sync happens after the draw that's about to happen
    220                     view.post(() -> {
    221                         activityTestRule.getActivity().getWindow().getDecorView().
    222                                 getViewTreeObserver().removeOnDrawListener(this);
    223                         latch.countDown();
    224                     });
    225                 }
    226             };
    227 
    228             activityTestRule.getActivity().getWindow().getDecorView().
    229                     getViewTreeObserver().addOnDrawListener(listener);
    230 
    231             if (runner != null) {
    232                 runner.run();
    233             }
    234             view.invalidate();
    235         });
    236 
    237         try {
    238             Assert.assertTrue("Expected draw pass occurred within 5 seconds",
    239                     latch.await(5, TimeUnit.SECONDS));
    240         } catch (InterruptedException e) {
    241             throw new RuntimeException(e);
    242         }
    243     }
    244 
    245     /**
    246      * Runs the specified Runnable on the main thread and ensures that the activity's view tree is
    247      * laid out before returning.
    248      *
    249      * @param activityTestRule the activity test rule used to run the test
    250      * @param runner the runnable to run on the main thread. {@code null} is
    251      * allowed, and simply means that there no runnable is required.
    252      * @param forceLayout true if there should be an explicit call to requestLayout(),
    253      * false otherwise
    254      */
    255     public static void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
    256             @Nullable final Runnable runner, boolean forceLayout)
    257             throws Throwable {
    258         runOnMainAndLayoutSync(activityTestRule,
    259                 activityTestRule.getActivity().getWindow().getDecorView(), runner, forceLayout);
    260     }
    261 
    262     /**
    263      * Runs the specified Runnable on the main thread and ensures that the specified view is
    264      * laid out before returning.
    265      *
    266      * @param activityTestRule the activity test rule used to run the test
    267      * @param view The view
    268      * @param runner the runnable to run on the main thread. {@code null} is
    269      * allowed, and simply means that there no runnable is required.
    270      * @param forceLayout true if there should be an explicit call to requestLayout(),
    271      * false otherwise
    272      */
    273     public static void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
    274             @NonNull final View view, @Nullable final Runnable runner, boolean forceLayout)
    275             throws Throwable {
    276         final View rootView = view.getRootView();
    277 
    278         final CountDownLatch latch = new CountDownLatch(1);
    279 
    280         activityTestRule.runOnUiThread(() -> {
    281             final OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
    282                 @Override
    283                 public void onGlobalLayout() {
    284                     rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    285                     // countdown immediately since the layout we were waiting on has happened
    286                     latch.countDown();
    287                 }
    288             };
    289 
    290             rootView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
    291 
    292             if (runner != null) {
    293                 runner.run();
    294             }
    295 
    296             if (forceLayout) {
    297                 rootView.requestLayout();
    298             }
    299         });
    300 
    301         try {
    302             Assert.assertTrue("Expected layout pass within 5 seconds",
    303                     latch.await(5, TimeUnit.SECONDS));
    304         } catch (InterruptedException e) {
    305             throw new RuntimeException(e);
    306         }
    307     }
    308 
    309 }
    310