Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidBerdik committed Aug 26, 2022
0 parents commit b5238f5
Show file tree
Hide file tree
Showing 37 changed files with 1,516 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
*.apk
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
.idea
/app/build
local.properties
/release-stuff
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Let Me Downgrade
<img align="left" src="play-store-images/ic_launcher-playstore.png" width="140" />

Android's app installation system does not allow users to downgrade to an older version of an app when they already have a newer version installed. On Android 5 through 12, the [XDowngrader](https://github.com/Xposed-Modules-Repo/com.alex193a.xdowngrader) Xposed module could be used on rooted devices to bypass this limitation. As of Android 13, however, XDowngrader no longer works. Let Me Downgrade is an Xposed module that replicates this functionality for Android 13, and as an added bonus, provides a Quick Settings tile to easily enable and disable the downgrade block.

**⚠️ WARNING:** Let Me Downgrade is intended for rooted devices running Android 13 and requires Xposed. The recommended Xposed variant to use is LSPosed. Other Xposed variants may work, but have not been tested. Additionally, this module cannot be guaranteed to work on all devices. In the worst case, it can cause a bootloop. Use at your own risk.

<p align="center">
<a href="https://play.google.com/store/apps/details?id=com.berdik.letmedowngrade">
<img src="play-store-images/google-play-badge.png" height="80" />
</a>
<a href="https://github.com/DavidBerdik/Let-Me-Downgrade/releases">
<img src="play-store-images/badge_github.png" height="80" />
</a>
</p>

To use Let Me Downgrade:
1. Install LSposed. This requires your device to be rooted with Magisk. Installation instructions for LSPosed are available [here](https://github.com/LSPosed/LSPosed#install).
2. Install Let Me Downgrade.
3. Activate the Let Me Downgrade module in the LSposed user interface.

<p align="center">
<img src="play-store-images/screenshots/1.png" width="300" />
<img src="play-store-images/screenshots/2.png" width="300" />
</p>

4. Reboot your device and sign in.

<p align="center">
<img src="play-store-images/screenshots/3.png" width="300" />
</p>

5. Open the quick settings panel. The Let Me Downgrade tile will appear.

<p align="center">
<img src="play-store-images/screenshots/4.png" width="300" />
</p>

6. Toggle the Let Me Downgrade tile on or off to enable or disable it.
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
40 changes: 40 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}

android {
compileSdk 33

defaultConfig {
applicationId "com.berdik.letmedowngrade"
minSdk 33
targetSdk 33
versionCode 1
versionName "1.0.0"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '11'
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.github.kyuubiran:EzXHelper:1.0.1'
implementation 'com.github.kyuubiran:EzXHelper:1.0.1:sources'
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
33 changes: 33 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.berdik.letmedowngrade">

<application
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="@string/xposed_description" />
<meta-data
android:name="xposedminversion"
android:value="93" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />

<service android:name="com.berdik.letmedowngrade.QuickTile"
android:icon="@drawable/ic_baseline_arrow_downward_24"
android:label="@string/tile_label"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
</application>
</manifest>
1 change: 1 addition & 0 deletions app/src/main/assets/xposed_init
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.berdik.letmedowngrade.LetMeDowngrade
Binary file added app/src/main/ic_launcher-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions app/src/main/java/com/berdik/letmedowngrade/LetMeDowngrade.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.berdik.letmedowngrade

import com.berdik.letmedowngrade.hookers.SystemUIHooker
import com.berdik.letmedowngrade.hookers.PackageManagerServiceHooker
import com.github.kyuubiran.ezxhelper.init.EzXHelperInit
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage

class LetMeDowngrade : IXposedHookZygoteInit, IXposedHookLoadPackage {
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
EzXHelperInit.initZygote(startupParam)
}

override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
if (lpparam != null) {
EzXHelperInit.initHandleLoadPackage(lpparam)
EzXHelperInit.setLogTag("Let Me Downgrade")
EzXHelperInit.setToastTag("Let Me Downgrade")

when (lpparam.packageName) {
"android" -> {
try {
PackageManagerServiceHooker.hook(lpparam)
} catch (e: Exception) {
XposedBridge.log("[Let Me Downgrade] ERROR: $e")
}
}
}

when (lpparam.packageName) {
"com.android.systemui" -> {
val prefs = XSharedPreferences(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID)
if (!prefs.getBoolean("tileRevealDone", false)) {
try {
XposedBridge.log("[Let Me Downgrade] Hooking System UI to add and reveal quick settings tile.")
SystemUIHooker.hook(lpparam)
} catch (e: Exception) {
XposedBridge.log("[Let Me Downgrade] ERROR: $e")
}
}
}
}
}
}
}
44 changes: 44 additions & 0 deletions app/src/main/java/com/berdik/letmedowngrade/PrefManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.berdik.letmedowngrade

