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.systemui.accessibility;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Point;
24 import android.graphics.PointF;
25 import android.os.Handler;
26 import android.util.Log;
27 import android.view.LayoutInflater;
28 import android.view.MotionEvent;
29 import android.view.View;
30 
31 import com.android.systemui.R;
32 
33 /**
34  * A basic control to move the mirror window.
35  */
36 class SimpleMirrorWindowControl extends MirrorWindowControl implements View.OnClickListener,
37         View.OnTouchListener, View.OnLongClickListener {
38 
39     private static final String TAG = "SimpleMirrorWindowControl";
40     private static final int MOVE_FRAME_DURATION_MS = 100;
41     private final int mMoveFrameAmountShort;
42     private final int mMoveFrameAmountLong;
43 
44     private boolean mIsDragState;
45     private boolean mShouldSetTouchStart;
46 
47     @Nullable private MoveWindowTask mMoveWindowTask;
48     private final PointF mLastDrag = new PointF();
49     private final Handler mHandler;
50 
SimpleMirrorWindowControl(Context context, Handler handler)51     SimpleMirrorWindowControl(Context context, Handler handler) {
52         super(context);
53         mHandler = handler;
54         final Resources resource = context.getResources();
55         mMoveFrameAmountShort = resource.getDimensionPixelSize(
56                 R.dimen.magnification_frame_move_short);
57         mMoveFrameAmountLong = resource.getDimensionPixelSize(
58                 R.dimen.magnification_frame_move_long);
59     }
60 
61     @Override
getWindowTitle()62     String getWindowTitle() {
63         return mContext.getString(R.string.magnification_controls_title);
64     }
65 
66     @Override
onCreateView(LayoutInflater layoutInflater, Point viewSize)67     View onCreateView(LayoutInflater layoutInflater, Point viewSize) {
68         final View view = layoutInflater.inflate(R.layout.magnifier_controllers, null);
69         final View leftControl = view.findViewById(R.id.left_control);
70         final View upControl = view.findViewById(R.id.up_control);
71         final View rightControl = view.findViewById(R.id.right_control);
72         final View bottomControl = view.findViewById(R.id.down_control);
73 
74         leftControl.setOnClickListener(this);
75         upControl.setOnClickListener(this);
76         rightControl.setOnClickListener(this);
77         bottomControl.setOnClickListener(this);
78 
79         leftControl.setOnLongClickListener(this);
80         upControl.setOnLongClickListener(this);
81         rightControl.setOnLongClickListener(this);
82         bottomControl.setOnLongClickListener(this);
83 
84         leftControl.setOnTouchListener(this);
85         upControl.setOnTouchListener(this);
86         rightControl.setOnTouchListener(this);
87         bottomControl.setOnTouchListener(this);
88 
89         view.setOnTouchListener(this);
90         view.setOnLongClickListener(this::onViewRootLongClick);
91         return view;
92     }
93 
findOffset(View v, int moveFrameAmount)94     private Point findOffset(View v, int moveFrameAmount) {
95         mTmpPoint.set(0, 0);
96         if (v.getId() == R.id.left_control) {
97             mTmpPoint.x = -moveFrameAmount;
98         } else if (v.getId() == R.id.up_control) {
99             mTmpPoint.y = -moveFrameAmount;
100         } else if (v.getId() == R.id.right_control) {
101             mTmpPoint.x = moveFrameAmount;
102         } else if (v.getId() == R.id.down_control) {
103             mTmpPoint.y = moveFrameAmount;
104         } else {
105             Log.w(TAG, "findOffset move is zero ");
106         }
107         return mTmpPoint;
108     }
109 
110     @Override
onClick(View v)111     public void onClick(View v) {
112         if (mMirrorWindowDelegate != null) {
113             Point offset = findOffset(v, mMoveFrameAmountShort);
114             mMirrorWindowDelegate.move(offset.x, offset.y);
115         }
116     }
117 
118     @Override
onTouch(View v, MotionEvent event)119     public boolean onTouch(View v, MotionEvent event) {
120         if (handleDragState(event)) {
121             return true;
122         }
123         switch (event.getAction()) {
124             case MotionEvent.ACTION_UP:
125             case MotionEvent.ACTION_CANCEL:
126                 if (mMoveWindowTask != null) {
127                     mMoveWindowTask.cancel();
128                     mMoveWindowTask = null;
129                 }
130                 break;
131         }
132         return false;
133     }
134 
135     @Override
onLongClick(View v)136     public boolean onLongClick(View v) {
137         Point offset = findOffset(v, mMoveFrameAmountLong);
138         mMoveWindowTask = new MoveWindowTask(mMirrorWindowDelegate, mHandler, offset.x, offset.y,
139                 MOVE_FRAME_DURATION_MS);
140         mMoveWindowTask.schedule();
141         return true;
142     }
143 
onViewRootLongClick(View view)144     private boolean onViewRootLongClick(View view) {
145         mIsDragState = true;
146         mShouldSetTouchStart = true;
147         return true;
148     }
149 
handleDragState(final MotionEvent event)150     private boolean handleDragState(final MotionEvent event) {
151         switch (event.getAction()) {
152             case MotionEvent.ACTION_MOVE:
153                 if (mIsDragState) {
154                     if (mShouldSetTouchStart) {
155                         mLastDrag.set(event.getRawX(), event.getRawY());
156                         mShouldSetTouchStart = false;
157                     }
158                     int xDiff = (int) (event.getRawX() - mLastDrag.x);
159                     int yDiff = (int) (event.getRawY() - mLastDrag.y);
160                     move(xDiff, yDiff);
161                     mLastDrag.set(event.getRawX(), event.getRawY());
162                     return true;
163                 }
164                 return false;
165             case MotionEvent.ACTION_UP:
166             case MotionEvent.ACTION_CANCEL:
167                 if (mIsDragState) {
168                     mIsDragState = false;
169                     return true;
170                 }
171                 return false;
172             default:
173                 return false;
174         }
175     }
176 
177     /**
178      * A timer task to move the mirror window periodically.
179      */
180     static class MoveWindowTask implements Runnable {
181         private final MirrorWindowDelegate mMirrorWindowDelegate;
182         private final int mXOffset;
183         private final int mYOffset;
184         private final Handler mHandler;
185         /** Time in milliseconds between successive task executions.*/
186         private final long mPeriod;
187         private boolean mCancel;
188 
MoveWindowTask(@onNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset, int yOffset, long period)189         MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset,
190                 int yOffset, long period) {
191             mMirrorWindowDelegate = windowDelegate;
192             mXOffset = xOffset;
193             mYOffset = yOffset;
194             mHandler = handler;
195             mPeriod = period;
196         }
197 
198         @Override
run()199         public void run() {
200             if (mCancel) {
201                 return;
202             }
203             mMirrorWindowDelegate.move(mXOffset, mYOffset);
204             schedule();
205         }
206 
207         /**
208          * Schedules the specified task periodically and immediately.
209          */
schedule()210         void schedule() {
211             mHandler.postDelayed(this, mPeriod);
212             mCancel = false;
213         }
214 
cancel()215         void cancel() {
216             mHandler.removeCallbacks(this);
217             mCancel = true;
218         }
219     }
220 }
221