1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.accessibility.magnification;
18 
19 import static java.lang.Math.abs;
20 
21 import android.annotation.NonNull;
22 import android.annotation.UiContext;
23 import android.content.Context;
24 import android.os.Handler;
25 import android.util.Log;
26 import android.util.Slog;
27 import android.util.TypedValue;
28 import android.view.GestureDetector;
29 import android.view.MotionEvent;
30 import android.view.ScaleGestureDetector;
31 
32 import com.android.internal.R;
33 
34 /**
35  * Handles the behavior while receiving scaling and panning gestures if it's enabled.
36  * Note it needs to receives all touch events even it's not enabled.
37  */
38 
39 class PanningScalingHandler extends
40         GestureDetector.SimpleOnGestureListener
41         implements ScaleGestureDetector.OnScaleGestureListener {
42 
43     private static final String TAG = "PanningScalingHandler";
44     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
45 
46     interface MagnificationDelegate {
processScroll(int displayId, float distanceX, float distanceY)47         boolean processScroll(int displayId, float distanceX, float distanceY);
setScale(int displayId, float scale)48         void setScale(int displayId, float scale);
getScale(int displayId)49         float getScale(int displayId);
50     }
51 
52     private final ScaleGestureDetector mScaleGestureDetector;
53     private final GestureDetector mScrollGestureDetector;
54     private final MagnificationDelegate mMagnificationDelegate;
55     private final float mScalingThreshold;
56     private final float mMinScale;
57     private final float mMaxScale;
58     private final int mDisplayId;
59     private float mInitialScaleFactor = -1;
60     // Used to identify if need to disable onScroll once scaling operation is ongoing.
61     // We can remove it if we can fully distinguish these two gestures.
62     private final boolean mBlockScroll;
63 
64     private boolean mScaling;
65     private boolean mEnable;
66 
PanningScalingHandler(@iContext Context context, float maxScale, float minScale, boolean blockScroll, @NonNull MagnificationDelegate magnificationDelegate)67     PanningScalingHandler(@UiContext Context context, float maxScale, float minScale,
68             boolean blockScroll, @NonNull MagnificationDelegate magnificationDelegate) {
69         mDisplayId = context.getDisplayId();
70         mMaxScale = maxScale;
71         mMinScale = minScale;
72         mBlockScroll = blockScroll;
73         mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
74         mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
75         mScaleGestureDetector.setQuickScaleEnabled(false);
76         mMagnificationDelegate = magnificationDelegate;
77         final TypedValue scaleValue = new TypedValue();
78         context.getResources().getValue(
79                 R.dimen.config_screen_magnification_scaling_threshold,
80                 scaleValue, false);
81         mScalingThreshold = scaleValue.getFloat();
82     }
83 
setEnabled(boolean enable)84     void setEnabled(boolean enable) {
85         clear();
86         mEnable = enable;
87     }
88 
onTouchEvent(MotionEvent motionEvent)89     void onTouchEvent(MotionEvent motionEvent) {
90         mScaleGestureDetector.onTouchEvent(motionEvent);
91         mScrollGestureDetector.onTouchEvent(motionEvent);
92     }
93 
94     @Override
95     // TODO: Try to distinguish onScroll with onScale correctly.
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)96     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
97         if (!mEnable || (mBlockScroll && mScaling)) {
98             return true;
99         }
100         return mMagnificationDelegate.processScroll(mDisplayId, distanceX, distanceY);
101     }
102 
103     @Override
onScale(ScaleGestureDetector detector)104     public boolean onScale(ScaleGestureDetector detector) {
105         if (DEBUG) {
106             Slog.i(TAG, "onScale: triggered ");
107         }
108         if (!mScaling) {
109             if (mInitialScaleFactor < 0) {
110                 mInitialScaleFactor = detector.getScaleFactor();
111                 return false;
112             }
113             final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
114             mScaling = abs(deltaScale) > mScalingThreshold;
115             return mScaling;
116         }
117 
118         // Don't allow a gesture to move the user further outside the
119         // desired bounds for gesture-controlled scaling.
120         final float scale;
121         final float initialScale = mMagnificationDelegate.getScale(mDisplayId);
122         final float targetScale = initialScale * detector.getScaleFactor();
123 
124         if (targetScale > mMaxScale && targetScale > initialScale) {
125             // The target scale is too big and getting bigger.
126             scale = mMaxScale;
127         } else if (targetScale < mMinScale && targetScale < initialScale) {
128             // The target scale is too small and getting smaller.
129             scale = mMinScale;
130         } else {
131             // The target scale may be outside our bounds, but at least
132             // it's moving in the right direction. This avoids a "jump" if
133             // we're at odds with some other service's desired bounds.
134             scale = targetScale;
135         }
136 
137         if (DEBUG) Slog.i(TAG, "Scaled content to: " + scale + "x");
138         mMagnificationDelegate.setScale(mDisplayId, scale);
139         return /* handled: */ true;
140     }
141 
142     @Override
onScaleBegin(ScaleGestureDetector detector)143     public boolean onScaleBegin(ScaleGestureDetector detector) {
144         return mEnable;
145     }
146 
147     @Override
onScaleEnd(ScaleGestureDetector detector)148     public void onScaleEnd(ScaleGestureDetector detector) {
149         clear();
150     }
151 
clear()152     void clear() {
153         mInitialScaleFactor = -1;
154         mScaling = false;
155     }
156 
157     @Override
toString()158     public String toString() {
159         return "PanningScalingHandler{"
160                 + "mInitialScaleFactor=" + mInitialScaleFactor
161                 + ", mScaling=" + mScaling
162                 + '}';
163     }
164 }
165 
166