Add tests for MagnificationController.
Also refactoring the class to make it easier to test and chaning behavior where the current behavior seemed poorly defined. Refactoring: - Combined all handlers into one. - Simplified animation to use a ValueAnimator. - Eliminated ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE setting. Move rest of settings reading into mockable class. - Move callbacks from WindowManager into the main class. - Pulled out my instrumented Handler from the MotionEventInjectorTest into its own class so I can reuse it. Behavior changes: - Always constraining out-of-bounds values rather than refusing to change them. - Constraining offsets on bounds changes. We previously left them alone, even if they were out of bounds. - Keeping track of the animation starting point. We were interpolating between the current magnification spec and the final one. This change means the magnification animates to a different profile. Test: This CL adds tests. I've also run a11y CTS. Bugs: 31855954, 30325691 Change-Id: Ie00e29ae88b75d9fe1016f9d107257c9cf6425bb
This commit is contained in:
@@ -5161,18 +5161,10 @@ public final class Settings {
|
||||
"accessibility_display_magnification_scale";
|
||||
|
||||
/**
|
||||
* Setting that specifies whether the display magnification should be
|
||||
* automatically updated. If this fearture is enabled the system will
|
||||
* exit magnification mode or pan the viewport when a context change
|
||||
* occurs. For example, on staring a new activity or rotating the screen,
|
||||
* the system may zoom out so the user can see the new context he is in.
|
||||
* Another example is on showing a window that is not visible in the
|
||||
* magnified viewport the system may pan the viewport to make the window
|
||||
* the has popped up so the user knows that the context has changed.
|
||||
* Whether a screen magnification is performed is controlled by
|
||||
* {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
|
||||
* Unused mangnification setting
|
||||
*
|
||||
* @hide
|
||||
* @deprecated
|
||||
*/
|
||||
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE =
|
||||
"accessibility_display_magnification_auto_update";
|
||||
@@ -6485,7 +6477,6 @@ public final class Settings {
|
||||
ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
||||
ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
|
||||
ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
|
||||
ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
|
||||
ACCESSIBILITY_SCRIPT_INJECTION,
|
||||
ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
|
||||
ENABLED_ACCESSIBILITY_SERVICES,
|
||||
|
||||
@@ -1310,10 +1310,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
|
||||
loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
|
||||
R.fraction.def_accessibility_display_magnification_scale, 1);
|
||||
stmt.close();
|
||||
stmt = db.compileStatement("INSERT INTO secure(name,value) VALUES(?,?);");
|
||||
loadBooleanSetting(stmt,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
|
||||
R.bool.def_accessibility_display_magnification_auto_update);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
@@ -2508,10 +2504,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
|
||||
loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
|
||||
R.fraction.def_accessibility_display_magnification_scale, 1);
|
||||
|
||||
loadBooleanSetting(stmt,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
|
||||
R.bool.def_accessibility_display_magnification_auto_update);
|
||||
|
||||
loadBooleanSetting(stmt, Settings.Secure.USER_SETUP_COMPLETE,
|
||||
R.bool.def_user_setup_complete);
|
||||
|
||||
|
||||
@@ -2137,7 +2137,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
private final class UpgradeController {
|
||||
private static final int SETTINGS_VERSION = 134;
|
||||
private static final int SETTINGS_VERSION = 135;
|
||||
|
||||
private final int mUserId;
|
||||
|
||||
@@ -2507,6 +2507,14 @@ public class SettingsProvider extends ContentProvider {
|
||||
currentVersion = 134;
|
||||
}
|
||||
|
||||
if (currentVersion == 134) {
|
||||
// Remove setting that specifies if magnification values should be preserved.
|
||||
// This setting defaulted to true and never has a UI.
|
||||
getSecureSettingsLocked(userId).deleteSettingLocked(
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE);
|
||||
currentVersion = 135;
|
||||
}
|
||||
|
||||
if (currentVersion != newVersion) {
|
||||
Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
|
||||
+ newVersion + " left it at "
|
||||
|
||||
@@ -827,9 +827,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
|
||||
* @param centerX the new screen-relative center X coordinate
|
||||
* @param centerY the new screen-relative center Y coordinate
|
||||
*/
|
||||
void notifyMagnificationChanged(@NonNull Region region,
|
||||
public void notifyMagnificationChanged(@NonNull Region region,
|
||||
float scale, float centerX, float centerY) {
|
||||
synchronized (mLock) {
|
||||
notifyClearAccessibilityCacheLocked();
|
||||
notifyMagnificationChangedLocked(region, scale, centerX, centerY);
|
||||
}
|
||||
}
|
||||
@@ -899,10 +900,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
|
||||
mSecurityPolicy.onTouchInteractionEnd();
|
||||
}
|
||||
|
||||
void onMagnificationStateChanged() {
|
||||
notifyClearAccessibilityCacheLocked();
|
||||
}
|
||||
|
||||
private void switchUser(int userId) {
|
||||
synchronized (mLock) {
|
||||
if (mCurrentUserId == userId && mInitialized) {
|
||||
|
||||
@@ -21,8 +21,6 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.TypeEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -34,12 +32,10 @@ import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.MathUtils;
|
||||
import android.util.Property;
|
||||
import android.util.Slog;
|
||||
import android.view.MagnificationSpec;
|
||||
import android.view.View;
|
||||
@@ -53,27 +49,29 @@ import java.util.Locale;
|
||||
* from the accessibility manager and related classes. It is responsible for
|
||||
* holding the current state of magnification and animation, and it handles
|
||||
* communication between the accessibility manager and window manager.
|
||||
*
|
||||
* Magnification is limited to the range [MIN_SCALE, MAX_SCALE], and can only occur inside the
|
||||
* magnification region. If a value is out of bounds, it will be adjusted to guarantee these
|
||||
* constraints.
|
||||
*/
|
||||
class MagnificationController {
|
||||
class MagnificationController implements Handler.Callback {
|
||||
private static final String LOG_TAG = "MagnificationController";
|
||||
|
||||
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
|
||||
public static final float MIN_SCALE = 1.0f;
|
||||
public static final float MAX_SCALE = 5.0f;
|
||||
|
||||
private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
|
||||
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
|
||||
|
||||
private static final int INVALID_ID = -1;
|
||||
|
||||
private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
|
||||
|
||||
private static final float MIN_SCALE = 1.0f;
|
||||
private static final float MAX_SCALE = 5.0f;
|
||||
|
||||
/**
|
||||
* The minimum scaling factor that can be persisted to secure settings.
|
||||
* This must be > 1.0 to ensure that magnification is actually set to an
|
||||
* enabled state when the scaling factor is restored from settings.
|
||||
*/
|
||||
private static final float MIN_PERSISTED_SCALE = 2.0f;
|
||||
// Messages
|
||||
private static final int MSG_SEND_SPEC_TO_ANIMATION = 1;
|
||||
private static final int MSG_SCREEN_TURNED_OFF = 2;
|
||||
private static final int MSG_ON_MAGNIFIED_BOUNDS_CHANGED = 3;
|
||||
private static final int MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED = 4;
|
||||
private static final int MSG_ON_USER_CONTEXT_CHANGED = 5;
|
||||
|
||||
private final Object mLock;
|
||||
|
||||
@@ -90,46 +88,95 @@ class MagnificationController {
|
||||
private final Rect mTempRect1 = new Rect();
|
||||
|
||||
private final AccessibilityManagerService mAms;
|
||||
private final ContentResolver mContentResolver;
|
||||
|
||||
private final SettingsBridge mSettingsBridge;
|
||||
|
||||
private final ScreenStateObserver mScreenStateObserver;
|
||||
private final WindowStateObserver mWindowStateObserver;
|
||||
|
||||
private final SpecAnimationBridge mSpecAnimationBridge;
|
||||
|
||||
private final WindowManagerInternal.MagnificationCallbacks mWMCallbacks =
|
||||
new WindowManagerInternal.MagnificationCallbacks () {
|
||||
@Override
|
||||
public void onMagnificationRegionChanged(Region region) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = Region.obtain(region);
|
||||
mHandler.obtainMessage(MSG_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.argi1 = left;
|
||||
args.argi2 = top;
|
||||
args.argi3 = right;
|
||||
args.argi4 = bottom;
|
||||
mHandler.obtainMessage(MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED, args)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotationChanged(int rotation) {
|
||||
// Treat as context change and reset
|
||||
mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserContextChanged() {
|
||||
mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED);
|
||||
}
|
||||
};
|
||||
|
||||
private int mUserId;
|
||||
|
||||
private final long mMainThreadId;
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
private int mIdOfLastServiceToMagnify = INVALID_ID;
|
||||
|
||||
private final WindowManagerInternal mWindowManager;
|
||||
|
||||
// Flag indicating that we are registered with window manager.
|
||||
private boolean mRegistered;
|
||||
|
||||
private boolean mUnregisterPending;
|
||||
|
||||
public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
|
||||
this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class),
|
||||
new ValueAnimator(), new SettingsBridge(context.getContentResolver()));
|
||||
mHandler = new Handler(context.getMainLooper(), this);
|
||||
}
|
||||
|
||||
public MagnificationController(Context context, AccessibilityManagerService ams, Object lock,
|
||||
Handler handler, WindowManagerInternal windowManagerInternal,
|
||||
ValueAnimator valueAnimator, SettingsBridge settingsBridge) {
|
||||
mHandler = handler;
|
||||
mWindowManager = windowManagerInternal;
|
||||
mMainThreadId = context.getMainLooper().getThread().getId();
|
||||
mAms = ams;
|
||||
mContentResolver = context.getContentResolver();
|
||||
mScreenStateObserver = new ScreenStateObserver(context, this);
|
||||
mWindowStateObserver = new WindowStateObserver(context, this);
|
||||
mLock = lock;
|
||||
mSpecAnimationBridge = new SpecAnimationBridge(context, mLock);
|
||||
mSpecAnimationBridge = new SpecAnimationBridge(
|
||||
context, mLock, mWindowManager, valueAnimator);
|
||||
mSettingsBridge = settingsBridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start tracking the magnification region for services that control magnification and the
|
||||
* magnification gesture handler.
|
||||
*
|
||||
* This tracking imposes a cost on the system, so we avoid tracking this data
|
||||
* unless it's required.
|
||||
* This tracking imposes a cost on the system, so we avoid tracking this data unless it's
|
||||
* required.
|
||||
*/
|
||||
public void register() {
|
||||
synchronized (mLock) {
|
||||
if (!mRegistered) {
|
||||
mScreenStateObserver.register();
|
||||
mWindowStateObserver.register();
|
||||
mWindowManager.setMagnificationCallbacks(mWMCallbacks);
|
||||
mSpecAnimationBridge.setEnabled(true);
|
||||
// Obtain initial state.
|
||||
mWindowStateObserver.getMagnificationRegion(mMagnificationRegion);
|
||||
mWindowManager.getMagnificationRegion(mMagnificationRegion);
|
||||
mMagnificationRegion.getBounds(mMagnificationBounds);
|
||||
mRegistered = true;
|
||||
}
|
||||
@@ -164,7 +211,7 @@ class MagnificationController {
|
||||
if (mRegistered) {
|
||||
mSpecAnimationBridge.setEnabled(false);
|
||||
mScreenStateObserver.unregister();
|
||||
mWindowStateObserver.unregister();
|
||||
mWindowManager.setMagnificationCallbacks(null);
|
||||
mMagnificationRegion.setEmpty();
|
||||
mRegistered = false;
|
||||
}
|
||||
@@ -183,40 +230,22 @@ class MagnificationController {
|
||||
* Update our copy of the current magnification region
|
||||
*
|
||||
* @param magnified the magnified region
|
||||
* @param updateSpec {@code true} to update the scale and center based on
|
||||
* the region bounds, {@code false} to leave them as-is
|
||||
*/
|
||||
private void onMagnificationRegionChanged(Region magnified, boolean updateSpec) {
|
||||
private void onMagnificationRegionChanged(Region magnified) {
|
||||
synchronized (mLock) {
|
||||
if (!mRegistered) {
|
||||
// Don't update if we've unregistered
|
||||
return;
|
||||
}
|
||||
boolean magnificationChanged = false;
|
||||
boolean boundsChanged = false;
|
||||
|
||||
if (!mMagnificationRegion.equals(magnified)) {
|
||||
mMagnificationRegion.set(magnified);
|
||||
mMagnificationRegion.getBounds(mMagnificationBounds);
|
||||
boundsChanged = true;
|
||||
}
|
||||
if (updateSpec) {
|
||||
final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
|
||||
final float scale = sentSpec.scale;
|
||||
final float offsetX = sentSpec.offsetX;
|
||||
final float offsetY = sentSpec.offsetY;
|
||||
|
||||
// Compute the new center and update spec as needed.
|
||||
final float centerX = (mMagnificationBounds.width() / 2.0f
|
||||
+ mMagnificationBounds.left - offsetX) / scale;
|
||||
final float centerY = (mMagnificationBounds.height() / 2.0f
|
||||
+ mMagnificationBounds.top - offsetY) / scale;
|
||||
magnificationChanged = setScaleAndCenterLocked(
|
||||
scale, centerX, centerY, false, INVALID_ID);
|
||||
}
|
||||
|
||||
// If magnification changed we already notified for the change.
|
||||
if (boundsChanged && updateSpec && !magnificationChanged) {
|
||||
// It's possible that our magnification spec is invalid with the new bounds.
|
||||
// Adjust the current spec's offsets if necessary.
|
||||
if (updateCurrentSpecWithOffsetsLocked(
|
||||
mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
|
||||
sendSpecToAnimation(mCurrentMagnificationSpec, false);
|
||||
}
|
||||
onMagnificationChangedLocked();
|
||||
}
|
||||
}
|
||||
@@ -328,7 +357,7 @@ class MagnificationController {
|
||||
*
|
||||
* @return the scale currently used by the window manager
|
||||
*/
|
||||
public float getSentScale() {
|
||||
private float getSentScale() {
|
||||
return mSpecAnimationBridge.mSentMagnificationSpec.scale;
|
||||
}
|
||||
|
||||
@@ -339,7 +368,7 @@ class MagnificationController {
|
||||
*
|
||||
* @return the X offset currently used by the window manager
|
||||
*/
|
||||
public float getSentOffsetX() {
|
||||
private float getSentOffsetX() {
|
||||
return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
|
||||
}
|
||||
|
||||
@@ -350,7 +379,7 @@ class MagnificationController {
|
||||
*
|
||||
* @return the Y offset currently used by the window manager
|
||||
*/
|
||||
public float getSentOffsetY() {
|
||||
private float getSentOffsetY() {
|
||||
return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
|
||||
}
|
||||
|
||||
@@ -380,7 +409,7 @@ class MagnificationController {
|
||||
onMagnificationChangedLocked();
|
||||
}
|
||||
mIdOfLastServiceToMagnify = INVALID_ID;
|
||||
mSpecAnimationBridge.updateSentSpec(spec, animate);
|
||||
sendSpecToAnimation(spec, animate);
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -475,7 +504,7 @@ class MagnificationController {
|
||||
private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
|
||||
boolean animate, int id) {
|
||||
final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
|
||||
mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
|
||||
sendSpecToAnimation(mCurrentMagnificationSpec, animate);
|
||||
if (isMagnifying() && (id != INVALID_ID)) {
|
||||
mIdOfLastServiceToMagnify = id;
|
||||
}
|
||||
@@ -483,27 +512,28 @@ class MagnificationController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Offsets the center of the magnified region.
|
||||
* Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
|
||||
* opposite direction as the offsets passed in here.
|
||||
*
|
||||
* @param offsetX the amount in pixels to offset the X center
|
||||
* @param offsetY the amount in pixels to offset the Y center
|
||||
* @param offsetX the amount in pixels to offset the region in the X direction, in current
|
||||
* screen pixels.
|
||||
* @param offsetY the amount in pixels to offset the region in the Y direction, in current
|
||||
* screen pixels.
|
||||
* @param id the ID of the service requesting the change
|
||||
*/
|
||||
public void offsetMagnifiedRegionCenter(float offsetX, float offsetY, int id) {
|
||||
public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
|
||||
synchronized (mLock) {
|
||||
if (!mRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MagnificationSpec currSpec = mCurrentMagnificationSpec;
|
||||
final float nonNormOffsetX = currSpec.offsetX - offsetX;
|
||||
currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
|
||||
final float nonNormOffsetY = currSpec.offsetY - offsetY;
|
||||
currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
|
||||
final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
|
||||
final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
|
||||
updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
|
||||
if (id != INVALID_ID) {
|
||||
mIdOfLastServiceToMagnify = id;
|
||||
}
|
||||
mSpecAnimationBridge.updateSentSpec(currSpec, false);
|
||||
sendSpecToAnimation(mCurrentMagnificationSpec, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,7 +547,6 @@ class MagnificationController {
|
||||
}
|
||||
|
||||
private void onMagnificationChangedLocked() {
|
||||
mAms.onMagnificationStateChanged();
|
||||
mAms.notifyMagnificationChanged(mMagnificationRegion,
|
||||
getScale(), getCenterX(), getCenterY());
|
||||
if (mUnregisterPending && !isMagnifying()) {
|
||||
@@ -535,8 +564,7 @@ class MagnificationController {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
Settings.Secure.putFloatForUser(mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
|
||||
mSettingsBridge.putMagnificationScale(scale, userId);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
@@ -550,9 +578,7 @@ class MagnificationController {
|
||||
* scale if none is available
|
||||
*/
|
||||
public float getPersistedScale() {
|
||||
return Settings.Secure.getFloatForUser(mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
|
||||
DEFAULT_MAGNIFICATION_SCALE, mUserId);
|
||||
return mSettingsBridge.getMagnificationScale(mUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,36 +604,20 @@ class MagnificationController {
|
||||
scale = getScale();
|
||||
}
|
||||
|
||||
// Ensure requested center is within the magnification region.
|
||||
if (!magnificationRegionContains(centerX, centerY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute changes.
|
||||
final MagnificationSpec currSpec = mCurrentMagnificationSpec;
|
||||
boolean changed = false;
|
||||
|
||||
final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
|
||||
if (Float.compare(currSpec.scale, normScale) != 0) {
|
||||
currSpec.scale = normScale;
|
||||
if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) {
|
||||
mCurrentMagnificationSpec.scale = normScale;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f
|
||||
+ mMagnificationBounds.left - centerX * scale;
|
||||
final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
|
||||
if (Float.compare(currSpec.offsetX, offsetX) != 0) {
|
||||
currSpec.offsetX = offsetX;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
+ mMagnificationBounds.left - centerX * normScale;
|
||||
final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f
|
||||
+ mMagnificationBounds.top - centerY * scale;
|
||||
final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
|
||||
if (Float.compare(currSpec.offsetY, offsetY) != 0) {
|
||||
currSpec.offsetY = offsetY;
|
||||
changed = true;
|
||||
}
|
||||
+ mMagnificationBounds.top - centerY * normScale;
|
||||
changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
|
||||
|
||||
if (changed) {
|
||||
onMagnificationChangedLocked();
|
||||
@@ -616,6 +626,21 @@ class MagnificationController {
|
||||
return changed;
|
||||
}
|
||||
|
||||
private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
|
||||
boolean changed = false;
|
||||
final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
|
||||
if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
|
||||
mCurrentMagnificationSpec.offsetX = offsetX;
|
||||
changed = true;
|
||||
}
|
||||
final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
|
||||
if (Float.compare(mCurrentMagnificationSpec.offsetY, offsetY) != 0) {
|
||||
mCurrentMagnificationSpec.offsetY = offsetY;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private float getMinOffsetXLocked() {
|
||||
final float viewportWidth = mMagnificationBounds.width();
|
||||
return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
|
||||
@@ -643,12 +668,6 @@ class MagnificationController {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isScreenMagnificationAutoUpdateEnabled() {
|
||||
return (Settings.Secure.getInt(mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
|
||||
DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets magnification if magnification and auto-update are both enabled.
|
||||
*
|
||||
@@ -658,7 +677,7 @@ class MagnificationController {
|
||||
*/
|
||||
boolean resetIfNeeded(boolean animate) {
|
||||
synchronized (mLock) {
|
||||
if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
|
||||
if (isMagnifying()) {
|
||||
reset(animate);
|
||||
return true;
|
||||
}
|
||||
@@ -715,18 +734,61 @@ class MagnificationController {
|
||||
}
|
||||
|
||||
final float scale = getScale();
|
||||
offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale, INVALID_ID);
|
||||
offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
|
||||
if (Thread.currentThread().getId() == mMainThreadId) {
|
||||
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
|
||||
} else {
|
||||
mHandler.obtainMessage(MSG_SEND_SPEC_TO_ANIMATION,
|
||||
animate ? 1 : 0, 0, spec).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void onScreenTurnedOff() {
|
||||
mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_OFF);
|
||||
}
|
||||
|
||||
public boolean handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_SEND_SPEC_TO_ANIMATION:
|
||||
final boolean animate = msg.arg1 == 1;
|
||||
final MagnificationSpec spec = (MagnificationSpec) msg.obj;
|
||||
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
|
||||
break;
|
||||
case MSG_SCREEN_TURNED_OFF:
|
||||
resetIfNeeded(false);
|
||||
break;
|
||||
case MSG_ON_MAGNIFIED_BOUNDS_CHANGED: {
|
||||
final SomeArgs args = (SomeArgs) msg.obj;
|
||||
final Region magnifiedBounds = (Region) args.arg1;
|
||||
onMagnificationRegionChanged(magnifiedBounds);
|
||||
magnifiedBounds.recycle();
|
||||
args.recycle();
|
||||
} break;
|
||||
case MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
|
||||
final SomeArgs args = (SomeArgs) msg.obj;
|
||||
final int left = args.argi1;
|
||||
final int top = args.argi2;
|
||||
final int right = args.argi3;
|
||||
final int bottom = args.argi4;
|
||||
requestRectangleOnScreen(left, top, right, bottom);
|
||||
args.recycle();
|
||||
} break;
|
||||
case MSG_ON_USER_CONTEXT_CHANGED:
|
||||
resetIfNeeded(true);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class responsible for animating spec on the main thread and sending spec
|
||||
* updates to the window manager.
|
||||
*/
|
||||
private static class SpecAnimationBridge {
|
||||
private static final int ACTION_UPDATE_SPEC = 1;
|
||||
|
||||
private final Handler mHandler;
|
||||
private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
|
||||
private final WindowManagerInternal mWindowManager;
|
||||
|
||||
/**
|
||||
@@ -735,34 +797,33 @@ class MagnificationController {
|
||||
*/
|
||||
private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
|
||||
|
||||
/**
|
||||
* The animator that updates the sent spec. This should only be accessed
|
||||
* and modified on the main (e.g. animation) thread.
|
||||
*/
|
||||
private final ValueAnimator mTransformationAnimator;
|
||||
private final MagnificationSpec mStartMagnificationSpec = MagnificationSpec.obtain();
|
||||
|
||||
private final MagnificationSpec mEndMagnificationSpec = MagnificationSpec.obtain();
|
||||
|
||||
private final MagnificationSpec mTmpMagnificationSpec = MagnificationSpec.obtain();
|
||||
|
||||
/**
|
||||
* The animator should only be accessed and modified on the main (e.g. animation) thread.
|
||||
*/
|
||||
private final ValueAnimator mValueAnimator;
|
||||
|
||||
private final long mMainThreadId;
|
||||
private final Object mLock;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean mEnabled = false;
|
||||
|
||||
private SpecAnimationBridge(Context context, Object lock) {
|
||||
private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm,
|
||||
ValueAnimator animator) {
|
||||
mLock = lock;
|
||||
final Looper mainLooper = context.getMainLooper();
|
||||
mMainThreadId = mainLooper.getThread().getId();
|
||||
|
||||
mHandler = new UpdateHandler(context);
|
||||
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
|
||||
|
||||
final MagnificationSpecProperty property = new MagnificationSpecProperty();
|
||||
final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
|
||||
mWindowManager = wm;
|
||||
final long animationDuration = context.getResources().getInteger(
|
||||
R.integer.config_longAnimTime);
|
||||
mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
|
||||
mSentMagnificationSpec);
|
||||
mTransformationAnimator.setDuration(animationDuration);
|
||||
mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
|
||||
mValueAnimator = animator;
|
||||
mValueAnimator.setDuration(animationDuration);
|
||||
mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
|
||||
mValueAnimator.setFloatValues(0.0f, 1.0f);
|
||||
mValueAnimator.addUpdateListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -781,22 +842,9 @@ class MagnificationController {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSentSpec(MagnificationSpec spec, boolean animate) {
|
||||
if (Thread.currentThread().getId() == mMainThreadId) {
|
||||
// Already on the main thread, don't bother proxying.
|
||||
updateSentSpecInternal(spec, animate);
|
||||
} else {
|
||||
mHandler.obtainMessage(ACTION_UPDATE_SPEC,
|
||||
animate ? 1 : 0, 0, spec).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sent spec.
|
||||
*/
|
||||
private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
|
||||
if (mTransformationAnimator.isRunning()) {
|
||||
mTransformationAnimator.cancel();
|
||||
public void updateSentSpecMainThread(MagnificationSpec spec, boolean animate) {
|
||||
if (mValueAnimator.isRunning()) {
|
||||
mValueAnimator.cancel();
|
||||
}
|
||||
|
||||
// If the current and sent specs don't match, update the sent spec.
|
||||
@@ -812,11 +860,6 @@ class MagnificationController {
|
||||
}
|
||||
}
|
||||
|
||||
private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
|
||||
mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
|
||||
mTransformationAnimator.start();
|
||||
}
|
||||
|
||||
private void setMagnificationSpecLocked(MagnificationSpec spec) {
|
||||
if (mEnabled) {
|
||||
if (DEBUG_SET_MAGNIFICATION_SPEC) {
|
||||
@@ -828,71 +871,40 @@ class MagnificationController {
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateHandler extends Handler {
|
||||
public UpdateHandler(Context context) {
|
||||
super(context.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case ACTION_UPDATE_SPEC:
|
||||
final boolean animate = msg.arg1 == 1;
|
||||
final MagnificationSpec spec = (MagnificationSpec) msg.obj;
|
||||
updateSentSpecInternal(spec, animate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
|
||||
mEndMagnificationSpec.setTo(toSpec);
|
||||
mStartMagnificationSpec.setTo(mSentMagnificationSpec);
|
||||
mValueAnimator.start();
|
||||
}
|
||||
|
||||
private static class MagnificationSpecProperty
|
||||
extends Property<SpecAnimationBridge, MagnificationSpec> {
|
||||
public MagnificationSpecProperty() {
|
||||
super(MagnificationSpec.class, "spec");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MagnificationSpec get(SpecAnimationBridge object) {
|
||||
synchronized (object.mLock) {
|
||||
return object.mSentMagnificationSpec;
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
synchronized (mLock) {
|
||||
if (mEnabled) {
|
||||
float fract = animation.getAnimatedFraction();
|
||||
mTmpMagnificationSpec.scale = mStartMagnificationSpec.scale +
|
||||
(mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
|
||||
mTmpMagnificationSpec.offsetX = mStartMagnificationSpec.offsetX +
|
||||
(mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
|
||||
* fract;
|
||||
mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
|
||||
(mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
|
||||
* fract;
|
||||
synchronized (mLock) {
|
||||
setMagnificationSpecLocked(mTmpMagnificationSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(SpecAnimationBridge object, MagnificationSpec value) {
|
||||
synchronized (object.mLock) {
|
||||
object.setMagnificationSpecLocked(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class MagnificationSpecEvaluator
|
||||
implements TypeEvaluator<MagnificationSpec> {
|
||||
private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
|
||||
|
||||
@Override
|
||||
public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
|
||||
MagnificationSpec toSpec) {
|
||||
final MagnificationSpec result = mTempSpec;
|
||||
result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
|
||||
result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
|
||||
result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScreenStateObserver extends BroadcastReceiver {
|
||||
private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
|
||||
|
||||
private final Context mContext;
|
||||
private final MagnificationController mController;
|
||||
private final Handler mHandler;
|
||||
|
||||
public ScreenStateObserver(Context context, MagnificationController controller) {
|
||||
mContext = context;
|
||||
mController = controller;
|
||||
mHandler = new StateChangeHandler(context);
|
||||
}
|
||||
|
||||
public void register() {
|
||||
@@ -905,151 +917,27 @@ class MagnificationController {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
|
||||
intent.getAction()).sendToTarget();
|
||||
}
|
||||
|
||||
private void handleOnScreenStateChange() {
|
||||
mController.resetIfNeeded(false);
|
||||
}
|
||||
|
||||
private class StateChangeHandler extends Handler {
|
||||
public StateChangeHandler(Context context) {
|
||||
super(context.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case MESSAGE_ON_SCREEN_STATE_CHANGE:
|
||||
handleOnScreenStateChange();
|
||||
break;
|
||||
}
|
||||
}
|
||||
mController.onScreenTurnedOff();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class handles the screen magnification when accessibility is enabled.
|
||||
*/
|
||||
private static class WindowStateObserver
|
||||
implements WindowManagerInternal.MagnificationCallbacks {
|
||||
private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
|
||||
private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
|
||||
private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
|
||||
private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
|
||||
// Extra class to get settings so tests can mock it
|
||||
public static class SettingsBridge {
|
||||
private final ContentResolver mContentResolver;
|
||||
|
||||
private final MagnificationController mController;
|
||||
private final WindowManagerInternal mWindowManager;
|
||||
private final Handler mHandler;
|
||||
|
||||
private boolean mSpecIsDirty;
|
||||
|
||||
public WindowStateObserver(Context context, MagnificationController controller) {
|
||||
mController = controller;
|
||||
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
|
||||
mHandler = new CallbackHandler(context);
|
||||
public SettingsBridge(ContentResolver contentResolver) {
|
||||
mContentResolver = contentResolver;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
mWindowManager.setMagnificationCallbacks(this);
|
||||
public void putMagnificationScale(float value, int userId) {
|
||||
Settings.Secure.putFloatForUser(mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
mWindowManager.setMagnificationCallbacks(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMagnificationRegionChanged(Region magnificationRegion) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = Region.obtain(magnificationRegion);
|
||||
mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
|
||||
}
|
||||
|
||||
private void handleOnMagnifiedBoundsChanged(Region magnificationRegion) {
|
||||
mController.onMagnificationRegionChanged(magnificationRegion, mSpecIsDirty);
|
||||
mSpecIsDirty = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.argi1 = left;
|
||||
args.argi2 = top;
|
||||
args.argi3 = right;
|
||||
args.argi4 = bottom;
|
||||
mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
|
||||
}
|
||||
|
||||
private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
|
||||
mController.requestRectangleOnScreen(left, top, right, bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotationChanged(int rotation) {
|
||||
mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
|
||||
}
|
||||
|
||||
private void handleOnRotationChanged() {
|
||||
// If there was a rotation and magnification is still enabled,
|
||||
// we'll need to rewrite the spec to reflect the new screen
|
||||
// configuration. Conveniently, we'll receive a callback from
|
||||
// the window manager with updated bounds for the magnified
|
||||
// region.
|
||||
mSpecIsDirty = !mController.resetIfNeeded(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserContextChanged() {
|
||||
mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
|
||||
}
|
||||
|
||||
private void handleOnUserContextChanged() {
|
||||
mController.resetIfNeeded(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the magnification region in the tiny time slice between
|
||||
* registering the callbacks and handling the message.
|
||||
* TODO: Elimiante this extra path, perhaps by processing the message immediately
|
||||
*
|
||||
* @param outMagnificationRegion
|
||||
*/
|
||||
public void getMagnificationRegion(@NonNull Region outMagnificationRegion) {
|
||||
mWindowManager.getMagnificationRegion(outMagnificationRegion);
|
||||
}
|
||||
|
||||
private class CallbackHandler extends Handler {
|
||||
public CallbackHandler(Context context) {
|
||||
super(context.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
|
||||
final SomeArgs args = (SomeArgs) message.obj;
|
||||
final Region magnifiedBounds = (Region) args.arg1;
|
||||
handleOnMagnifiedBoundsChanged(magnifiedBounds);
|
||||
magnifiedBounds.recycle();
|
||||
} break;
|
||||
case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
|
||||
final SomeArgs args = (SomeArgs) message.obj;
|
||||
final int left = args.argi1;
|
||||
final int top = args.argi2;
|
||||
final int right = args.argi3;
|
||||
final int bottom = args.argi4;
|
||||
handleOnRectangleOnScreenRequested(left, top, right, bottom);
|
||||
args.recycle();
|
||||
} break;
|
||||
case MESSAGE_ON_USER_CONTEXT_CHANGED: {
|
||||
handleOnUserContextChanged();
|
||||
} break;
|
||||
case MESSAGE_ON_ROTATION_CHANGED: {
|
||||
handleOnRotationChanged();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
public float getMagnificationScale(int userId) {
|
||||
return Settings.Secure.getFloatForUser(mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
|
||||
DEFAULT_MAGNIFICATION_SCALE, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
|
||||
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
|
||||
+ " scrollY: " + distanceY);
|
||||
}
|
||||
mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY,
|
||||
mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
|
||||
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,827 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.accessibility;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.view.MagnificationSpec;
|
||||
import android.view.WindowManagerInternal;
|
||||
import android.view.WindowManagerInternal.MagnificationCallbacks;
|
||||
|
||||
import com.android.internal.R;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MagnificationControllerTest {
|
||||
static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 100, 200);
|
||||
static final PointF INITIAL_MAGNIFICATION_BOUNDS_CENTER = new PointF(
|
||||
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY());
|
||||
static final PointF INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER = new PointF(25, 50);
|
||||
static final PointF INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(75, 150);
|
||||
static final Rect OTHER_MAGNIFICATION_BOUNDS = new Rect(100, 200, 500, 600);
|
||||
static final PointF OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(400, 500);
|
||||
static final Region INITIAL_MAGNIFICATION_REGION = new Region(INITIAL_MAGNIFICATION_BOUNDS);
|
||||
static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
|
||||
static final int SERVICE_ID_1 = 1;
|
||||
static final int SERVICE_ID_2 = 2;
|
||||
|
||||
final Context mMockContext = mock(Context.class);
|
||||
final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
|
||||
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
|
||||
final MessageCapturingHandler mMessageCapturingHandler =
|
||||
new MessageCapturingHandler(new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
return mMagnificationController.handleMessage(msg);
|
||||
}
|
||||
});
|
||||
final ArgumentCaptor<MagnificationSpec> mMagnificationSpecCaptor =
|
||||
ArgumentCaptor.forClass(MagnificationSpec.class);
|
||||
final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
|
||||
MagnificationController.SettingsBridge mMockSettingsBridge;
|
||||
|
||||
|
||||
MagnificationController mMagnificationController;
|
||||
ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
|
||||
|
||||
@BeforeClass
|
||||
public static void oneTimeInitialization() {
|
||||
if (Looper.myLooper() == null) {
|
||||
Looper.prepare();
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
when(mMockContext.getMainLooper()).thenReturn(Looper.myLooper());
|
||||
Resources mockResources = mock(Resources.class);
|
||||
when(mMockContext.getResources()).thenReturn(mockResources);
|
||||
when(mockResources.getInteger(R.integer.config_longAnimTime))
|
||||
.thenReturn(1000);
|
||||
mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
|
||||
mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
|
||||
mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
|
||||
mMockSettingsBridge);
|
||||
|
||||
doAnswer(new Answer<Void>() {
|
||||
@Override
|
||||
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
Object[] args = invocationOnMock.getArguments();
|
||||
Region regionArg = (Region) args[0];
|
||||
regionArg.set(INITIAL_MAGNIFICATION_REGION);
|
||||
return null;
|
||||
}
|
||||
}).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
|
||||
|
||||
ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
|
||||
verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
|
||||
mTargetAnimationListener = listenerArgumentCaptor.getValue();
|
||||
Mockito.reset(mMockValueAnimator); // Ignore other initialization
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegister_WindowManagerAndContextRegisterListeners() {
|
||||
mMagnificationController.register();
|
||||
verify(mMockContext).registerReceiver(
|
||||
(BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
|
||||
verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
|
||||
assertTrue(mMagnificationController.isRegisteredLocked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegister_WindowManagerAndContextUnregisterListeners() {
|
||||
mMagnificationController.register();
|
||||
mMagnificationController.unregister();
|
||||
|
||||
verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
|
||||
verify(mMockWindowManager).setMagnificationCallbacks(null);
|
||||
assertFalse(mMagnificationController.isRegisteredLocked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
|
||||
mMagnificationController.register();
|
||||
MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
|
||||
Region initialMagRegion = new Region();
|
||||
Rect initialBounds = new Rect();
|
||||
|
||||
assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
|
||||
mMagnificationController.getMagnificationRegion(initialMagRegion);
|
||||
mMagnificationController.getMagnificationBounds(initialBounds);
|
||||
assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
|
||||
assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
|
||||
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
|
||||
mMagnificationController.getCenterX(), 0.0f);
|
||||
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
|
||||
mMagnificationController.getCenterY(), 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotRegistered_publicMethodsShouldBeBenign() {
|
||||
assertFalse(mMagnificationController.isMagnifying());
|
||||
assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
|
||||
assertFalse(mMagnificationController.reset(true));
|
||||
assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
|
||||
assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
|
||||
assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
|
||||
assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
|
||||
|
||||
mMagnificationController.getMagnificationRegion(new Region());
|
||||
mMagnificationController.getMagnificationBounds(new Rect());
|
||||
mMagnificationController.getScale();
|
||||
mMagnificationController.getOffsetX();
|
||||
mMagnificationController.getOffsetY();
|
||||
mMagnificationController.getCenterX();
|
||||
mMagnificationController.getCenterY();
|
||||
mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
|
||||
mMagnificationController.unregister();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
|
||||
mMagnificationController.register();
|
||||
final float scale = 2.0f;
|
||||
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
|
||||
final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
|
||||
assertTrue(mMagnificationController
|
||||
.setScale(scale, center.x, center.y, false, SERVICE_ID_1));
|
||||
|
||||
final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
|
||||
assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
|
||||
assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
|
||||
verify(mMockValueAnimator, times(0)).start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
|
||||
mMagnificationController.register();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
float scale = 2.0f;
|
||||
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
assertTrue(mMagnificationController
|
||||
.setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
|
||||
|
||||
// New center should be halfway between original center and pivot
|
||||
PointF newCenter = new PointF((pivotPoint.x + INITIAL_MAGNIFICATION_BOUNDS.centerX()) / 2,
|
||||
(pivotPoint.y + INITIAL_MAGNIFICATION_BOUNDS.centerY()) / 2);
|
||||
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
|
||||
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
|
||||
|
||||
assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
|
||||
assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
|
||||
verify(mMockValueAnimator).start();
|
||||
|
||||
// Initial value
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(startSpec);
|
||||
|
||||
// Intermediate point
|
||||
Mockito.reset(mMockWindowManager);
|
||||
float fraction = 0.5f;
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(
|
||||
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
|
||||
|
||||
// Final value
|
||||
Mockito.reset(mMockWindowManager);
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
|
||||
mMagnificationController.register();
|
||||
// First zoom in
|
||||
float scale = 2.0f;
|
||||
assertTrue(mMagnificationController.setScale(scale,
|
||||
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
|
||||
false, SERVICE_ID_1));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
assertTrue(mMagnificationController
|
||||
.setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
|
||||
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
|
||||
MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
|
||||
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
|
||||
assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
|
||||
assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
|
||||
verify(mMockValueAnimator, times(0)).start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
|
||||
mMagnificationController.register();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
float scale = 2.5f;
|
||||
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
|
||||
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
|
||||
|
||||
assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
|
||||
true, SERVICE_ID_1));
|
||||
|
||||
assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
|
||||
assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
|
||||
verify(mMockAms).notifyMagnificationChanged(
|
||||
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
|
||||
verify(mMockValueAnimator).start();
|
||||
|
||||
// Initial value
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(startSpec);
|
||||
|
||||
// Intermediate point
|
||||
Mockito.reset(mMockWindowManager);
|
||||
float fraction = 0.33f;
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(
|
||||
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
|
||||
|
||||
// Final value
|
||||
Mockito.reset(mMockWindowManager);
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
|
||||
mMagnificationController.register();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
|
||||
MagnificationController.MAX_SCALE);
|
||||
MagnificationSpec endSpec = getMagnificationSpec(
|
||||
MagnificationController.MAX_SCALE, offsets);
|
||||
|
||||
assertTrue(mMagnificationController.setScaleAndCenter(
|
||||
MagnificationController.MAX_SCALE + 1.0f,
|
||||
newCenter.x, newCenter.y, false, SERVICE_ID_1));
|
||||
|
||||
assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
|
||||
assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
// Verify that we can't zoom below 1x
|
||||
assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
|
||||
INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
|
||||
false, SERVICE_ID_1));
|
||||
|
||||
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
|
||||
mMagnificationController.getCenterX(), 0.5);
|
||||
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
|
||||
mMagnificationController.getCenterY(), 0.5);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
|
||||
mMagnificationController.register();
|
||||
float scale = 2.0f;
|
||||
|
||||
// Off the edge to the top and left
|
||||
assertTrue(mMagnificationController.setScaleAndCenter(
|
||||
scale, -100f, -200f, false, SERVICE_ID_1));
|
||||
|
||||
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
|
||||
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
|
||||
assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
|
||||
assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
|
||||
verify(mMockWindowManager).setMagnificationSpec(
|
||||
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
// Off the edge to the bottom and right
|
||||
assertTrue(mMagnificationController.setScaleAndCenter(scale,
|
||||
INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
|
||||
false, SERVICE_ID_1));
|
||||
newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
|
||||
assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
|
||||
assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
|
||||
verify(mMockWindowManager).setMagnificationSpec(
|
||||
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMagnificationRegionChanged_serviceNotified() {
|
||||
mMagnificationController.register();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
callbacks.onMagnificationRegionChanged(OTHER_REGION);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
|
||||
OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
|
||||
mMagnificationController.register();
|
||||
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
|
||||
float scale = 2.0f;
|
||||
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
|
||||
// First zoom in
|
||||
assertTrue(mMagnificationController
|
||||
.setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
|
||||
mMagnificationController.offsetMagnifiedRegion(
|
||||
startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
|
||||
|
||||
MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
|
||||
assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
|
||||
assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
|
||||
verify(mMockValueAnimator, times(0)).start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
|
||||
mMagnificationController.register();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
|
||||
mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
|
||||
verifyNoMoreInteractions(mMockWindowManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
|
||||
mMagnificationController.register();
|
||||
float scale = 2.0f;
|
||||
|
||||
// Upper left edges
|
||||
PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
|
||||
assertTrue(mMagnificationController
|
||||
.setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
MagnificationSpec ulSpec = getCurrentMagnificationSpec();
|
||||
mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
|
||||
verifyNoMoreInteractions(mMockWindowManager);
|
||||
|
||||
// Lower right edges
|
||||
PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
assertTrue(mMagnificationController
|
||||
.setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
MagnificationSpec lrSpec = getCurrentMagnificationSpec();
|
||||
mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
|
||||
verifyNoMoreInteractions(mMockWindowManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
|
||||
mMagnificationController.register();
|
||||
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
|
||||
assertTrue(mMagnificationController
|
||||
.setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
|
||||
assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
|
||||
assertTrue(mMagnificationController
|
||||
.setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
|
||||
assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetUserId_resetsOnlyIfIdChanges() {
|
||||
final int userId1 = 1;
|
||||
final int userId2 = 2;
|
||||
|
||||
mMagnificationController.register();
|
||||
mMagnificationController.setUserId(userId1);
|
||||
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
|
||||
float scale = 2.0f;
|
||||
mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
|
||||
|
||||
mMagnificationController.setUserId(userId1);
|
||||
assertTrue(mMagnificationController.isMagnifying());
|
||||
mMagnificationController.setUserId(userId2);
|
||||
assertFalse(mMagnificationController.isMagnifying());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetIfNeeded_doesWhatItSays() {
|
||||
mMagnificationController.register();
|
||||
zoomIn2xToMiddle();
|
||||
assertTrue(mMagnificationController.resetIfNeeded(false));
|
||||
verify(mMockAms).notifyMagnificationChanged(
|
||||
eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyInt(), anyInt());
|
||||
assertFalse(mMagnificationController.isMagnifying());
|
||||
assertFalse(mMagnificationController.resetIfNeeded(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTurnScreenOff_resetsMagnification() {
|
||||
mMagnificationController.register();
|
||||
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
verify(mMockContext).registerReceiver(
|
||||
broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
|
||||
BroadcastReceiver br = broadcastReceiverCaptor.getValue();
|
||||
zoomIn2xToMiddle();
|
||||
br.onReceive(mMockContext, null);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertFalse(mMagnificationController.isMagnifying());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserContextChange_resetsMagnification() {
|
||||
mMagnificationController.register();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
zoomIn2xToMiddle();
|
||||
callbacks.onUserContextChanged();
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertFalse(mMagnificationController.isMagnifying());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRotation_resetsMagnification() {
|
||||
mMagnificationController.register();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
zoomIn2xToMiddle();
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertTrue(mMagnificationController.isMagnifying());
|
||||
callbacks.onRotationChanged(0);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertFalse(mMagnificationController.isMagnifying());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
|
||||
// Going from a small region to a large one leads to no issues
|
||||
mMagnificationController.register();
|
||||
zoomIn2xToMiddle();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
callbacks.onMagnificationRegionChanged(OTHER_REGION);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
|
||||
verifyNoMoreInteractions(mMockWindowManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
|
||||
mMagnificationController.register();
|
||||
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
|
||||
float scale = 2.0f;
|
||||
mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
callbacks.onMagnificationRegionChanged(OTHER_REGION);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
|
||||
verifyNoMoreInteractions(mMockWindowManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
|
||||
// In a large region, pan to the farthest point possible
|
||||
mMagnificationController.register();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
callbacks.onMagnificationRegionChanged(OTHER_REGION);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
float scale = 2.0f;
|
||||
mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
|
||||
MagnificationSpec endSpec = getCurrentMagnificationSpec();
|
||||
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
|
||||
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
|
||||
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
|
||||
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
|
||||
mMagnificationController.register();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
callbacks.onMagnificationRegionChanged(OTHER_REGION);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
float scale = 2.0f;
|
||||
mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
when (mMockValueAnimator.isRunning()).thenReturn(true);
|
||||
|
||||
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
verify(mMockValueAnimator).cancel();
|
||||
|
||||
MagnificationSpec endSpec = getCurrentMagnificationSpec();
|
||||
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
|
||||
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
|
||||
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
|
||||
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
|
||||
mMagnificationController.register();
|
||||
zoomIn2xToMiddle();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
|
||||
int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
|
||||
callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
|
||||
verifyNoMoreInteractions(mMockWindowManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
|
||||
mMagnificationController.register();
|
||||
zoomIn2xToMiddle();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRectOnScreen_garbageInput_doesNothing() {
|
||||
mMagnificationController.register();
|
||||
zoomIn2xToMiddle();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
|
||||
verifyNoMoreInteractions(mMockWindowManager);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
|
||||
Locale.setDefault(new Locale("en", "us"));
|
||||
mMagnificationController.register();
|
||||
zoomIn2xToMiddle();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
Rect wideRect = new Rect(0, 50, 100, 51);
|
||||
callbacks.onRectangleOnScreenRequested(
|
||||
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
// Repeat with RTL
|
||||
Locale.setDefault(new Locale("he", "il"));
|
||||
callbacks.onRectangleOnScreenRequested(
|
||||
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
|
||||
mMagnificationController.register();
|
||||
zoomIn2xToMiddle();
|
||||
MagnificationCallbacks callbacks = getMagnificationCallbacks();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
Mockito.reset(mMockWindowManager);
|
||||
Rect tallRect = new Rect(50, 0, 51, 100);
|
||||
callbacks.onRectangleOnScreenRequested(
|
||||
tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
|
||||
mMessageCapturingHandler.sendAllMessages();
|
||||
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeMagnification_duringAnimation_animatesToNewValue() {
|
||||
mMagnificationController.register();
|
||||
MagnificationSpec startSpec = getCurrentMagnificationSpec();
|
||||
float scale = 2.5f;
|
||||
PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
|
||||
MagnificationSpec firstEndSpec = getMagnificationSpec(
|
||||
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
|
||||
|
||||
assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
|
||||
true, SERVICE_ID_1));
|
||||
|
||||
assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
|
||||
assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
|
||||
assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
|
||||
verify(mMockValueAnimator, times(1)).start();
|
||||
|
||||
// Initial value
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(startSpec);
|
||||
verify(mMockAms).notifyMagnificationChanged(
|
||||
INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
// Intermediate point
|
||||
float fraction = 0.33f;
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
MagnificationSpec intermediateSpec1 =
|
||||
getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
|
||||
MagnificationSpec newEndSpec = getMagnificationSpec(
|
||||
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
|
||||
assertTrue(mMagnificationController.setCenter(
|
||||
newCenter.x, newCenter.y, true, SERVICE_ID_1));
|
||||
|
||||
// Animation should have been restarted
|
||||
verify(mMockValueAnimator, times(2)).start();
|
||||
verify(mMockAms).notifyMagnificationChanged(
|
||||
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
|
||||
|
||||
// New starting point should be where we left off
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
// Second intermediate point
|
||||
fraction = 0.5f;
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(
|
||||
argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
|
||||
Mockito.reset(mMockWindowManager);
|
||||
|
||||
// Final value should be the new center
|
||||
Mockito.reset(mMockWindowManager);
|
||||
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
|
||||
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
|
||||
verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
|
||||
}
|
||||
|
||||
private void zoomIn2xToMiddle() {
|
||||
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
|
||||
float scale = 2.0f;
|
||||
mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
|
||||
assertTrue(mMagnificationController.isMagnifying());
|
||||
}
|
||||
|
||||
private MagnificationCallbacks getMagnificationCallbacks() {
|
||||
ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
|
||||
ArgumentCaptor.forClass(MagnificationCallbacks.class);
|
||||
verify(mMockWindowManager)
|
||||
.setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
|
||||
return magnificationCallbacksCaptor.getValue();
|
||||
}
|
||||
|
||||
private PointF computeOffsets(Rect magnifiedBounds, PointF center, float scale) {
|
||||
return new PointF(
|
||||
magnifiedBounds.centerX() - scale * center.x,
|
||||
magnifiedBounds.centerY() - scale * center.y);
|
||||
}
|
||||
|
||||
private MagnificationSpec getInterpolatedMagSpec(MagnificationSpec start, MagnificationSpec end,
|
||||
float fraction) {
|
||||
MagnificationSpec interpolatedSpec = MagnificationSpec.obtain();
|
||||
interpolatedSpec.scale = start.scale + fraction * (end.scale - start.scale);
|
||||
interpolatedSpec.offsetX = start.offsetX + fraction * (end.offsetX - start.offsetX);
|
||||
interpolatedSpec.offsetY = start.offsetY + fraction * (end.offsetY - start.offsetY);
|
||||
return interpolatedSpec;
|
||||
}
|
||||
|
||||
private MagnificationSpec getMagnificationSpec(float scale, PointF offsets) {
|
||||
return getMagnificationSpec(scale, offsets.x, offsets.y);
|
||||
}
|
||||
|
||||
private MagnificationSpec getMagnificationSpec(float scale, float offsetX, float offsetY) {
|
||||
MagnificationSpec spec = MagnificationSpec.obtain();
|
||||
spec.scale = scale;
|
||||
spec.offsetX = offsetX;
|
||||
spec.offsetY = offsetY;
|
||||
return spec;
|
||||
}
|
||||
|
||||
private MagnificationSpec getCurrentMagnificationSpec() {
|
||||
return getMagnificationSpec(mMagnificationController.getScale(),
|
||||
mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
|
||||
}
|
||||
|
||||
private MagSpecMatcher closeTo(MagnificationSpec spec) {
|
||||
return new MagSpecMatcher(spec, 0.01f, 0.5f);
|
||||
}
|
||||
|
||||
private class MagSpecMatcher extends TypeSafeMatcher<MagnificationSpec> {
|
||||
final MagnificationSpec mMagSpec;
|
||||
final float mScaleTolerance;
|
||||
final float mOffsetTolerance;
|
||||
|
||||
MagSpecMatcher(MagnificationSpec spec, float scaleTolerance, float offsetTolerance) {
|
||||
mMagSpec = spec;
|
||||
mScaleTolerance = scaleTolerance;
|
||||
mOffsetTolerance = offsetTolerance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(MagnificationSpec magnificationSpec) {
|
||||
if (Math.abs(mMagSpec.scale - magnificationSpec.scale) > mScaleTolerance) {
|
||||
return false;
|
||||
}
|
||||
if (Math.abs(mMagSpec.offsetX - magnificationSpec.offsetX) > mOffsetTolerance) {
|
||||
return false;
|
||||
}
|
||||
if (Math.abs(mMagSpec.offsetY - magnificationSpec.offsetY) > mOffsetTolerance) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Match spec: " + mMagSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.accessibility;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class to capture messages dispatched through a handler and control when they arrive
|
||||
* at their target.
|
||||
*/
|
||||
public class MessageCapturingHandler extends Handler {
|
||||
List<Pair<Message, Long>> timedMessages = new ArrayList<>();
|
||||
|
||||
Handler.Callback mCallback;
|
||||
|
||||
public MessageCapturingHandler(Handler.Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMessageAtTime(Message message, long uptimeMillis) {
|
||||
timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
|
||||
return super.sendMessageAtTime(message, uptimeMillis);
|
||||
}
|
||||
|
||||
public void sendOneMessage() {
|
||||
Message message = timedMessages.remove(0).first;
|
||||
removeMessages(message.what, message.obj);
|
||||
mCallback.handleMessage(message);
|
||||
removeStaleMessages();
|
||||
}
|
||||
|
||||
public void sendAllMessages() {
|
||||
while (!timedMessages.isEmpty()) {
|
||||
sendOneMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendLastMessage() {
|
||||
Message message = timedMessages.remove(timedMessages.size() - 1).first;
|
||||
removeMessages(message.what, message.obj);
|
||||
mCallback.handleMessage(message);
|
||||
removeStaleMessages();
|
||||
}
|
||||
|
||||
public boolean hasMessages() {
|
||||
removeStaleMessages();
|
||||
return !timedMessages.isEmpty();
|
||||
}
|
||||
|
||||
private void removeStaleMessages() {
|
||||
for (int i = 0; i < timedMessages.size(); i++) {
|
||||
Message message = timedMessages.get(i).first;
|
||||
if (!hasMessages(message.what, message.obj)) {
|
||||
timedMessages.remove(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,12 @@ public class MotionEventInjectorTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mMessageCapturingHandler = new MessageCapturingHandler();
|
||||
mMessageCapturingHandler = new MessageCapturingHandler(new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
return mMotionEventInjector.handleMessage(msg);
|
||||
}
|
||||
});
|
||||
mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
|
||||
mClickList.add(
|
||||
MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
|
||||
@@ -501,48 +506,4 @@ public class MotionEventInjectorTest {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageCapturingHandler extends Handler {
|
||||
List<Pair<Message, Long>> timedMessages = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean sendMessageAtTime(Message message, long uptimeMillis) {
|
||||
timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
|
||||
return super.sendMessageAtTime(message, uptimeMillis);
|
||||
}
|
||||
|
||||
void sendOneMessage() {
|
||||
Message message = timedMessages.remove(0).first;
|
||||
removeMessages(message.what, message.obj);
|
||||
mMotionEventInjector.handleMessage(message);
|
||||
removeStaleMessages();
|
||||
}
|
||||
|
||||
void sendAllMessages() {
|
||||
while (!timedMessages.isEmpty()) {
|
||||
sendOneMessage();
|
||||
}
|
||||
}
|
||||
|
||||
void sendLastMessage() {
|
||||
Message message = timedMessages.remove(timedMessages.size() - 1).first;
|
||||
removeMessages(message.what, message.obj);
|
||||
mMotionEventInjector.handleMessage(message);
|
||||
removeStaleMessages();
|
||||
}
|
||||
|
||||
boolean hasMessages() {
|
||||
removeStaleMessages();
|
||||
return !timedMessages.isEmpty();
|
||||
}
|
||||
|
||||
private void removeStaleMessages() {
|
||||
for (int i = 0; i < timedMessages.size(); i++) {
|
||||
Message message = timedMessages.get(i).first;
|
||||
if (!hasMessages(message.what, message.obj)) {
|
||||
timedMessages.remove(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user