Android R WindowManagerService模块(7) 屏幕旋转流程(2)

4,238 阅读14分钟

在上一篇文章中,主要对获取屏幕方向值的流程进行了总结,这篇文章中,将接着上篇的流程,对以下几点功能实现进行分析:

    1. 根据屏幕方向值+当前窗口属性,确定显示方向;
    1. 逻辑屏方向的更新;
    1. 转屏动画的执行;

接着上文分析,当DisplayRotation发现屏幕方向改变后,通过WindowManagerService#updateRotation()方法通知WMS进行全局更新。WMS#updateRotation()方法中直接调用updateRotationUnchecked()方法:

//  frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    /**
     * @param alwaysSendConfiguration 是否要进行Configuration的更新
     * @param forceRelayout           是否强制进行layout操作
     */
    private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
        ......
        try {
            synchronized (mGlobalLock) {
                boolean layoutNeeded = false;
                final int displayCount = mRoot.mChildren.size();
                // 遍历DisplayContent
                for (int i = 0; i < displayCount; ++i) {
                    final DisplayContent displayContent = mRoot.mChildren.get(i);
                    // 进入DisplayContent更新屏幕方向值
                    final boolean rotationChanged = displayContent.updateRotationUnchecked();

                    if (rotationChanged) {
                        mAtmService.getTaskChangeNotificationController()
                                .notifyOnActivityRotation(displayContent.mDisplayId);
                    }
                    // 是否要进行layout
                    if (!rotationChanged || forceRelayout) {
                        displayContent.setLayoutNeeded();
                        layoutNeeded = true;
                    }
                    // 是否要进行Configuration的更新
                    if (rotationChanged || alwaysSendConfiguration) {
                        displayContent.sendNewConfiguration();
                    }
                }
                // 进行layout
                if (layoutNeeded) {
                    mWindowPlacerLocked.performSurfacePlacement();
                }
            }
        } 
        .......
    }

在以上方法中,会对每一个DisplayContent进行方向更新,并会在DisplayContent中则直接调用DisplayRotation.updateRotationUnchecked()方法,然后根据返回结果和参数决定是否进行Configuration的更新和layout操作。

DisplayRotation.updateRotationUnchecked()方法如下:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
 
        boolean updateRotationUnchecked(boolean forceUpdate) {
        final int displayId = mDisplayContent.getDisplayId();
         
            // 获取该DisplayContent的转屏动画对象
            final ScreenRotationAnimation screenRotationAnimation =
                    mDisplayContent.getRotationAnimation();
            // 如果当前有正在执行的转屏动画,则不会进行后续动作
            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
                return false;
            }
            // 如果当前屏幕处于冻结状态,则不会更新
            if (mService.mDisplayFrozen) {
                return false;
            }
            // 如果顶层Activity执行最近任务动画,且该Activity设置了一个固定方向,则不会更新
            if (mDisplayContent.mFixedRotationTransitionListener
                    .isTopFixedOrientationRecentsAnimating()) {
                return false;
            }
        }
 
        // 如果WMS没有启动完成,则不会更新
        if (!mService.mDisplayEnabled) {
            return false;
        }
        // 当前(未更新前)屏幕方向
        final int oldRotation = mRotation;
        // 最近一次应用设置的显示方向
        final int lastOrientation = mLastOrientation;
 
        // 根据给定的显示方向确定一个屏幕方向
        final int rotation = rotationForOrientation(lastOrientation, oldRotation);
       
        // 屏幕方向没有发生变化,则不会更新,否则说明屏幕方向值发生了变化
        if (oldRotation == rotation) {
            return false;
        }
        // 如果屏幕方向值转了不超过两次,则设置mDisplayContent.mWaitingForConfig为true,以防还有一次转动,一个小优化
        if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
            mDisplayContent.mWaitingForConfig = true;
        }
        // 更新当前屏幕方向
        mRotation = rotation;
        //
        mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
        mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
                mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
        // 表示需要进行layout操作
        mDisplayContent.setLayoutNeeded();
         
        // 选择转屏动画
        if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
            // 无缝衔接动画
            prepareSeamlessRotation();
        } else {
            prepareNormalRotationAnimation();
        }
 
        // 给SystemUI发送一个屏幕方向变化的消息,会在SystemUI中的DisplayChangeController中处理,并会回调fw
        startRemoteRotation(oldRotation, mRotation);
 
        return true;
    }

