Skip to content

Commit b52600f

Browse files
implement SettingsActivity for RotationControl
1 parent fc398f1 commit b52600f

File tree

14 files changed

+343
-50
lines changed

14 files changed

+343
-50
lines changed

RotationControl/build.gradle.kts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
alias(libs.plugins.buildlogic.android.application)
3+
alias(libs.plugins.kotlin.android)
34
}
45

56
android {
@@ -9,6 +10,11 @@ android {
910
defaultConfig {
1011
applicationId = packageName
1112
minSdk = 24
12-
targetSdk = 33
13+
targetSdk = 36
1314
}
1415
}
16+
17+
dependencies {
18+
implementation(libs.androidx.fragment.ktx)
19+
implementation(libs.androidx.preference.ktx)
20+
}

RotationControl/src/main/AndroidManifest.xml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,31 @@
22
<manifest
33
xmlns:android="http://schemas.android.com/apk/res/android">
44

5-
<application android:label="RotationControl">
5+
<application android:label="@string/app_name">
6+
<activity
7+
android:name=".SettingsActivity"
8+
android:exported="true"
9+
android:label="@string/title_activity_settings"
10+
android:theme="@style/AppTheme"
11+
android:excludeFromRecents="true"
12+
>
13+
<intent-filter>
14+
<action android:name="android.intent.action.MAIN" />
15+
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
16+
</intent-filter>
17+
</activity>
18+
619
<meta-data
720
android:name="xposedmodule"
821
android:value="true"
922
/>
1023
<meta-data
1124
android:name="xposeddescription"
12-
android:value="Force rotation for selected packages"
25+
android:value="@string/description"
1326
/>
1427
<meta-data
1528
android:name="xposedminversion"
16-
android:value="82"
29+
android:value="93"
1730
/>
1831
<meta-data
1932
android:name="xposedscope"
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.programminghoch10.RotationControl
2+
3+
import android.content.pm.ActivityInfo
4+
5+
enum class ROTATION_MODE(val key: String, val value: Int, val title: String, val summary: String) {
6+
// yoinked from https://developer.android.com/reference/android/R.attr.html#screenOrientation
7+
8+
SCREEN_ORIENTATION_UNSET(
9+
"UNSET",
10+
-2,
11+
"UNSET",
12+
"Do not override screen orientation. " +
13+
"You might as well disable the module instead of using this option.",
14+
),
15+
SCREEN_ORIENTATION_UNSPECIFIED(
16+
"UNSPECIFIED",
17+
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
18+
"UNSPECIFIED",
19+
"No preference specified: " +
20+
"let the system decide the best orientation. " +
21+
"This will either be the orientation selected by the activity below, " +
22+
"or the user's preferred orientation if this activity is the bottom of a task. " +
23+
"If the user explicitly turned off sensor based orientation " +
24+
"through settings sensor based device rotation will be ignored. " +
25+
"If not by default sensor based orientation will be taken into account " +
26+
"and the orientation will changed based on how the user rotates the device.",
27+
),
28+
SCREEN_ORIENTATION_LANDSCAPE(
29+
"LANDSCAPE",
30+
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
31+
"LANDSCAPE",
32+
"Would like to have the screen in a landscape orientation: " +
33+
"that is, with the display wider than it is tall, ignoring sensor data.",
34+
),
35+
SCREEN_ORIENTATION_PORTRAIT(
36+
"PORTRAIT",
37+
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
38+
"PORTRAIT",
39+
"Would like to have the screen in a portrait orientation: " +
40+
"that is, with the display taller than it is wide, ignoring sensor data.",
41+
),
42+
SCREEN_ORIENTATION_USER(
43+
"USER",
44+
ActivityInfo.SCREEN_ORIENTATION_USER,
45+
"USER",
46+
"Use the user's current preferred orientation of the handset.",
47+
),
48+
SCREEN_ORIENTATION_BEHIND(
49+
"BEHIND",
50+
ActivityInfo.SCREEN_ORIENTATION_BEHIND,
51+
"BEHIND",
52+
"Keep the screen in the same orientation as whatever is behind this activity.",
53+
),
54+
SCREEN_ORIENTATION_SENSOR(
55+
"SENSOR",
56+
ActivityInfo.SCREEN_ORIENTATION_SENSOR,
57+
"SENSOR",
58+
"Orientation is determined by a physical orientation sensor: " +
59+
"the display will rotate based on how the user moves the device. " +
60+
"Ignores user's setting to turn off sensor-based rotation.",
61+
),
62+
SCREEN_ORIENTATION_NOSENSOR(
63+
"NOSENSOR",
64+
ActivityInfo.SCREEN_ORIENTATION_NOSENSOR,
65+
"NOSENSOR",
66+
"Always ignore orientation determined by orientation sensor: " +
67+
"the display will not rotate when the user moves the device.",
68+
),
69+
SCREEN_ORIENTATION_SENSOR_LANDSCAPE(
70+
"SENSOR_LANDSCAPE",
71+
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
72+
"SENSOR_LANDSCAPE",
73+
"Would like to have the screen in landscape orientation, " +
74+
"but can use the sensor to change which direction the screen is facing. ",
75+
),
76+
SCREEN_ORIENTATION_SENSOR_PORTRAIT(
77+
"SENSOR_PORTRAIT",
78+
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
79+
"SENSOR_PORTRAIT",
80+
"Would like to have the screen in portrait orientation, " +
81+
"but can use the sensor to change which direction the screen is facing.",
82+
),
83+
SCREEN_ORIENTATION_REVERSE_LANDSCAPE(
84+
"REVERSE_LANDSCAPE",
85+
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
86+
"REVERSE_LANDSCAPE",
87+
"Would like to have the screen in landscape orientation, " +
88+
"turned in the opposite direction from normal landscape.",
89+
),
90+
SCREEN_ORIENTATION_REVERSE_PORTRAIT(
91+
"REVERSE_PORTRAIT",
92+
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
93+
"REVERSE_PORTRAIT",
94+
"Would like to have the screen in portrait orientation, " +
95+
"turned in the opposite direction from normal portrait.",
96+
),
97+
SCREEN_ORIENTATION_FULL_SENSOR(
98+
"FULL_SENSOR",
99+
ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR,
100+
"FULL_SENSOR",
101+
"Orientation is determined by a physical orientation sensor: " +
102+
"the display will rotate based on how the user moves the device. " +
103+
"This allows any of the 4 possible rotations, " +
104+
"regardless of what the device will normally do " +
105+
"(for example some devices won't normally use 180 degree rotation).",
106+
),
107+
SCREEN_ORIENTATION_USER_LANDSCAPE(
108+
"USER_LANDSCAPE",
109+
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE,
110+
"USER_LANDSCAPE",
111+
"Would like to have the screen in landscape orientation, " +
112+
"but if the user has enabled sensor-based rotation " +
113+
"then we can use the sensor to change which direction the screen is facing.",
114+
),
115+
SCREEN_ORIENTATION_USER_PORTRAIT(
116+
"USER_PORTRAIT",
117+
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT,
118+
"USER_PORTRAIT",
119+
"Would like to have the screen in portrait orientation, " +
120+
"but if the user has enabled sensor-based rotation " +
121+
"then we can use the sensor to change which direction the screen is facing.",
122+
),
123+
SCREEN_ORIENTATION_FULL_USER(
124+
"FULL_USER",
125+
ActivityInfo.SCREEN_ORIENTATION_FULL_USER,
126+
"FULL_USER",
127+
"Respect the user's sensor-based rotation preference, " +
128+
"but if sensor-based rotation is enabled " +
129+
"then allow the screen to rotate in all 4 possible directions " +
130+
"regardless of what the device will normally do " +
131+
"(for example some devices won't normally use 180 degree rotation).",
132+
),
133+
SCREEN_ORIENTATION_LOCKED(
134+
"LOCKED",
135+
ActivityInfo.SCREEN_ORIENTATION_LOCKED,
136+
"LOCKED",
137+
"Screen is locked to its current rotation, whatever that is.",
138+
),
139+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.programminghoch10.RotationControl
2+
3+
import android.annotation.SuppressLint
4+
import android.content.Context
5+
import androidx.preference.CheckBoxPreference
6+
7+
@SuppressLint("PrivateResource")
8+
class RadioPreference(context: Context) : CheckBoxPreference(context) {
9+
10+
init {
11+
widgetLayoutResource = R.layout.radio
12+
}
13+
14+
override fun setChecked(checked: Boolean) {
15+
if (isChecked) return
16+
super.setChecked(checked)
17+
}
18+
19+
fun onRadioPreferenceSelected(radioPreference: RadioPreference) {
20+
if (radioPreference == this) return
21+
super.setChecked(false)
22+
}
23+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.programminghoch10.RotationControl
2+
3+
import android.annotation.SuppressLint
4+
import android.os.Bundle
5+
import androidx.fragment.app.FragmentActivity
6+
import androidx.preference.Preference
7+
import androidx.preference.PreferenceCategory
8+
import androidx.preference.PreferenceFragmentCompat
9+
10+
val ROTATION_MODE_DEFAULT = ROTATION_MODE.SCREEN_ORIENTATION_FULL_SENSOR
11+
const val SHARED_PREFERENCES_NAME = "rotation_mode"
12+
13+
class SettingsActivity : FragmentActivity() {
14+
15+
override fun onCreate(savedInstanceState: Bundle?) {
16+
super.onCreate(savedInstanceState)
17+
setContentView(R.layout.settings_activity)
18+
if (savedInstanceState == null) {
19+
supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment()).commit()
20+
}
21+
actionBar?.setDisplayHomeAsUpEnabled(true)
22+
}
23+
24+
override fun onNavigateUp(): Boolean {
25+
finishAndRemoveTask()
26+
return true
27+
}
28+
29+
class SettingsFragment : PreferenceFragmentCompat() {
30+
val radioPreferences: MutableList<RadioPreference> = mutableListOf()
31+
32+
@SuppressLint("WorldReadableFiles")
33+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
34+
setPreferencesFromResource(R.xml.root_preferences, rootKey)
35+
preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME
36+
preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE
37+
val preferenceCategory = findPreference<PreferenceCategory>("category_rotation_mode")!!
38+
val context = requireContext()
39+
40+
for (rotationMode in ROTATION_MODE.entries) {
41+
val preference = RadioPreference(context)
42+
preference.key = rotationMode.key
43+
preference.title = rotationMode.title
44+
preference.summary = rotationMode.summary
45+
preference.setDefaultValue(rotationMode == ROTATION_MODE_DEFAULT)
46+
radioPreferences.add(preference)
47+
preference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
48+
radioPreferences.forEach { it.onRadioPreferenceSelected(preference as RadioPreference) }
49+
true
50+
}
51+
preferenceCategory.addPreference(preference)
52+
}
53+
}
54+
}
55+
}

