android framework13-launcher3【06手机旋转问题】

1.简介

平板模式没啥问题,手机模式的话,桌面默认是不可以旋转的,打开桌面旋转开关以后,也只能横向旋转,180度那个是没效果的,下边就具体找下原因,看看是哪里做了限制


注意:我们这里分析手机模式不能旋转的问题。

这里贴下平板和手机判断的逻辑,取设备宽和高里比较小的值,计算其dp值,比600大的是认为是tablet,否则就是phone

arduino 复制代码
    public static final int DENSITY_MEDIUM = 160;
    public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
    //像素转化为DP值
    public static float dpiFromPx(float size, int densityDpi) {
        float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
        return (size / densityRatio);
    }

参考:

blog.csdn.net/sgmenghuo/a...

2.桌面设置页面

2.1 打开桌面自动旋转

桌面空白处长按,弹出的菜单选择 home settings,然后就能看到桌面的设置页面了,那个allow home screen rotation就是旋转开关,手机模式默认是关闭的,平板模式这个偏好隐藏了。

2.2.布局相关

>清单文件

ini 复制代码
        The settings activity. To extend point settings_fragment_name to appropriate fragment class
        -->
        <activity
            android:name="com.android.launcher3.settings.SettingsActivity"
            android:label="@string/settings_button_text"
            android:theme="@style/HomeSettings.Theme"
            android:exported="true"
            android:autoRemoveFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

>settings_activity.xml

可以看到,就一个toolbar显示标题,完事就是个帧布局,到时候替换成fragment了

ini 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <Toolbar
        android:id="@+id/action_bar"
        style="?android:attr/actionBarStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="?android:attr/actionBarTheme" />

    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

>launcher_preferences.xml

fragment用到的preference文件如下,

ini 复制代码
<androidx.preference.PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto">

    <com.android.launcher3.settings.NotificationDotsPreference
        android:key="pref_icon_badging"
        android:title="@string/notification_dots_title"
        android:persistent="false"
        android:widgetLayout="@layout/notification_pref_warning" />

    <!--
      LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED(613)
      LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_DISABLED(614)
    -->
    <SwitchPreference
        android:key="pref_add_icon_to_home"
        android:title="@string/auto_add_shortcuts_label"
        android:summary="@string/auto_add_shortcuts_description"
        android:defaultValue="true"
        android:persistent="true"
        launcher:logIdOn="613"
        launcher:logIdOff="614" />

    <!--
      LAUNCHER_HOME_SCREEN_ROTATION_ENABLED(615)
      LAUNCHER_HOME_SCREEN_ROTATION_DISABLED(616)
    -->
    <SwitchPreference
        android:key="pref_allowRotation"
        android:title="@string/allow_rotation_title"
        android:summary="@string/allow_rotation_desc"
        android:defaultValue="false"
        android:persistent="true"
        launcher:logIdOn="615"
        launcher:logIdOff="616" />

    <androidx.preference.PreferenceScreen
        android:key="pref_developer_options"
        android:persistent="false"
        android:title="@string/developer_options_title"
        android:fragment="com.android.launcher3.settings.DeveloperOptionsFragment"/>

</androidx.preference.PreferenceScreen>

2.3.SettingsActivity

>onCreate

scss 复制代码
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.settings_activity);
        setActionBar(findViewById(R.id.action_bar));
//..
        if (savedInstanceState == null) {
//..
            final FragmentManager fm = getSupportFragmentManager();
            final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(),
            //获取要加载的fragment
                    getPreferenceFragment());
            f.setArguments(args);
            // 替换成fragment
            fm.beginTransaction().replace(R.id.content_frame, f).commit();
        }

>getPreferenceFragment

php 复制代码
<string name="settings_fragment_name" translatable="false">
com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment</string>
typescript 复制代码
    private String getPreferenceFragment() {
        String preferenceFragment = getIntent().getStringExtra(EXTRA_FRAGMENT);
        //我们这里用的是默认的这个
        String defaultFragment = getString(R.string.settings_fragment_name);

        if (TextUtils.isEmpty(preferenceFragment)) {
            return defaultFragment;
        } else if (!preferenceFragment.equals(defaultFragment)
                && !VALID_PREFERENCE_FRAGMENTS.contains(preferenceFragment)) {
            如果是extra传递的,需要验证是否在注册的集合里
            throw new IllegalArgumentException(
                    "Invalid fragment for this activity: " + preferenceFragment);
        } else {
            return preferenceFragment;
        }
    }

2.4.LauncherSettingsFragment

默认加载的就是这个Fragment

>onCreatePreferences

scss 复制代码
    public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
//...

   public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
//..
            getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
            //设置偏好资源
            setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
            PreferenceScreen screen = getPreferenceScreen();
            for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
                Preference preference = screen.getPreference(i);
                //对偏好进行初始化
                if (initPreference(preference)) {
                    
                } else {
                //初始化失败的话移除选项
                    screen.removePreference(preference);
                }
            }
            

>initPreference

java 复制代码
        protected boolean initPreference(Preference preference) {
            switch (preference.getKey()) {
                case NOTIFICATION_DOTS_PREFERENCE_KEY:
                //注意:这个类在src_shortcuts_overrides目录下重写了,值改成了false,所以返回的是true
                    return !WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS;

                case ALLOW_ROTATION_PREFERENCE_KEY:
                    DisplayController.Info info =
                            DisplayController.INSTANCE.get(getContext()).getInfo();
                    if (info.isTablet(info.realBounds)) {
                      //平板模式支持自动旋转,所以这里返回false,移除选项
                        return false;
                    }
                    // 非平板模式,默认值是false
                    preference.setDefaultValue(RotationHelper.getAllowRotationDefaultValue(info));
                    return true;

                case FLAGS_PREFERENCE_KEY:
                    // Only show flag toggler UI if this build variant implements that.
                    return FeatureFlags.showFlagTogglerUi(getContext());

                case DEVELOPER_OPTIONS_KEY:
                    mDeveloperOptionPref = preference;
                    return updateDeveloperOption();
            }

            return true;
        }

