Skip to content

Commit

Permalink
feat: Shizuku support
Browse files Browse the repository at this point in the history
Users affected by recent Files app patches can now use PF Tool using Shizuku

* added an option to switch between SAF (Scoped storage) and Shizuku file management methods
* app now guides you through all the SAF workarounds and eventually enables Shizuku if none of the workarounds work (Android 11+)
* it is now harder to select a random path for maps folder
* selecting exported maps recommended directory is now more optional
* small improvements
  • Loading branch information
aliernfrog committed Feb 20, 2024
1 parent 443b4c5 commit 6341d1e
Show file tree
Hide file tree
Showing 47 changed files with 1,424 additions and 390 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
> [!WARNING]
> On Android 12 and above, September 2023 update of the Files app prevents other apps from requesting access to files in Android/data.
>
> Check [this page](https://aliernfrog.github.io/android-data-access) for more info, workarounds and the new fix which is currently being implemented in [this pull request](https://github.com/aliernfrog/pf-tool/pull/29).
<div align="center">

<img alt="PF Tool icon" src="images/icon.png" width="120px"/>
Expand All @@ -25,6 +20,13 @@

</div>

## 🦝 Shizuku support
[Shizuku](https://play.google.com/store/apps/details?id=moe.shizuku.privileged.api) is an app which lets other apps elevate their permissions using wireless debugging (or root, if you have one).

Shizuku method in PF Tool can be enabled or disabled anytime from settings.

Shizuku method will automatically be enabled if there is no other way for the app to access Polyfield data. The app will guide you to setup Shizuku if this mode is enabled.

## 🔧 Building
- Clone the repository
- Do your changes
Expand Down
7 changes: 7 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.parcelize")
}

val composeMaterialVersion = "1.6.0"
val composeMaterial3Version = "1.2.0-rc01"
val composeCompilerVersion = "1.5.8"
val shizukuVersion = "13.1.5"

android {
namespace = "com.aliernfrog.pftool"
Expand Down Expand Up @@ -35,6 +37,7 @@ android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
isCoreLibraryDesugaringEnabled = true
}

kotlinOptions {
Expand All @@ -43,6 +46,7 @@ android {
}

buildFeatures {
aidl = true
buildConfig = true
compose = true
}
Expand Down Expand Up @@ -89,6 +93,9 @@ dependencies {
implementation("io.insert-koin:koin-androidx-compose:3.5.3")
implementation("aliernfrog:top-toast-compose:1.4.0-alpha05")
implementation("com.lazygeniouz:dfc:1.0.8")
implementation("dev.rikka.shizuku:api:$shizukuVersion")
implementation("dev.rikka.shizuku:provider:$shizukuVersion")
implementation("io.coil-kt:coil-compose:2.5.0")
implementation("com.github.jeziellago:compose-markdown:0.3.7")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}
6 changes: 5 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile

-keepclassmembers class com.aliernfrog.pftool.service.FileService {
public <init>(...);
}
17 changes: 16 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-sdk tools:overrideLibrary="rikka.shizuku.api"/>

<uses-permission android:name="android.permission.INTERNET"/>

Expand Down Expand Up @@ -60,6 +63,14 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/>
</provider>

<provider
android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku"
android:multiprocess="false"
android:enabled="true"
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />

<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
Expand All @@ -69,4 +80,8 @@
android:value="true" />
</service>
</application>

<queries>
<package android:name="moe.shizuku.privileged.api"/>
</queries>
</manifest>
27 changes: 27 additions & 0 deletions app/src/main/aidl/com/aliernfrog/pftool/IFileService.aidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.aliernfrog.pftool;

import com.aliernfrog.pftool.data.ServiceFile;

interface IFileService {
void destroy() = 16777114; // Destroy method defined by Shizuku server

void exit() = 1;

void copy(String sourcePath, String targetPath) = 2;

void delete(String path) = 3;

boolean exists(String path) = 4;

byte[] getByteArray(String path) = 5;

ServiceFile getFile(String path) = 6;

ServiceFile[] listFiles(String path) = 7;

void renameFile(String oldPath, String newPath) = 8;

void unzipMap(String path, String targetPath) = 9;

void zipMap(String path, String targetPath) = 10;
}
3 changes: 3 additions & 0 deletions app/src/main/aidl/com/aliernfrog/pftool/data/ServiceFile.aidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.aliernfrog.pftool.data;

parcelable ServiceFile;
1 change: 1 addition & 0 deletions app/src/main/java/com/aliernfrog/pftool/Constant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const val crowdinURL = "https://crowdin.com/project/pf-tool"
val externalStorageRoot = Environment.getExternalStorageDirectory().toString()+"/"
val supportsPerAppLanguagePreferences = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
val folderPickerSupportsInitialUri = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
val hasAndroidDataRestrictions = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
val filesAppMightBlockAndroidData = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S

object ConfigKey {
Expand Down
10 changes: 6 additions & 4 deletions app/src/main/java/com/aliernfrog/pftool/data/PermissionData.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.aliernfrog.pftool.data

import android.net.Uri
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import com.aliernfrog.pftool.R

data class PermissionData(
val titleId: Int,
@StringRes val title: Int,
val recommendedPath: String?,
val recommendedPathDescriptionId: Int?,
val doesntExistHintId: Int? = R.string.permissions_recommendedFolder_manuallyCreate,
@StringRes val recommendedPathDescription: Int?,
@StringRes val createFolderHint: Int? = null,
@StringRes val useUnrecommendedAnywayDescription: Int? = null,
val forceRecommendedPath: Boolean = true,
val getUri: () -> String,
val onUriUpdate: (Uri) -> Unit,
val content: @Composable () -> Unit
Expand Down
45 changes: 45 additions & 0 deletions app/src/main/java/com/aliernfrog/pftool/data/ServiceFile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.aliernfrog.pftool.data

import android.os.Parcelable
import com.aliernfrog.pftool.ui.viewmodel.ShizukuViewModel
import com.aliernfrog.pftool.util.getKoinInstance
import com.aliernfrog.pftool.util.staticutil.FileUtil
import kotlinx.parcelize.Parcelize

@Parcelize
data class ServiceFile(
val name: String,
val path: String,
val parentPath: String?,
val size: Long,
val lastModified: Long,
val isFile: Boolean
): Parcelable

val ServiceFile.nameWithoutExtension
get() = FileUtil.removeExtension(this.name)

fun ServiceFile.delete() {
val shizukuViewModel = getKoinInstance<ShizukuViewModel>()
return shizukuViewModel.fileService!!.delete(path)
}

fun ServiceFile.exists(): Boolean {
val shizukuViewModel = getKoinInstance<ShizukuViewModel>()
return shizukuViewModel.fileService!!.exists(path)
}

fun ServiceFile.getByteArray(): ByteArray {
val shizukuViewModel = getKoinInstance<ShizukuViewModel>()
return shizukuViewModel.fileService!!.getByteArray(path)
}

fun ServiceFile.listFiles(): Array<ServiceFile>? {
val shizukuViewModel = getKoinInstance<ShizukuViewModel>()
return shizukuViewModel.fileService!!.listFiles(path)
}

fun ServiceFile.renameTo(newPath: String) {
val shizukuViewModel = getKoinInstance<ShizukuViewModel>()
shizukuViewModel.fileService!!.renameFile(path, newPath)
}
6 changes: 4 additions & 2 deletions app/src/main/java/com/aliernfrog/pftool/di/ViewModelModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import org.koin.dsl.module

val viewModelModule = module {
singleOf(::MainViewModel)
singleOf(::InsetsViewModel)
singleOf(::ShizukuViewModel)

singleOf(::SettingsViewModel)
singleOf(::PermissionsViewModel)
singleOf(::MapsViewModel)
singleOf(::MapsListViewModel)

singleOf(::InsetsViewModel)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.aliernfrog.pftool.enum

import androidx.annotation.StringRes
import com.aliernfrog.pftool.ConfigKey
import com.aliernfrog.pftool.R
import com.aliernfrog.pftool.util.manager.PreferenceManager
import com.aliernfrog.pftool.util.staticutil.FileUtil

enum class FileManagementMethod(
@StringRes val label: Int,
val enable: (PreferenceManager) -> Unit
) {
SAF(
label = R.string.settings_general_fileManagementService_saf,
enable = {
it.fileManagementMethod = SAF.ordinal
it.pfMapsDir = FileUtil.getTreeUriForPath(it.pfMapsDir).toString()
it.exportedMapsDir = FileUtil.getTreeUriForPath(it.exportedMapsDir).toString()
}
),

SHIZUKU(
label = R.string.settings_general_fileManagementService_shizuku,
enable = {
it.fileManagementMethod = SHIZUKU.ordinal
it.pfMapsDir = FileUtil.getFilePath(it.pfMapsDir) ?: ConfigKey.RECOMMENDED_MAPS_DIR
it.exportedMapsDir = FileUtil.getFilePath(it.exportedMapsDir) ?: ConfigKey.RECOMMENDED_EXPORTED_MAPS_DIR
}
)
}
1 change: 1 addition & 0 deletions app/src/main/java/com/aliernfrog/pftool/enum/MapAction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ enum class MapAction(
}
) {
override suspend fun execute(context: Context, vararg maps: MapFile) {
// TODO show progress dialog here
val files = maps.map { it.file }
maps.first().runInIOThreadSafe {
FileUtil.shareFiles(*files.toTypedArray(), context = context)
Expand Down
55 changes: 55 additions & 0 deletions app/src/main/java/com/aliernfrog/pftool/enum/SAFWorkaroundLevel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.aliernfrog.pftool.enum

import android.content.Intent
import android.net.Uri
import android.provider.Settings
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.aliernfrog.pftool.R

enum class SAFWorkaroundLevel(
@StringRes val title: Int? = null,
@StringRes val description: Int? = null,
val button: (@Composable () -> Unit)? = null
) {
/**
* Telling the user to make sure the folder exists.
*/
MAKE_SURE_FOLDER_EXISTS,

/**
* Telling the user to uninstall updates of Files app.
*/
UNINSTALL_FILES_APP_UPDATES(
title = R.string.permissions_uninstallFilesAppUpdates,
description = R.string.permissions_uninstallFilesAppUpdates_description,
button = {
val context = LocalContext.current
Button(
onClick = {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
data = Uri.parse("package:com.google.android.documentsui")
context.startActivity(this)
}
Toast.makeText(context, context.getString(R.string.permissions_uninstallFilesAppUpdates_guide), Toast.LENGTH_SHORT).show()
}
) {
Text(stringResource(R.string.permissions_uninstallFilesAppUpdates_uninstall))
}
}
),

/**
* No workarounds anymore.
*/
SETUP_SHIZUKU(
title = R.string.permissions_setupShizuku,
description = R.string.permissions_setupShizuku_description
)
}
9 changes: 9 additions & 0 deletions app/src/main/java/com/aliernfrog/pftool/enum/ShizukuStatus.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.aliernfrog.pftool.enum

enum class ShizukuStatus {
NOT_INSTALLED,
WAITING_FOR_BINDER,
UNAUTHORIZED,
AVAILABLE,
UNKNOWN
}
Loading

0 comments on commit 6341d1e

Please sign in to comment.