blob: 0e3a6e1d72fbb6d48d11d0377bafe07f07ed3b4a [file] [log] [blame]
package com.android.server.wm;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Pools.SynchronizedPool;
import android.view.DisplayInfo;
import android.view.IDisplayMagnificationController;
import android.view.IDisplayMagnificationMediator;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.WindowManager;
import android.view.WindowManagerPolicy;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.impl.PhoneWindowManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
final class DisplayMagnificationMediator extends IDisplayMagnificationMediator.Stub
implements DisplayListener {
private static final String LOG_TAG = DisplayMagnificationMediator.class.getSimpleName();
private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
private static final boolean DEBUG_ROTATION = false;
private static final boolean DEBUG_LAYERS = false;
private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
private static final int MESSAGE_NOTIFY_MAGNIFIED_FRAME_CHANGED = 1;
private static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
private static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
private static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
private static final String METHOD_SIGNATURE_ADD_CONTROLLER =
"addController(int, IDisplayMagnificationController)";
private static final String METHOD_SIGNATURE_REMOVE_CONTROLLER =
"removeController(IDisplayMagnificationController, int)";
private static final String METHOD_SIGNATURE_SET_MAGNIFICATION_SPEC =
"setMagnificationSpec(IDisplayMagnificationController, MagnificationSpec)";
private final Rect mTempRect = new Rect();
private final Rect mTempRect1 = new Rect();
private final Region mTempRegion = new Region();
private final SparseArray<DisplayState> mDisplayStates =
new SparseArray<DisplayMagnificationMediator.DisplayState>();
private final Context mContext;
private final WindowManagerService mWindowManagerService;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_NOTIFY_MAGNIFIED_FRAME_CHANGED: {
SomeArgs args = (SomeArgs) message.obj;
IDisplayMagnificationController client =
(IDisplayMagnificationController) args.arg1;
final int left = args.argi1;
final int top = args.argi2;
final int right = args.argi3;
final int bottom = args.argi4;
try {
client.onMagnifedFrameChanged(left, top, right, bottom);
} catch (RemoteException re) {
/* ignore */
} finally {
args.recycle();
}
} break;
case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
SomeArgs args = (SomeArgs) message.obj;
IDisplayMagnificationController client =
(IDisplayMagnificationController) args.arg1;
final int left = args.argi1;
final int top = args.argi2;
final int right = args.argi3;
final int bottom = args.argi4;
try {
client.onRectangleOnScreenRequested(left, top, right, bottom);
} catch (RemoteException re) {
/* ignore */
} finally {
args.recycle();
}
} break;
case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
IDisplayMagnificationController client =
(IDisplayMagnificationController) message.obj;
try {
client.onUserContextChanged();
} catch (RemoteException re) {
/* ignore */
}
} break;
case MESSAGE_NOTIFY_ROTATION_CHANGED: {
IDisplayMagnificationController client =
(IDisplayMagnificationController) message.obj;
final int rotation = message.arg1;
try {
client.onRotationChanged(rotation);
} catch (RemoteException re) {
/* ignore */
}
} break;
}
}
};
public DisplayMagnificationMediator(WindowManagerService windowManagerService) {
mContext = windowManagerService.mContext;
mWindowManagerService = windowManagerService;
DisplayManager displayManager = (DisplayManager)
mContext.getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(this, mHandler);
}
@Override
public void addController(int displayId, IDisplayMagnificationController controller) {
enforceCallingPermission(Manifest.permission.MAGNIFY_DISPLAY,
METHOD_SIGNATURE_ADD_CONTROLLER);
synchronized (mWindowManagerService.mWindowMap) {
DisplayState displayState = mDisplayStates.get(displayId);
if (displayState != null) {
displayState.clearLw();
}
mDisplayStates.remove(displayId);
mDisplayStates.put(displayId, new DisplayState(displayId, controller));
}
}
@Override
public void removeController(IDisplayMagnificationController controller) {
enforceCallingPermission(Manifest.permission.MAGNIFY_DISPLAY,
METHOD_SIGNATURE_REMOVE_CONTROLLER);
synchronized (mWindowManagerService.mWindowMap) {
final int displayStateCount = mDisplayStates.size();
for (int i = 0; i < displayStateCount; i++) {
DisplayState displayState = mDisplayStates.valueAt(i);
if (displayState.mClient.asBinder() == controller.asBinder()) {
displayState.clearLw();
mDisplayStates.removeAt(i);
return;
}
}
}
}
@Override
public void setMagnificationSpec(IDisplayMagnificationController controller,
MagnificationSpec spec) {
enforceCallingPermission(Manifest.permission.MAGNIFY_DISPLAY,
METHOD_SIGNATURE_SET_MAGNIFICATION_SPEC);
synchronized (mWindowManagerService.mWindowMap) {
DisplayState displayState = null;
final int displayStateCount = mDisplayStates.size();
for (int i = 0; i < displayStateCount; i++) {
DisplayState candidate = mDisplayStates.valueAt(i);
if (candidate.mClient.asBinder() == controller.asBinder()) {
displayState = candidate;
break;
}
}
if (displayState == null) {
Slog.e(LOG_TAG, "Setting magnification spec for unregistered controller "
+ controller);
return;
}
displayState.mMagnificationSpec.initialize(spec.scale, spec.offsetX,
spec.offsetY);
spec.recycle();
mWindowManagerService.scheduleAnimationLocked();
}
}
@Override
public MagnificationSpec getCompatibleMagnificationSpec(IBinder windowToken) {
synchronized (mWindowManagerService.mWindowMap) {
WindowState windowState = mWindowManagerService.mWindowMap.get(windowToken);
if (windowState == null) {
return null;
}
MagnificationSpec spec = getMagnificationSpecLw(windowState);
if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
return null;
}
if (spec == null) {
spec = MagnificationSpec.obtain();
} else {
spec = MagnificationSpec.obtain(spec);
}
spec.scale *= windowState.mGlobalScale;
return spec;
}
}
@Override
public void onDisplayAdded(int displayId) {
/* do nothing */
}
@Override
public void onDisplayRemoved(int displayId) {
synchronized (mWindowManagerService.mWindowMap) {
DisplayState displayState = mDisplayStates.get(displayId);
if (displayState != null) {
displayState.clearLw();
mDisplayStates.remove(displayId);
}
}
}
@Override
public void onDisplayChanged(int displayId) {
/* do nothing */
}
public void onRectangleOnScreenRequestedLw(WindowState windowState, Rect rectangle,
boolean immediate) {
DisplayState displayState = mDisplayStates.get(windowState.getDisplayId());
if (displayState == null) {
return;
}
if (DEBUG_RECTANGLE_REQUESTED) {
Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle
+ " displayId: " + windowState.getDisplayId());
}
if (!displayState.isMagnifyingLw()) {
return;
}
Rect magnifiedRegionBounds = mTempRect1;
displayState.getMagnifiedFrameInContentCoordsLw(magnifiedRegionBounds);
if (magnifiedRegionBounds.contains(rectangle)) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = displayState.mClient;
args.argi1 = rectangle.left;
args.argi2 = rectangle.top;
args.argi3 = rectangle.right;
args.argi4 = rectangle.bottom;
mHandler.obtainMessage(MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
args).sendToTarget();
}
public void onWindowLayersChangedLw(int displayId) {
DisplayState displayState = mDisplayStates.get(displayId);
if (displayState == null) {
return;
}
if (DEBUG_LAYERS) {
Slog.i(LOG_TAG, "Layers changed displayId: " + displayId);
}
displayState.mViewport.recomputeBoundsLw();
}
public void onRotationChangedLw(int displayId, int rotation) {
DisplayState displayState = mDisplayStates.get(displayId);
if (displayState == null) {
return;
}
if (DEBUG_ROTATION) {
Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation)
+ " displayId: " + displayId);
}
displayState.mViewport.recomputeBoundsLw();
mHandler.obtainMessage(MESSAGE_NOTIFY_ROTATION_CHANGED, rotation, 0,
displayState.mClient).sendToTarget();
}
public void onWindowTransitionLw(WindowState windowState, int transition) {
DisplayState displayState = mDisplayStates.get(windowState.getDisplayId());
if (displayState == null) {
return;
}
if (DEBUG_WINDOW_TRANSITIONS) {
Slog.i(LOG_TAG, "Window transition: "
+ PhoneWindowManager.windowTransitionToString(transition)
+ " displayId: " + windowState.getDisplayId());
}
final boolean magnifying = displayState.isMagnifyingLw();
if (magnifying) {
switch (transition) {
case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
case WindowManagerPolicy.TRANSIT_TASK_OPEN:
case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
mHandler.obtainMessage(MESSAGE_NOTIFY_USER_CONTEXT_CHANGED,
displayState.mClient).sendToTarget();
}
}
}
final int type = windowState.mAttrs.type;
if (type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
|| type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
|| type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG
|| type == WindowManager.LayoutParams.TYPE_KEYGUARD
|| type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
switch (transition) {
case WindowManagerPolicy.TRANSIT_ENTER:
case WindowManagerPolicy.TRANSIT_SHOW:
case WindowManagerPolicy.TRANSIT_EXIT:
case WindowManagerPolicy.TRANSIT_HIDE: {
displayState.mViewport.recomputeBoundsLw();
} break;
}
}
switch (transition) {
case WindowManagerPolicy.TRANSIT_ENTER:
case WindowManagerPolicy.TRANSIT_SHOW: {
if (!magnifying) {
break;
}
switch (type) {
case WindowManager.LayoutParams.TYPE_APPLICATION:
case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
case WindowManager.LayoutParams.TYPE_PHONE:
case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
case WindowManager.LayoutParams.TYPE_TOAST:
case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
Rect magnifiedRegionBounds = mTempRect1;
displayState.getMagnifiedFrameInContentCoordsLw(magnifiedRegionBounds);
Rect touchableRegionBounds = mTempRect;
windowState.getTouchableRegion(mTempRegion);
mTempRegion.getBounds(touchableRegionBounds);
if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = displayState.mClient;
args.argi1 = touchableRegionBounds.left;
args.argi2 = touchableRegionBounds.top;
args.argi3 = touchableRegionBounds.right;
args.argi4 = touchableRegionBounds.bottom;
mHandler.obtainMessage(MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
args).sendToTarget();
}
} break;
} break;
}
}
}
public MagnificationSpec getMagnificationSpecLw(WindowState windowState) {
DisplayState displayState = mDisplayStates.get(windowState.getDisplayId());
if (displayState == null) {
return null;
}
MagnificationSpec spec = displayState.mMagnificationSpec;
if (spec != null && !spec.isNop()) {
if (windowState.mAttachedWindow != null) {
if (!canMagnifyWindow(windowState.mAttachedWindow.mAttrs.type)) {
return null;
}
}
if (!canMagnifyWindow(windowState.mAttrs.type)) {
return null;
}
}
return spec;
}
private void enforceCallingPermission(String permission, String function) {
if (Process.myPid() == Binder.getCallingPid()) {
return;
}
if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("The caller does not have " + permission
+ " required to call " + function);
}
}
private static boolean canMagnifyWindow(int type) {
switch (type) {
case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
return false;
}
}
return true;
}
private static final class Viewport {
private final ArrayList<WindowStateInfo> mTempWindowStateInfoList =
new ArrayList<WindowStateInfo>();
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
private final Rect mTempRect3 = new Rect();
private final Rect mBounds = new Rect();
private final Handler mHandler;
private final IDisplayMagnificationController mClient;
private final WindowManagerService mWindowManagerService;
private final DisplayInfo mDisplayInfo;
private final int mDisplayId;
public Viewport(Context context, int displayId, Handler handler,
IDisplayMagnificationController client, WindowManagerService windowManagerService) {
mDisplayId = displayId;
mHandler = handler;
mWindowManagerService = windowManagerService;
mDisplayInfo = mWindowManagerService.getDisplayContentLocked(displayId)
.getDisplayInfo();
mClient = client;
recomputeBoundsLw();
}
private final Comparator<WindowStateInfo> mWindowInfoInverseComparator =
new Comparator<WindowStateInfo>() {
@Override
public int compare(WindowStateInfo lhs, WindowStateInfo rhs) {
if (lhs.mWindowState.mLayer != rhs.mWindowState.mLayer) {
return rhs.mWindowState.mLayer - lhs.mWindowState.mLayer;
}
if (lhs.mTouchableRegion.top != rhs.mTouchableRegion.top) {
return rhs.mTouchableRegion.top - lhs.mTouchableRegion.top;
}
if (lhs.mTouchableRegion.left != rhs.mTouchableRegion.left) {
return rhs.mTouchableRegion.left - lhs.mTouchableRegion.left;
}
if (lhs.mTouchableRegion.right != rhs.mTouchableRegion.right) {
return rhs.mTouchableRegion.right - lhs.mTouchableRegion.right;
}
if (lhs.mTouchableRegion.bottom != rhs.mTouchableRegion.bottom) {
return rhs.mTouchableRegion.bottom - lhs.mTouchableRegion.bottom;
}
return 0;
}
};
public void recomputeBoundsLw() {
Rect magnifiedFrame = mBounds;
magnifiedFrame.set(0, 0, 0, 0);
Rect oldmagnifiedFrame = mTempRect3;
oldmagnifiedFrame.set(magnifiedFrame);
Rect availableFrame = mTempRect1;
availableFrame.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
ArrayList<WindowStateInfo> visibleWindows = mTempWindowStateInfoList;
visibleWindows.clear();
getVisibleWindowsLw(visibleWindows);
Collections.sort(visibleWindows, mWindowInfoInverseComparator);
final int visibleWindowCount = visibleWindows.size();
for (int i = 0; i < visibleWindowCount; i++) {
WindowStateInfo info = visibleWindows.get(i);
if (info.mWindowState.mAttrs.type == WindowManager
.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
continue;
}
Rect windowFrame = mTempRect2;
windowFrame.set(info.mTouchableRegion);
if (canMagnifyWindow(info.mWindowState.mAttrs.type)) {
magnifiedFrame.union(windowFrame);
magnifiedFrame.intersect(availableFrame);
} else {
subtract(windowFrame, magnifiedFrame);
subtract(availableFrame, windowFrame);
}
if (availableFrame.equals(magnifiedFrame)) {
break;
}
}
for (int i = visibleWindowCount - 1; i >= 0; i--) {
visibleWindows.remove(i).recycle();
}
final int displayWidth = mDisplayInfo.logicalWidth;
final int displayHeight = mDisplayInfo.logicalHeight;
magnifiedFrame.intersect(0, 0, displayWidth, displayHeight);
if (!oldmagnifiedFrame.equals(magnifiedFrame)) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = mClient;
args.argi1 = magnifiedFrame.left;
args.argi2 = magnifiedFrame.top;
args.argi3 = magnifiedFrame.right;
args.argi4 = magnifiedFrame.bottom;
mHandler.obtainMessage(MESSAGE_NOTIFY_MAGNIFIED_FRAME_CHANGED, args)
.sendToTarget();
}
}
private void getVisibleWindowsLw(ArrayList<WindowStateInfo> outWindowStates) {
DisplayContent displayContent = mWindowManagerService.getDisplayContentLocked(
mDisplayId);
WindowList windowList = displayContent.getWindowList();
final int windowCount = windowList.size();
for (int i = 0; i < windowCount; i++) {
WindowState windowState = windowList.get(i);
if (windowState.isVisibleLw() || windowState.mAttrs.type == WindowManager
.LayoutParams.TYPE_UNIVERSE_BACKGROUND) {
outWindowStates.add(WindowStateInfo.obtain(windowState));
}
}
}
public Rect getBoundsLw() {
return mBounds;
}
private static boolean subtract(Rect lhs, Rect rhs) {
if (lhs.right < rhs.left || lhs.left > rhs.right
|| lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
return false;
}
if (lhs.left < rhs.left) {
lhs.right = rhs.left;
}
if (lhs.top < rhs.top) {
lhs.bottom = rhs.top;
}
if (lhs.right > rhs.right) {
lhs.left = rhs.right;
}
if (lhs.bottom > rhs.bottom) {
lhs.top = rhs.bottom;
}
return true;
}
private static final class WindowStateInfo {
private static final int MAX_POOL_SIZE = 30;
private static final SynchronizedPool<WindowStateInfo> sPool =
new SynchronizedPool<WindowStateInfo>(MAX_POOL_SIZE);
private static final Region mTempRegion = new Region();
public WindowState mWindowState;
public final Rect mTouchableRegion = new Rect();
public static WindowStateInfo obtain(WindowState windowState) {
WindowStateInfo info = sPool.acquire();
if (info == null) {
info = new WindowStateInfo();
}
info.mWindowState = windowState;
windowState.getTouchableRegion(mTempRegion);
mTempRegion.getBounds(info.mTouchableRegion);
return info;
}
public void recycle() {
mWindowState = null;
mTouchableRegion.setEmpty();
sPool.release(this);
}
}
}
private final class DisplayState {
final int mDisplayId;
final MagnificationSpec mMagnificationSpec;
final Viewport mViewport;
final IDisplayMagnificationController mClient;
DisplayState(int displayId, IDisplayMagnificationController client) {
mDisplayId = displayId;
mClient = client;
mMagnificationSpec = MagnificationSpec.obtain();
mViewport = new Viewport(mContext, mDisplayId, mHandler,
mClient, mWindowManagerService);
}
public boolean isMagnifyingLw() {
return mMagnificationSpec.scale > 1.0f;
}
private void getMagnifiedFrameInContentCoordsLw(Rect rect) {
MagnificationSpec spec = mMagnificationSpec;
rect.set(mViewport.getBoundsLw());
rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
rect.scale(1.0f / spec.scale);
}
public void clearLw() {
mMagnificationSpec.recycle();
}
}
}