以上方法中,首先会进行一些判断来确定是否需要更新屏幕方向值,只有在满足更新条件时才会进行更新,这些条件如下:

  1. 如果当前有正在执行的转屏动画,则不会更新;
  2. 如果当前屏幕依然处于冻结状态,则不会更新;
  3. 如果顶层Activity执行最近任务动画,且该Activity设置了一个固定方向,则不会更新;
  4. 如果WMS没有启动完成,则不会更新;

如果通过以上四个条件的验证,那么接下来就会根据当前屏幕方向值和当前显示方向(未更新前的值),结合Sensor检测到的屏幕方向值、Activity设置的方向状态等获得一个最终的显示方向。

1.根据屏幕方向值+当前窗口属性,确定显示方向

这部分流程在rotationForOrientation()方法中进行,代码如下:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
 
    int rotationForOrientation(@ScreenOrientation int orientation,
            @Surface.Rotation int lastRotation) {
        // 如果用户固定了屏幕方向,则返回用户设置的屏幕方向值
        if (isFixedToUserRotation()) {
            return mUserRotation;
        }
        // 获取Sensor检测到的屏幕方向值
        int sensorRotation = mOrientationListener != null
                ? mOrientationListener.getProposedRotation() // may be -1
                : -1;
        if (sensorRotation < 0) {
            sensorRotation = lastRotation;
        }
 
        // lid switch状态
        final int lidState = mDisplayPolicy.getLidState();
        // 各种dock连接状态
        final int dockMode = mDisplayPolicy.getDockMode();
        final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();
        final boolean carDockEnablesAccelerometer =
                mDisplayPolicy.isCarDockEnablesAccelerometer();
        final boolean deskDockEnablesAccelerometer =
                mDisplayPolicy.isDeskDockEnablesAccelerometer();
 
        // 表示要选择的屏幕方向优先值
        final int preferredRotation;
        if (!isDefaultDisplay) {
            // 第二屏幕永远使用用户设置的屏幕方向值
            preferredRotation = mUserRotation;
        } else if (lidState == LID_OPEN && mLidOpenRotation >= 0) {
            // lid_switch open时,使用lid open指定的值
            preferredRotation = mLidOpenRotation;
        } else if (dockMode == Intent.EXTRA_DOCK_STATE_CAR
            ........
        } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
            // Activity将显示方向锁定为其当前的任一显示方向,因此继续使用当前屏幕方向值
            preferredRotation = lastRotation;
        } else if (!mSupportAutoRotation) {
            // 不支持自动旋转功能
            preferredRotation = -1;
        } else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE                 
                        // 如果开启了自动旋转,且显示方向值满足以下几种,
                        && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER))
                || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
                || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
                || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
            // mAllowAllRotations表示是否允许全方位都进行旋转,有的设备不能旋转180度,原因就是这里为false
            if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) {
                mAllowAllRotations = mContext.getResources().getBoolean(
                        R.bool.config_allowAllRotations)
                                ? ALLOW_ALL_ROTATIONS_ENABLED
                                : ALLOW_ALL_ROTATIONS_DISABLED;
            }
            // sensor检测当前屏幕方向值为180度
            if (sensorRotation != Surface.ROTATION_180
                    // 允许全方位旋转
                    || mAllowAllRotations == ALLOW_ALL_ROTATIONS_ENABLED
                    // 显示方向为fullsensor
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR 
                    // 显示方向为fullsensor
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {  
                // 满足任一条件,都会优先使用sensor检测到的屏幕方向值
                preferredRotation = sensorRotation;                                
            } else {   
                // 否则使用当前屏幕方向值
                preferredRotation = lastRotation;
            }
        } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED  
        // 如果关闭了方向旋转且最近一次显示方向满足以下条件时,使用用户设置的屏幕方向值
                && orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR     
                && orientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
                && orientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
                && orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
                && orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
            // 优先使用用户设置屏幕方向值
            preferredRotation = mUserRotation;
        } else {
            // 说明没有优先使用的条件
            preferredRotation = -1;
        }
 
        // 根据最近一次显示方向确定
        switch (orientation) {
            // 显示方向为竖屏
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:  
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                return mPortraitRotation;
 
            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                return mLandscapeRotation;
 
            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                return mUpsideDownRotation;
 
            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                return mSeascapeRotation;
 
            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
            case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                if (isLandscapeOrSeascape(lastRotation)) {
                    return lastRotation;
                }
                return mLandscapeRotation;
 
            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
            case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                if (isAnyPortrait(lastRotation)) {
                    return lastRotation;
                }
                return mPortraitRotation;
 
            default:
                // 说明显示方向为USER, UNSPECIFIED, NOSENSOR, SENSOR,FULL_SENSOR时,返回preferredRotation值或0度
                if (preferredRotation >= 0) {
                    return preferredRotation;
                }
                return Surface.ROTATION_0;
        }
    }

