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 static android.view.WindowManager.LayoutParams; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.graphics.PixelFormat; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.util.Log; 28 import android.util.MathUtils; 29 import android.view.Gravity; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.WindowManager; 33 34 import com.android.systemui.R; 35 36 /** 37 * Contains a movable control UI to manipulate mirrored window's position, size and scale. The 38 * window type of the UI is {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} to 39 * ensure it won't be magnified. It is not movable to the navigation bar. 40 */ 41 public abstract class MirrorWindowControl { 42 private static final String TAG = "MirrorWindowControl"; 43 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG) | false; 44 45 /** 46 * A delegate handling a mirrored window's offset. 47 */ 48 public interface MirrorWindowDelegate { 49 /** 50 * Moves the window with specified offset. 51 * 52 * @param xOffset the amount in pixels to offset the window in the X coordinate, in current 53 * display pixels. 54 * @param yOffset the amount in pixels to offset the window in the Y coordinate, in current 55 * display pixels. 56 */ move(int xOffset, int yOffset)57 void move(int xOffset, int yOffset); 58 } 59 60 protected final Context mContext; 61 private final Rect mDraggableBound = new Rect(); 62 final Point mTmpPoint = new Point(); 63 64 @Nullable 65 protected MirrorWindowDelegate mMirrorWindowDelegate; 66 protected View mControlsView; 67 /** 68 * The left top position of the control UI. Initialized when the control UI is visible. 69 * 70 * @see #setDefaultPosition(LayoutParams) 71 */ 72 private final Point mControlPosition = new Point(); 73 private final WindowManager mWindowManager; 74 MirrorWindowControl(Context context)75 MirrorWindowControl(Context context) { 76 mContext = context; 77 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 78 } 79 setWindowDelegate(@ullable MirrorWindowDelegate windowDelegate)80 public void setWindowDelegate(@Nullable MirrorWindowDelegate windowDelegate) { 81 mMirrorWindowDelegate = windowDelegate; 82 } 83 84 /** 85 * Shows the control UI. 86 * 87 * {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} window. 88 */ showControl()89 public final void showControl() { 90 if (mControlsView != null) { 91 Log.w(TAG, "control view is visible"); 92 return; 93 } 94 final Point viewSize = mTmpPoint; 95 mControlsView = onCreateView(LayoutInflater.from(mContext), viewSize); 96 97 final LayoutParams lp = new LayoutParams(); 98 final int defaultSize = mContext.getResources().getDimensionPixelSize( 99 R.dimen.magnification_controls_size); 100 lp.width = viewSize.x <= 0 ? defaultSize : viewSize.x; 101 lp.height = viewSize.y <= 0 ? defaultSize : viewSize.y; 102 setDefaultParams(lp); 103 setDefaultPosition(lp); 104 mWindowManager.addView(mControlsView, lp); 105 updateDraggableBound(lp.width, lp.height); 106 } 107 setDefaultParams(LayoutParams lp)108 private void setDefaultParams(LayoutParams lp) { 109 lp.gravity = Gravity.TOP | Gravity.LEFT; 110 lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 111 | LayoutParams.FLAG_NOT_FOCUSABLE; 112 lp.type = LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; 113 lp.format = PixelFormat.RGBA_8888; 114 lp.setTitle(getWindowTitle()); 115 } 116 setDefaultPosition(LayoutParams layoutParams)117 private void setDefaultPosition(LayoutParams layoutParams) { 118 final Point displaySize = mTmpPoint; 119 mContext.getDisplay().getSize(displaySize); 120 layoutParams.x = displaySize.x - layoutParams.width; 121 layoutParams.y = displaySize.y - layoutParams.height; 122 mControlPosition.set(layoutParams.x, layoutParams.y); 123 } 124 125 /** 126 * Removes the UI from the scene. 127 */ destroyControl()128 public final void destroyControl() { 129 if (mControlsView != null) { 130 mWindowManager.removeView(mControlsView); 131 mControlsView = null; 132 } 133 } 134 135 /** 136 * Moves the control view with specified offset. 137 * 138 * @param xOffset the amount in pixels to offset the UI in the X coordinate, in current 139 * display pixels. 140 * @param yOffset the amount in pixels to offset the UI in the Y coordinate, in current 141 * display pixels. 142 */ move(int xOffset, int yOffset)143 public void move(int xOffset, int yOffset) { 144 if (mControlsView == null) { 145 Log.w(TAG, "control view is not available yet or destroyed"); 146 return; 147 } 148 final Point nextPosition = mTmpPoint; 149 nextPosition.set(mControlPosition.x, mControlPosition.y); 150 mTmpPoint.offset(xOffset, yOffset); 151 setPosition(mTmpPoint); 152 } 153 setPosition(Point point)154 private void setPosition(Point point) { 155 constrainFrameToDraggableBound(point); 156 if (point.equals(mControlPosition)) { 157 return; 158 } 159 mControlPosition.set(point.x, point.y); 160 LayoutParams lp = (LayoutParams) mControlsView.getLayoutParams(); 161 lp.x = mControlPosition.x; 162 lp.y = mControlPosition.y; 163 mWindowManager.updateViewLayout(mControlsView, lp); 164 } 165 constrainFrameToDraggableBound(Point point)166 private void constrainFrameToDraggableBound(Point point) { 167 point.x = MathUtils.constrain(point.x, mDraggableBound.left, mDraggableBound.right); 168 point.y = MathUtils.constrain(point.y, mDraggableBound.top, mDraggableBound.bottom); 169 } 170 updateDraggableBound(int viewWidth, int viewHeight)171 private void updateDraggableBound(int viewWidth, int viewHeight) { 172 final Point size = mTmpPoint; 173 mContext.getDisplay().getSize(size); 174 mDraggableBound.set(0, 0, size.x - viewWidth, size.y - viewHeight); 175 if (DBG) { 176 Log.d(TAG, "updateDraggableBound :" + mDraggableBound); 177 } 178 } 179 getWindowTitle()180 abstract String getWindowTitle(); 181 182 /** 183 * Called when the UI is going to show. 184 * 185 * @param inflater The LayoutInflater object used to inflate the view. 186 * @param viewSize The {@link Point} to specify view's width with {@link Point#x)} and height 187 * with {@link Point#y)} .The value should be greater than 0, otherwise will 188 * fall back to the default size. 189 * @return the View for the control's UI. 190 */ 191 @NonNull onCreateView(@onNull LayoutInflater inflater, @NonNull Point viewSize)192 abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull Point viewSize); 193 } 194