放大功能 - 安卓R

放大功能简介

在手机设置 → 更多设置 → 无障碍 → 视觉 → 放大功能里面可以开启放大功能。这个功能可以把屏幕放大,放大屏幕后可以拖动屏幕位置。

可以放大的窗口: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);
                        }
                    }
                }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值