以上方法中,首先根据各种系统状态确定一个优先使用的屏幕方向,然后再根据最近一次Activity设置的的显示方向,确认最终的屏幕方向,这些流程的决策流程总结如下:

首先确定优先屏幕方向值:

  1. 如果用户固定了屏幕方向值,则直接返回用户设置的屏幕方向值。固定屏幕方向使用WMS#setFixedToUserRotation()方法;
  2. 如果不是默认屏,则直接返回用户设置的屏幕方向值。通过WMS#freezeDisplayRotation(int displayId, int rotation)方法设置指定屏幕方向值;
  3. 如果lid状态处于OPEN状态,且对该模式下配置了指定的屏幕方向,则优先使用指定的lid_switch open时的屏幕方向值。lid_switch通过inputmanger上报,如霍尔传感器的事件就是lid_switch的形式上报;
  4. 如果设备处于各种Dock模式,且对该模式下配置了指定的屏幕方向,则优先使用该指定的屏幕方向,这种场景不多,这里我们直接跳过它的流程;
  5. 如果当前Activity设置的显示方向值为LOCKED,即将屏幕方向锁定为其当前的任一显示方向,则优先使用当前屏幕方向值。Activity显示方向可通过android:screenOrientation="locked"或Activity#setRequestedOrientation(int requestedOrientation)方法设置,下同;
  6. 如果不支持自动旋转功能,则表示优先屏幕方向的变量将会被置为-1。通过config.xml中”config_supportAutoRotation“配置是否支持自动旋转功能;
  7. 如果开启了自动旋转,且Activity设置的显示方向值为USER、UNSPECIFIED、USER_LANDSCAPE、USER_PORTRAIT、FULL_USER,则优先使用Sensor获得的屏幕方向或最近一次的屏幕方向值,这几种方向值都是支持根据Sensor来调整方向的;
  8. 如果Activity设置的显示方向值为SENSOR、FULL_SENSOR、SENSOR_LANDSCAPE、SENSOR_PORTRAIT,这种情况下同上条;
  9. 如果关闭了自动旋转,且Activity设置的显示方向值为NOSENSOR、LANDSCAPE、PORTRAIT、REVERSE_LANDSCAPE、REVERSE_PORTRAIT,这种情况下,则优先使用用户设置的屏幕方向;
  10. 如果以上条件都不满足,则优先屏幕方向变量将会被置为-1。

其次确定最终屏幕方向值:

  1. 如果最近一次Activity设置的显示方向值为PORTRAIT,则屏幕方向一般返回Surface.ROTATION_0;
  2. 如果最近一次Activity设置的显示方向值为LANDSCAPE,则屏幕方向一般返回Surface.ROTATION_90;
  3. 如果最近一次Activity设置的显示方向值为REVERSE_PORTRAIT,则屏幕方向一般返回Surface.ROTATION_180;
  4. 如果最近一次Activity设置的显示方向值为REVERSE_LANDSCAPE,则屏幕方向一般返回Surface.ROTATION_270;
  5. 如果最近一次Activity设置的显示方向值为SENSOR_LANDSCAPE或USER_LANDSCAPE,则屏幕方向一般返回Surface.ROTATION_90或Surface.ROTATION_270;
  6. 如果最近一次Activity设置的显示方向值为SENSOR_PORTRAIT或USER_PORTRAIT,则屏幕方向一般返回ROTATION_0或Surface.ROTATION_270;
  7. 如果以上条件都不满足,则当优先屏幕方向大于等于0时,屏幕方向返回优先值,否则返回Surface.ROTATION_0。

