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