Skip to content

Commit ea2785c

Browse files
add multiple snapping mods
1 parent cedfcb1 commit ea2785c

File tree

15 files changed

+543
-44
lines changed

15 files changed

+543
-44
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
com.programminghoch10.SplitScreenMods.AdditionalSnapTargetsHook
12
com.programminghoch10.SplitScreenMods.AlwaysAllowMultiInstanceSplitHook
3+
com.programminghoch10.SplitScreenMods.CalculateRatiosHook
4+
com.programminghoch10.SplitScreenMods.CustomFixedRatioHook
25
com.programminghoch10.SplitScreenMods.FreeSnapHook
36
com.programminghoch10.SplitScreenMods.KeepSplitScreenRatioHook
7+
com.programminghoch10.SplitScreenMods.RemoveMinimalTaskSizeHook
8+
com.programminghoch10.SplitScreenMods.SnapModeHook
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.programminghoch10.SplitScreenMods
2+
3+
import android.content.res.Resources
4+
import android.graphics.Rect
5+
import android.os.Build
6+
import com.programminghoch10.SplitScreenMods.AdditionalSnapTargetsHookConfig.enabled
7+
import com.programminghoch10.SplitScreenMods.BuildConfig.SHARED_PREFERENCES_NAME
8+
import de.binarynoise.logger.Logger.log
9+
import de.robv.android.xposed.IXposedHookInitPackageResources
10+
import de.robv.android.xposed.IXposedHookLoadPackage
11+
import de.robv.android.xposed.XC_MethodHook
12+
import de.robv.android.xposed.XSharedPreferences
13+
import de.robv.android.xposed.XposedBridge
14+
import de.robv.android.xposed.XposedHelpers
15+
import de.robv.android.xposed.callbacks.XC_InitPackageResources
16+
import de.robv.android.xposed.callbacks.XC_LoadPackage
17+
18+
// https://github.com/LineageOS/android_frameworks_base/blob/f0964915f5992fd38cf1e5e3f87c0d3ac719aa09/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
19+
const val SNAP_TO_2_33_66 = 0
20+
const val SNAP_TO_2_50_50 = 1
21+
const val SNAP_TO_2_66_33 = 2
22+
const val SNAP_TO_NONE = 10
23+
const val SNAP_TO_START_AND_DISMISS = 11
24+
const val SNAP_TO_END_AND_DISMISS = 12
25+
26+
object AdditionalSnapTargetsHookConfig {
27+
val enabled = SnapModeHookConfig.enabled && CustomFixedRatioHookConfig.enabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA
28+
}
29+
30+
class AdditionalSnapTargetsHook : IXposedHookLoadPackage, IXposedHookInitPackageResources {
31+
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
32+
if (lpparam.packageName != "com.android.systemui") return
33+
if (!enabled) return
34+
log("handleLoadPackage(${lpparam.packageName} in process ${lpparam.processName})")
35+
val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
36+
val selectedSnapTargetsString = preferences.getString("SnapTargets", "SYSTEM")!!
37+
if (selectedSnapTargetsString == "SYSTEM" || selectedSnapTargetsString == "CUSTOM") return
38+
val selectedSnapTargets = selectedSnapTargetsString.split(",").map { it.toFloat() }.sorted().drop(1)
39+
log("additional snap targets are ${selectedSnapTargets.joinToString()}")
40+
41+
if (selectedSnapTargets.isEmpty()) return // handled by resource hook below
42+
43+
val DividerSnapAlgorithmClass = XposedHelpers.findClass("com.android.wm.shell.common.split.DividerSnapAlgorithm", lpparam.classLoader)
44+
val SnapTargetClass = XposedHelpers.findClass(DividerSnapAlgorithmClass.name + "\$SnapTarget", lpparam.classLoader)
45+
val SnapTargetClassConstructor = XposedHelpers.findConstructorExact(SnapTargetClass, Int::class.java, Int::class.java)
46+
47+
XposedBridge.hookAllConstructors(DividerSnapAlgorithmClass, object : XC_MethodHook() {
48+
override fun afterHookedMethod(param: MethodHookParam) {
49+
log("after ${DividerSnapAlgorithmClass.simpleName} constructor")
50+
val res = param.args[0] as Resources
51+
val mTargets = XposedHelpers.getObjectField(param.thisObject, "mTargets") as ArrayList<Any?>
52+
if (mTargets.isEmpty()) return
53+
54+
// reimplementation of inlined methods getStartInset() and getEndInset()
55+
val mIsLeftRightSplit = XposedHelpers.getBooleanField(param.thisObject, "mIsLeftRightSplit")
56+
val mInsets = XposedHelpers.getObjectField(param.thisObject, "mInsets") as Rect
57+
val startInset = if (mIsLeftRightSplit) mInsets.left else mInsets.top
58+
val endInset = if (mIsLeftRightSplit) mInsets.right else mInsets.bottom
59+
60+
// basically reimplemented addFixedDivisionTargets
61+
val mDisplayWidth = XposedHelpers.getIntField(param.thisObject, "mDisplayWidth")
62+
val mDisplayHeight = XposedHelpers.getIntField(param.thisObject, "mDisplayHeight")
63+
val start = startInset
64+
val end = if (mIsLeftRightSplit) mDisplayWidth - endInset else mDisplayHeight - endInset
65+
val mDividerSize = XposedHelpers.getIntField(param.thisObject, "mDividerSize")
66+
val mCalculateRatiosBasedOnAvailableSpace = getCalculateRatiosBasedOnAvailableSpace(res)
67+
log("mCalculateRatiosBasedOnAvailableSpace=$mCalculateRatiosBasedOnAvailableSpace")
68+
val mMinimalSizeResizableTask = XposedHelpers.getIntField(param.thisObject, "mMinimalSizeResizableTask")
69+
log("mMinimalSizeResizableTask=$mMinimalSizeResizableTask")
70+
fun getSize(ratio: Float): Int {
71+
var size = (ratio * (end - start)).toInt() - mDividerSize / 2
72+
if (mCalculateRatiosBasedOnAvailableSpace) size = Math.max(size, mMinimalSizeResizableTask)
73+
return size
74+
}
75+
76+
for (snapTargetRatio in selectedSnapTargets) {
77+
val size = getSize(snapTargetRatio)
78+
val startPosition = start + size
79+
val endPosition = end - size
80+
81+
// slightly changed reimplementation of inlined method maybeAddTarget()
82+
fun maybeAddTarget(position: Int, size: Int, snapPosition: Int?) {
83+
if (size < mMinimalSizeResizableTask) {
84+
log("Cannot add snap target ${snapTargetRatio} because it's size of ${size} would be less than minimal size ${mMinimalSizeResizableTask}!")
85+
return
86+
}
87+
log("adding snap target ${snapTargetRatio} at $position of size $size")
88+
val snapTarget = SnapTargetClassConstructor.newInstance(position, snapPosition ?: SNAP_TO_NONE)
89+
mTargets.add(snapTarget)
90+
}
91+
92+
maybeAddTarget(startPosition, startPosition - startInset, SNAP_TO_2_33_66)
93+
maybeAddTarget(endPosition, end - endPosition, SNAP_TO_2_66_33)
94+
}
95+
96+
log("final mTargets[${mTargets.size}]: ${mTargets.joinToString()}")
97+
}
98+
})
99+
}
100+
101+
fun getCalculateRatiosBasedOnAvailableSpace(res: Resources): Boolean {
102+
val mCalculateRatiosBasedOnAvailableSpaceId = res.getIdentifier("config_flexibleSplitRatios", "bool", "android")
103+
return res.getBoolean(mCalculateRatiosBasedOnAvailableSpaceId)
104+
}
105+
106+
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
107+
if (resparam.packageName != "com.android.systemui") return
108+
if (!enabled) return
109+
log("handleInitPackageResources(${resparam.packageName})")
110+
val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
111+
val selectedSnapTargetsString = preferences.getString("SnapTargets", "SYSTEM")!!
112+
if (selectedSnapTargetsString == "SYSTEM" || selectedSnapTargetsString == "CUSTOM") return
113+
val selectedSnapTargets = selectedSnapTargetsString.split(",")
114+
if (selectedSnapTargets.isEmpty()) return
115+
val customRatio = selectedSnapTargets.map { it.toFloat() }.minOf { it }
116+
log("min split ratio is ${customRatio}")
117+
if (customRatio < 0 || customRatio >= 0.5f) return
118+
CustomFixedRatioHook.overrideFixedRatio(resparam, customRatio)
119+
}
120+
}