上面这些步骤看起来令人非常难受,但其实这段逻辑就是android:screenOrientation属性功能的实现之处,了解该属性的各个值之后就自然明白了。 经过rotationForOrientation()方法后,将得出一个屏幕方向值。

回到DisplayRotation#updateRotationUnchecked()方法中,如果得到的屏幕方向值发生了变化,说明就需要更新显示方向了,接下来将选择转屏动画,用来执行旋转动画。

2.确定转屏方式和转屏动画类型

转屏根据是否有动画分为两种:

  1. 一种是Seamless Rotation(无缝衔接),使用它时不会对屏幕进行冻结,直接由一个方向过渡到另一个方向;
  2. 一种是Normal Rotation,使用它时,将会对旧方向截图并冻结屏幕,并在更新完成后,通过动画缓慢过渡到新的显示方向。

根据动画执行方式,又可以分为四类,并且这四类是应用可以自行设置的,通过WindowManager.LayoutParams.rotationAnimation属性来指定:

  1. ROTATION_ANIMATION_ROTATE:默认执行方式;
  2. ROTATION_ANIMATION_CROSSFADE:fade-in、fade-out的方式;
  3. ROTATION_ANIMATION_JUMPCUT:当前窗口显示或退出时立即跳入/跳出;
  4. ROTATION_ANIMATION_SEAMLESS:使用Seamless Rotation,但是如果不支持Seamless Rotation时,将使用CROSSFADE方式;

以上四个值只能用在全屏非透明窗口上。

在触发转屏流程后,首先是选择转屏动画的类型,通过shouldRotateSeamlessly()方法来是否使用Seamless Rotation:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java

    /**
     * @param oldRotation   旧屏幕方向
     * @param newRotation   新屏幕方向
     * @param forceUpdate   是否强制进行更新
     * @return  true表示采用seamless Rotation
     */
    boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
        if (mDisplayContent.hasTopFixedRotationLaunchingApp()) {
            return true;
        }
 
        // 获取当前全屏非透明窗口
        final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
        // 如果当前全屏非透明窗口没有获得焦点,不使用Seamless Rotation
        if (w == null || w != mDisplayContent.mCurrentFocus) {
            return false;
        }
        // 如果顶层窗口动画属性非ROTATION_ANIMATION_SEAMLESS,或正在执行动画,不使用Seamless Rotation
        if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
            return false;
        }
        // 如果屏幕方向值相对发生了180度,不使用Seamless Rotation
        if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
            return false;
        }
        // 其他几类场景不常见,略去
        ......
 
        // 如果当前全屏非透明窗口的mSeamlesslyRotated属性被设置为false,不使用Seamless Rotation
        if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
            return false;
        }
         // 不满足以上条件,返回true
        return true;
    }

正常情况下,特殊场景除外,大部分场景都使用Normal Rotation,即在旋转屏幕的时候,会以动画的形式进行。

使用Normal Rotation时,首先通过prepareNormalRotationAnimation()方法选择动画执行方式,然后将开始冻结屏幕:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
 
    void prepareNormalRotationAnimation() {
        // 取消正在执行的Seamless Rotation
        cancelSeamlessRotation();
        // 选择动画执行方式
        final RotationAnimationPair anim = selectRotationAnimation();
        // 冻结屏幕
        mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
    }

selectRotationAnimation()方法中选择动画执行方式时,会根据当前顶层窗口的WindowManager.LayoutParams#rotationAnimation来选择一组动画,选择动画执行方式的流程比较简单,这里就直接略去。

app如果想操作横竖屏动画,通过WindowManager.LayoutParams#rotationAnimation控制,就是在selectRotationAnimation()这里选择的

3.冻结屏幕

如果不使用Seamless Rotation动画,那么在确定好转屏动画方式后,将会冻结屏幕。冻结屏幕就是把显示内容冻结在当前的内容上,冻结的实现原理很简单:

  1. 冻结掉Input事件;
  2. 对当前屏幕截图,并显示在最顶层;

之所以在转屏时需要冻结屏幕,是因为屏幕的冻结和解冻,都可以通过参数以动画的形式进行,当屏幕实际旋转完成后,再解冻后以动画形式回到新显示方向,用户体验更好。而转屏动画的实现,也和屏幕冻结绑定在一起。后面会对屏幕冻结进行专门的分析。

