MagnificationControllerTest.java revision 192bb0bc54f6bb418f5778fe26eb2e68514290fb
1/*
2 * Copyright (C) 2016 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
17package com.android.server.accessibility;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertThat;
21import static org.junit.Assert.assertTrue;
22import static org.junit.Assert.assertFalse;
23import static org.mockito.Matchers.anyInt;
24import static org.mockito.Matchers.anyObject;
25import static org.mockito.Matchers.eq;
26import static org.mockito.Mockito.doAnswer;
27import static org.mockito.Mockito.mock;
28import static org.mockito.Mockito.times;
29import static org.mockito.Mockito.verify;
30import static org.mockito.Mockito.verifyNoMoreInteractions;
31import static org.mockito.Mockito.when;
32import static org.mockito.hamcrest.MockitoHamcrest.argThat;
33
34import android.animation.ValueAnimator;
35import android.content.BroadcastReceiver;
36import android.content.Context;
37import android.content.IntentFilter;
38import android.content.res.Resources;
39import android.graphics.PointF;
40import android.graphics.Rect;
41import android.graphics.Region;
42import android.os.Handler;
43import android.os.Looper;
44import android.os.Message;
45import android.support.test.runner.AndroidJUnit4;
46import android.view.MagnificationSpec;
47import android.view.WindowManagerInternal;
48import android.view.WindowManagerInternal.MagnificationCallbacks;
49
50import com.android.internal.R;
51import org.hamcrest.CoreMatchers;
52import org.hamcrest.Description;
53import org.hamcrest.TypeSafeMatcher;
54import org.junit.Before;
55import org.junit.BeforeClass;
56import org.junit.Test;
57import org.junit.runner.RunWith;
58import org.mockito.ArgumentCaptor;
59import org.mockito.Mockito;
60import org.mockito.invocation.InvocationOnMock;
61import org.mockito.stubbing.Answer;
62
63import java.util.Locale;
64
65@RunWith(AndroidJUnit4.class)
66public class MagnificationControllerTest {
67    static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 100, 200);
68    static final PointF INITIAL_MAGNIFICATION_BOUNDS_CENTER = new PointF(
69            INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY());
70    static final PointF INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER = new PointF(25, 50);
71    static final PointF INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(75, 150);
72    static final Rect OTHER_MAGNIFICATION_BOUNDS = new Rect(100, 200, 500, 600);
73    static final PointF OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(400, 500);
74    static final Region INITIAL_MAGNIFICATION_REGION = new Region(INITIAL_MAGNIFICATION_BOUNDS);
75    static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
76    static final int SERVICE_ID_1 = 1;
77    static final int SERVICE_ID_2 = 2;
78
79    final Context mMockContext = mock(Context.class);
80    final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
81    final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
82    final MessageCapturingHandler mMessageCapturingHandler =
83            new MessageCapturingHandler(new Handler.Callback() {
84        @Override
85        public boolean handleMessage(Message msg) {
86            return mMagnificationController.handleMessage(msg);
87        }
88    });
89    final ArgumentCaptor<MagnificationSpec> mMagnificationSpecCaptor =
90            ArgumentCaptor.forClass(MagnificationSpec.class);
91    final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
92    MagnificationController.SettingsBridge mMockSettingsBridge;
93
94
95    MagnificationController mMagnificationController;
96    ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
97
98    @BeforeClass
99    public static void oneTimeInitialization() {
100        if (Looper.myLooper() == null) {
101            Looper.prepare();
102        }
103    }
104
105    @Before
106    public void setUp() {
107        when(mMockContext.getMainLooper()).thenReturn(Looper.myLooper());
108        Resources mockResources = mock(Resources.class);
109        when(mMockContext.getResources()).thenReturn(mockResources);
110        when(mockResources.getInteger(R.integer.config_longAnimTime))
111                .thenReturn(1000);
112        mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
113        mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
114                mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
115                mMockSettingsBridge);
116
117        doAnswer(new Answer<Void>() {
118            @Override
119            public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
120                Object[] args = invocationOnMock.getArguments();
121                Region regionArg = (Region) args[0];
122                regionArg.set(INITIAL_MAGNIFICATION_REGION);
123                return null;
124            }
125        }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
126
127        ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
128                ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
129        verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
130        mTargetAnimationListener = listenerArgumentCaptor.getValue();
131        Mockito.reset(mMockValueAnimator); // Ignore other initialization
132    }
133
134    @Test
135    public void testRegister_WindowManagerAndContextRegisterListeners() {
136        mMagnificationController.register();
137        verify(mMockContext).registerReceiver(
138                (BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
139        verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
140        assertTrue(mMagnificationController.isRegisteredLocked());
141    }
142
143    @Test
144    public void testRegister_WindowManagerAndContextUnregisterListeners() {
145        mMagnificationController.register();
146        mMagnificationController.unregister();
147
148        verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
149        verify(mMockWindowManager).setMagnificationCallbacks(null);
150        assertFalse(mMagnificationController.isRegisteredLocked());
151    }
152
153    @Test
154    public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
155        mMagnificationController.register();
156        MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
157        Region initialMagRegion = new Region();
158        Rect initialBounds = new Rect();
159
160        assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
161        mMagnificationController.getMagnificationRegion(initialMagRegion);
162        mMagnificationController.getMagnificationBounds(initialBounds);
163        assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
164        assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
165        assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
166                mMagnificationController.getCenterX(), 0.0f);
167        assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
168                mMagnificationController.getCenterY(), 0.0f);
169    }
170
171    @Test
172    public void testNotRegistered_publicMethodsShouldBeBenign() {
173        assertFalse(mMagnificationController.isMagnifying());
174        assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
175        assertFalse(mMagnificationController.reset(true));
176        assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
177        assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
178        assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
179        assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
180
181        mMagnificationController.getMagnificationRegion(new Region());
182        mMagnificationController.getMagnificationBounds(new Rect());
183        mMagnificationController.getScale();
184        mMagnificationController.getOffsetX();
185        mMagnificationController.getOffsetY();
186        mMagnificationController.getCenterX();
187        mMagnificationController.getCenterY();
188        mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
189        mMagnificationController.unregister();
190    }
191
192    @Test
193    public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
194        mMagnificationController.register();
195        final float scale = 2.0f;
196        final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
197        final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
198        assertTrue(mMagnificationController
199                .setScale(scale, center.x, center.y, false, SERVICE_ID_1));
200
201        final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
202        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
203        assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
204        assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
205        assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
206        verify(mMockValueAnimator, times(0)).start();
207    }
208
209    @Test
210    public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
211        mMagnificationController.register();
212        MagnificationSpec startSpec = getCurrentMagnificationSpec();
213        float scale = 2.0f;
214        PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
215        assertTrue(mMagnificationController
216                .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
217
218        // New center should be halfway between original center and pivot
219        PointF newCenter = new PointF((pivotPoint.x + INITIAL_MAGNIFICATION_BOUNDS.centerX()) / 2,
220                (pivotPoint.y + INITIAL_MAGNIFICATION_BOUNDS.centerY()) / 2);
221        PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
222        MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
223
224        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
225        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
226        assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
227        verify(mMockValueAnimator).start();
228
229        // Initial value
230        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
231        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
232        verify(mMockWindowManager).setMagnificationSpec(startSpec);
233
234        // Intermediate point
235        Mockito.reset(mMockWindowManager);
236        float fraction = 0.5f;
237        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
238        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
239        verify(mMockWindowManager).setMagnificationSpec(
240                argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
241
242        // Final value
243        Mockito.reset(mMockWindowManager);
244        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
245        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
246        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
247    }
248
249    @Test
250    public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
251        mMagnificationController.register();
252        // First zoom in
253        float scale = 2.0f;
254        assertTrue(mMagnificationController.setScale(scale,
255                INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
256                false, SERVICE_ID_1));
257        Mockito.reset(mMockWindowManager);
258
259        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
260        assertTrue(mMagnificationController
261                .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
262        PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
263        MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
264
265        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
266        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
267        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
268        verify(mMockValueAnimator, times(0)).start();
269    }
270
271    @Test
272    public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
273        mMagnificationController.register();
274        MagnificationSpec startSpec = getCurrentMagnificationSpec();
275        float scale = 2.5f;
276        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
277        PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
278        MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
279
280        assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
281                true, SERVICE_ID_1));
282
283        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
284        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
285        assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
286        verify(mMockAms).notifyMagnificationChanged(
287                INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
288        verify(mMockValueAnimator).start();
289
290        // Initial value
291        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
292        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
293        verify(mMockWindowManager).setMagnificationSpec(startSpec);
294
295        // Intermediate point
296        Mockito.reset(mMockWindowManager);
297        float fraction = 0.33f;
298        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
299        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
300        verify(mMockWindowManager).setMagnificationSpec(
301                argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
302
303        // Final value
304        Mockito.reset(mMockWindowManager);
305        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
306        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
307        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
308    }
309
310    @Test
311    public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
312        mMagnificationController.register();
313        MagnificationSpec startSpec = getCurrentMagnificationSpec();
314        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
315        PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
316                MagnificationController.MAX_SCALE);
317        MagnificationSpec endSpec = getMagnificationSpec(
318                MagnificationController.MAX_SCALE, offsets);
319
320        assertTrue(mMagnificationController.setScaleAndCenter(
321                MagnificationController.MAX_SCALE + 1.0f,
322                newCenter.x, newCenter.y, false, SERVICE_ID_1));
323
324        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
325        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
326        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
327        Mockito.reset(mMockWindowManager);
328
329        // Verify that we can't zoom below 1x
330        assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
331                INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
332                false, SERVICE_ID_1));
333
334        assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
335                mMagnificationController.getCenterX(), 0.5);
336        assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
337                mMagnificationController.getCenterY(), 0.5);
338        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
339    }
340
341    @Test
342    public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
343        mMagnificationController.register();
344        float scale = 2.0f;
345
346        // Off the edge to the top and left
347        assertTrue(mMagnificationController.setScaleAndCenter(
348                scale, -100f, -200f, false, SERVICE_ID_1));
349
350        PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
351        PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
352        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
353        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
354        verify(mMockWindowManager).setMagnificationSpec(
355                argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
356        Mockito.reset(mMockWindowManager);
357
358        // Off the edge to the bottom and right
359        assertTrue(mMagnificationController.setScaleAndCenter(scale,
360                INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
361                false, SERVICE_ID_1));
362        newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
363        newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
364        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
365        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
366        verify(mMockWindowManager).setMagnificationSpec(
367                argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
368    }
369
370    @Test
371    public void testMagnificationRegionChanged_serviceNotified() {
372        mMagnificationController.register();
373        MagnificationCallbacks callbacks = getMagnificationCallbacks();
374        callbacks.onMagnificationRegionChanged(OTHER_REGION);
375        mMessageCapturingHandler.sendAllMessages();
376        verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
377                OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
378    }
379
380    @Test
381    public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
382        mMagnificationController.register();
383        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
384        float scale = 2.0f;
385        PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
386        // First zoom in
387        assertTrue(mMagnificationController
388                .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
389        Mockito.reset(mMockWindowManager);
390
391        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
392        PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
393        mMagnificationController.offsetMagnifiedRegion(
394                startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
395
396        MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
397        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
398        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
399        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
400        verify(mMockValueAnimator, times(0)).start();
401    }
402
403    @Test
404    public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
405        mMagnificationController.register();
406        Mockito.reset(mMockWindowManager);
407        MagnificationSpec startSpec = getCurrentMagnificationSpec();
408        mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
409        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
410        mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
411        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
412        verifyNoMoreInteractions(mMockWindowManager);
413    }
414
415    @Test
416    public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
417        mMagnificationController.register();
418        float scale = 2.0f;
419
420        // Upper left edges
421        PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
422        assertTrue(mMagnificationController
423                .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
424        Mockito.reset(mMockWindowManager);
425        MagnificationSpec ulSpec = getCurrentMagnificationSpec();
426        mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
427        assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
428        verifyNoMoreInteractions(mMockWindowManager);
429
430        // Lower right edges
431        PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
432        assertTrue(mMagnificationController
433                .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
434        Mockito.reset(mMockWindowManager);
435        MagnificationSpec lrSpec = getCurrentMagnificationSpec();
436        mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
437        assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
438        verifyNoMoreInteractions(mMockWindowManager);
439    }
440
441    @Test
442    public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
443        mMagnificationController.register();
444        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
445        assertTrue(mMagnificationController
446                .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
447        assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
448        assertTrue(mMagnificationController
449                .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
450        assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
451    }
452
453    @Test
454    public void testSetUserId_resetsOnlyIfIdChanges() {
455        final int userId1 = 1;
456        final int userId2 = 2;
457
458        mMagnificationController.register();
459        mMagnificationController.setUserId(userId1);
460        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
461        float scale = 2.0f;
462        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
463
464        mMagnificationController.setUserId(userId1);
465        assertTrue(mMagnificationController.isMagnifying());
466        mMagnificationController.setUserId(userId2);
467        assertFalse(mMagnificationController.isMagnifying());
468    }
469
470    @Test
471    public void testResetIfNeeded_doesWhatItSays() {
472        mMagnificationController.register();
473        zoomIn2xToMiddle();
474        assertTrue(mMagnificationController.resetIfNeeded(false));
475        verify(mMockAms).notifyMagnificationChanged(
476                eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyInt(), anyInt());
477        assertFalse(mMagnificationController.isMagnifying());
478        assertFalse(mMagnificationController.resetIfNeeded(false));
479    }
480
481    @Test
482    public void testTurnScreenOff_resetsMagnification() {
483        mMagnificationController.register();
484        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
485                ArgumentCaptor.forClass(BroadcastReceiver.class);
486        verify(mMockContext).registerReceiver(
487                broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
488        BroadcastReceiver br = broadcastReceiverCaptor.getValue();
489        zoomIn2xToMiddle();
490        br.onReceive(mMockContext, null);
491        mMessageCapturingHandler.sendAllMessages();
492        assertFalse(mMagnificationController.isMagnifying());
493    }
494
495    @Test
496    public void testUserContextChange_resetsMagnification() {
497        mMagnificationController.register();
498        MagnificationCallbacks callbacks = getMagnificationCallbacks();
499        zoomIn2xToMiddle();
500        callbacks.onUserContextChanged();
501        mMessageCapturingHandler.sendAllMessages();
502        assertFalse(mMagnificationController.isMagnifying());
503    }
504
505    @Test
506    public void testRotation_resetsMagnification() {
507        mMagnificationController.register();
508        MagnificationCallbacks callbacks = getMagnificationCallbacks();
509        zoomIn2xToMiddle();
510        mMessageCapturingHandler.sendAllMessages();
511        assertTrue(mMagnificationController.isMagnifying());
512        callbacks.onRotationChanged(0);
513        mMessageCapturingHandler.sendAllMessages();
514        assertFalse(mMagnificationController.isMagnifying());
515    }
516
517    @Test
518    public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
519        // Going from a small region to a large one leads to no issues
520        mMagnificationController.register();
521        zoomIn2xToMiddle();
522        MagnificationSpec startSpec = getCurrentMagnificationSpec();
523        MagnificationCallbacks callbacks = getMagnificationCallbacks();
524        Mockito.reset(mMockWindowManager);
525        callbacks.onMagnificationRegionChanged(OTHER_REGION);
526        mMessageCapturingHandler.sendAllMessages();
527        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
528        verifyNoMoreInteractions(mMockWindowManager);
529    }
530
531    @Test
532    public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
533        mMagnificationController.register();
534        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
535        float scale = 2.0f;
536        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
537        MagnificationSpec startSpec = getCurrentMagnificationSpec();
538        MagnificationCallbacks callbacks = getMagnificationCallbacks();
539        Mockito.reset(mMockWindowManager);
540        callbacks.onMagnificationRegionChanged(OTHER_REGION);
541        mMessageCapturingHandler.sendAllMessages();
542        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
543        verifyNoMoreInteractions(mMockWindowManager);
544    }
545
546    @Test
547    public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
548        // In a large region, pan to the farthest point possible
549        mMagnificationController.register();
550        MagnificationCallbacks callbacks = getMagnificationCallbacks();
551        callbacks.onMagnificationRegionChanged(OTHER_REGION);
552        mMessageCapturingHandler.sendAllMessages();
553        PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
554        float scale = 2.0f;
555        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
556        MagnificationSpec startSpec = getCurrentMagnificationSpec();
557        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
558        Mockito.reset(mMockWindowManager);
559
560        callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
561        mMessageCapturingHandler.sendAllMessages();
562
563        MagnificationSpec endSpec = getCurrentMagnificationSpec();
564        assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
565        PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
566                INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
567        assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
568        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
569    }
570
571    @Test
572    public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
573        mMagnificationController.register();
574        MagnificationCallbacks callbacks = getMagnificationCallbacks();
575        callbacks.onMagnificationRegionChanged(OTHER_REGION);
576        mMessageCapturingHandler.sendAllMessages();
577        PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
578        float scale = 2.0f;
579        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
580        MagnificationSpec startSpec = getCurrentMagnificationSpec();
581        when (mMockValueAnimator.isRunning()).thenReturn(true);
582
583        callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
584        mMessageCapturingHandler.sendAllMessages();
585        verify(mMockValueAnimator).cancel();
586
587        MagnificationSpec endSpec = getCurrentMagnificationSpec();
588        assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
589        PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
590                INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
591        assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
592        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
593    }
594
595    @Test
596    public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
597        mMagnificationController.register();
598        zoomIn2xToMiddle();
599        MagnificationSpec startSpec = getCurrentMagnificationSpec();
600        MagnificationCallbacks callbacks = getMagnificationCallbacks();
601        Mockito.reset(mMockWindowManager);
602        int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
603        int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
604        callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
605        mMessageCapturingHandler.sendAllMessages();
606        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
607        verifyNoMoreInteractions(mMockWindowManager);
608    }
609
610    @Test
611    public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
612        mMagnificationController.register();
613        zoomIn2xToMiddle();
614        MagnificationCallbacks callbacks = getMagnificationCallbacks();
615        Mockito.reset(mMockWindowManager);
616        callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
617        mMessageCapturingHandler.sendAllMessages();
618        MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
619        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
620        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
621    }
622
623    @Test
624    public void testRequestRectOnScreen_garbageInput_doesNothing() {
625        mMagnificationController.register();
626        zoomIn2xToMiddle();
627        MagnificationSpec startSpec = getCurrentMagnificationSpec();
628        MagnificationCallbacks callbacks = getMagnificationCallbacks();
629        Mockito.reset(mMockWindowManager);
630        callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
631        mMessageCapturingHandler.sendAllMessages();
632        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
633        verifyNoMoreInteractions(mMockWindowManager);
634    }
635
636
637    @Test
638    public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
639        Locale.setDefault(new Locale("en", "us"));
640        mMagnificationController.register();
641        zoomIn2xToMiddle();
642        MagnificationCallbacks callbacks = getMagnificationCallbacks();
643        MagnificationSpec startSpec = getCurrentMagnificationSpec();
644        Mockito.reset(mMockWindowManager);
645        Rect wideRect = new Rect(0, 50, 100, 51);
646        callbacks.onRectangleOnScreenRequested(
647                wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
648        mMessageCapturingHandler.sendAllMessages();
649        MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
650        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
651        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
652        Mockito.reset(mMockWindowManager);
653
654        // Repeat with RTL
655        Locale.setDefault(new Locale("he", "il"));
656        callbacks.onRectangleOnScreenRequested(
657                wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
658        mMessageCapturingHandler.sendAllMessages();
659        expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
660        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
661        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
662    }
663
664    @Test
665    public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
666        mMagnificationController.register();
667        zoomIn2xToMiddle();
668        MagnificationCallbacks callbacks = getMagnificationCallbacks();
669        MagnificationSpec startSpec = getCurrentMagnificationSpec();
670        Mockito.reset(mMockWindowManager);
671        Rect tallRect = new Rect(50, 0, 51, 100);
672        callbacks.onRectangleOnScreenRequested(
673                tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
674        mMessageCapturingHandler.sendAllMessages();
675        MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
676        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
677        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
678    }
679
680    @Test
681    public void testChangeMagnification_duringAnimation_animatesToNewValue() {
682        mMagnificationController.register();
683        MagnificationSpec startSpec = getCurrentMagnificationSpec();
684        float scale = 2.5f;
685        PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
686        MagnificationSpec firstEndSpec = getMagnificationSpec(
687                scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
688
689        assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
690                true, SERVICE_ID_1));
691
692        assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
693        assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
694        assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
695        verify(mMockValueAnimator, times(1)).start();
696
697        // Initial value
698        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
699        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
700        verify(mMockWindowManager).setMagnificationSpec(startSpec);
701        verify(mMockAms).notifyMagnificationChanged(
702                INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
703        Mockito.reset(mMockWindowManager);
704
705        // Intermediate point
706        float fraction = 0.33f;
707        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
708        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
709        MagnificationSpec intermediateSpec1 =
710                getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
711        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
712        Mockito.reset(mMockWindowManager);
713
714        PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
715        MagnificationSpec newEndSpec = getMagnificationSpec(
716                scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
717        assertTrue(mMagnificationController.setCenter(
718                newCenter.x, newCenter.y, true, SERVICE_ID_1));
719
720        // Animation should have been restarted
721        verify(mMockValueAnimator, times(2)).start();
722        verify(mMockAms).notifyMagnificationChanged(
723                INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
724
725        // New starting point should be where we left off
726        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
727        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
728        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
729        Mockito.reset(mMockWindowManager);
730
731        // Second intermediate point
732        fraction = 0.5f;
733        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
734        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
735        verify(mMockWindowManager).setMagnificationSpec(
736                argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
737        Mockito.reset(mMockWindowManager);
738
739        // Final value should be the new center
740        Mockito.reset(mMockWindowManager);
741        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
742        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
743        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
744    }
745
746    private void zoomIn2xToMiddle() {
747        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
748        float scale = 2.0f;
749        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
750        assertTrue(mMagnificationController.isMagnifying());
751    }
752
753    private MagnificationCallbacks getMagnificationCallbacks() {
754        ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
755                ArgumentCaptor.forClass(MagnificationCallbacks.class);
756        verify(mMockWindowManager)
757                .setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
758        return magnificationCallbacksCaptor.getValue();
759    }
760
761    private PointF computeOffsets(Rect magnifiedBounds, PointF center, float scale) {
762        return new PointF(
763                magnifiedBounds.centerX() - scale * center.x,
764                magnifiedBounds.centerY() - scale * center.y);
765    }
766
767    private MagnificationSpec getInterpolatedMagSpec(MagnificationSpec start, MagnificationSpec end,
768            float fraction) {
769        MagnificationSpec interpolatedSpec = MagnificationSpec.obtain();
770        interpolatedSpec.scale = start.scale + fraction * (end.scale - start.scale);
771        interpolatedSpec.offsetX = start.offsetX + fraction * (end.offsetX - start.offsetX);
772        interpolatedSpec.offsetY = start.offsetY + fraction * (end.offsetY - start.offsetY);
773        return interpolatedSpec;
774    }
775
776    private MagnificationSpec getMagnificationSpec(float scale, PointF offsets) {
777        return getMagnificationSpec(scale, offsets.x, offsets.y);
778    }
779
780    private MagnificationSpec getMagnificationSpec(float scale, float offsetX, float offsetY) {
781        MagnificationSpec spec = MagnificationSpec.obtain();
782        spec.scale = scale;
783        spec.offsetX = offsetX;
784        spec.offsetY = offsetY;
785        return spec;
786    }
787
788    private MagnificationSpec getCurrentMagnificationSpec() {
789        return getMagnificationSpec(mMagnificationController.getScale(),
790                mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
791    }
792
793    private MagSpecMatcher closeTo(MagnificationSpec spec) {
794        return new MagSpecMatcher(spec, 0.01f, 0.5f);
795    }
796
797    private class MagSpecMatcher extends TypeSafeMatcher<MagnificationSpec> {
798        final MagnificationSpec mMagSpec;
799        final float mScaleTolerance;
800        final float mOffsetTolerance;
801
802        MagSpecMatcher(MagnificationSpec spec, float scaleTolerance, float offsetTolerance) {
803            mMagSpec = spec;
804            mScaleTolerance = scaleTolerance;
805            mOffsetTolerance = offsetTolerance;
806        }
807
808        @Override
809        protected boolean matchesSafely(MagnificationSpec magnificationSpec) {
810            if (Math.abs(mMagSpec.scale - magnificationSpec.scale) > mScaleTolerance) {
811                return false;
812            }
813            if (Math.abs(mMagSpec.offsetX - magnificationSpec.offsetX) > mOffsetTolerance) {
814                return false;
815            }
816            if (Math.abs(mMagSpec.offsetY - magnificationSpec.offsetY) > mOffsetTolerance) {
817                return false;
818            }
819            return true;
820        }
821
822        @Override
823        public void describeTo(Description description) {
824            description.appendText("Match spec: " + mMagSpec);
825        }
826    }
827}
828