3.RotationHelper.java

这个是launcher3里管理桌面旋转的工具类,只是用来控制桌面app能否旋转的,和我们要研究的手机无法旋转180度的问题不相关。

3.1.getAllowRotationDefaultValue

这个就是上边settings里自动旋转开关的默认值

arduino 复制代码
    public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";

    public static boolean getAllowRotationDefaultValue(DisplayController.Info info) {
        float originalSmallestWidth = dpiFromPx(Math.min(info.currentSize.x, info.currentSize.y),
                DENSITY_DEVICE_STABLE);
        return originalSmallestWidth >= MIN_TABLET_WIDTH;//600
    }

DENSITY_DEVICE_STABLE

arduino 复制代码
    //这个是不变的
    public static final int DENSITY_DEVICE_STABLE = getDeviceDensity();
    
    private static int getDeviceDensity() {
        //ro.sf.lcd_density的值在初始化进程的时候从build.prop里读取写入一次,之后不会修改
        //qemu.sf.lcd_density覆写上边的值,目的是在模拟器的时候可以动态修改
        return SystemProperties.getInt("qemu.sf.lcd_density",
                SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT));
    }
    
ublic static final int DENSITY_DEFAULT = DENSITY_MEDIUM;//160    

3.2.initialize

ini 复制代码
    public void initialize() {
        if (!mInitialized) {
            mInitialized = true;
            DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
            DisplayController.Info info = displayController.getInfo();
            //可以看到第一个参数,平板的话为true,忽略自动旋转设置
            setIgnoreAutoRotateSettings(info.isTablet(info.realBounds), info);
            //添加设备info改变监听
            displayController.addChangeListener(this);
            notifyChange();
        }
    }

>setIgnoreAutoRotateSettings

ini 复制代码
    private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings,
            DisplayController.Info info) {
        // On large devices we do not handle auto-rotate differently.
        mIgnoreAutoRotateSettings = ignoreAutoRotateSettings;
        if (!mIgnoreAutoRotateSettings) {
            //非平板
            if (mSharedPrefs == null) {
                mSharedPrefs = LauncherPrefs.getPrefs(mActivity);
                mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
            }
            //非平板,那么这个就看auto rotation选项开关有没有打开,默认值是false
            mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                    getAllowRotationDefaultValue(info));
        } else {
            //平板的话不用监听改变
            if (mSharedPrefs != null) {
                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
                mSharedPrefs = null;
            }
        }
    }

>onDisplayInfoChanged

scss 复制代码
    public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
        boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds);
        if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
         //平板,非平板模式变化的时候重新设置
            setIgnoreAutoRotateSettings(ignoreAutoRotateSettings, info);
            notifyChange();
        }
    }

3.3.notifyChange

简单来讲,就是根据不同的请求,返回activity需要的flag(就是屏幕方向旋转的参数)

  • 这里最终就整合了3种flag
  • SCREEN_ORIENTATION_UNSPECIFIED :旋转方向不限制
  • SCREEN_ORIENTATION_LOCKED :屏幕锁定,不可以旋转
  • SCREEN_ORIENTATION_NOSENSOR :这个也不能旋转了,都设置成没有传感器了
ini 复制代码
    private void notifyChange() {
        if (!mInitialized || mDestroyed) {
            return;
        }

        final int activityFlags;
        if (mStateHandlerRequest != REQUEST_NONE) {
            activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
                    SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
        } else if (mCurrentTransitionRequest != REQUEST_NONE) {
            activityFlags = mCurrentTransitionRequest == REQUEST_LOCK ?
                    SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
        } else if (mCurrentStateRequest == REQUEST_LOCK) {
            activityFlags = SCREEN_ORIENTATION_LOCKED;
        } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
                || mHomeRotationEnabled || mForceAllowRotationForTesting) {
            activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
        } else {
            // If auto rotation is off, allow rotation on the activity, in case the user is using
            // forced rotation.
            activityFlags = SCREEN_ORIENTATION_NOSENSOR;
        }
        if (activityFlags != mLastActivityFlags) {
            mLastActivityFlags = activityFlags;
            mRequestOrientationHandler.sendEmptyMessage(activityFlags);
        }
    }

>setOrientationAsync

handle最终就是把上边的flag给到activity去请求,设置旋转参数

java 复制代码
    private boolean setOrientationAsync(Message msg) {
        Activity activity = mActivity;
        if (activity != null) {
            activity.setRequestedOrientation(msg.what);
        }
        return true;
    }

3.5.build.prop

www.cnblogs.com/myitm/archi...

文件位于手机的/system/目录下,记录一些系统设置,是一个属性文件。

>生成

Make系统解析build/core/Makefile,调用build/tools/buildinfo.sh执行脚本生成build.prop文件,并把系统默认的system.prop以及定制的system.prop中的属性追加到build.prop文件中,编译完成之后,文件生成在out/target/product/[board]/system/目录下

>常用属性说明