此时,新的屏幕方向、转屏动画类型和方式都得到了确定,并且进行了屏幕的冻结。

4.通知SystemUI方向发生变化

回到DisplayRotation#updateRotationUnchecked()中,接下来执行startRemoteRotation()方法,这个方法将方向旋转信息通知给SystemUI,让它对应的做相关处理:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
 
    private void startRemoteRotation(int fromRotation, int toRotation) {
        // 表示正在等待客户端的回调
        mIsWaitingForRemoteRotation = true;
        try {
            // 通知SystemUI模块方向旋转信息
            mService.mDisplayRotationController.onRotateDisplay(mDisplayContent.getDisplayId(),
                    fromRotation, toRotation, mRemoteRotationCallback);
        } catch (RemoteException e) {
        }
    }

当SystemUI中收到调用并处理完毕后,会回调DisplayRotation.mRemoteRotationCallback#continueRotateDisplay()方法来通知WMS模块。

此时,DisplayRotation#updateRotationUnchecked()方法执行完毕,并返回true。接下来将等待SystemUI的回调......

5.SystemUI收到调用后,向system_server进行回调

在收到SystemUI回调或者回调超时时,DisplayRotation#continueRotation()方法将会执行,来继续进行旋转流程:

//  frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 
    private void continueRotation(int targetRotation, WindowContainerTransaction t) {
        synchronized (mService.mGlobalLock) {
            mIsWaitingForRemoteRotation = false;
            // defer layout
            mService.mAtmService.deferWindowLayout();
            try {
                // Configuration的更新
                mDisplayContent.sendNewConfiguration();
            } finally {
                // continue layout
                mService.mAtmService.continueWindowLayout();
            }
        }
    }

以上方法中,通过DisplayContent#sendNewConfiguration()方法进行新的Configuration的更新。

6.逻辑屏配置更新

Configuration对象的更新入口方法如下:

// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
 
    void sendNewConfiguration() {
        ......
        final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
        // configUpdated为true时,后面流程不执行
        if (configUpdated) {
            return;
        }
        ......
    }

从sendNewConfiguration()方法开始,将会进行全局配置的更新,对这部分流程这里不做详细介绍,只做一个简单的介绍。

在sendNewConfiguration()方法中,会调用updateDisplayOverrideConfigurationLocked()方法更新Display的覆盖Configuration对象:

  1. 首先会创建一个新的Configuration对象;
  2. 然后在computeScreenConfiguration()方法中对这个Configuration对象进行填充屏幕相关的属性,比如范围边界、屏幕密度、布局、键盘、逻辑屏信息等.....

而在更新逻辑屏信息时,就会根据DisplayRotation#mRotation对屏幕宽高进行设置:

//  frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 
    private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
        // 从DisplayRotation中获取屏幕方向值
        final int rotation = getRotation();
        // 屏幕方向值为1(90度)和3(270度)时,需要交换屏幕宽高
        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
        // 根据高度和宽度获取logical display宽高
        final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
        final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
        ......
        // 设置屏幕方向值
        mDisplayInfo.rotation = rotation;
        ......
        
         // 向DMS中设置WMS中的逻辑屏信息
         mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
                overrideDisplayInfo);
        ......
        return mDisplayInfo;
    }

此时,如果方向发生了旋转,逻辑屏幕的宽和高将进行交换。

  1. 对新创建Configuration对象填充完毕后,接下来,通过updateDisplayOverrideConfigurationLocked()方法进一步执行更新override 配置的流程。在该方法中,如果是Default Display,则不仅需要更新override Configuration,还需要更新全局Configuration;如果是非Default Display,则仅更新override Configuration:
// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

    boolean updateDisplayOverrideConfigurationLocked(Configuration values,
            ActivityRecord starting, boolean deferResume,
            ActivityTaskManagerService.UpdateConfigurationResult result) {
        ......
        mAtmService.deferWindowLayout();
        try {
            if (values != null) {
                if (mDisplayId == DEFAULT_DISPLAY) {
                    // 更新全局Configuration
                    changes = mAtmService.updateGlobalConfigurationLocked(values,
                            false /* initLocale */, false /* persistent */,
                            UserHandle.USER_NULL /* userId */, deferResume);
                } else {
                    // 更新Override Configuration
                    changes = performDisplayOverrideConfigUpdate(values, deferResume);
                }
            }
        } 
        return kept;
    }

