blob: e76a2093f3f488417077bae001734c0ff82e7ab3 [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.systemui.accessibility;
import static android.view.WindowManager.LayoutParams;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.Log;
import android.util.MathUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import com.android.systemui.R;
/**
* Contains a movable control UI to manipulate mirrored window's position, size and scale. The
* window type of the UI is {@link LayoutParams#TYPE_APPLICATION_SUB_PANEL} and the window type
* of the window token should be {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} to
* ensure it is above all windows and won't be mirrored. It is not movable to the navigation bar.
*/
public abstract class MirrorWindowControl {
private static final String TAG = "MirrorWindowControl";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG) | false;
/**
* A delegate handling a mirrored window's offset.
*/
public interface MirrorWindowDelegate {
/**
* Moves the window with specified offset.
*
* @param xOffset the amount in pixels to offset the window in the X coordinate, in current
* display pixels.
* @param yOffset the amount in pixels to offset the window in the Y coordinate, in current
* display pixels.
*/
void move(int xOffset, int yOffset);
}
protected final Context mContext;
private final Rect mDraggableBound = new Rect();
final Point mTmpPoint = new Point();
@Nullable
protected MirrorWindowDelegate mMirrorWindowDelegate;
protected View mControlsView;
/**
* The left top position of the control UI. Initialized when the control UI is visible.
*
* @see #setDefaultPosition(LayoutParams)
*/
private final Point mControlPosition = new Point();
private final WindowManager mWindowManager;
MirrorWindowControl(Context context) {
mContext = context;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
public void setWindowDelegate(@Nullable MirrorWindowDelegate windowDelegate) {
mMirrorWindowDelegate = windowDelegate;
}
/**
* Shows the control UI.
*
* @param binder the window token of the
* {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} window.
*/
public final void showControl(IBinder binder) {
if (mControlsView != null) {
Log.w(TAG, "control view is visible");
return;
}
final Point viewSize = mTmpPoint;
mControlsView = onCreateView(LayoutInflater.from(mContext), viewSize);
final LayoutParams lp = new LayoutParams();
final int defaultSize = mContext.getResources().getDimensionPixelSize(
R.dimen.magnification_controls_size);
lp.width = viewSize.x <= 0 ? defaultSize : viewSize.x;
lp.height = viewSize.y <= 0 ? defaultSize : viewSize.y;
lp.token = binder;
setDefaultParams(lp);
setDefaultPosition(lp);
mWindowManager.addView(mControlsView, lp);
updateDraggableBound(lp.width, lp.height);
}
private void setDefaultParams(LayoutParams lp) {
lp.gravity = Gravity.TOP | Gravity.LEFT;
lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;
lp.type = LayoutParams.TYPE_APPLICATION_SUB_PANEL;
lp.format = PixelFormat.RGBA_8888;
lp.setTitle(getWindowTitle());
}
private void setDefaultPosition(LayoutParams layoutParams) {
final Point displaySize = mTmpPoint;
mContext.getDisplay().getSize(displaySize);
layoutParams.x = displaySize.x - layoutParams.width;
layoutParams.y = displaySize.y - layoutParams.height;
mControlPosition.set(layoutParams.x, layoutParams.y);
}
/**
* Removes the UI from the scene.
*/
public final void destroyControl() {
if (mControlsView != null) {
mWindowManager.removeView(mControlsView);
mControlsView = null;
}
}
/**
* Moves the control view with specified offset.
*
* @param xOffset the amount in pixels to offset the UI in the X coordinate, in current
* display pixels.
* @param yOffset the amount in pixels to offset the UI in the Y coordinate, in current
* display pixels.
*/
public void move(int xOffset, int yOffset) {
if (mControlsView == null) {
Log.w(TAG, "control view is not available yet or destroyed");
return;
}
final Point nextPosition = mTmpPoint;
nextPosition.set(mControlPosition.x, mControlPosition.y);
mTmpPoint.offset(xOffset, yOffset);
setPosition(mTmpPoint);
}
private void setPosition(Point point) {
constrainFrameToDraggableBound(point);
if (point.equals(mControlPosition)) {
return;
}
mControlPosition.set(point.x, point.y);
LayoutParams lp = (LayoutParams) mControlsView.getLayoutParams();
lp.x = mControlPosition.x;
lp.y = mControlPosition.y;
mWindowManager.updateViewLayout(mControlsView, lp);
}
private void constrainFrameToDraggableBound(Point point) {
point.x = MathUtils.constrain(point.x, mDraggableBound.left, mDraggableBound.right);
point.y = MathUtils.constrain(point.y, mDraggableBound.top, mDraggableBound.bottom);
}
private void updateDraggableBound(int viewWidth, int viewHeight) {
final Point size = mTmpPoint;
mContext.getDisplay().getSize(size);
mDraggableBound.set(0, 0, size.x - viewWidth, size.y - viewHeight);
if (DBG) {
Log.d(TAG, "updateDraggableBound :" + mDraggableBound);
}
}
abstract String getWindowTitle();
/**
* Called when the UI is going to show.
*
* @param inflater The LayoutInflater object used to inflate the view.
* @param viewSize The {@link Point} to specify view's width with {@link Point#x)} and height
* with {@link Point#y)} .The value should be greater than 0, otherwise will
* fall back to the default size.
* @return the View for the control's UI.
*/
@NonNull
abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull Point viewSize);
}