放大功能简介
在手机设置 → 更多设置 → 无障碍 → 视觉 → 放大功能里面可以开启放大功能。这个功能可以把屏幕放大,放大屏幕后可以拖动屏幕位置。
可以放大的窗口:app、状态栏等
不能放大的窗口:导航栏、输入法、圆角等
放大功能实现
响应输入准备放大
从frameworks/base/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java的内部类DetectingState的onTripleTap方法开始:
private void onTripleTap(MotionEvent up) {
if (DEBUG_DETECTING) {
Slog.i(LOG_TAG, "onTripleTap(); delayed: "
+ MotionEventInfo.toString(mDelayedEventQueue));
}
clear();
// Toggle zoom
if (mMagnificationController.isMagnifying(mDisplayId)) {
zoomOff();
} else {
zoomOn(up.getX(), up.getY());
}
}
调用了FullScreenMagnificationGestureHandler的zoomOn方法:
private void zoomOn(float centerX, float centerY) {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")");
final float scale = MathUtils.constrain(
mMagnificationController.getPersistedScale(),
MIN_SCALE, MAX_SCALE);
mMagnificationController.setScaleAndCenter(mDisplayId,
scale, centerX, centerY,
/* animate */ true,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
}
调用了frameworks/base/services/accessibility/java/com/android/server/accessibility/MagnificationController.java的setScaleAndCenter方法:
/**
* Sets the scale and center of the magnified region, optionally
* animating the transition. If animation is disabled, the transition
* is immediate.
*
* @param displayId The logical display id.
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
* @param centerY the screen-relative Y coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
boolean animate, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
if (display == null) {
return false;
}
return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
}
}
调用了MagnificationController内部类DisplayMagnification的setScaleAndCenter方法:
@GuardedBy("mLock")
boolean setScaleAndCenter(float scale, float centerX, float centerY,
boolean animate, int id) {
if (!mRegistered) {
return false;
}
if (DEBUG) {
Slog.i(LOG_TAG,
"setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+ ", centerY = " + centerY + ", animate = " + animate
+ ", id = " + id
+ ")");
}
final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
sendSpecToAnimation(mCurrentMagnificationSpec, animate);
if (isMagnifying() && (id != INVALID_ID)) {
mIdOfLastServiceToMagnify = id;
}
return changed;
}
调用了MagnificationController内部类DisplayMagnification的updateMagnificationSpecLocked方法:
/**
* Updates the current magnification spec.
*
* @param scale the magnification scale
* @param centerX the unscaled, screen-relative X coordinate of the center
* of the viewport, or {@link Float#NaN} to leave unchanged
* @param centerY the unscaled, screen-relative Y coordinate of the center
* of the viewport, or {@link Float#NaN} to leave unchanged
* @return {@code true} if the magnification spec changed or {@code false}
* otherwise
*/
boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
// Handle defaults.
if (Float.isNaN(centerX)) {
centerX = getCenterX();
}
if (Float.isNaN(centerY)) {
centerY = getCenterY();
}
if (Float.isNaN(scale)) {
scale = getScale();
}
// Compute changes.
boolean changed = false;
final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) {
mCurrentMagnificationSpec.scale = normScale;
changed = true;
}
final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f
+ mMagnificationBounds.left - centerX * normScale;
final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f
+ mMagnificationBounds.top - centerY * normScale;
changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
if (changed) {
onMagnificationChangedLocked();
}
return changed;
}
放大窗口
回到MagnificationController内部类DisplayMagnification的setScaleAndCenter方法中,后面还调用了MagnificationController内部类DisplayMagnification的sendSpecToAnimation方法:
void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
if (DEBUG) {
Slog.i(LOG_TAG,
"sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")");
}
if (Thread.currentThread().getId() == mMainThreadId) {
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
} else {
final Message m = PooledLambda.obtainMessage(
SpecAnimationBridge::updateSentSpecMainThread,
mSpecAnimationBridge, spec, animate);
mControllerCtx.getHandler().sendMessage(m);
}
}
调用了MagnificationController的内部类SpecAnimationBridge的updateSentSpecMainThread方法:
public void updateSentSpecMainThread(MagnificationSpec spec, boolean animate) {
if (mValueAnimator.isRunning()) {
mValueAnimator.cancel();
}
// If the current and sent specs don't match, update the sent spec.
synchronized (mLock) {
final boolean changed = !mSentMagnificationSpec.equals(spec);
if (changed) {
if (animate) {
animateMagnificationSpecLocked(spec);
} else {
setMagnificationSpecLocked(spec);
}
}
}
}
这里animate传过来的是true,因此调用了MagnificationController的内部类SpecAnimationBridge的animateMagnificationSpecLocked方法:
private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
mEndMagnificationSpec.setTo(toSpec);
mStartMagnificationSpec.setTo(mSentMagnificationSpec);
mValueAnimator.start();
}
MagnificationController的内部类SpecAnimationBridge本身实现了ValueAnimator.AnimatorUpdateListener接口,这里的mValueAnimator在开始执行动画后,每帧都会调用MagnificationController的内部类SpecAnimationBridge的onAnimationUpdate方法:
/**
* Class responsible for animating spec on the main thread and sending spec
* updates to the window manager.
*/
private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
......
@Override
public void onAnimationUpdate(ValueAnimator animation) {
synchronized (mLock) {
if (mEnabled) {
float fract = animation.getAnimatedFraction();
mTmpMagnificationSpec.scale = mStartMagnificationSpec.scale +
(mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
mTmpMagnificationSpec.offsetX = mStartMagnificationSpec.offsetX +
(mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
* fract;
mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
(mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
* fract;
setMagnificationSpecLocked(mTmpMagnificationSpec);
}
}
}
}
将动画当前的scale、offsetX、offsetY计算出来保存到mTmpMagnificationSpec中,然后调用了MagnificationController的内部类SpecAnimationBridge的setMagnificationSpecLocked方法:
@GuardedBy("mLock")
private void setMagnificationSpecLocked(MagnificationSpec spec) {
if (mEnabled) {
if (DEBUG_SET_MAGNIFICATION_SPEC) {
Slog.i(LOG_TAG, "Sending: " + spec);
}
mSentMagnificationSpec.setTo(spec);
mControllerCtx.getWindowManager().setMagnificationSpec(
mDisplayId, mSentMagnificationSpec);
}
}
调用了frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java的内部类LocalService的setMagnificationSpec方法:
@Override
public void setMagnificationSpec(int displayId, MagnificationSpec spec) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
mAccessibilityController.setMagnificationSpecLocked(displayId, spec);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
}
if (Binder.getCallingPid() != myPid()) {
spec.recycle();
}
}
调用了frameworks/base/services/core/java/com/android/server/wm/AccessibilityController.java的setMagnificationSpecLocked方法:
public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.setMagnificationSpecLocked(spec);
}
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver != null) {
windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
}
}
调用了AccessibilityController的内部静态类DisplayMagnifier的setMagnificationSpecLocked方法:
public void setMagnificationSpecLocked(MagnificationSpec spec) {
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
调用了WindowManagerService的applyMagnificationSpecLocked方法
/** Called from Accessibility Controller to apply magnification spec */
public void applyMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
displayContent.applyMagnificationSpec(spec);
}
}
调用了frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java的applyMagnificationSpec方法:
void applyMagnificationSpec(MagnificationSpec spec) {
if (spec.scale != 1.0) {
mMagnificationSpec = spec;
} else {
mMagnificationSpec = null;
}
// Re-parent IME's SurfaceControl when MagnificationSpec changed.
updateImeParent();
if (spec.scale != 1.0) {
applyMagnificationSpec(getPendingTransaction(), spec);
} else {
clearMagnificationSpec(getPendingTransaction());
}
getPendingTransaction().apply();
}
调用了frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java的applyMagnificationSpec方法:
// TODO(b/68336570): Should this really be on WindowContainer since it
// can only be used on the top-level nodes that aren't animated?
// (otherwise we would be fighting other callers of setMatrix).
void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
if (shouldMagnify()) {
t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
.setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
mLastMagnificationSpec = spec;
} else {
clearMagnificationSpec(t);
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).applyMagnificationSpec(t, spec);
}
}
}
这里首先调用了WindowContainer的shouldMagnify方法判断当前窗口是否需要放大,如果需要则调用frameworks/base/core/java/android/view/SurfaceControl.java的内部静态类Transaction的setMatrix方法和setPosition方法执行放大,如果不需要则依次调用子节点窗口的applyMagnificationSpec方法。
确定哪些窗口需要放大
接下来看下是如何确定哪些窗口需要放大的,看WindowContainer的shouldMagnify方法:
/**
* @return Whether this WindowContainer should be magnified by the accessibility magnifier.
*/
boolean shouldMagnify() {
if (mSurfaceControl == null) {
return false;
}
for (int i = 0; i < mChildren.size(); i++) {
if (!mChildren.get(i).shouldMagnify()) {
return false;
}
}
return true;
}
依次调用所有子节点的shouldMagnify方法来判断是否需要放大。
如果是frameworks/base/services/core/java/com/android/server/wm/WindowState.java,其shouldMagnify方法:
@Override
boolean shouldMagnify() {
if (mAttrs.type == TYPE_INPUT_METHOD ||
mAttrs.type == TYPE_INPUT_METHOD_DIALOG ||
mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
mAttrs.type == TYPE_NAVIGATION_BAR ||
// It's tempting to wonder: Have we forgotten the rounded corners overlay?
// worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL
mAttrs.type == TYPE_NAVIGATION_BAR_PANEL) {
return false;
}
return true;
}
根据窗口类型判断是否需要放大,如果窗口类型是TYPE_INPUT_METHOD、TYPE_INPUT_METHOD_DIALOG、TYPE_MAGNIFICATION_OVERLAY、TYPE_NAVIGATION_BAR或TYPE_NAVIGATION_BAR_PANEL则不放大。
如果是DisplayContent的内部类NonAppWindowContainers,其shouldMagnify方法:
@Override
boolean shouldMagnify() {
// Omitted from Screen-Magnification
return false;
}
直接返回false,说明NonAppWindowContainers不需要放大。
放大区域边界的显示
从frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java的animate方法开始:
private void animate(long frameTimeNs) {
if (!mInitialized) {
return;
}
// Schedule next frame already such that back-pressure happens continuously.
scheduleAnimation();
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
mService.openSurfaceTransaction();
try {
final AccessibilityController accessibilityController =
mService.mAccessibilityController;
final int numDisplays = mDisplayContentsAnimators.size();
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
// Update animations of all applications, including those associated with
// exiting/removed apps.
dc.updateWindowsForAnimator();
dc.prepareSurfaces();
}
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
dc.checkAppWindowsReadyToShow();
if (accessibilityController != null) {
accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId,
mTransaction);
}
}
cancelAnimation();
if (mService.mWatermark != null) {
mService.mWatermark.drawIfNeeded();
}
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
}
......
}
调用了AccessibilityController的drawMagnifiedRegionBorderIfNeededLocked方法:
public void drawMagnifiedRegionBorderIfNeededLocked(int displayId,
SurfaceControl.Transaction t) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(t);
}
// Not relevant for the window observer.
}
调用了AccessibilityController的内部类DisplayMagnifier的drawMagnifiedRegionBorderIfNeededLocked方法:
public void drawMagnifiedRegionBorderIfNeededLocked(SurfaceControl.Transaction t) {
mMagnifedViewport.drawWindowIfNeededLocked(t);
}
调用了AccessibilityController的内部静态类DisplayMagnifier的内部类MagnifiedViewport的drawWindowIfNeededLocked方法:
public void drawWindowIfNeededLocked(SurfaceControl.Transaction t) {
recomputeBoundsLocked();
mWindow.drawIfNeeded(t);
}
调用了AccessibilityController的内部静态类DisplayMagnifier的内部类MagnifiedViewport的内部类ViewportWindow的drawIfNeeded方法:
public void drawIfNeeded(SurfaceControl.Transaction t) {
synchronized (mService.mGlobalLock) {
if (!mInvalidated) {
return;
}
mInvalidated = false;
if (mAlpha > 0) {
Canvas canvas = null;
try {
// Empty dirty rectangle means unspecified.
if (mDirtyRect.isEmpty()) {
mBounds.getBounds(mDirtyRect);
}
mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
canvas = mSurface.lockCanvas(mDirtyRect);
if (DEBUG_VIEWPORT_WINDOW) {
Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
}
} catch (IllegalArgumentException iae) {
/* ignore */
} catch (Surface.OutOfResourcesException oore) {
/* ignore */
}
if (canvas == null) {
return;
}
if (DEBUG_VIEWPORT_WINDOW) {
Slog.i(LOG_TAG, "Bounds: " + mBounds);
}
canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
mPaint.setAlpha(mAlpha);
Path path = mBounds.getBoundaryPath();
canvas.drawPath(path, mPaint);
mSurface.unlockCanvasAndPost(canvas);
t.show(mSurfaceControl);
} else {
t.hide(mSurfaceControl);
}
}
}