Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2017 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.widget.cts;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertFalse;
     21 import static org.junit.Assert.assertTrue;
     22 import static org.junit.Assume.assumeTrue;
     23 
     24 import android.app.Activity;
     25 import android.graphics.Bitmap;
     26 import android.graphics.PointF;
     27 import android.graphics.Rect;
     28 import android.support.test.annotation.UiThreadTest;
     29 import android.support.test.filters.SmallTest;
     30 import android.support.test.rule.ActivityTestRule;
     31 import android.support.test.runner.AndroidJUnit4;
     32 import android.util.DisplayMetrics;
     33 import android.view.View;
     34 import android.widget.LinearLayout;
     35 import android.widget.LinearLayout.LayoutParams;
     36 import android.widget.Magnifier;
     37 
     38 import com.android.compatibility.common.util.PollingCheck;
     39 import com.android.compatibility.common.util.WidgetTestUtils;
     40 
     41 import org.junit.Before;
     42 import org.junit.Rule;
     43 import org.junit.Test;
     44 import org.junit.runner.RunWith;
     45 
     46 import java.util.ArrayList;
     47 import java.util.Collections;
     48 import java.util.HashMap;
     49 import java.util.List;
     50 import java.util.Map;
     51 import java.util.concurrent.CountDownLatch;
     52 import java.util.concurrent.TimeUnit;
     53 
     54 /**
     55  * Tests for {@link Magnifier}.
     56  */
     57 @SmallTest
     58 @RunWith(AndroidJUnit4.class)
     59 public class MagnifierTest {
     60     private static final String TIME_LIMIT_EXCEEDED =
     61             "Completing the magnifier operation took too long";
     62 
     63     private Activity mActivity;
     64     private LinearLayout mLayout;
     65     private Magnifier mMagnifier;
     66 
     67     @Rule
     68     public ActivityTestRule<MagnifierCtsActivity> mActivityRule =
     69             new ActivityTestRule<>(MagnifierCtsActivity.class);
     70 
     71     @Before
     72     public void setup() {
     73         mActivity = mActivityRule.getActivity();
     74         PollingCheck.waitFor(mActivity::hasWindowFocus);
     75         mLayout = mActivity.findViewById(R.id.magnifier_activity_basic_layout);
     76 
     77         // Do not run the tests, unless the device screen is big enough to fit the magnifier.
     78         assumeTrue(isScreenBigEnough());
     79     }
     80 
     81     private boolean isScreenBigEnough() {
     82         // Get the size of the screen in dp.
     83         final DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
     84         final float dpScreenWidth = displayMetrics.widthPixels / displayMetrics.density;
     85         final float dpScreenHeight = displayMetrics.heightPixels / displayMetrics.density;
     86         // Get the size of the magnifier window in dp.
     87         final PointF dpMagnifier = Magnifier.getMagnifierDefaultSize();
     88 
     89         return dpScreenWidth >= dpMagnifier.x * 1.1 && dpScreenHeight >= dpMagnifier.y * 1.1;
     90     }
     91 
     92     @Test
     93     public void testConstructor() {
     94         new Magnifier(new View(mActivity));
     95     }
     96 
     97     @Test(expected = NullPointerException.class)
     98     public void testConstructor_NPE() {
     99         new Magnifier(null);
    100     }
    101 
    102     @Test
    103     @UiThreadTest
    104     public void testShow() {
    105         final View view = new View(mActivity);
    106         mLayout.addView(view, new LayoutParams(200, 200));
    107         mMagnifier = new Magnifier(view);
    108         // Valid coordinates.
    109         mMagnifier.show(0, 0);
    110         // Invalid coordinates, should both be clamped to 0.
    111         mMagnifier.show(-1, -1);
    112         // Valid coordinates.
    113         mMagnifier.show(10, 10);
    114         // Same valid coordinate as above, should skip making another copy request.
    115         mMagnifier.show(10, 10);
    116     }
    117 
    118     @Test
    119     @UiThreadTest
    120     public void testDismiss() {
    121         final View view = new View(mActivity);
    122         mLayout.addView(view, new LayoutParams(200, 200));
    123         mMagnifier = new Magnifier(view);
    124         // Valid coordinates.
    125         mMagnifier.show(10, 10);
    126         mMagnifier.dismiss();
    127         // Should be no-op.
    128         mMagnifier.dismiss();
    129     }
    130 
    131     @Test
    132     @UiThreadTest
    133     public void testUpdate() {
    134         final View view = new View(mActivity);
    135         mLayout.addView(view, new LayoutParams(200, 200));
    136         mMagnifier = new Magnifier(view);
    137         // Should be no-op.
    138         mMagnifier.update();
    139         // Valid coordinates.
    140         mMagnifier.show(10, 10);
    141         // Should not crash.
    142         mMagnifier.update();
    143         mMagnifier.dismiss();
    144         // Should be no-op.
    145         mMagnifier.update();
    146     }
    147 
    148     @Test
    149     @UiThreadTest
    150     public void testSizeAndZoom_areValid() {
    151         final View view = new View(mActivity);
    152         mLayout.addView(view, new LayoutParams(200, 200));
    153         mMagnifier = new Magnifier(view);
    154         mMagnifier.show(10, 10);
    155         // Size should be non-zero.
    156         assertTrue(mMagnifier.getWidth() > 0);
    157         assertTrue(mMagnifier.getHeight() > 0);
    158         // The magnified view region should be zoomed in, not out.
    159         assertTrue(mMagnifier.getZoom() > 1.0f);
    160     }
    161 
    162     @Test
    163     public void testWindowContent() throws Throwable {
    164         prepareFourQuadrantsScenario();
    165         final CountDownLatch latch = new CountDownLatch(1);
    166         mMagnifier.setOnOperationCompleteCallback(latch::countDown);
    167 
    168         // Show the magnifier at the center of the activity.
    169         mActivityRule.runOnUiThread(() -> {
    170             mMagnifier.show(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
    171         });
    172         assertTrue(TIME_LIMIT_EXCEEDED, latch.await(1, TimeUnit.SECONDS));
    173 
    174         assertEquals(mMagnifier.getWidth(), mMagnifier.getContent().getWidth());
    175         assertEquals(mMagnifier.getHeight(), mMagnifier.getContent().getHeight());
    176         assertFourQuadrants(mMagnifier.getContent());
    177     }
    178 
    179     @Test
    180     public void testWindowPosition() throws Throwable {
    181         prepareFourQuadrantsScenario();
    182         final CountDownLatch latch = new CountDownLatch(1);
    183         mMagnifier.setOnOperationCompleteCallback(latch::countDown);
    184 
    185         // Show the magnifier at the center of the activity.
    186         mActivityRule.runOnUiThread(() -> {
    187             mMagnifier.show(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
    188         });
    189         assertTrue(TIME_LIMIT_EXCEEDED, latch.await(1, TimeUnit.SECONDS));
    190 
    191         // Assert that the magnifier position represents a valid rectangle on screen.
    192         final Rect position = mMagnifier.getWindowPositionOnScreen();
    193         assertFalse(position.isEmpty());
    194         assertTrue(0 <= position.left && position.right <= mLayout.getWidth());
    195         assertTrue(0 <= position.top && position.bottom <= mLayout.getHeight());
    196     }
    197 
    198     @Test
    199     public void testWindowContent_modifiesAfterUpdate() throws Throwable {
    200         prepareFourQuadrantsScenario();
    201 
    202         // Show the magnifier at the center of the activity.
    203         final CountDownLatch latchForShow = new CountDownLatch(1);
    204         mMagnifier.setOnOperationCompleteCallback(latchForShow::countDown);
    205         mActivityRule.runOnUiThread(() -> {
    206             mMagnifier.show(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
    207         });
    208         assertTrue(TIME_LIMIT_EXCEEDED, latchForShow.await(1, TimeUnit.SECONDS));
    209 
    210         final Bitmap initialBitmap = mMagnifier.getContent()
    211                 .copy(mMagnifier.getContent().getConfig(), true);
    212         assertFourQuadrants(initialBitmap);
    213 
    214         // Make the one quadrant white.
    215         final View quadrant1 =
    216                 mActivity.findViewById(R.id.magnifier_activity_four_quadrants_layout_quadrant_1);
    217         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, quadrant1, () -> {
    218             quadrant1.setBackground(null);
    219         });
    220 
    221         // Update the magnifier.
    222         final CountDownLatch latchForUpdate = new CountDownLatch(1);
    223         mMagnifier.setOnOperationCompleteCallback(latchForUpdate::countDown);
    224         mActivityRule.runOnUiThread(mMagnifier::update);
    225         assertTrue(TIME_LIMIT_EXCEEDED, latchForUpdate.await(1, TimeUnit.SECONDS));
    226 
    227         final Bitmap newBitmap = mMagnifier.getContent();
    228         assertFourQuadrants(newBitmap);
    229         assertFalse(newBitmap.sameAs(initialBitmap));
    230     }
    231 
    232     /**
    233      * Sets the activity to contain four equal quadrants coloured differently and
    234      * instantiates a magnifier. This method should not be called on the UI thread.
    235      */
    236     private void prepareFourQuadrantsScenario() throws Throwable {
    237         WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
    238             mActivity.setContentView(R.layout.magnifier_activity_four_quadrants_layout);
    239             mLayout = mActivity.findViewById(R.id.magnifier_activity_four_quadrants_layout);
    240             mMagnifier = new Magnifier(mLayout);
    241         }, false);
    242         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mLayout, null);
    243     }
    244 
    245     /**
    246      * Asserts that the current bitmap contains four different dominant colors, which
    247      * are (almost) equally distributed. The test takes into account an amount of
    248      * noise, possible consequence of upscaling and filtering the magnified content.
    249      *
    250      * @param bitmap the bitmap to be checked
    251      */
    252     private void assertFourQuadrants(final Bitmap bitmap) {
    253         final int expectedQuadrants = 4;
    254         final int totalPixels = bitmap.getWidth() * bitmap.getHeight();
    255 
    256         final Map<Integer, Integer> colorCount = new HashMap<>();
    257         for (int x = 0; x < bitmap.getWidth(); ++x) {
    258             for (int y = 0; y < bitmap.getHeight(); ++y) {
    259                 final int currentColor = bitmap.getPixel(x, y);
    260                 colorCount.put(currentColor, colorCount.getOrDefault(currentColor, 0) + 1);
    261             }
    262         }
    263         assertTrue(colorCount.size() >= expectedQuadrants);
    264 
    265         final List<Integer> counts = new ArrayList<>(colorCount.values());
    266         Collections.sort(counts);
    267 
    268         int quadrantsTotal = 0;
    269         for (int i = counts.size() - expectedQuadrants; i < counts.size(); ++i) {
    270             quadrantsTotal += counts.get(i);
    271         }
    272         assertTrue(1.0f * (totalPixels - quadrantsTotal) / totalPixels <= 0.1f);
    273 
    274         for (int i = counts.size() - expectedQuadrants; i < counts.size(); ++i) {
    275             final float proportion = 1.0f
    276                     * Math.abs(expectedQuadrants * counts.get(i) - quadrantsTotal) / quadrantsTotal;
    277             assertTrue(proportion <= 0.1f);
    278         }
    279     }
    280 }
    281