实现步骤
- 屏幕旋转功能可以让用户选择屏幕的默认方向,包括0度(竖屏)、90度(横屏)、180度(反向竖屏)和270度(反向横屏)。
- 强制应用旋转功能可以让用户强制所有应用以横屏或竖屏的方式显示,无论应用本身是否支持旋转。
- 修改都支持重启后保存哦,强制APP旋转优先级>系统方向优先级。
修改示例
DisplayRotation模块
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
这个模块负责处理屏幕旋转的逻辑,需要在其中添加1个系统属性:persist.sys.app.rotation,分别用于控制强制应用旋转和屏幕旋转的设置。
在rotationForOrientation()和updateOrientation()方法中,需要根据persist.sys.app.rotation的值来修改当前应用的方向,如果是force_landscape,则强制为横屏;如果是force_portrait,则强制为竖屏;否则按照应用本身的方向设置。
+++ b/frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java @@ -380,6 +380,12 @@ public class DisplayRotation { if (newOrientation != mCurrentAppOrientation) { mCurrentAppOrientation = newOrientation; String rot = SystemProperties.get("persist.sys.app.rotation", "middle_port"); + if (rot.equals("force_landscape")){ + mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + }else if (rot.equals("force_portrait")){ + mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + + } if (rot.equals("force_land") && "box".equals(SystemProperties.get("ro.target.product"))) mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; if (isDefaultDisplay) { @@ -1204,6 +1210,13 @@ public class DisplayRotation { Slog.v(TAG, "asx force_land :" + mLandscapeRotation); return mLandscapeRotation; } + + if (rot.equals("force_landscape")){ + return mLandscapeRotation; + }else if (rot.equals("force_portrait")){ + return mPortraitRotation; + } + switch (orientation) { case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: // Return portrait unless overridden.
Settings模块
packages/apps/Settings/res/values/arrays.xml
这个文件定义了屏幕旋转和强制应用旋转的选项列表,需要在其中添加两个数组:screen_rotate_entries和screen_rotate_values,以及forceapp_rotate_entries和forceapp_rotate_values。
screen_rotate_entries和screen_rotate_values分别表示屏幕旋转的显示名称和对应的值,包括0度、90度、180度和270度。
- forceapp_rotate_entries和forceapp_rotate_values分别表示强制应用旋转的显示名称和对应的值,包括默认、横屏和竖屏。
+++ b/packages/apps/Settings/res/values-zh-rCN/arrays.xml @@ -20,6 +20,45 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + + <string-array name="screen_totate_entries"> + <item>0 Degree</item> + <item>90 Degree</item> + <item>180 Degree</item> + <item>270 Degree</item> + </string-array> + + <!-- Do not translate. --> + <string-array name="screen_rotate_values" translatable="false"> + <!-- Do not translate. --> + <item>0</item> + <!-- Do not translate. --> + <item>1</item> + <!-- Do not translate. --> + <item>2</item> + <!-- Do not translate. --> + <item>3</item> + </string-array> + + <string-array name="forceapp_rotate_entries"> + <item>default</item> + <item>portrait</item> + <item>landscape</item> + + + </string-array> + + <!-- Do not translate. --> + <string-array name="forceapp_rotate_values" translatable="false"> + <!-- Do not translate. --> + <item>0</item> + <!-- Do not translate. --> + <item>1</item> + <!-- Do not translate. --> + <item>2</item> + + </string-array>
packages/apps/Settings/res/values/strings.xml
这个文件定义了英文版的字符串资源,需要在其中添加屏幕旋转和强制应用旋转的标题和摘要,以及对应的格式化字符串。
+++ b/packages/apps/Settings/res/values/strings.xml @@ -14,6 +14,10 @@ limitations under the License. --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="forceapp_rotate_summary"> <xliff:g id="forceapprotate_description">%1$s</xliff:g> </string> + <string name="screen_rotate_summary"> <xliff:g id="screenrotate_description">%1$s</xliff:g> </string> + <string name="ctrl_forceapp_rotate" >"Force App Rotate"</string> + <string name="ctrl_screen_rotate">"Screen Rotate"</string> <string name="ctrl_statusbar">StatusBar</string> <string name="ctrl_explan">ExPlan</string> <string name="ctrl_navigationbar">NavigationBar</string>
packages/apps/Settings/res/values-zh-rCN/strings.xml
这个文件定义了中文版的字符串资源,需要在其中添加屏幕旋转和强制应用旋转的标题和摘要,以及对应的格式化字符串。
+++ b/packages/apps/Settings/res/values-zh-rCN/strings.xml @@ -16,6 +16,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="forceapp_rotate_summary"> <xliff:g id="forceapprotate_description">%1$s</xliff:g> </string> + <string name="screen_rotate_summary"> <xliff:g id="screenrotate_description">%1$s</xliff:g> </string> + <string name="ctrl_forceapp_rotate" >"APP旋转"</string> + <string name="ctrl_screen_rotate">"屏幕旋转"</string> <string name="ctrl_statusbar">状态栏</string> <string name="ctrl_explan">下拉菜单</string> <string name="ctrl_navigationbar">导航栏</string>
packages/apps/Settings/res/xml/display_settings.xml
这个文件定义了显示设置的界面布局,添加屏幕旋转和强制应用旋转的列表偏好,以及对应的键值、标题、摘要和选项。
我们使用自定义的ScreenRotateListPreference和ForceAppRotateListPreference类来实现列表偏好的功能,这两个类继承了RestrictedListPreference类,并重写了一些方法来处理管理员限制和对话框显示。
+++ b/packages/apps/Settings/res/xml/display_settings.xml @@ -31,7 +31,18 @@ <SwitchPreference android:key="ctrl_explan" android:title="@string/ctrl_explan"/> - + <com.android.settings.display.ScreenRotateListPreference + android:key="screen_rotate" + android:title="@string/ctrl_screen_rotate" + android:summary="@string/summary_placeholder" + android:entries="@array/screen_rotate_entries" + android:entryValues="@array/screen_rotate_values"/> + <com.android.settings.display.ForceAppRotateListPreference + android:key="forceapp_rotate" + android:title="@string/ctrl_forceapp_rotate" + android:summary="@string/summary_placeholder" + android:entries="@array/forceapp_rotate_entries" + android:entryValues="@array/forceapp_rotate_values"/> <com.android.settingslib.RestrictedPreference android:key="brightness" android:title="@string/brightness"
packages/apps/Settings/src/com/android/settings/DisplaySettings.java
这个文件定义了显示设置的控制器,我们需要在其中添加屏幕旋转和强制应用旋转的偏好控制器,以及对应的键值和上下文。
使用自定义的ScreenRotatePreferenceController和ForceAppRotatePreferenceController类来实现偏好控制器的功能,这两个类继承了AbstractPreferenceController类,并实现了Preference.OnPreferenceChangeListener接口,用于处理偏好变化的事件。
+++ b/packages/apps/Settings/src/com/android/settings/DisplaySettings.java @@ -40,6 +40,8 @@ import com.android.settingslib.search.SearchIndexable; import com.android.settings.display.StatusBarPreferenceController; import com.android.settings.display.NavigationBarPreferenceController; import com.android.settings.display.ExPlanPreferenceController; +import com.android.settings.display.ForceAppRotatePreferenceController; +import com.android.settings.display.ScreenRotatePreferenceController; import java.util.ArrayList; import java.util.List; @@ -98,6 +100,8 @@ public class DisplaySettings extends DashboardFragment { controllers.add(new StatusBarPreferenceController(context)); controllers.add(new NavigationBarPreferenceController(context)); controllers.add(new ExPlanPreferenceController(context)); + controllers.add(new ForceAppRotatePreferenceController(context,"forceapp_rotate")); + controllers.add(new ScreenRotatePreferenceController(context,"screen_rotate")); return controllers; }
packages/apps/Settings/src/com/android/settings/display/ScreenRotateListPreference.java
- 这个文件定义了屏幕旋转的列表偏好类,需要在其中实现以下功能:
- 继承RestrictedListPreference类,并在构造方法中初始化初始的选项和值。
- 重写onPrepareDialogBuilder()方法,在对话框中添加管理员限制的视图,如果有的话。
- 重写onDialogCreated()方法,在对话框中添加管理员限制的点击事件,如果有的话。
- 定义一个removeUnusableRotates()方法,用于移除不可用的选项,并根据管理员限制来禁用或启用偏好。
package com.android.settings.display; import android.content.Context; import android.provider.Settings; import androidx.preference.SwitchPreference; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; import android.content.Intent; import android.util.Log; import com.android.settings.R; import android.os.SystemProperties; public class ScreenRotatePreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String TAG = "ScreenRotatePrefContr"; /** If there is no setting in the provider, use this. */ public static final int FALLBACK_SCREEN_ROTATE_VALUE = 0; private final String mScreenRotateKey; public ScreenRotatePreferenceController(Context context, String key) { super(context); mScreenRotateKey = key; } @Override public boolean isAvailable() { return true; } @Override public String getPreferenceKey() { return mScreenRotateKey; } @Override public void updateState(Preference preference) { final ScreenRotateListPreference screenRotateListPreference = (ScreenRotateListPreference) preference; long currentRotate = Settings.System.getLong(mContext.getContentResolver(), Settings.System.USER_ROTATION, FALLBACK_SCREEN_ROTATE_VALUE); screenRotateListPreference.setValue(String.valueOf(currentRotate)); updateRotatePreferenceDescription(screenRotateListPreference, currentRotate); } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { try { int value = Integer.parseInt((String) newValue); Settings.System.putInt(mContext.getContentResolver(), Settings.System.USER_ROTATION, value); updateRotatePreferenceDescription((ScreenRotateListPreference) preference, value); } catch (NumberFormatException e) { Log.e(TAG, "could not persist screen rotate setting", e); } return true; } public static CharSequence getRotateDescription( long currentRotate, CharSequence[] entries, CharSequence[] values) { if (currentRotate < 0 || entries == null || values == null || values.length != entries.length) { return null; } for (int i = 0; i < values.length; i++) { long rotate = Long.parseLong(values[i].toString()); if (currentRotate == rotate) { return entries[i]; } } return null; } private void updateRotatePreferenceDescription(ScreenRotateListPreference preference, long currentRotate) { final CharSequence[] entries = preference.getEntries(); final CharSequence[] values = preference.getEntryValues(); final String summary; if (preference.isDisabledByAdmin()) { summary = mContext.getString(com.android.settings.R.string.disabled_by_policy_title); } else { final CharSequence rotateDescription = getRotateDescription( currentRotate, entries, values); summary = rotateDescription == null ? "" : mContext.getString(R.string.screen_rotate_summary, rotateDescription); } preference.setSummary(summary); } }
packages/apps/Settings/src/com/android/settings/display/ForceAppRotateListPreference.java
- 这个文件定义了强制应用旋转的列表偏好类,需要在其中实现以下功能:
- 继承RestrictedListPreference类,并在构造方法中初始化初始的选项和值。
- 重写onPrepareDialogBuilder()方法,在对话框中添加管理员限制的视图,如果有的话。
- 重写onDialogCreated()方法,在对话框中添加管理员限制的点击事件,如果有的话。
- 定义一个removeUnusableRotates()方法,用于移除不可用的选项,并根据管理员限制来禁用或启用偏好。
package com.android.settings.display; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.Log; import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.core.AbstractPreferenceController; import android.app.Dialog; import java.util.ArrayList; import android.view.View; import androidx.appcompat.app.AlertDialog; import android.util.AttributeSet; import com.android.settings.R; import com.android.settings.RestrictedListPreference; import android.content.DialogInterface; public class ForceAppRotateListPreference extends RestrictedListPreference { private EnforcedAdmin mAdmin; private final CharSequence[] mInitialEntries; private final CharSequence[] mInitialValues; public ForceAppRotateListPreference(Context context, AttributeSet attrs) { super(context, attrs); mInitialEntries = getEntries(); mInitialValues = getEntryValues(); } @Override protected void onPrepareDialogBuilder(AlertDialog.Builder builder, DialogInterface.OnClickListener listener) { super.onPrepareDialogBuilder(builder, listener); if (mAdmin != null) { builder.setView(R.layout.admin_disabled_other_options_footer); } else { builder.setView(null); } } @Override protected void onDialogCreated(Dialog dialog) { super.onDialogCreated(dialog); dialog.create(); if (mAdmin != null) { View footerView = dialog.findViewById(R.id.admin_disabled_other_options); footerView.findViewById(R.id.admin_more_details_link).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { // getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); RestrictedLockUtils.sendShowAdminSupportDetailsIntent( getContext(), mAdmin); } }); } } public void removeUnusableRotates(long maxRotate, EnforcedAdmin admin) { final DevicePolicyManager dpm = (DevicePolicyManager) getContext().getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return; } if (admin == null && mAdmin == null && !isDisabledByAdmin()) { return; } if (admin == null) { maxRotate = Long.MAX_VALUE; } ArrayList<CharSequence> revisedEntries = new ArrayList<CharSequence>(); ArrayList<CharSequence> revisedValues = new ArrayList<CharSequence>(); for (int i = 0; i < mInitialValues.length; ++i) { long rotate = Long.parseLong(mInitialValues[i].toString()); if (rotate <= maxRotate) { revisedEntries.add(mInitialEntries[i]); revisedValues.add(mInitialValues[i]); } } // If there are no possible options for the user, then set this preference as disabled // by admin, otherwise remove the padlock in case it was set earlier. if (revisedValues.size() == 0) { setDisabledByAdmin(admin); return; } else { setDisabledByAdmin(null); } if (revisedEntries.size() != getEntries().length) { final int userPreference = Integer.parseInt(getValue()); setEntries(revisedEntries.toArray(new CharSequence[0])); setEntryValues(revisedValues.toArray(new CharSequence[0])); mAdmin = admin; if (userPreference <= maxRotate) { setValue(String.valueOf(userPreference)); } else if (revisedValues.size() > 0 && Long.parseLong(revisedValues.get(revisedValues.size() - 1).toString()) == maxRotate) { // If the last one happens to be the same as the max rotate, select that setValue(String.valueOf(maxRotate)); } else { // There will be no highlighted selection since nothing in the list matches // maxRotate. The user can still select anything less than maxRotate. // TODO: maybe append maxRotate to the list and mark selected. } } } }
packages/apps/Settings/src/com/android/settings/display/ForceAppRotatePreferenceController.java
- 这个文件定义了强制应用旋转的偏好控制器类,需要在其中实现以下功能:
- 继承AbstractPreferenceController类,并实现Preference.OnPreferenceChangeListener接口,用于处理偏好变化的事件。
- 在构造方法中初始化键值和上下文。
- 在isAvailable()方法中返回true,表示该控制器可用。
- 在getPreferenceKey()方法中返回键值。
- 在updateState()方法中根据系统属性persist.sys.app.rotation的值来更新偏好的选项和摘要。
- 在onPreferenceChange()方法中根据用户选择的值来修改系统属性persist.sys.app.rotation,并更新偏好的摘要。
- 定义一个getRotateDescription()方法,用于根据当前的值和选项列表来获取对应的描述。
package com.android.settings.display; import android.content.Context; import android.provider.Settings; import androidx.preference.SwitchPreference; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; import android.content.Intent; import android.util.Log; import com.android.settings.R; import android.os.SystemProperties; public class ScreenRotatePreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String TAG = "ScreenRotatePrefContr"; /** If there is no setting in the provider, use this. */ public static final int FALLBACK_SCREEN_ROTATE_VALUE = 0; private final String mScreenRotateKey; public ScreenRotatePreferenceController(Context context, String key) { super(context); mScreenRotateKey = key; } @Override public boolean isAvailable() { return true; } @Override public String getPreferenceKey() { return mScreenRotateKey; } @Override public void updateState(Preference preference) { final ScreenRotateListPreference screenRotateListPreference = (ScreenRotateListPreference) preference; long currentRotate = Settings.System.getLong(mContext.getContentResolver(), Settings.System.USER_ROTATION, FALLBACK_SCREEN_ROTATE_VALUE); screenRotateListPreference.setValue(String.valueOf(currentRotate)); updateRotatePreferenceDescription(screenRotateListPreference, currentRotate); } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { try { int value = Integer.parseInt((String) newValue); Settings.System.putInt(mContext.getContentResolver(), Settings.System.USER_ROTATION, value); updateRotatePreferenceDescription((ScreenRotateListPreference) preference, value); } catch (NumberFormatException e) { Log.e(TAG, "could not persist screen rotate setting", e); } return true; } public static CharSequence getRotateDescription( long currentRotate, CharSequence[] entries, CharSequence[] values) { if (currentRotate < 0 || entries == null || values == null || values.length != entries.length) { return null; } for (int i = 0; i < values.length; i++) { long rotate = Long.parseLong(values[i].toString()); if (currentRotate == rotate) { return entries[i]; } } return null; } private void updateRotatePreferenceDescription(ScreenRotateListPreference preference, long currentRotate) { final CharSequence[] entries = preference.getEntries(); final CharSequence[] values = preference.getEntryValues(); final String summary; if (preference.isDisabledByAdmin()) { summary = mContext.getString(com.android.settings.R.string.disabled_by_policy_title); } else { final CharSequence rotateDescription = getRotateDescription( currentRotate, entries, values); summary = rotateDescription == null ? "" : mContext.getString(R.string.screen_rotate_summary, rotateDescription); } preference.setSummary(summary); } }
packages/apps/Settings/src/com/android/settings/display/ScreenRotateListPreference.java
- 这个文件定义了屏幕旋转的列表偏好类,需要在其中实现以下功能:
- 继承RestrictedListPreference类,并在构造方法中初始化初始的选项和值。
- 重写onPrepareDialogBuilder()方法,在对话框中添加管理员限制的视图,如果有的话。
- 重写onDialogCreated()方法,在对话框中添加管理员限制的点击事件,如果有的话。
- 定义一个removeUnusableRotates()方法,用于移除不可用的选项,并根据管理员限制来禁用或启用偏好。
package com.android.settings.display; import android.content.Context; import android.provider.Settings; import androidx.preference.SwitchPreference; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; import android.content.Intent; import android.util.Log; import com.android.settings.R; import android.os.SystemProperties; public class ForceAppRotatePreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String TAG = "ForceAppRotatePrefContr"; /** If there is no setting in the provider, use this. */ public static final int FALLBACK_FORCE_APP_ROTATE_VALUE = 0; private final String mForceAppRotateKey; public ForceAppRotatePreferenceController(Context context, String key) { super(context); mForceAppRotateKey = key; } @Override public boolean isAvailable() { return true; } @Override public String getPreferenceKey() { return mForceAppRotateKey; } @Override public void updateState(Preference preference) { final ForceAppRotateListPreference forceAppRotateListPreference = (ForceAppRotateListPreference) preference; long currentRotate = 0; String rot = SystemProperties.get("persist.sys.app.rotation", "middle_port"); if (rot.equals("force_landscape")){ currentRotate = 1; }else if (rot.equals("force_portrait")){ currentRotate = 2; }else{ currentRotate = 0; } forceAppRotateListPreference.setValue(String.valueOf(currentRotate)); updateRotatePreferenceDescription(forceAppRotateListPreference, currentRotate); } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { try { int value = Integer.parseInt((String) newValue); if(value==0){ SystemProperties.set("persist.sys.app.rotation", ""); }else if (value==1){ SystemProperties.set("persist.sys.app.rotation", "force_portrait"); }else if (value==2){ SystemProperties.set("persist.sys.app.rotation", "force_landscape"); } //Settings.System.putInt(mContext.getContentResolver(), "FORCE_APP_ROTATION", value); updateRotatePreferenceDescription((ForceAppRotateListPreference) preference, value); } catch (NumberFormatException e) { Log.e(TAG, "could not persist force app rotate setting", e); } return true; } public static CharSequence getRotateDescription( long currentRotate, CharSequence[] entries, CharSequence[] values) { if (currentRotate < 0 || entries == null || values == null || values.length != entries.length) { return null; } for (int i = 0; i < values.length; i++) { long rotate = Long.parseLong(values[i].toString()); if (currentRotate == rotate) { return entries[i]; } } return null; } private void updateRotatePreferenceDescription(ForceAppRotateListPreference preference, long currentRotate) { final CharSequence[] entries = preference.getEntries(); final CharSequence[] values = preference.getEntryValues(); final String summary; if (preference.isDisabledByAdmin()) { summary = mContext.getString(com.android.settings.R.string.disabled_by_policy_title); } else { final CharSequence rotateDescription = getRotateDescription( currentRotate, entries, values); summary = rotateDescription == null ? "" : mContext.getString(R.string.forceapp_rotate_summary, rotateDescription); } preference.setSummary(summary); } }
OK 到这里就全部添加完成 没有难度~
其他记录:
- 比如ForceAppRotateListPreference文件为什么要新建这个东西? 早期版本没这么麻烦
- 有个坑xxxListPreference里面的AlertDialog11+版本要用
androidx.appcompat.app.AlertDialog;
不能用早期版本的 , 如果同时导入2个包 会一直编译报错 只需保留一个就可以。
测试效果:
不管应用是否支持竖屏 , 都会被强制竖屏。
总结:
本文介绍了如何在Android系统Settings中添加屏幕旋转和强制App应用旋转的功能,以便客户可以根据项目需求调整屏幕方向