ini 复制代码
# begin build properties    #开始设置系统性能
# autogenerated by buildinfo.sh #以下内容由脚本在编译时自动产生
ro.build.id=JRO03C    #build的标识,一般在编译时产生不必修改
ro.build.display.id=TBDG1073-eng 4.1.1 JRO03C 20130723.v016 test-keys   #显示的标识,可以任意修改,显示为手机信息的版本
ro.build.version.incremental=20130723.v016  #版本的增加说明,一般不显示也没必要修改
ro.build.version.sdk=16 #系统编译时,使用的SDK的版本,勿修改.
ro.build.version.codename=REL     #版本编码名称,一般不显示也没必要修改
ro.build.version.release=4.1.1    #公布的版本,显示为手机信息的系统版本
ro.build.date=Tue Jul 23 17:14:43 CST 2013   #系统编译的时间,没必要修改
ro.build.date.utc=1374570883     #系统编译的时间(数字版),没必要修改
ro.build.type=eng   #系统编译类型,一般不显示也没必要修改
ro.build.user=pyou  #系统用户名,可以修改成自己的名字
ro.build.host=roco-ubuntu    #系统主机名,随便起个名字,英文字母表示
ro.build.tags=test-keys  #系统标记,无意义,不修改
ro.product.model=TBDG1073_OuyangPeng    #机器型号,随你创造
ro.product.brand=TBDG1073    #机器品牌,随你创造
ro.product.name=TBDG1073     #机器名,随你创造
ro.product.device=TBDG1073   #设备名,随你创造
ro.product.board=TBDG1073    #主板名,随你创造
ro.product.cpu.abi=armeabi-v7a   #CPU,最好别修改,避免有些软件在识别机器时,出现错乱
ro.product.cpu.abi2=armeabi  #CPU品牌
ro.product.manufacturer=TBDG1073     #制造商,随你创造
ro.product.locale.language=en   #系统语言
ro.product.locale.region=US #系统所在地区
ro.wifi.channels=11     #无线局域网络的通信信道,空白表示自动识别
ro.board.platform=meson6    #主板系统
# ro.build.product is obsolete; use ro.product.device
ro.build.product=TBDG1073   #设备名,被废弃了,修改也没用
# Do not try to parse ro.build.description or .fingerprint  #以下的内容不要试图修改
ro.build.description=TBDG1073-eng 4.1.1 JRO03C 20130723.v016 test-keys  #用户的KEY
ro.build.fingerprint=TBDG1073/TBDG1073/TBDG1073:4.1.1/JRO03C/20130723.v016:eng/test-keys  #机身码
ro.build.characteristics=tablet
# end build properties  #创建属性结束
# system.prop for M1 reference board    #系统技术支持由M1提供
# This overrides settings in the products/generic/system.prop file
#
#rild.libpath=/system/lib/libreference-ril.so
#rild.libargs=-d /dev/ttyS0
ro.sf.lcd_density=120 #显示屏分辨率,数值越大分辨率越底
keyguard.no_require_sim=1   #无需SIM卡也可操作手机
#set font
ro.fontScale=1.0    #字体大小缩放
#set keyguard.enable=false to disable keyguard
keyguard.enable=true    #锁屏
ro.statusbar.widget=true
ro.statusbar.button=true
ro.statusbar.yearmonthdayweek=true


#wifi.interface=ra0 #WIFI界面
# Time between scans in seconds. Keep it high to minimize battery drain.
# This only affects the case in which there are remembered access points,
# but none are in range.
#wifi.supplicant_scan_interval = 60 #WIFI扫描间隔时间,这里设置是45秒。把这个时间设置长点能省电
#alsa.mixer.playback.master=DAC2 Analog
#alsa.mixer.capture.master=Analog
#configure the Dalvik heap for a standard tablet device.
#frameworks/base/build/tablet-dalvik-heap.mk
dalvik.vm.heapstartsize=5m  #单个应用程序分配的初始内存
dalvik.vm.heapgrowthlimit=48m   #单个应用程序最大内存限制,超过将被Kill,这或许是某些大体积程序闪退的原因
dalvik.vm.heapsize=256m  #dalvik的虚拟内存大小


hwui.render_dirty_regions=false


# Disable un-supported Android feature
hw.nopm=false
hw.nobattery=false
hw.nophone=true
hw.novibrate=true
hw.cameras=1
hw.hasethernet=false
#hw.hasdata=true
ro.platform.has.touch=true
hw.nodatausage=true
# Wi-Fi sleep policy
ro.platform.has.sleeppolicy=false
#set to 0 temporarily so touch works without other changes
ro.sf.hwrotation=270    #0的话自动转屏
#0~7 You are required to get the correct install direction according the sensor placement on target board
#ro.sf.gsensorposition=6
ro.sf.ecompassposition=4
allow_all_orientations=1




# Set Camera Orientation
ro.camera.orientation.front=270
ro.camera.orientation.back=90


# Use OSD2 mouse patch
ro.ui.cursor=osd2


ro.hardware=amlogic


# Enable 32-bit OSD
sys.fb.bits=32


# Disable GPS
gps.enable=false


# Enable player buildin
media.amsuperplayer.enable=true
media.amplayer.enable-acodecs=asf,ape,flac,dts
media.amplayer.enable=true
media.amsuperplayer.m4aplayer=STAGEFRIGHT_PLAYER
media.amsuperplayer.defplayer=PV_PLAYER
media.amplayer.thumbnail=true
media.amplayer.stopbuflevel=0.05
media.amplayer.widevineenable=true
media.amplayer.html5_stretch=true
media.libplayer.fastswitch=0
media.libplayer.ipv4only=1
media.amplayer.dsource4local=1
#media.amplayer.hdmicloseauthen=1
media.amplayer.delaybuffering=2
media.amplayer.buffertime=5
media.amplayer.v4osd.enable=1
media.arm.audio.decoder=ape
#fix doubleTwist apk can not play radio
media.player.forcemp3softdec=true


