实现步骤
屏幕旋转功能可以让用户选择屏幕的默认方向,包括0度(竖屏)、90度(横屏)、180度(反向竖屏)和270度(反向横屏)。
强制应用旋转功能可以让用户强制所有应用以横屏或竖屏的方式显示,无论应用本身是否支持旋转。
修改都支持重启后保存哦,强制APP旋转优先级>系统方向优先级。
修改代码
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
updateOrientation()
if (newOrientation != mCurrentAppOrientation) {
mCurrentAppOrientation = newOrientation;
String rot = SystemProperties.get("persist.sys.app.rotation", "middle_port");
/**
* 在rotationForOrientation()和updateOrientation()方法中,
* 需要根据persist.sys.app.rotation的值来修改当前应用的方向,
* 如果是force_landscape,则强制为横屏;
* 如果是force_portrait,则强制为竖屏;否则按照应用本身的方向设置。
*/
Slog.d(TAG,"-----updateOrientation-----rot:"+rot);
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) {
Slog.v(TAG, "asx force_land :" + mLandscapeRotation);
return mLandscapeRotation;
}
rotationForOrientation()
Slog.v(TAG, "----rotationForOrientation-------rot :" + rot);
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.
packages/apps/Settings/res/values-zh-rCN/strings.xml
<!--屏幕旋转 -->
<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>
packages/apps/Settings/res/values/arrays.xml
<string-array name="screen_rotate_entries">
<item>0 度</item>
<item>90 度</item>
<item>180 度</item>
<item>270 度</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>默认</item>
<item>竖屏</item>
<item>横屏</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
<!--屏幕旋转 -->
<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>
packages/apps/Settings/res/xml/display_settings.xml
<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"/>
packages/apps/Settings/src/com/android/settings/DisplaySettings.java
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import com.android.settings.display.ForceAppRotatePreferenceController;
import com.android.settings.display.ScreenRotatePreferenceController;
import java.util.ArrayList;
import java.util.List;
controllers.add(new ThemePreferenceController(context));
controllers.add(new BrightnessLevelPreferenceController(context, lifecycle));
controllers.add(new HdmiSettingsPreferenceController(context, KET_HDMI_SETTINGS));
controllers.add(new ForceAppRotatePreferenceController(context,"forceapp_rotate"));
controllers.add(new ScreenRotatePreferenceController(context,"screen_rotate"));
return controllers;
}
packages/apps/Settings/src/com/android/settings/display/ForceAppRotateListPreference.java
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;
/**
*
* 定义了强制应用旋转的列表偏好类,需要在其中实现以下功能:
* 继承RestrictedListPreference类,并在构造方法中初始化初始的选项和值。
* 重写onPrepareDialogBuilder()方法,在对话框中添加管理员限制的视图,如果有的话。
* 重写onDialogCreated()方法,在对话框中添加管理员限制的点击事件,如果有的话。
* 定义一个removeUnusableRotates()方法,用于移除不可用的选项,并根据管理员限制来禁用或启用偏好。
*/
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
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;
/**
* 定义了强制应用旋转的偏好控制器类,需要在其中实现以下功能:
* 继承AbstractPreferenceController类,并实现Preference.OnPreferenceChangeListener接口,用于处理偏好变化的事件。
* 在构造方法中初始化键值和上下文。
* 在isAvailable()方法中返回true,表示该控制器可用。
* 在getPreferenceKey()方法中返回键值。
* 在updateState()方法中根据系统属性persist.sys.app.rotation的值来更新偏好的选项和摘要。
* 在onPreferenceChange()方法中根据用户选择的值来修改系统属性persist.sys.app.rotation,并更新偏好的摘要。
* 定义一个getRotateDescription()方法,用于根据当前的值和选项列表来获取对应的描述。
*/
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");
Log.d(TAG, "--------updateState------------rot:"+rot);
if (rot.equals("force_landscape")){
currentRotate = 2;
}else if (rot.equals("force_portrait")){
currentRotate = 1;
}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");
}
Log.d(TAG, "--------onPreferenceChange------------value:"+String.valueOf(value));
//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);
}
}
packages/apps/Settings/src/com/android/settings/display/ScreenRotateListPreference.java
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;
/**
*
* 定义了屏幕旋转的列表偏好类,需要在其中实现以下功能:
* 继承RestrictedListPreference类,并在构造方法中初始化初始的选项和值。
* 重写onPrepareDialogBuilder()方法,在对话框中添加管理员限制的视图,如果有的话。
* 重写onDialogCreated()方法,在对话框中添加管理员限制的点击事件,如果有的话。
*定义一个removeUnusableRotates()方法,用于移除不可用的选项,并根据管理员限制来禁用或启用偏好
*/
public class ScreenRotateListPreference extends RestrictedListPreference {
private EnforcedAdmin mAdmin;
private final CharSequence[] mInitialEntries;
private final CharSequence[] mInitialValues;
public ScreenRotateListPreference (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/ScreenRotatePreferenceController.javad
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));
Log.d(TAG, "--------updateState------------currentRotate:"+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);
}
}
效果图