blob: c5495d98226ee0e58f847678e6cac04541be94d5 [file] [log] [blame]
/*
* Copyright (C) 2020 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.magnification;
import static java.lang.Math.abs;
import android.annotation.NonNull;
import android.annotation.UiContext;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.android.internal.R;
/**
* Handles the behavior while receiving scaling and panning gestures if it's enabled.
* Note it needs to receives all touch events even it's not enabled.
*/
class PanningScalingHandler extends
GestureDetector.SimpleOnGestureListener
implements ScaleGestureDetector.OnScaleGestureListener {
private static final String TAG = "PanningScalingHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
interface MagnificationDelegate {
boolean processScroll(int displayId, float distanceX, float distanceY);
void setScale(int displayId, float scale);
float getScale(int displayId);
}
private final ScaleGestureDetector mScaleGestureDetector;
private final GestureDetector mScrollGestureDetector;
private final MagnificationDelegate mMagnificationDelegate;
private final float mScalingThreshold;
private final float mMinScale;
private final float mMaxScale;
private final int mDisplayId;
private float mInitialScaleFactor = -1;
// Used to identify if need to disable onScroll once scaling operation is ongoing.
// We can remove it if we can fully distinguish these two gestures.
private final boolean mBlockScroll;
private boolean mScaling;
private boolean mEnable;
PanningScalingHandler(@UiContext Context context, float maxScale, float minScale,
boolean blockScroll, @NonNull MagnificationDelegate magnificationDelegate) {
mDisplayId = context.getDisplayId();
mMaxScale = maxScale;
mMinScale = minScale;
mBlockScroll = blockScroll;
mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
mScaleGestureDetector.setQuickScaleEnabled(false);
mMagnificationDelegate = magnificationDelegate;
final TypedValue scaleValue = new TypedValue();
context.getResources().getValue(
R.dimen.config_screen_magnification_scaling_threshold,
scaleValue, false);
mScalingThreshold = scaleValue.getFloat();
}
void setEnabled(boolean enable) {
clear();
mEnable = enable;
}
void onTouchEvent(MotionEvent motionEvent) {
mScaleGestureDetector.onTouchEvent(motionEvent);
mScrollGestureDetector.onTouchEvent(motionEvent);
}
@Override
// TODO: Try to distinguish onScroll with onScale correctly.
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!mEnable || (mBlockScroll && mScaling)) {
return true;
}
return mMagnificationDelegate.processScroll(mDisplayId, distanceX, distanceY);
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (DEBUG) {
Slog.i(TAG, "onScale: triggered ");
}
if (!mScaling) {
if (mInitialScaleFactor < 0) {
mInitialScaleFactor = detector.getScaleFactor();
return false;
}
final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
mScaling = abs(deltaScale) > mScalingThreshold;
return mScaling;
}
// Don't allow a gesture to move the user further outside the
// desired bounds for gesture-controlled scaling.
final float scale;
final float initialScale = mMagnificationDelegate.getScale(mDisplayId);
final float targetScale = initialScale * detector.getScaleFactor();
if (targetScale > mMaxScale && targetScale > initialScale) {
// The target scale is too big and getting bigger.
scale = mMaxScale;
} else if (targetScale < mMinScale && targetScale < initialScale) {
// The target scale is too small and getting smaller.
scale = mMinScale;
} else {
// The target scale may be outside our bounds, but at least
// it's moving in the right direction. This avoids a "jump" if
// we're at odds with some other service's desired bounds.
scale = targetScale;
}
if (DEBUG) Slog.i(TAG, "Scaled content to: " + scale + "x");
mMagnificationDelegate.setScale(mDisplayId, scale);
return /* handled: */ true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return mEnable;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
clear();
}
void clear() {
mInitialScaleFactor = -1;
mScaling = false;
}
@Override
public String toString() {
return "PanningScalingHandler{"
+ "mInitialScaleFactor=" + mInitialScaleFactor
+ ", mScaling=" + mScaling
+ '}';
}
}