#fix online video block issue
libplayer.livets.softdemux=1
libplayer.netts.recalcpts=1


# Nand write need force sync when gadget
gadget.nand.force_sync=true




# Status bar customization
ro.statusbar.widget.power=true
ro.statusbar.yearmonthdayweek=true


# HDMI 
#ro.hdmi480p.enable=true
#rw.fb.need2xscale=ok
#media.amplayer.osd2xenable=true


#camera DCIM dir. 0:sd only; 1:nand only; 2,sd first
ro.camera.dcim=1


# Disable preload-class
ro.amlogic.no.preloadclass=0


# App optimization
ro.app.optimization=true


persist.sys.timezone=America/New_York   #强制时区,此处为美洲纽约时间
#Dual display
ro.vout.dualdisplay3=true
ro.vout.player.exit=false


# CPU settings
ro.has.cpu.setting=true


# CPU freq customized in setting menu
# normal, performance, powersaving
ro.cpumode.maxfreq=1200000,1320000,800000


# when usbstorage, CPU mode and freq
ro.usbstorage.cpumode=performance
ro.usbstorage.maxfreq=600000


ro.bootanimation.rotation=0


#used to set default surface size, set 1 when hwrotation is 270, set 3 when hwrotation is 90;need set ro.bootanimation.rotation 0;
debug.default.dimention=1


#support media poll uevent,can use sd cardread on usb port
has.media.poll=true


#used forward seek for libplayer
media.libplayer.seek.fwdsearch=1


#for tabletui display
ro.ui.tabletui=true
#enable address bar cover issue fixing
ro.flashplayer.surfacehack=1


#add vol button in statusbar.
ro.statusbar.volume=true


ro.screen.has.usbstorage=true
hw.erase.internalSdcard=true


#media partition name
ro.media.partition.label=OuyangPeng


#USB PID and VID name
#ro.usb.vendor.string=AML
#ro.usb.product.string=MID
#CTS
#media.amplayer.widevineenable=true
#media.amplayer.dsource4local=true
ro.com.google.gmsversion=4.1_r5
ro.com.google.clientidbase=android-fih  #谷歌客户身份
ro.setupwizard.mode=OPTIONAL    #安装向导模式 开机出现的帐号设置向导,ENABLED为显示,DISABLED为禁用,OPTIONAL为可选
ro.statusbar.screenshot=true


#
# ADDITIONAL_BUILD_PROPERTIES
#
ro.com.android.dateformat=MM-dd-yyyy     #默认时间格式,改为yyyy-MM-dd,显示效果就是XXXX年XX月XX日
ro.config.ringtone=Ring_Synth_04.ogg     #默认响铃铃声,文件在/system/media/audio/ringtones 把喜欢的铃声放这里
ro.config.notification_sound=pixiedust.ogg  #默认提示音,文件在/system/media/audio/notifications 修改方法同上
ro.carrier=unknown
ro.opengles.version=131072  #开放式绘图介面参数
ro.config.alarm_alert=Alarm_Classic.ogg     #默认闹铃,文件在/system/media/audio/alarms 修改方法同上
drm.service.enabled=true
ro.setupwizard.mode=OPTIONAL #默认开机时使用设置向导
ro.com.google.gmsversion=4.1_r4
ro.kernel.android.checkjni=1
net.bt.name=Android #蓝牙网络中显示的名称,可以修改
dalvik.vm.stack-trace-file=/data/anr/traces.txt

>

4.auto rotate 开关

我们知道快速设置里的旋转开关可以控制整个系统是否可以旋转,所以先看下这个开关状态改变都做了啥?

4.1.RotationLockTile.java

先找到这个控件的相关类,看下点击事件都干啥了

>getLongClickIntent

长按跳转的intent

csharp 复制代码
    public Intent getLongClickIntent() {
        return new Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS);
    }

>handleClick

点击切换开关状态,开关打开,rotationLock为false,开关关闭,rotationLock为true

less 复制代码
    protected void handleClick(@Nullable View view) {
        final boolean newState = !mState.value;
        //controller就是4.2的类,最终实现见 5.1
        mController.setRotationLocked(!newState);
        refreshState(newState);
    }

>handleUpdateState

可以看到开关的状态和旋转锁定是反着的,也就是

  • 旋转锁定的时候,开关是false,
  • 旋转打开的时候,开关是true,
