Introduction to amplification function
The zoom in function can be turned on in the mobile phone settings → more settings → accessibility → vision → zoom in function. This function can enlarge the screen. After enlarging the screen, you can drag the screen position.
Windows that can be enlarged: app, status bar, etc
Windows that cannot be enlarged: navigation bar, input method, fillet, etc
Realization of amplification function
Response input ready for amplification
From frameworks / base / services / accessibility / Java / COM / Android / server / accessibility / fullscreenmagicagesturehandler Start with the ontrupletap method of the internal class DetectingState of Java:
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()); } }
The zoomOn method of FullScreenMagnificationGestureHandler was called:
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); }
Called frameworks / base / services / accessibility / Java / COM / Android / server / accessibility / magiconcontroller setScaleAndCenter method of Java:
/** * 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); } }
The setScaleAndCenter method of the MagnificationController internal class DisplayMagnification was called:
@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; }
The updatemagnitionspeclocked method of the internal class DisplayMagnification of the MagnificationController was called:
/** * 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; }
Enlarge window
Go back to the setScaleAndCenter method of the magnizationcontroller internal class DisplayMagnification, and then call the sendSpecToAnimation method of the magnizationcontroller internal class DisplayMagnification:
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); } }
The updateSentSpecMainThread method of the internal class SpecAnimationBridge of magnizationcontroller is called:
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); } } } }
Here, true is passed from animate, so the animatemagnizationspeclocked method of the internal class SpecAnimationBridge of magnizationcontroller is called:
private void animateMagnificationSpecLocked(MagnificationSpec toSpec) { mEndMagnificationSpec.setTo(toSpec); mStartMagnificationSpec.setTo(mSentMagnificationSpec); mValueAnimator.start(); }
The internal class SpecAnimationBridge of magnizationcontroller implements valueanimator Animatorupdatelistener interface. The mValueAnimator here will call the onAnimationUpdate method of the internal class SpecAnimationBridge of the magiccontroller every frame after starting to execute the animation:
/** * 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); } } } }
Save the current scale, offsetX, and offsetY of the animation to mTmpMagnificationSpec, then call the setMagnificationSpecLocked method of the inner class SpecAnimationBridge of MagnificationController:
@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); } }
Called frameworks / base / services / core / Java / COM / Android / server / WM / windowmanagerservice Setmagicationspec method of Java's internal class LocalService:
@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(); } }
Called frameworks / base / services / core / Java / COM / Android / server / WM / accessibilitycontroller Setmagnizationspeclocked method of Java:
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(); } }
The setmagnizationspeclocked method of the internal static class DisplayMagnifier of AccessibilityController was called:
public void setMagnificationSpecLocked(MagnificationSpec spec) { mMagnifedViewport.updateMagnificationSpecLocked(spec); mMagnifedViewport.recomputeBoundsLocked(); mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec); mService.scheduleAnimationLocked(); }
The applymagnicationspeclocked method of WindowManagerService was called
/** 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); } }
Called frameworks / base / services / core / Java / COM / Android / server / WM / displaycontent applyMagnificationSpec method of Java:
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(); }
Called frameworks / base / services / core / Java / COM / Android / server / WM / windowcontainer applyMagnificationSpec method of Java:
// 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); } } }
Firstly, the shouldMagnify method of WindowContainer is called to judge whether the current window needs to be enlarged. If necessary, the frameworks / base / core / Java / Android / view / surfacecontrol The setMatrix method and setPosition method of the internal static class Transaction of Java perform amplification. If not required, call the applyMagnificationSpec method of the child node window in turn.
Determine which windows need to be enlarged
Next, see how to determine which windows need to be enlarged. See the shouldMagnify method of WindowContainer:
/** * @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; }
Call the shouldMagnify method of all child nodes in turn to determine whether to zoom in.
If it is frameworks / base / services / core / Java / COM / Android / server / WM / windowstate Java, its shouldMagnify method:
@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; }
Judge whether to enlarge according to the window type. If the window type is TYPE_INPUT_METHOD,TYPE_INPUT_METHOD_DIALOG,TYPE_MAGNIFICATION_OVERLAY,TYPE_NAVIGATION_BAR or TYPE_NAVIGATION_BAR_PANEL is not enlarged.
If it is the internal class NonAppWindowContainers of DisplayContent, its shouldMagnify method:
@Override boolean shouldMagnify() { // Omitted from Screen-Magnification return false; }
If false is returned directly, it means that NonAppWindowContainers do not need to be enlarged.
Zoom in on the display of area boundaries
From frameworks / base / services / core / Java / COM / Android / server / WM / windowanimator Start with the animate method of Java:
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); } ...... }
The drawmagicedregionborderifneededlocked method of AccessibilityController was called:
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. }
The drawmagicedregionborderifneededlocked method of the internal class DisplayMagnifier of AccessibilityController was called:
public void drawMagnifiedRegionBorderIfNeededLocked(SurfaceControl.Transaction t) { mMagnifedViewport.drawWindowIfNeededLocked(t); }
The drawWindowIfNeededLocked method of the internal class MagnifiedViewport of the internal static class DisplayMagnifier of AccessibilityController was called:
public void drawWindowIfNeededLocked(SurfaceControl.Transaction t) { recomputeBoundsLocked(); mWindow.drawIfNeeded(t); }
The drawIfNeeded method of the internal static class of AccessibilityController, the internal class of DisplayMagnifier, the internal class of magicviewport, and the internal class of ViewportWindow of AccessibilityController was called:
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); } } }