RotationControl/src/main/java/com/programminghoch10/RotationControl/XposedHook.java

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.programminghoch10.RotationControl
2+
3+
import android.app.Activity
4+
import android.content.ContextWrapper
5+
import android.content.SharedPreferences
6+
import android.content.pm.ActivityInfo
7+
import android.view.Window
8+
import de.robv.android.xposed.IXposedHookLoadPackage
9+
import de.robv.android.xposed.XC_MethodHook
10+
import de.robv.android.xposed.XSharedPreferences
11+
import de.robv.android.xposed.XposedHelpers
12+
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam
13+
14+
class XposedHook : IXposedHookLoadPackage {
15+
override fun handleLoadPackage(lpparam: LoadPackageParam) {
16+
if (lpparam.packageName == BuildConfig.APPLICATION_ID) return
17+
18+
XposedHelpers.findAndHookMethod(
19+
Activity::class.java,
20+
"setRequestedOrientation",
21+
Int::class.java,
22+
object : XC_MethodHook() {
23+
override fun beforeHookedMethod(param: MethodHookParam) {
24+
val sharedPreferences: SharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
25+
val selectedRotationMode = ROTATION_MODE.entries.find { sharedPreferences.getBoolean(it.key, false) } ?: ROTATION_MODE_DEFAULT
26+
if (selectedRotationMode == ROTATION_MODE.SCREEN_ORIENTATION_UNSET) return
27+
param.args[0] = selectedRotationMode.value
28+
}
29+
},
30+
)
31+
32+
XposedHelpers.findAndHookMethod(
33+
"com.android.internal.policy.PhoneWindow",
34+
lpparam.classLoader,
35+
"generateLayout",
36+
"com.android.internal.policy.DecorView",
37+
object : XC_MethodHook() {
38+
override fun beforeHookedMethod(param: MethodHookParam) {
39+
var context = (param.thisObject as Window).context
40+
while (context is ContextWrapper) {
41+
if (context is Activity) {
42+
// we only need to call setRequestedOrientation
43+
// the value doesn't matter, since the hook above replaces it
44+
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
45+
return
46+
}
47+
context = context.baseContext
48+
}
49+
}
50+
},
51+
)
52+
}
53+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:id="@android:id/checkbox"
4+
style="@style/Widget.AppCompat.CompoundButton.RadioButton"
5+
android:background="@null"
6+
android:layout_width="wrap_content"
7+
android:layout_height="wrap_content"
8+
android:clickable="false"
9+
android:focusable="false" />
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="match_parent"
5+
style="@style/AppTheme.Edge2EdgeFix">
6+
7+
<FrameLayout
8+
android:id="@+id/settings"
9+
android:layout_width="match_parent"
10+
android:layout_height="match_parent" />
11+
</LinearLayout>

0 commit comments

Comments
 (0)