-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ShadowMagnificationController for N+
A single instance of MagnificationController exists per AccessibilityService. It abstracts over sending commands to the framework over Binder. These commands allow the client to magnify areas of the screen, and to query if the screen is currently being magnified. Before this shadow existed, using MagnificationController from Robolectric would have no visible effects. i.e., if you were to call .setScale(n, animate) to modify the magnification scale, then calling .getScale() would not return n: it would instead return the default scale (1f). This shadow keeps internal magnification state, so that the APIs behave as expected. Currently an assumption baked into this code is that only one AccessibilityService is active at once. This should be a safe assumption in general, as there are various disadvantages to having multiple services in an apk (from both the performance perspective, but also from user convenience, as each service has to be activated separately, from a deeply buried Settings page). PiperOrigin-RevId: 350651958
- Loading branch information
1 parent
ff49c49
commit 10fa97d
Showing
2 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
195 changes: 195 additions & 0 deletions
195
robolectric/src/test/java/org/robolectric/shadows/ShadowMagnificationControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
package org.robolectric.shadows; | ||
|
||
import static android.os.Build.VERSION_CODES.N; | ||
import static com.google.common.truth.Truth.assertThat; | ||
import static org.robolectric.Shadows.shadowOf; | ||
|
||
import android.accessibilityservice.AccessibilityService; | ||
import android.accessibilityservice.AccessibilityService.MagnificationController; | ||
import android.graphics.Region; | ||
import android.os.Looper; | ||
import android.view.accessibility.AccessibilityEvent; | ||
import androidx.test.ext.junit.runners.AndroidJUnit4; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.robolectric.Robolectric; | ||
import org.robolectric.annotation.Config; | ||
|
||
/** Test for ShadowMagnificationController. */ | ||
@RunWith(AndroidJUnit4.class) | ||
@Config(minSdk = N) | ||
public final class ShadowMagnificationControllerTest { | ||
|
||
private MyService myService; | ||
private MagnificationController magnificationController; | ||
|
||
@Before | ||
public void setUp() { | ||
myService = Robolectric.setupService(MyService.class); | ||
magnificationController = myService.getMagnificationController(); | ||
} | ||
|
||
@Test | ||
public void getCenterX_byDefault_returns0() { | ||
assertThat(magnificationController.getCenterX()).isEqualTo(0.0f); | ||
} | ||
|
||
@Test | ||
public void getCenterY_byDefault_returns0() { | ||
assertThat(magnificationController.getCenterY()).isEqualTo(0.0f); | ||
} | ||
|
||
@Test | ||
public void getScale_byDefault_returns1() { | ||
assertThat(magnificationController.getScale()).isEqualTo(1.0f); | ||
} | ||
|
||
@Test | ||
public void setCenter_setsCenterX() { | ||
float newCenterX = 450.0f; | ||
|
||
magnificationController.setCenter(newCenterX, /*centerY=*/ 0.0f, /*animate=*/ false); | ||
|
||
assertThat(magnificationController.getCenterX()).isEqualTo(newCenterX); | ||
} | ||
|
||
@Test | ||
public void setCenter_setsCenterY() { | ||
float newCenterY = 250.0f; | ||
|
||
magnificationController.setCenter(/*centerX=*/ 0.0f, newCenterY, /*animate=*/ false); | ||
|
||
assertThat(magnificationController.getCenterY()).isEqualTo(newCenterY); | ||
} | ||
|
||
@Test | ||
public void setCenter_notifiesListener() { | ||
float centerX = 55f; | ||
float centerY = 22.5f; | ||
TestListener testListener = new TestListener(); | ||
magnificationController.addListener(testListener); | ||
|
||
magnificationController.setCenter(centerX, centerY, /*animate=*/ false); | ||
|
||
shadowOf(Looper.getMainLooper()).idle(); | ||
assertThat(testListener.invoked).isTrue(); | ||
assertThat(testListener.centerX).isEqualTo(centerX); | ||
assertThat(testListener.centerY).isEqualTo(centerY); | ||
} | ||
|
||
@Test | ||
public void setScale_setsScale() { | ||
float newScale = 5.0f; | ||
|
||
magnificationController.setScale(newScale, /*animate=*/ false); | ||
|
||
assertThat(magnificationController.getScale()).isEqualTo(newScale); | ||
} | ||
|
||
@Test | ||
public void setScale_notifiesListener() { | ||
float scale = 5.0f; | ||
TestListener testListener = new TestListener(); | ||
magnificationController.addListener(testListener); | ||
|
||
magnificationController.setScale(scale, /*animate=*/ false); | ||
|
||
shadowOf(Looper.getMainLooper()).idle(); | ||
assertThat(testListener.invoked).isTrue(); | ||
assertThat(testListener.scale).isEqualTo(scale); | ||
} | ||
|
||
@Test | ||
public void reset_resetsCenterX() { | ||
magnificationController.setCenter(/*centerX=*/ 100.0f, /*centerY=*/ 0.0f, /*animate=*/ false); | ||
|
||
magnificationController.reset(/*animate=*/ false); | ||
|
||
assertThat(magnificationController.getCenterX()).isEqualTo(0.0f); | ||
} | ||
|
||
@Test | ||
public void reset_resetsCenterY() { | ||
magnificationController.setCenter(/*centerX=*/ 0.0f, /*centerY=*/ 100.0f, /*animate=*/ false); | ||
|
||
magnificationController.reset(/*animate=*/ false); | ||
|
||
assertThat(magnificationController.getCenterY()).isEqualTo(0.0f); | ||
} | ||
|
||
@Test | ||
public void reset_resetsScale() { | ||
magnificationController.setScale(5.0f, /*animate=*/ false); | ||
|
||
magnificationController.reset(/*animate=*/ false); | ||
|
||
assertThat(magnificationController.getScale()).isEqualTo(1.0f); | ||
} | ||
|
||
@Test | ||
public void reset_notifiesListener() { | ||
magnificationController.setCenter(/*centerX=*/ 150.5f, /*centerY=*/ 11.5f, /*animate=*/ false); | ||
magnificationController.setScale(/*scale=*/ 5.0f, /*animate=*/ false); | ||
TestListener testListener = new TestListener(); | ||
magnificationController.addListener(testListener); | ||
|
||
magnificationController.reset(/*animate=*/ false); | ||
|
||
shadowOf(Looper.getMainLooper()).idle(); | ||
assertThat(testListener.invoked).isTrue(); | ||
assertThat(testListener.centerX).isEqualTo(0.0f); | ||
assertThat(testListener.centerY).isEqualTo(0.0f); | ||
assertThat(testListener.scale).isEqualTo(1.0f); | ||
} | ||
|
||
@Test | ||
public void removeListener_removesListener() { | ||
float scale = 5.0f; | ||
TestListener testListener = new TestListener(); | ||
magnificationController.addListener(testListener); | ||
|
||
magnificationController.removeListener(testListener); | ||
|
||
magnificationController.setScale(scale, /*animate=*/ false); | ||
shadowOf(Looper.getMainLooper()).idle(); | ||
assertThat(testListener.invoked).isFalse(); | ||
} | ||
|
||
/** Test OnMagnificationChangedListener that records when it's invoked. */ | ||
private static class TestListener | ||
implements MagnificationController.OnMagnificationChangedListener { | ||
|
||
private boolean invoked = false; | ||
private float scale = -1f; | ||
private float centerX = -1f; | ||
private float centerY = -1f; | ||
|
||
@Override | ||
public void onMagnificationChanged( | ||
MagnificationController controller, | ||
Region region, | ||
float scale, | ||
float centerX, | ||
float centerY) { | ||
this.invoked = true; | ||
this.scale = scale; | ||
this.centerX = centerX; | ||
this.centerY = centerY; | ||
} | ||
} | ||
|
||
/** Empty implementation of AccessibilityService, for test purposes. */ | ||
private static class MyService extends AccessibilityService { | ||
|
||
@Override | ||
public void onAccessibilityEvent(AccessibilityEvent arg0) { | ||
// Do nothing | ||
} | ||
|
||
@Override | ||
public void onInterrupt() { | ||
// Do nothing | ||
} | ||
} | ||
} |
106 changes: 106 additions & 0 deletions
106
shadows/framework/src/main/java/org/robolectric/shadows/ShadowMagnificationController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package org.robolectric.shadows; | ||
|
||
import static android.os.Build.VERSION_CODES.N; | ||
|
||
import android.accessibilityservice.AccessibilityService.MagnificationController; | ||
import android.graphics.Region; | ||
import android.os.Handler; | ||
import android.os.Looper; | ||
import java.util.HashMap; | ||
import org.robolectric.annotation.Implementation; | ||
import org.robolectric.annotation.Implements; | ||
import org.robolectric.annotation.RealObject; | ||
|
||
/** Shadow of MagnificationController. */ | ||
@Implements(value = MagnificationController.class, minSdk = N) | ||
public class ShadowMagnificationController { | ||
|
||
private static final float DEFAULT_CENTER_X = 0.0f; | ||
private static final float DEFAULT_CENTER_Y = 0.0f; | ||
private static final float DEFAULT_SCALE = 1.0f; | ||
|
||
@RealObject private MagnificationController realObject; | ||
|
||
private final HashMap<MagnificationController.OnMagnificationChangedListener, Handler> listeners = | ||
new HashMap<>(); | ||
|
||
private final Region magnificationRegion = new Region(); | ||
private float centerX = DEFAULT_CENTER_X; | ||
private float centerY = DEFAULT_CENTER_Y; | ||
private float scale = DEFAULT_SCALE; | ||
|
||
@Implementation | ||
protected void addListener( | ||
MagnificationController.OnMagnificationChangedListener listener, Handler handler) { | ||
listeners.put(listener, handler); | ||
} | ||
|
||
@Implementation | ||
protected void addListener(MagnificationController.OnMagnificationChangedListener listener) { | ||
addListener(listener, new Handler(Looper.getMainLooper())); | ||
} | ||
|
||
@Implementation | ||
protected float getCenterX() { | ||
return centerX; | ||
} | ||
|
||
@Implementation | ||
protected float getCenterY() { | ||
return centerY; | ||
} | ||
|
||
@Implementation | ||
protected Region getMagnificationRegion() { | ||
return magnificationRegion; | ||
} | ||
|
||
@Implementation | ||
protected float getScale() { | ||
return scale; | ||
} | ||
|
||
@Implementation | ||
protected boolean removeListener( | ||
MagnificationController.OnMagnificationChangedListener listener) { | ||
if (!listeners.containsKey(listener)) { | ||
return false; | ||
} | ||
listeners.remove(listener); | ||
return true; | ||
} | ||
|
||
@Implementation | ||
protected boolean reset(boolean animate) { | ||
centerX = DEFAULT_CENTER_X; | ||
centerY = DEFAULT_CENTER_Y; | ||
scale = DEFAULT_SCALE; | ||
notifyListeners(); | ||
return true; | ||
} | ||
|
||
@Implementation | ||
protected boolean setCenter(float centerX, float centerY, boolean animate) { | ||
this.centerX = centerX; | ||
this.centerY = centerY; | ||
notifyListeners(); | ||
return true; | ||
} | ||
|
||
@Implementation | ||
protected boolean setScale(float scale, boolean animate) { | ||
this.scale = scale; | ||
notifyListeners(); | ||
return true; | ||
} | ||
|
||
private void notifyListeners() { | ||
for (MagnificationController.OnMagnificationChangedListener listener : listeners.keySet()) { | ||
Handler handler = listeners.get(listener); | ||
handler.post( | ||
() -> | ||
listener.onMagnificationChanged( | ||
realObject, magnificationRegion, scale, centerX, centerY)); | ||
} | ||
} | ||
} |