Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion RotationControl/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.buildlogic.android.application)
alias(libs.plugins.kotlin.android)
}

android {
Expand All @@ -9,6 +10,11 @@ android {
defaultConfig {
applicationId = packageName
minSdk = 24
targetSdk = 33
targetSdk = 36
}
}

dependencies {
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.preference.ktx)
}
19 changes: 16 additions & 3 deletions RotationControl/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">

<application android:label="RotationControl">
<application android:label="@string/app_name">
<activity
android:name=".SettingsActivity"
android:exported="true"
android:label="@string/title_activity_settings"
android:theme="@style/AppTheme"
android:excludeFromRecents="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
</intent-filter>
</activity>

<meta-data
android:name="xposedmodule"
android:value="true"
/>
<meta-data
android:name="xposeddescription"
android:value="Force rotation for selected packages"
android:value="@string/description"
/>
<meta-data
android:name="xposedminversion"
android:value="82"
android:value="93"
/>
<meta-data
android:name="xposedscope"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.programminghoch10.RotationControl

import android.app.Application
import android.content.pm.ActivityInfo

val context = run {
val applicationContext = Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null) as Application?
if (applicationContext?.packageName == BuildConfig.APPLICATION_ID) return@run applicationContext.resources
return@run null
}

private fun getString(id: Int): String {
if (context == null) return ""
return context.getString(id)
}