import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences

class PrefManager {
companion object {
private var prefs: SharedPreferences? = null
private var hookActive: Boolean? = null

// Since we are an Xposed module, we don't care about MODE_WORLD_READABLE being deprecated.
// In fact, we need to use it despite being deprecated because without it, the Xposed
// hooking mechanism cannot access the preference value.
@SuppressLint("WorldReadableFiles")
@Suppress("DEPRECATION")
fun loadPrefs(context: Context) {
if (prefs == null) {
prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_WORLD_READABLE)
markTileRevealAsDone()
}
}

fun isHookOn(): Boolean {
if (hookActive == null) {
hookActive = prefs!!.getBoolean("hookActive", false)
}
return hookActive as Boolean
}

fun toggleHookState() {
hookActive = !isHookOn()
val prefEdit = prefs!!.edit()
prefEdit.putBoolean("hookActive", hookActive!!)
prefEdit.apply()
}

private fun markTileRevealAsDone() {
val prefEdit = prefs!!.edit()
prefEdit.putBoolean("tileRevealDone", true)
prefEdit.apply()
}
}
}
26 changes: 26 additions & 0 deletions app/src/main/java/com/berdik/letmedowngrade/QuickTile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.berdik.letmedowngrade

import android.service.quicksettings.Tile
import android.service.quicksettings.TileService

class QuickTile: TileService() {
override fun onStartListening() {
super.onStartListening()
PrefManager.loadPrefs(applicationContext)
setButtonState()
}

override fun onClick() {
super.onClick()
PrefManager.toggleHookState()
setButtonState()
}

private fun setButtonState() {
if (PrefManager.isHookOn())
qsTile.state = Tile.STATE_ACTIVE
else
qsTile.state = Tile.STATE_INACTIVE
qsTile.updateTile()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.berdik.letmedowngrade.hookers

import android.annotation.SuppressLint
import com.berdik.letmedowngrade.BuildConfig
import com.github.kyuubiran.ezxhelper.utils.*
import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage

class PackageManagerServiceHooker {
companion object {
@SuppressLint("PrivateApi")
fun hook(lpparam: XC_LoadPackage.LoadPackageParam) {
findAllMethods(lpparam.classLoader.loadClass("com.android.server.pm.PackageManagerServiceUtils")) {
name == "checkDowngrade"
}.hookMethod {
var packageName = ""
var isHookActive = false

before { param ->
// Get the package name of the app being processed.
packageName = XposedHelpers.getObjectField(param.args[1], "packageName") as String

// Get the active state of the hook.
val prefs = XSharedPreferences(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID)
isHookActive = prefs.getBoolean("hookActive", false)

// If the hook is active, log a block of the downgrade check and bypass the real function.
if (isHookActive) {
XposedBridge.log("[Let Me Downgrade] Blocked downgrade check on package: $packageName")
param.result = null
}
}

after {
// If the hook is not active, make a log entry after the real function executes indicating so.
if (!isHookActive) {
XposedBridge.log("[Let Me Downgrade] Allowed downgrade check on package: $packageName")
}
}
}
}
}
}
Loading

0 comments on commit b5238f5

Please sign in to comment.