typescript 复制代码
    protected void handleUpdateState(BooleanState state, Object arg) {
        final boolean rotationLocked = mController.isRotationLocked();
        //
        state.value = !rotationLocked;

4.2.RotationLockControllerImpl

简单看下构造方法

less 复制代码
    public RotationLockControllerImpl(
            RotationPolicyWrapper rotationPolicyWrapper,
            DeviceStateRotationLockSettingController deviceStateRotationLockSettingController,
            @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
    ) {

可以看到的方法的实现交给了RotationPolicyWrapper

typescript 复制代码
    public void setRotationLocked(boolean locked) {
        mRotationPolicy.setRotationLock(locked);
    }

>RotationPolicyWrapper

而这个RotationPolicyWrapper的实现又都交给了RotationPolicy,所以最终看下 小节5 即可

kotlin 复制代码
class RotationPolicyWrapperImpl @Inject constructor(
    private val context: Context,
    private val secureSettings: SecureSettings
) :RotationPolicyWrapper {

    override fun setRotationLock(enabled: Boolean) {
        traceSection("RotationPolicyWrapperImpl#setRotationLock") {
            RotationPolicy.setRotationLock(context, enabled)
        }
    }

    override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int) {
        RotationPolicy.setRotationLockAtAngle(context, enabled, rotation)
    }

5.RotationPolicy

这个类里的都是静态方法,一个个的看一下

arduino 复制代码
    private static final int CURRENT_ROTATION = -1;

    public static final int NATURAL_ROTATION = Surface.ROTATION_0;

    private RotationPolicy() {
    }

5.1.setRotationLock

设置是否可以旋转

arduino 复制代码
    public static void setRotationLock(Context context, final boolean enabled) {
    //见 5.2 
        final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
        //见5.3
        setRotationLockAtAngle(context, enabled, rotation);
    }

5.2.areAllRotationsAllowed

是否允许所有角度的旋转,目前 phone的配置里是false,tablet下是true

arduino 复制代码
    private static boolean areAllRotationsAllowed(Context context) {
        return context.getResources().getBoolean(R.bool.config_allowAllRotations);
    }

5.3.setRotationLockAtAngle

设置是否可以旋转以及角度(-1或者0),-1表示所有方向都可以旋转,0表示部分角度可以旋转(排除180度的)

arduino 复制代码
    public static void setRotationLockAtAngle(Context context, final boolean enabled,
            final int rotation) {
            //可以看到,存储的是固定的值 0,表示不隐藏旋转开关
        Settings.System.putIntForUser(context.getContentResolver(),
                Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
                UserHandle.USER_CURRENT);

        setRotationLock(enabled, rotation);
    }

>HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY

  • 控制是否隐藏系统UI中的旋转锁定开关。通常,这样做是出于可访问性的目的,使用户在显示旋转被锁定时更难以意外地切换旋转锁定。
  • 如果为0,则不隐藏旋转锁定开关以方便访问(尽管可能由于其他原因而不可用)。
  • 如果为1,则隐藏旋转锁定开关
arduino 复制代码
        public static final String HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY =
                "hide_rotation_lock_toggle_for_accessibility";

5.4.setRotationLock

java 复制代码
    private static void setRotationLock(final boolean enabled, final int rotation) {
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
                    if (enabled) {
                        wm.freezeRotation(rotation);
                    } else {
                        wm.thawRotation();
                    }
                } 
            }
        });
    }

>getWindowManagerService

csharp 复制代码
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                //查下这个
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                        sUseBLASTAdapter = sWindowManagerService.useBLAST();
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }

>SystemServer.java

startOtherService方法里初始化的,所以上边的"window"对应的就是WindowManagerService

ini 复制代码
            mSystemServiceManager.startBootPhase(t, SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE);
            wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                    new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
            ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);

5.5.getRotationLockOrientation

获取禁止屏幕旋转以后应该显示的方向,横屏或者竖屏

java 复制代码
    public static int getRotationLockOrientation(Context context) {
        if (areAllRotationsAllowed(context)) {
        //所有旋转方向都支持,返回0
            return Configuration.ORIENTATION_UNDEFINED;
        }
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        //旋转角度,0,1,2,3
        final int rotation =
                context.getResources().getConfiguration().windowConfiguration.getRotation();
        final boolean rotated = rotation % 2 != 0;(1或者3表示旋转了,也就是横屏模式)
        final int w = rotated ? metrics.heightPixels : metrics.widthPixels;
        final int h = rotated ? metrics.widthPixels : metrics.heightPixels;
        return w < h ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
    }

6.WindowManagerService.java

这个是中间类,最终交给 小节7处理了

6.1.freezeRotation

arduino 复制代码
    public void freezeRotation(int rotation) {
        freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);
    }

>freezeDisplayRotation

java 复制代码
    public void freezeDisplayRotation(int displayId, int rotation) {
       //..
        final long origId = Binder.clearCallingIdentity();
        try {
            synchronized (mGlobalLock) {
                final DisplayContent display = mRoot.getDisplayContent(displayId);
                if (display == null) {
                    return;
                }
                //走这里
                display.getDisplayRotation().freezeRotation(rotation);
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }

        updateRotationUnchecked(false, false);
    }

6.2.thawRotation

csharp 复制代码
    public void thawRotation() {
        thawDisplayRotation(Display.DEFAULT_DISPLAY);
    }

>thawDisplayRotation

java 复制代码
    public void thawDisplayRotation(int displayId) {
        //..
        final long origId = Binder.clearCallingIdentity();
        try {
            synchronized (mGlobalLock) {
                final DisplayContent display = mRoot.getDisplayContent(displayId);
                if (display == null) {
                    return;
                }
                //走这里
                display.getDisplayRotation().thawRotation();
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }

        updateRotationUnchecked(false, false);
    }

7.DisplayRotation.java

WindowManagerPolicy.java

less 复制代码
    @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED})
    @Retention(RetentionPolicy.SOURCE)
    public @interface UserRotationMode {}

    /** When not otherwise specified by the activity's screenOrientation, rotation should be
     * determined by the system (that is, using sensors). */
    public final int USER_ROTATION_FREE = 0;
    /** When not otherwise specified by the activity's screenOrientation, rotation is set by
     * the user. */
    public final int USER_ROTATION_LOCKED = 1;

7.1.thawRotation

  • mUserRotation 就是屏幕当前的旋转角度
scss 复制代码
    void thawRotation() {
        setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation);
    }

