android framework13-launcher3【
1.简介
平板模式没啥问题,手机模式的话,桌面默认是不可以旋转的,打开桌面旋转开关以后,也只能横向旋转,180度那个是没效果的,下边就具体找下原因,看看是哪里做了限制
注意:我们这里分析手机模式不能旋转的问题。
这里贴下平板和手机判断的逻辑,取设备宽和高里比较小的值,计算其dp值,比600大的是认为是tablet,否则就是phone
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);
}
参考:
2.桌面设置页面
2.1 打开桌面自动旋转
桌面空白处长按,弹出的菜单选择 home settings,然后就能看到桌面的设置页面了,那个allow home screen rotation就是旋转开关,手机模式默认是关闭的,平板模式这个偏好隐藏了。
2.2.布局相关
>清单文件
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了
<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文件如下,
<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
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
<string name="settings_fragment_name" translatable="false">
com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment</string>
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
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
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里自动旋转开关的默认值
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
//这个是不变的
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
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
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
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 :这个也不能旋转了,都设置成没有传感器了
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去请求,设置旋转参数
private boolean setOrientationAsync(Message msg) {
Activity activity = mActivity;
if (activity != null) {
activity.setRequestedOrientation(msg.what);
}
return true;
}
3.5.build.prop
文件位于手机的/system/目录下,记录一些系统设置,是一个属性文件。
>生成
Make系统解析build/core/Makefile,调用build/tools/buildinfo.sh执行脚本生成build.prop文件,并把系统默认的system.prop以及定制的system.prop中的属性追加到build.prop文件中,编译完成之后,文件生成在out/target/product/[board]/system/目录下
>常用属性说明
# 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
public Intent getLongClickIntent() {
return new Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS);
}
>handleClick
点击切换开关状态,开关打开,rotationLock为false,开关关闭,rotationLock为true
protected void handleClick(@Nullable View view) {
final boolean newState = !mState.value;
//controller就是4.2的类,最终实现见 5.1
mController.setRotationLocked(!newState);
refreshState(newState);
}
>handleUpdateState
可以看到开关的状态和旋转锁定是反着的,也就是
- 旋转锁定的时候,开关是false,
- 旋转打开的时候,开关是true,
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean rotationLocked = mController.isRotationLocked();
//
state.value = !rotationLocked;
4.2.RotationLockControllerImpl
简单看下构造方法
public RotationLockControllerImpl(
RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateRotationLockSettingController deviceStateRotationLockSettingController,
@Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
) {
可以看到的方法的实现交给了RotationPolicyWrapper
public void setRotationLocked(boolean locked) {
mRotationPolicy.setRotationLock(locked);
}
>RotationPolicyWrapper
而这个RotationPolicyWrapper的实现又都交给了RotationPolicy,所以最终看下 小节5 即可
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
这个类里的都是静态方法,一个个的看一下
private static final int CURRENT_ROTATION = -1;
public static final int NATURAL_ROTATION = Surface.ROTATION_0;
private RotationPolicy() {
}
5.1.setRotationLock
设置是否可以旋转
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
private static boolean areAllRotationsAllowed(Context context) {
return context.getResources().getBoolean(R.bool.config_allowAllRotations);
}
5.3.setRotationLockAtAngle
设置是否可以旋转以及角度(-1或者0),-1表示所有方向都可以旋转,0表示部分角度可以旋转(排除180度的)
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,则隐藏旋转锁定开关
public static final String HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY =
"hide_rotation_lock_toggle_for_accessibility";
5.4.setRotationLock
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
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
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
获取禁止屏幕旋转以后应该显示的方向,横屏或者竖屏
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
public void freezeRotation(int rotation) {
freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);
}
>freezeDisplayRotation
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
public void thawRotation() {
thawDisplayRotation(Display.DEFAULT_DISPLAY);
}
>thawDisplayRotation
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
@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 就是屏幕当前的旋转角度
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了,比如原本是横屏,开关关闭,直接就成竖屏显示了。
void freezeRotation(int rotation) {
rotation = (rotation == -1) ? mRotation : rotation;
setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
}
7.3.setUserRotation
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
/**
* 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行代码出使用
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.构造方法
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方法就是父类计算出的角度回调。
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监听器的条件。
/**
* 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处
>更新角度的时候
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发生变化的时候
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方法里都会调用到
public void updateOrientationListener() {
synchronized (mLock) {
updateOrientationListenerLw();
}
}
8.5.WindowManagerService.java
>updateRotation
8.3小节的监听器回调里会执行这个方法
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
boolean updateRotationUnchecked() {
return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
}
8.7.updateRotationUnchecked
这个方法里会计算新的旋转角度,如果角度发生变化的话进行处理
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
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种里边的一种
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER
也就是需要在activity里调用如下的代码
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
//setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
9.总结
- 手机桌面app,屏幕旋转的控制逻辑,简单学习下桌面设置页面
- 学习下快速设置里旋转开关打开关闭以后会修改哪些值,见小节 5
- 小节8就是限制手机旋转到180度的关键代码,具体逻辑在8.8