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