7.2.freezeRotation

  • mRotation 就是当前屏幕的方向[0,1,2,3]
  • rotation 从5.1传过来的,-1表示可以4个方向旋转,0表示可以3个方向旋转
  • 可以看到,旋转开关关闭,如果是rotation是-1,那么最终的角度还是当前的角度,
  • 而如果rotation是0(旋转受限,180度的不行),这种最终的角度就固定是0了,比如原本是横屏,开关关闭,直接就成竖屏显示了。
scss 复制代码
    void freezeRotation(int rotation) {
        rotation = (rotation == -1) ? mRotation : rotation;
        setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
    }

7.3.setUserRotation

java 复制代码
    void setUserRotation(int userRotationMode, int userRotation) {
        if (isDefaultDisplay) {
            // We'll be notified via settings listener, so we don't need to update internal values.
            final ContentResolver res = mContext.getContentResolver();
            final int accelerometerRotation =
                    userRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED ? 0 : 1;
            //禁止旋转的话是0,允许旋转的话是1
            Settings.System.putIntForUser(res, Settings.System.ACCELEROMETER_ROTATION,
                    accelerometerRotation, UserHandle.USER_CURRENT);
            //存储当前屏幕的旋转角度
            Settings.System.putIntForUser(res, Settings.System.USER_ROTATION, userRotation,
                    UserHandle.USER_CURRENT);
            return;
        }

>settings key

arduino 复制代码
        /**
         * Control whether the accelerometer will be used to change screen
         * orientation.  If 0, it will not be used unless explicitly requested
         * by the application; if 1, it will be used by default unless explicitly
         * disabled by the application.
         */
        // 0表示不使用加速度传感器改变屏幕方向,
        // 1表示使用加速度传感器,
        public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";

        /**
         * Default screen rotation when no other policy applies.
         * When {@link #ACCELEROMETER_ROTATION} is zero and no on-screen Activity expresses a
         * preference, this rotation value will be used. Must be one of the
         * {@link android.view.Surface#ROTATION_0 Surface rotation constants}.
         *
         */
        //当ACCELEROMETER_ROTATION为0的时候,这个值就是屏幕的方向
        public static final String USER_ROTATION = "user_rotation";

7.4.整理一下

  • 旋转开关关闭 ,freezeRotation ,ACCELEROMETER_ROTATION 为0,USER_ROTATION 为 0或者当前角度
  • 旋转开关打开 ,thawRotation ,ACCELEROMETER_ROTATION 为1 ,USER_ROTATION 为0 这个好像没看到限制旋转180度的逻辑,只看到allow all rotation为false的时候,关闭旋转以后,角度默认变成0了,见7.2

8.DisplayRotation.java

手机模式会限制180度旋转的逻辑,在这个类里

8.1.getAllowAllRotations

  • 和5.2小节用的字段一样 config_allowAllRotations ,决定是否所有角度都可以旋转,
  • 在8.8小节的 88行代码出使用
csharp 复制代码
    private int getAllowAllRotations() {
        if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) {
            mAllowAllRotations = mContext.getResources().getBoolean(
                    R.bool.config_allowAllRotations)
                    ? ALLOW_ALL_ROTATIONS_ENABLED
                    : ALLOW_ALL_ROTATIONS_DISABLED;
        }

        return mAllowAllRotations;
    }

8.2.构造方法

scss 复制代码
        if (isDefaultDisplay) {
            final Handler uiHandler = UiThread.getHandler();
            //旋转方向监听器
            mOrientationListener = new OrientationListener(mContext, uiHandler);
            mOrientationListener.setCurrentRotation(mRotation);
            //监听设置里的旋转开关的状态改变
            mSettingsObserver = new SettingsObserver(uiHandler);
            //见8.3
            mSettingsObserver.observe();
//..
        }

8.3.OrientationListener

  • 这个方法和我们平时在app里使用的OrientationEventListener方法逻辑差不多,
  • 父类WindowOrientationListener里同样是sensorManager注册listener来监听传感器的数据变化,根据x,y,z的值算出角度,具体逻辑就不看了,
  • enable方法就是开启监听,disable方法就是关闭监听
  • onProposedRotationChanged方法就是父类计算出的角度回调。
java 复制代码
    private class OrientationListener extends WindowOrientationListener implements Runnable {
        transient boolean mEnabled;
        //..
        @Override
        public void onProposedRotationChanged(@Surface.Rotation int rotation) {
            mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
            //
            if (isRotationChoiceAllowed(rotation)) {
                final boolean isValid = isValidRotationChoice(rotation);
                sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
            } else {
            //走这里,见8.5
                mService.updateRotation(false /* alwaysSendConfiguration */,
                        false /* forceRelayout */);
            }
        }

        @Override
        public void enable() {
            mEnabled = true;
            getHandler().post(this);//执行run方法
        }

        @Override
        public void disable() {
            mEnabled = false;
            getHandler().post(this);//执行run方法
        }

        @Override
        public void run() {
        //调用父类的方法,开启或者关闭传感器监听
            if (mEnabled) {
                super.enable();
            } else {
                super.disable();
            }
        }
    }

8.4.updateOrientationListenerLw

  • 方法的注解列出了enable以及disable监听器的条件。