全局Configuration的更新通过ATMS#updateGlobalConfigurationLocked()方法进行,Override Configuration的更新通过DisplayContent#performDisplayOverrideConfigUpdate()方法进行。

在更新Override Configuration过程中,会在onRequestedOverrideConfigurationChanged()方法中进行全局变量mRequestedOverrideConfiguration的更新,并且在这里会判断是否屏幕方向发生变化,在发生变化后,进行方向的更新:

// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
        // 获取当前override Configuration
        final Configuration currOverrideConfig = getRequestedOverrideConfiguration();
        final int currRotation = currOverrideConfig.windowConfiguration.getRotation();
        final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation();
        // 屏幕方向发生变化
        if (currRotation != ROTATION_UNDEFINED && currRotation != overrideRotation) {
            applyRotationAndFinishFixedRotation(currRotation, overrideRotation);
        }
        mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration);
        // 更新mRequestedOverrideConfiguration
        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
        mCurrentOverrideConfigurationChanges = 0;
        mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, this);
        mAtmService.addWindowLayoutReasons(
                ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
    }

如果当前override Configuration对象和新的override Configuration对象中mRotation属性不一致,说明屏幕方向发生了变化,接下来将会将会通过DC#applyRotationAndFinishFixedRotation()方法更新屏幕显示方向,这个方法中又通过applyRotation()方法进行。

7.使用新屏幕方向

在applyRotation()方法中,会使用新屏幕方向,进行相关属性的更新,这个过程中会向DMS中发起请求,进行逻辑屏的配置流程:

// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
 
    private void applyRotation(final int oldRotation, final int rotation) {
         
        // 更新WindowOrientationListener#mCurrentRotation变量
        mDisplayRotation.applyCurrentRotation(rotation);
        // Seamless rotation 没有动画
        final boolean rotateSeamlessly = mDisplayRotation.isRotatingSeamlessly();
        final Transaction transaction = getPendingTransaction();
        // 转屏动画对象
        ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
                ? null : getRotationAnimation();
        // 再更新逻辑屏信息,确保屏幕方向的正确性
        updateDisplayAndOrientation(getConfiguration().uiMode, null /* outConfig */);
        // 转屏动画设置初始位置矩阵
        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
            screenRotationAnimation.setRotation(transaction, rotation);
        }
        // 更新WindowState中Seamless Rotation相关属性
        forAllWindows(w -> {
            w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
        }, true /* traverseTopToBottom */);
        // 调用DMS更新Display配置
        mWmService.mDisplayManagerInternal.performTraversal(transaction);
        // 执行动画
        scheduleAnimation();
         
        // 更新WindowState#mOrientationChanging属性
        forAllWindows(w -> {
            if (w.mHasSurface && !rotateSeamlessly) {
                w.setOrientationChanging(true);
                // mOrientationChangeComplete表示显示方向的更新是否完成
                mWmService.mRoot.mOrientationChangeComplete = false;
                w.mLastFreezeDuration = 0;
            }
            // 表示需要通知客户端发生了转屏
            w.mReportOrientationChanged = true;
        }, true /* traverseTopToBottom */);
 
        // 发起RotationWatcher#onRotationChanged()
        for (int i = mWmService.mRotationWatchers.size() - 1; i >= 0; i--) {
            final WindowManagerService.RotationWatcher rotationWatcher
                    = mWmService.mRotationWatchers.get(i);
            if (rotationWatcher.mDisplayId == mDisplayId) {
                try {
                    rotationWatcher.mWatcher.onRotationChanged(rotation);
                } catch (RemoteException e) {
                    // Ignore
                }
            }
        }
    }