enum class ROTATION_MODE(val key: String, val value: Int, val title: String, val summary: String) {
// yoinked from https://developer.android.com/reference/android/R.attr.html#screenOrientation

SCREEN_ORIENTATION_UNSET(
"UNSET",
-2,
getString(R.string.screen_orientation_unset_title),
getString(R.string.screen_orientation_unset_summary),
),
SCREEN_ORIENTATION_UNSPECIFIED(
"UNSPECIFIED",
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
getString(R.string.screen_orientation_unspecified_title),
getString(R.string.screen_orientation_unspecified_summary),
),
SCREEN_ORIENTATION_LANDSCAPE(
"LANDSCAPE",
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
getString(R.string.screen_orientation_landscape_title),
getString(R.string.screen_orientation_landscape_summary),
),
SCREEN_ORIENTATION_PORTRAIT(
"PORTRAIT",
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
getString(R.string.screen_orientation_portrait_title),
getString(R.string.screen_orientation_portrait_summary),
),
SCREEN_ORIENTATION_USER(
"USER",
ActivityInfo.SCREEN_ORIENTATION_USER,
getString(R.string.screen_orientation_user_title),
getString(R.string.screen_orientation_user_summary),
),
SCREEN_ORIENTATION_BEHIND(
"BEHIND",
ActivityInfo.SCREEN_ORIENTATION_BEHIND,
getString(R.string.screen_orientation_behind_title),
getString(R.string.screen_orientation_behind_summary),
),
SCREEN_ORIENTATION_SENSOR(
"SENSOR",
ActivityInfo.SCREEN_ORIENTATION_SENSOR,
getString(R.string.screen_orientation_sensor_title),
getString(R.string.screen_orientation_sensor_summary),
),
SCREEN_ORIENTATION_NOSENSOR(
"NOSENSOR",
ActivityInfo.SCREEN_ORIENTATION_NOSENSOR,
getString(R.string.screen_orientation_nosensor_title),
getString(R.string.screen_orientation_nosensor_summary),
),
SCREEN_ORIENTATION_SENSOR_LANDSCAPE(
"SENSOR_LANDSCAPE",
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
getString(R.string.screen_orientation_sensor_landscape_title),
getString(R.string.screen_orientation_sensor_landscape_summary),
),
SCREEN_ORIENTATION_SENSOR_PORTRAIT(
"SENSOR_PORTRAIT",
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
getString(R.string.screen_orientation_sensor_portrait_title),
getString(R.string.screen_orientation_sensor_portrait_summary),
),
SCREEN_ORIENTATION_REVERSE_LANDSCAPE(
"REVERSE_LANDSCAPE",
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
getString(R.string.screen_orientation_reverse_landscape_title),
getString(R.string.screen_orientation_reverse_landscape_summary),
),
SCREEN_ORIENTATION_REVERSE_PORTRAIT(
"REVERSE_PORTRAIT",
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
getString(R.string.screen_orientation_reverse_portrait_title),
getString(R.string.screen_orientation_reverse_portrait_summary),
),
SCREEN_ORIENTATION_FULL_SENSOR(
"FULL_SENSOR",
ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR,
getString(R.string.screen_orientation_full_sensor_title),
getString(R.string.screen_orientation_full_sensor_summary),
),
SCREEN_ORIENTATION_USER_LANDSCAPE(
"USER_LANDSCAPE",
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE,
getString(R.string.screen_orientation_user_landscape),
getString(R.string.screen_orientation_user_landscape_summary),
),
SCREEN_ORIENTATION_USER_PORTRAIT(
"USER_PORTRAIT",
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT,
getString(R.string.screen_orientation_user_portrait_title),
getString(R.string.screen_orientation_user_portrait_summary),
),
SCREEN_ORIENTATION_FULL_USER(
"FULL_USER",
ActivityInfo.SCREEN_ORIENTATION_FULL_USER,
getString(R.string.screen_orientation_full_user_title),
getString(R.string.screen_orientation_full_user_summary),
),
SCREEN_ORIENTATION_LOCKED(
"LOCKED",
ActivityInfo.SCREEN_ORIENTATION_LOCKED,
getString(R.string.screen_orientation_locked_title),
getString(R.string.screen_orientation_locked_summary),
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.programminghoch10.RotationControl

import android.annotation.SuppressLint
import android.content.Context
import androidx.preference.CheckBoxPreference

@SuppressLint("PrivateResource")
class RadioPreference(context: Context) : CheckBoxPreference(context) {

init {
widgetLayoutResource = R.layout.radio
}

override fun setChecked(checked: Boolean) {
if (isChecked) return
super.setChecked(checked)
}

fun onRadioPreferenceSelected(radioPreference: RadioPreference) {
if (radioPreference == this) return
super.setChecked(false)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.programminghoch10.RotationControl

val rewriteLockedOrientation = mapOf(
ROTATION_MODE.SCREEN_ORIENTATION_LANDSCAPE to ROTATION_MODE.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
ROTATION_MODE.SCREEN_ORIENTATION_PORTRAIT to ROTATION_MODE.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
ROTATION_MODE.SCREEN_ORIENTATION_REVERSE_LANDSCAPE to ROTATION_MODE.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
ROTATION_MODE.SCREEN_ORIENTATION_REVERSE_PORTRAIT to ROTATION_MODE.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
)

val rewriteSensorOrientation = mapOf(
ROTATION_MODE.SCREEN_ORIENTATION_SENSOR to ROTATION_MODE.SCREEN_ORIENTATION_FULL_SENSOR,
ROTATION_MODE.SCREEN_ORIENTATION_USER to ROTATION_MODE.SCREEN_ORIENTATION_FULL_USER,
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.programminghoch10.RotationControl

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceGroup
import androidx.preference.children

val ROTATION_MODE_DEFAULT = ROTATION_MODE.SCREEN_ORIENTATION_SENSOR
const val SHARED_PREFERENCES_NAME = "rotation_mode"

class SettingsActivity : FragmentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment()).commit()
}
actionBar?.setDisplayHomeAsUpEnabled(true)
}

override fun onNavigateUp(): Boolean {
finishAndRemoveTask()
return true
}

class SettingsFragment : PreferenceFragmentCompat() {
val radioPreferences: MutableList<RadioPreference> = mutableListOf()

@SuppressLint("WorldReadableFiles")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME
preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE
setPreferencesFromResource(R.xml.root_preferences, rootKey)
val preferenceCategory = findPreference<PreferenceCategory>("category_rotation_mode")!!
val context = requireContext()

for (rotationMode in ROTATION_MODE.entries) {
val preference = RadioPreference(context)
preference.key = rotationMode.key
preference.title = rotationMode.title
preference.summary = rotationMode.summary
preference.setDefaultValue(rotationMode == ROTATION_MODE_DEFAULT)
radioPreferences.add(preference)
preference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
radioPreferences.forEach { it.onRadioPreferenceSelected(preference as RadioPreference) }
true
}
preferenceCategory.addPreference(preference)
if (rotationMode in rewriteLockedOrientation.keys) preference.dependency = "rewrite_locked_orientations"
if (rotationMode in rewriteSensorOrientation.keys) preference.dependency = "rewrite_sensor_orientations"
}

preferenceScreen.setIconSpaceReservedRecursive()
}

private fun Preference.setIconSpaceReservedRecursive(iconSpaceReserved: Boolean = false) {
this.isIconSpaceReserved = iconSpaceReserved
if (this is PreferenceGroup) this.children.forEach { it.setIconSpaceReservedRecursive(iconSpaceReserved) }
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.programminghoch10.RotationControl

import android.app.Activity
import android.content.ContextWrapper
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.view.Window
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam

class XposedHook : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: LoadPackageParam) {
if (lpparam.packageName == BuildConfig.APPLICATION_ID) return

XposedHelpers.findAndHookMethod(
Activity::class.java,
"setRequestedOrientation",
Int::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
val originalRotationMode = ROTATION_MODE.entries.find { it.value == param.args[0] } ?: ROTATION_MODE.SCREEN_ORIENTATION_UNSET
val sharedPreferences: SharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
var selectedRotationMode = ROTATION_MODE.entries.find { sharedPreferences.getBoolean(it.key, false) } ?: ROTATION_MODE_DEFAULT
if (selectedRotationMode == ROTATION_MODE.SCREEN_ORIENTATION_UNSET) selectedRotationMode = originalRotationMode
if (sharedPreferences.getBoolean("rewrite_locked_orientations", false)) {
selectedRotationMode = rewriteLockedOrientation.get(selectedRotationMode) ?: selectedRotationMode
}
if (sharedPreferences.getBoolean("rewrite_sensor_orientations", false)) {
selectedRotationMode = rewriteSensorOrientation.get(selectedRotationMode) ?: selectedRotationMode
}
if (selectedRotationMode == ROTATION_MODE.SCREEN_ORIENTATION_UNSET) return
param.args[0] = selectedRotationMode.value
}
},
)

XposedHelpers.findAndHookMethod(
"com.android.internal.policy.PhoneWindow",
lpparam.classLoader,
"generateLayout",
"com.android.internal.policy.DecorView",
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
var context = (param.thisObject as Window).context
while (context is ContextWrapper) {
if (context is Activity) {
// we only need to call setRequestedOrientation
// the value doesn't matter, since the hook above replaces it
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
return
}
context = context.baseContext
}
}
},
)
}
}
Loading