SplitScreenMods/src/main/java/com/programminghoch10/SplitScreenMods/AlwaysAllowMultiInstanceSplitHook.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,8 @@ class AlwaysAllowMultiInstanceSplitHook : IXposedHookLoadPackage {
6363
}
6464
"android" -> {
6565
try {
66-
val ActivityStarterClass =
67-
Class.forName("com.android.server.wm.ActivityStarter", false, lpparam.classLoader)
68-
val ActivityStarterRequestClass =
69-
Class.forName(ActivityStarterClass.name + "\$Request", false, lpparam.classLoader)
66+
val ActivityStarterClass = Class.forName("com.android.server.wm.ActivityStarter", false, lpparam.classLoader)
67+
val ActivityStarterRequestClass = Class.forName(ActivityStarterClass.name + "\$Request", false, lpparam.classLoader)
7068
XposedHelpers.findAndHookMethod(
7169
ActivityStarterClass,
7270
"executeRequest",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.programminghoch10.SplitScreenMods
2+
3+
import com.programminghoch10.SplitScreenMods.BuildConfig.SHARED_PREFERENCES_NAME
4+
import com.programminghoch10.SplitScreenMods.SnapModeHookConfig.enabled
5+
import de.binarynoise.logger.Logger.log
6+
import de.robv.android.xposed.IXposedHookInitPackageResources
7+
import de.robv.android.xposed.XSharedPreferences
8+
import de.robv.android.xposed.callbacks.XC_InitPackageResources
9+
10+
object CalculateRatiosHookConfig {
11+
val enabled = SnapModeHookConfig.enabled
12+
}
13+
14+
class CalculateRatiosHook : IXposedHookInitPackageResources {
15+
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
16+
if (resparam.packageName != "com.android.systemui") return
17+
if (!enabled) return
18+
log("handleInitPackageResources(${resparam.packageName})")
19+
val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
20+
if (!preferences.contains("CalculateRatios")) return
21+
22+
val enabled = preferences.getBoolean("CalculateRatios", false)
23+
log("set config_flexibleSplitRatios to $enabled")
24+
resparam.res.setReplacement("android", "bool", "config_flexibleSplitRatios", enabled)
25+
}
26+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.programminghoch10.SplitScreenMods
2+
3+
import android.content.res.AssetManager
4+
import android.content.res.Configuration
5+
import android.content.res.Resources
6+
import android.content.res.XResForwarder
7+
import android.util.DisplayMetrics
8+
import com.programminghoch10.SplitScreenMods.BuildConfig.SHARED_PREFERENCES_NAME
9+
import com.programminghoch10.SplitScreenMods.SnapModeHookConfig.enabled
10+
import de.binarynoise.logger.Logger.log
11+
import de.robv.android.xposed.IXposedHookInitPackageResources
12+
import de.robv.android.xposed.XSharedPreferences
13+
import de.robv.android.xposed.XposedHelpers
14+
import de.robv.android.xposed.callbacks.XC_InitPackageResources
15+
16+
object CustomFixedRatioHookConfig {
17+
val enabled = SnapModeHookConfig.enabled
18+
}
19+
20+
class CustomFixedRatioHook : IXposedHookInitPackageResources {
21+
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
22+
if (resparam.packageName != "com.android.systemui") return
23+
if (!enabled) return
24+
log("handleInitPackageResources(${resparam.packageName})")
25+
val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
26+
if (preferences.getString("SnapMode", "SYSTEM") != "FIXED") return
27+
val selectedSnapTargetsString = preferences.getString("SnapTargets", "SYSTEM")!!
28+
if (selectedSnapTargetsString != "CUSTOM") return
29+
if (!preferences.contains("CustomRatio")) return
30+
val customFixedRatio = preferences.getInt("CustomRatio", 33) / 100f
31+
log("overriding custom fixed ratio ${customFixedRatio}")
32+
overrideFixedRatio(resparam, customFixedRatio)
33+
}
34+
35+
companion object {
36+
fun overrideFixedRatio(resparam: XC_InitPackageResources.InitPackageResourcesParam, customFixedRatio: Float) {
37+
log("overriding fixed ratio with $customFixedRatio")
38+
resparam.res.setReplacement("android", "fraction", "docked_stack_divider_fixed_ratio", FractionReplacement(customFixedRatio))
39+
}
40+
41+
fun FractionReplacement(fraction: Float): XResForwarder {
42+
val assetManager = XposedHelpers.getStaticObjectField(AssetManager::class.java, "sSystem") as AssetManager
43+
val res = object : Resources(assetManager, DisplayMetrics(), Configuration()) {
44+
override fun getFraction(id: Int, base: Int, pbase: Int): Float {
45+
return fraction
46+
}
47+
}
48+
return XResForwarder(res, 0)
49+
}
50+
}
51+
}

SplitScreenMods/src/main/java/com/programminghoch10/SplitScreenMods/FreeSnapHook.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ class FreeSnapHook : IXposedHookLoadPackage {
2525
val enabled = preferences.getBoolean("FreeSnap", false)
2626
if (!enabled) return
2727

28-
val DividerSnapAlgorithmClass =
29-
XposedHelpers.findClass("com.android.wm.shell.common.split.DividerSnapAlgorithm", lpparam.classLoader)
28+
val DividerSnapAlgorithmClass = XposedHelpers.findClass("com.android.wm.shell.common.split.DividerSnapAlgorithm", lpparam.classLoader)
3029
XposedBridge.hookAllConstructors(DividerSnapAlgorithmClass, object : XC_MethodHook() {
3130
override fun afterHookedMethod(param: MethodHookParam) {
3231
XposedHelpers.setBooleanField(param.thisObject, "mFreeSnapMode", true)

SplitScreenMods/src/main/java/com/programminghoch10/SplitScreenMods/KeepSplitScreenRatioHook.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ class KeepSplitScreenRatioHook : IXposedHookLoadPackage {
2929
val enabled = preferences.getBoolean("KeepSplitScreenRatio", false)
3030
if (!enabled) return
3131

32-
val SplitLayoutClass =
33-
XposedHelpers.findClass("com.android.wm.shell.common.split.SplitLayout", lpparam.classLoader)
34-
val StageCoordinatorClass =
35-
XposedHelpers.findClass("com.android.wm.shell.splitscreen.StageCoordinator", lpparam.classLoader)
32+
val SplitLayoutClass = XposedHelpers.findClass("com.android.wm.shell.common.split.SplitLayout", lpparam.classLoader)
33+
val StageCoordinatorClass = XposedHelpers.findClass("com.android.wm.shell.splitscreen.StageCoordinator", lpparam.classLoader)
3634

3735
/*
3836
Currently this module disables SplitScreen enter and dismiss animations completely.
@@ -100,8 +98,7 @@ class KeepSplitScreenRatioHook : IXposedHookLoadPackage {
10098
val binder = param.args[0] as IBinder
10199
val stageCoordinator = param.thisObject
102100
val mSplitTransitions = XposedHelpers.getObjectField(stageCoordinator, "mSplitTransitions")
103-
val isPendingEnter =
104-
XposedHelpers.callMethod(mSplitTransitions, "isPendingEnter", binder) as Boolean
101+
val isPendingEnter = XposedHelpers.callMethod(mSplitTransitions, "isPendingEnter", binder) as Boolean
105102
log("${startPendingAnimationMethod.name}: isPendingEnter=$isPendingEnter")
106103
if (!isPendingEnter) return
107104
val mPendingEnter = XposedHelpers.getObjectField(mSplitTransitions, "mPendingEnter")
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.programminghoch10.SplitScreenMods
2+
3+
import android.content.res.XResources
4+
import android.os.Build
5+
import android.util.TypedValue
6+
import com.programminghoch10.SplitScreenMods.BuildConfig.SHARED_PREFERENCES_NAME
7+
import com.programminghoch10.SplitScreenMods.RemoveMinimalTaskSizeHookConfig.enabled
8+
import de.binarynoise.logger.Logger.log
9+
import de.robv.android.xposed.IXposedHookInitPackageResources
10+
import de.robv.android.xposed.XSharedPreferences
11+
import de.robv.android.xposed.callbacks.XC_InitPackageResources
12+
13+
object RemoveMinimalTaskSizeHookConfig {
14+
@JvmField
15+
val enabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA
16+
}
17+
18+
class RemoveMinimalTaskSizeHook : IXposedHookInitPackageResources {
19+
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
20+
if (resparam.packageName != "com.android.systemui") return
21+
if (!enabled) return
22+
log("handleInitPackageResources(${resparam.packageName})")
23+
val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
24+
val enabled = preferences.getBoolean("RemoveMinimalTaskSize", false)
25+
if (!enabled) return
26+
27+
resparam.res.setReplacement(
28+
"android",
29+
"dimen",
30+
"default_minimal_size_resizable_task",
31+
object : XResources.DimensionReplacement(0f, TypedValue.COMPLEX_UNIT_DIP) {},
32+
)
33+
log("set replacement for default_minimal_size_resizable_task")
34+
}
35+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.programminghoch10.SplitScreenMods
2+
3+
// yoinked from https://github.com/LineageOS/android_frameworks_base/blob/2dc97cf3d6234c87497ca78b2734bb3ed604c349/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
4+
5+
enum class SNAP_MODE(val key: String, val value: Int) {
6+
7+
SNAP_MODE_UNCHANGED(
8+
"SYSTEM",
9+
-1,
10+
),
11+
12+
SNAP_MODE_16_9(
13+
"16_9",
14+
0,
15+
),
16+
17+
SNAP_FIXED_RATIO(
18+
"FIXED",
19+
1,
20+
),
21+
22+
SNAP_ONLY_1_1(
23+
"1_1",
24+
2,
25+
),
26+
27+
/*
28+
While this target does work,
29+
it is clearly intended for "select a second task to split" mode,
30+
where only one task's icon is shown as a preview for split selection.
31+
*/
32+
SNAP_MODE_MINIMIZED(
33+
"MINIMIZED",
34+
3,
35+
),
36+
37+
/*
38+
This target provides the offscreen ratios,
39+
but the required functionality seems to be stripped out on current builds.
40+
The fallback ratio is 33%, which can be obtained with the SNAP_FIXED_RATIO as well.
41+
*/
42+
SNAP_FLEXIBLE_SPLIT(
43+
"FLEXIBLE",
44+
4,
45+
),
46+
}

0 commit comments

Comments
 (0)