以上方法中,主要流程有:

  1. 首先通过DisplayRotation#applyCurrentRotation()方法,更新WindowOrientationListener#mCurrentRotation变量;
  2. 如果转屏方式为Seamless Rotation,则不需要任何动画;否则,说明需要转屏动画,会设置初始转屏动画的转移矩阵,然后遍历WindowState, 将WindowState#mOrientationChanging属性设置为true,表示处于转屏过程中。
  3. 执行updateDisplayAndOrientation()更新逻辑屏属性;
  4. 通过mDisplayManagerInternal.performTraversal()配置更新Display属性;
  5. 接下来,将mWmService.mRoot.mOrientationChangeComplete值设置为false,这个值表示显示方向的更新是否完成,当转屏动画完成后,该值将会被设置为true,开始解冻流程。
  6. 最后发起RotationWatcher#onRotationChanged()调用,通知所有的RotationWatcher方向发生了变化。

此时,applyRotation()方法中的流程执行完毕。

接下来将等待VSync信号的到来,进行刷新定位流程,在这个流程中,将解冻屏幕,并执行转屏动画。

8.解冻屏幕,执行转屏动画

之后,当WindowAnimator对象收到VSync信号后,开始进行动画,在这个动画中:

//  frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java

    private void animate(long frameTimeNs) {
        ......
        // SET_ORIENTATION_CHANGE_COMPLETE标记位表示方向改变完成
        mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
        // 开启事务
        mService.openSurfaceTransaction();
        ......

        final boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
        // 更新mOrientationChangeComplete属性
        final boolean doRequest = mBulkUpdateParams != 0 && mService.mRoot.copyAnimToLayoutParams();
        // 发起WMSS遍历
        if (hasPendingLayoutChanges || doRequest) {
            mService.mWindowPlacerLocked.requestTraversal();
        }
        ......
        SurfaceControl.mergeToGlobalTransaction(mTransaction);
        // 关闭事务,进行提交
        mService.closeSurfaceTransaction("WindowAnimator");
    }

在copyAnimToLayoutParams()方法中,会将mOrientationChangeComplete属性更新为true,表示显示方向更新完毕:

// frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

    boolean copyAnimToLayoutParams() {
        boolean doRequest = false;

        final int bulkUpdateParams = mWmService.mAnimator.mBulkUpdateParams;
        .......
        if ((bulkUpdateParams & SET_ORIENTATION_CHANGE_COMPLETE) == 0) {
            mOrientationChangeComplete = false;
        } else {
            mOrientationChangeComplete = true;
            mLastWindowFreezeSource = mWmService.mAnimator.mLastWindowFreezeSource;
            if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
                doRequest = true;
            }
        }
        .....
        return doRequest;
    }

这个值变为true后,在之后的遍历操作中,将进行解冻处理。

屏幕的解冻是由WMS#stopFreezingDisplayLocked()方法进行,当WMS模块组件收到VSync信号后执行定位过程时,会调用此方法进行解冻流程:

    void performSurfacePlacementNoTrace() {
        ......
        // 显示方向是否改变完成
        if (mOrientationChangeComplete) {
            if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
                mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
                mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
                mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
            }
            // 解冻
            mWmService.stopFreezingDisplayLocked();
        }
        ......
    }

以上逻辑中可以看到,是否解冻取决于mOrientationChangeComplete属性,该值在上一步流程中已经被设置为true,下面来看具体的解冻流程:

// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    void stopFreezingDisplayLocked() {
        ......

        // 重置mDisplayFrozen为false,表示不处于冻结状态
        mDisplayFrozen = false;
        // 解冻IMS
        mInputManagerCallback.thawInputDispatchingLw();

        boolean updateRotation = false;
        // 获取ScreenRotationAnimation对象
        ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
                : displayContent.getRotationAnimation();
        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
            DisplayInfo displayInfo = displayContent.getDisplayInfo();
            // Get rotation animation again, with new top window
            if (!displayContent.getDisplayRotation().validateRotationAnimation(
                    mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
                mExitAnimId = mEnterAnimId = 0;
            }
            // 执行转屏动画
            if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
                    getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
                        displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
                mTransaction.apply();
            // 直接过渡
            } else {
                screenRotationAnimation.kill();
                displayContent.setRotationAnimation(null);
                updateRotation = true;
            }
        } else {
            if (screenRotationAnimation != null) {
                screenRotationAnimation.kill();
                displayContent.setRotationAnimation(null);
            }
            updateRotation = true;
        }
        ......
    }

以上方法中,将解冻屏幕,并执行转屏动画。当转屏动画执行完毕后,整个显示方向的旋转过程也就完成了。