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