arduino 复制代码
    /**
     * Various use cases for invoking this function:
     * <li>Screen turning off, should always disable listeners if already enabled.</li>
     * <li>Screen turned on and current app has sensor based orientation, enable listeners
     *     if not already enabled.</li>
     * <li>Screen turned on and current app does not have sensor orientation, disable listeners
     *     if already enabled.</li>
     * <li>Screen turning on and current app has sensor based orientation, enable listeners
     *     if needed.</li>
     * <li>screen turning on and current app has nosensor based orientation, do nothing.</li>
     */
    private void updateOrientationListenerLw() {
        if (mOrientationListener == null || !mOrientationListener.canDetectOrientation()) {
            // If sensor is turned off or nonexistent for some reason.
            return;
        }
        //屏幕 trun on的时候为true,turn off的时候为false
        final boolean screenOnEarly = mDisplayPolicy.isScreenOnEarly();
        final boolean awake = mDisplayPolicy.isAwake();
        final boolean keyguardDrawComplete = mDisplayPolicy.isKeyguardDrawComplete();
        final boolean windowManagerDrawComplete = mDisplayPolicy.isWindowManagerDrawComplete();


        boolean disable = true;
        //下边就是enable监听器的条件
        //1:屏幕点亮
        //2:唤醒状态或者传感器需要在待机状态下工作
        //3:锁屏界面绘制完成并且窗口绘制完成
        if (screenOnEarly
                && (awake || mOrientationListener.shouldStayEnabledWhileDreaming())
                && ((keyguardDrawComplete && windowManagerDrawComplete))) {
            //这里又判断了是否需要监听器,默认是需要的,具体逻辑自己看    
            if (needSensorRunning()) {
                disable = false;
                //监听器没打开的话打开监听器
                if (!mOrientationListener.mEnabled) {
                    mOrientationListener.enable();
                }
            }
        }
        //关闭监听器,比如息屏
        if (disable) {
            mOrientationListener.disable();
        }
    }
  • 这个方法的调用地方在本类里有3处

>更新角度的时候

ini 复制代码
    boolean updateOrientation(@ScreenOrientation int newOrientation, boolean forceUpdate) {
        if (newOrientation == mLastOrientation && !forceUpdate) {
            return false;
        }
        mLastOrientation = newOrientation;
        if (newOrientation != mCurrentAppOrientation) {
            mCurrentAppOrientation = newOrientation;
            if (isDefaultDisplay) {
                updateOrientationListenerLw();
            }
        }
        return updateRotationUnchecked(forceUpdate);
    }

>settings发生变化的时候

ini 复制代码
    private boolean updateSettings() {
    //..
            if (mShowRotationSuggestions != showRotationSuggestions) {
                shouldUpdateOrientationListener = true;
            }
            //..
            if (mUserRotationMode != userRotationMode) {
                shouldUpdateOrientationListener = true;
                shouldUpdateRotation = true;
            }
            if (shouldUpdateOrientationListener) {
                updateOrientationListenerLw(); // Enable or disable the orientation listener.
            }

>phoneWindowManager

在systemReady方法里,awake方法里, screen turn on/off方法里都会调用到

scss 复制代码
    public void updateOrientationListener() {
        synchronized (mLock) {
            updateOrientationListenerLw();
        }
    }

8.5.WindowManagerService.java

>updateRotation

8.3小节的监听器回调里会执行这个方法

java 复制代码
    public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
        updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
    }
    
    private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
        try {
            synchronized (mGlobalLock) {
                boolean layoutNeeded = false;
                //一般设备就一个屏幕,折叠屏手机不知道算2个不?
                final int displayCount = mRoot.mChildren.size();
                for (int i = 0; i < displayCount; ++i) {
                    final DisplayContent displayContent = mRoot.mChildren.get(i);
                    //根据各种条件的判断,看下最终旋转角度是否发生变化,见 8.6
                    final boolean rotationChanged = displayContent.updateRotationUnchecked();

                    if (rotationChanged) {
                        mAtmService.getTaskChangeNotificationController()
                                .notifyOnActivityRotation(displayContent.mDisplayId);
                    }

                //...

                if (layoutNeeded) {
                    mWindowPlacerLocked.performSurfacePlacement();
                }
            }
        }
    }    

8.6.updateRotationUnchecked

DisplayContent.java

typescript 复制代码
    boolean updateRotationUnchecked() {
        return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
    }

8.7.updateRotationUnchecked

这个方法里会计算新的旋转角度,如果角度发生变化的话进行处理

java 复制代码
    boolean updateRotationUnchecked(boolean forceUpdate) {
        final int displayId = mDisplayContent.getDisplayId();
    //...
        final int oldRotation = mRotation;
        final int lastOrientation = mLastOrientation;
        //最终计算出的角度是这个,具体看下  8.8
        int rotation = rotationForOrientation(lastOrientation, oldRotation);
        if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
            int prevRotation = rotation;
            rotation = mFoldController.revertOverriddenRotation();
    //...
        if (oldRotation == rotation) {
            // No change.
            return false;
        } 
 //..
         mRotation = rotation;

        mDisplayContent.setLayoutNeeded();
//..
//后边就是对旋转的处理,不研究了

        if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
            // The screen rotation animation uses a screenshot to freeze the screen while windows
            // resize underneath. When we are rotating seamlessly, we allow the elements to
            // transition to their rotated state independently and without a freeze required.
            prepareSeamlessRotation();
        } else {
            prepareNormalRotationAnimation();
        }

        // Give a remote handler (system ui) some time to reposition things.
        startRemoteRotation(oldRotation, mRotation);

        return true;
    }

8.8.rotationForOrientation

ini 复制代码
    int rotationForOrientation(@ScreenOrientation int orientation,
            @Surface.Rotation int lastRotation) {
        
        if (isFixedToUserRotation()) {
        //不支持旋转,返回当前的角度
            return mUserRotation;
        }
        //获取listener里通过传感器计算出的角度,或者如果没有传感器,返回-1
        int sensorRotation = mOrientationListener != null
                ? mOrientationListener.getProposedRotation() // may be -1
                : -1;
        mLastSensorRotation = sensorRotation;
        if (sensorRotation < 0) {
        //没有传感器的情况,设置为默认的角度
            sensorRotation = lastRotation;
        }

        final int lidState = mDisplayPolicy.getLidState();
        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) {
        //输入法旋转开关打开,并且旋转角度大于0,默认值这两个都不满足
            // Ignore sensor when lid switch is open and rotation is forced.
            preferredRotation = mLidOpenRotation;
        } else if (dockMode == Intent.EXTRA_DOCK_STATE_CAR
                && (carDockEnablesAccelerometer || mCarDockRotation >= 0)) {
            //手机插在汽车基座上,如果允许传感器的话,使用传感器角度,
            //否则如果汽车基座固定角度大于等于0,使用此角度
            preferredRotation = carDockEnablesAccelerometer ? sensorRotation : mCarDockRotation;
        } else if ((dockMode == Intent.EXTRA_DOCK_STATE_DESK
                || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
                || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
                && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0)) {
            // Ignore sensor when in desk dock unless explicitly enabled.
            // This case can override the behavior of NOSENSOR, and can also
            // enable 180 degree rotation while docked.
            //其他几种基座模式
            preferredRotation = deskDockEnablesAccelerometer ? sensorRotation : mDeskDockRotation;
        } else if (hdmiPlugged && mDemoHdmiRotationLock) {
            // Ignore sensor when plugged into HDMI when demo HDMI rotation lock enabled.
            // Note that the dock orientation overrides the HDMI orientation.
            preferredRotation = mDemoHdmiRotation;
        } else if (hdmiPlugged && dockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
                && mUndockedHdmiRotation >= 0) {
            // Ignore sensor when plugged into HDMI and an undocked orientation has
            // been specified in the configuration (only for legacy devices without
            // full multi-display support).
            // Note that the dock orientation overrides the HDMI orientation.
            preferredRotation = mUndockedHdmiRotation;
        } else if (mDemoRotationLock) {
            // Ignore sensor when demo rotation lock is enabled.
            //演示旋转锁定为true的情况,忽略旋转角度,使用配置的角度
            preferredRotation = mDemoRotation;
        } else if (mDisplayPolicy.isPersistentVrModeEnabled()) {
            //VR模式,固定为竖屏,使用配置的角度
            preferredRotation = mPortraitRotation;
        } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
            //方向锁定的情况,使用当前的角度
            preferredRotation = lastRotation;
        } else if (!mSupportAutoRotation) {
            //不支持自动旋转
            preferredRotation = -1;
        } else if (
        //旋转开关打开
        ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
                            || isTabletopAutoRotateOverrideEnabled())
            //并且屏幕方向满足以下5种里的一种
                        && (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) {
            //这里对180度进行了处理 
            if (sensorRotation != Surface.ROTATION_180
                //如果是180度,还必须满足下边3条件之一,旋转角度才能是180度
                //1: allow all rotation 为true,配置里设置的,手机默认为false,平板为true
                    || getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED
                    // 2: 旋转方向为full sensor或者full user
                    //调用activity的setRequestedOrientation方法可以设置
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
                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) {
            //旋转开关关闭的情况,并且屏幕旋转方向不是上边5种里的任意一种,使用用户默认的角度
            preferredRotation = mUserRotation;
        } else {
        
            preferredRotation = -1;
        }
        //下边继续根据屏幕方向对角度进行处理
        switch (orientation) {
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
                //要求是竖屏,判断下角度是否是0或者180
                if (isAnyPortrait(preferredRotation)) {
                //旋转角度是竖屏的情况
                    return preferredRotation;
                }
                //旋转角度不是竖屏的,返回默认的竖屏角度0
                return mPortraitRotation;

            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
                //逻辑同竖屏一样
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                //返回0度
                return mLandscapeRotation;

            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
                // Return reverse portrait unless overridden.
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                //旋转角度不是竖屏的,返回默认的竖屏角度180
                return mUpsideDownRotation;

            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
                // Return seascape unless overridden.
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                //返回270度
                return mSeascapeRotation;

            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
            case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
                // Return either landscape rotation.
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                if (isLandscapeOrSeascape(lastRotation)) {
                    return lastRotation;
                }
                return mLandscapeRotation;

            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
            case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
                // Return either portrait rotation.
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                if (isAnyPortrait(lastRotation)) {
                    return lastRotation;
                }
                return mPortraitRotation;

            default:
                // For USER, UNSPECIFIED, NOSENSOR, SENSOR and FULL_SENSOR,
                // just return the preferred orientation we already calculated.
                if (preferredRotation >= 0) {
                    return preferredRotation;
                }
                return Surface.ROTATION_0;
        }
    }

>ps

通过上边代码里88行的if条件可以知道,手机模式下,app其实是可以旋转到180度的,不过必须设置activity的屏幕旋转为下边2种里边的一种

ini 复制代码
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER

也就是需要在activity里调用如下的代码

scss 复制代码
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
//setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);

9.总结

  • 手机桌面app,屏幕旋转的控制逻辑,简单学习下桌面设置页面
  • 学习下快速设置里旋转开关打开关闭以后会修改哪些值,见小节 5
  • 小节8就是限制手机旋转到180度的关键代码,具体逻辑在8.8
相关推荐
Estar.Lee8 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯1 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey2 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!4 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟5 小时前
Android音频采集
android·音视频
小白也想学C6 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程6 小时前
初级数据结构——树
android·java·数据结构
闲暇部落8 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX10 小时前
Android 分区相关介绍
android
大白要努力!11 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle