Skip to content

Commit

Permalink
fix permission to read images
Browse files Browse the repository at this point in the history
  • Loading branch information
Dev4Mod committed Jul 19, 2024
1 parent 901cb6b commit d0380da
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 54 deletions.
1 change: 1 addition & 0 deletions app/src/main/java/com/wmods/wppenhacer/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.wmods.wppenhacer.activities.AboutActivity;
import com.wmods.wppenhacer.adapter.MainPagerAdapter;
import com.wmods.wppenhacer.databinding.ActivityMainBinding;
import com.wmods.wppenhacer.utils.FilePicker;

import java.io.File;
import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;

import com.wmods.wppenhacer.FilePicker;
import com.wmods.wppenhacer.R;
import com.wmods.wppenhacer.utils.FilePicker;

import java.io.File;
import java.nio.file.Files;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import androidx.preference.PreferenceManager;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.wmods.wppenhacer.FilePicker;
import com.wmods.wppenhacer.R;
import com.wmods.wppenhacer.activities.TextEditorActivity;
import com.wmods.wppenhacer.utils.FilePicker;
import com.wmods.wppenhacer.xposed.utils.Utils;

import java.io.File;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@

import com.wmods.wppenhacer.App;
import com.wmods.wppenhacer.BuildConfig;
import com.wmods.wppenhacer.FilePicker;
import com.wmods.wppenhacer.MainActivity;
import com.wmods.wppenhacer.R;
import com.wmods.wppenhacer.databinding.FragmentHomeBinding;
import com.wmods.wppenhacer.ui.fragments.base.BaseFragment;
import com.wmods.wppenhacer.utils.FilePicker;
import com.wmods.wppenhacer.xposed.core.FeatureLoader;
import com.wmods.wppenhacer.xposed.utils.Utils;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.wmods.wppenhacer;
package com.wmods.wppenhacer.utils;

import android.net.Uri;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import com.wmods.wppenhacer.utils.RealPathUtil;

import java.io.File;

public class FilePicker {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
Expand All @@ -36,10 +39,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.documentfile.provider.DocumentFile;

import com.wmods.wppenhacer.preference.ThemePreference;
import com.wmods.wppenhacer.utils.IColors;
import com.wmods.wppenhacer.xposed.core.Feature;
import com.wmods.wppenhacer.xposed.core.WppCore;
import com.wmods.wppenhacer.xposed.core.components.AlertDialogWpp;
import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator;
import com.wmods.wppenhacer.xposed.utils.ReflectionUtils;
import com.wmods.wppenhacer.xposed.utils.ResId;
Expand All @@ -58,7 +64,6 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

import cz.vutbr.web.css.CSSFactory;
import cz.vutbr.web.css.CombinedSelector;
Expand All @@ -79,43 +84,24 @@
public class CustomView extends Feature {

private DrawableCache cacheImages;
private HashMap<String, Drawable> chacheDrawables;
private ExecutorService mThreadService;
private static final int REQUEST_PERMISSION = 852582;
private static File themeDir;
private static final int REQUEST_FOLDER = 852583;
private final HashMap<String, Drawable> chacheDrawables = new HashMap<>();
private final HashMap<String, DocumentFile> chacheUris = new HashMap<>();

public CustomView(@NonNull ClassLoader loader, @NonNull XSharedPreferences preferences) {
super(loader, preferences);
}

@Override
public void doHook() throws Throwable {
var filter_itens = prefs.getString("css_theme", "");
var folder_theme = prefs.getString("folder_theme", "");
var custom_css = prefs.getString("custom_css", "");

checkPermissions();

if ((TextUtils.isEmpty(filter_itens) && TextUtils.isEmpty(folder_theme) && TextUtils.isEmpty(custom_css)) || !prefs.getBoolean("custom_filters", true))
return;

hookDrawableViews();

themeDir = new File(ThemePreference.rootDirectory, folder_theme);
filter_itens += "\n" + custom_css;
cacheImages = new DrawableCache(Utils.getApplication(), 100 * 1024 * 1024);
chacheDrawables = new HashMap<>();

var sheet = CSSFactory.parseString(filter_itens, new URL("https://base.url/"));

XposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
var activity = (Activity) param.thisObject;
View rootView = activity.getWindow().getDecorView().getRootView();
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> CompletableFuture.runAsync(() -> registerCssRules(activity, (ViewGroup) rootView, sheet)));
}
});

private static void showDialogUriPermission(Activity activity) {
new AlertDialogWpp(activity).setTitle("Custom CSS")
.setMessage(activity.getString(ResId.string.uri_permission))
.setPositiveButton(activity.getString(ResId.string.allow), (dialog, which) -> {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, getDownloadsUri());
activity.startActivityForResult(intent, REQUEST_FOLDER);
}).setNegativeButton(activity.getString(ResId.string.cancel), (dialog, which) -> dialog.dismiss()).show();
}

private void hookDrawableViews() {
Expand Down Expand Up @@ -153,14 +139,77 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {

}

private static Uri getDownloadsUri() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return MediaStore.Downloads.EXTERNAL_CONTENT_URI;
} else {
return Uri.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
}
}

@Override
public void doHook() throws Throwable {
var filter_itens = prefs.getString("css_theme", "");
var folder_theme = prefs.getString("folder_theme", "");
var custom_css = prefs.getString("custom_css", "");

if ((TextUtils.isEmpty(filter_itens) && TextUtils.isEmpty(folder_theme) && TextUtils.isEmpty(custom_css)) || !prefs.getBoolean("custom_filters", true))
return;

checkPermissions();

hookDrawableViews();

themeDir = new File(ThemePreference.rootDirectory, folder_theme);
filter_itens += "\n" + custom_css;
cacheImages = new DrawableCache(Utils.getApplication(), 100 * 1024 * 1024);

var sheet = CSSFactory.parseString(filter_itens, new URL("https://base.url/"));

XposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
var activity = (Activity) param.thisObject;
View rootView = activity.getWindow().getDecorView().getRootView();
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> CompletableFuture.runAsync(() -> registerCssRules(activity, (ViewGroup) rootView, sheet)));
}
});

}

private void checkPermissions() {


XposedHelpers.findAndHookMethod("com.whatsapp.HomeActivity", classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
var activity = (Activity) param.thisObject;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
activity.requestPermissions(new String[]{Manifest.permission.READ_MEDIA_IMAGES}, 852582);
activity.requestPermissions(new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSION);
}
}
var wae = WppCore.getPrivString("folder_wae", null);
if (wae == null || !isUriPermissionGranted(activity, Uri.parse(wae))) {
showDialogUriPermission(activity);
}
}
});

XposedHelpers.findAndHookMethod(Activity.class, "onActivityResult", int.class, int.class, Intent.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
var activity = (Activity) param.thisObject;
if ((int) param.args[0] == REQUEST_FOLDER && (int) param.args[1] == Activity.RESULT_OK) {
var uri = (Uri) ((Intent) param.args[2]).getData();
if (uri.getPath().endsWith("Download/WaEnhancer")) {
WppCore.setPrivString("folder_wae", uri.toString());
activity.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Utils.doRestart(activity);
} else {
showDialogUriPermission(activity);
Utils.showToast(activity.getString(ResId.string.invalid_folder), Toast.LENGTH_LONG);
}
}
}
Expand All @@ -171,10 +220,9 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
var activity = (Activity) param.thisObject;
if ((int) param.args[0] == 852582) {
if ((int) param.args[0] == REQUEST_PERMISSION) {
var results = (int[]) param.args[2];
if (Arrays.stream(results).anyMatch(result -> result != PackageManager.PERMISSION_GRANTED)) {
// start intent to permissions
activity.startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", activity.getPackageName(), null)));
Utils.showToast(activity.getString(ResId.string.grant_permission), Toast.LENGTH_LONG);
}
Expand All @@ -184,6 +232,12 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {

}

private boolean isUriPermissionGranted(Context context, Uri uri) {
int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
int permissionCheck = context.checkUriPermission(uri, android.os.Process.myPid(), android.os.Process.myUid(), takeFlags);
return permissionCheck == PackageManager.PERMISSION_GRANTED;
}

private void registerCssRules(Activity activity, ViewGroup currenView, StyleSheet sheet) {
try {
for (var selector : sheet) {
Expand Down Expand Up @@ -284,9 +338,11 @@ private void setRuleInView(RuleItem ruleItem, View view) {
case "background-image" -> {
if (!(declaration.get(0) instanceof TermURI uri)) continue;
var draw = cacheImages.getDrawable(uri.getValue(), view.getWidth(), view.getHeight());
// log("background-image: " + uri.getValue() + " " + draw);
if (draw == null) continue;
if (XposedHelpers.getAdditionalInstanceField(view, "mHookedBackground") != null || XposedHelpers.getAdditionalInstanceField(view, "mHookedDrawable") != null)
continue;
log("set background " + uri.getValue());
setHookedDrawable(view, draw);
}
case "background-size" -> {
Expand Down Expand Up @@ -642,42 +698,78 @@ private Drawable loadDrawableFromFile(String filePath, int reqWidth, int reqHeig
var newHeight = reqHeight < 10 ? bitmap.getHeight() : Math.min(bitmap.getHeight(), reqHeight);
var newWidth = reqWidth < 10 ? bitmap.getWidth() : Math.min(bitmap.getWidth(), reqWidth);
bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);

return new BitmapDrawable(context.getResources(), bitmap);
}

private Drawable loadDrawableFromUri(Uri uri, int reqWidth, int reqHeight) {
try {
var inputStream = context.getContentResolver().openInputStream(uri);
var bitmap = BitmapFactory.decodeStream(inputStream);
var newHeight = reqHeight < 10 ? bitmap.getHeight() : Math.min(bitmap.getHeight(), reqHeight);
var newWidth = reqWidth < 10 ? bitmap.getWidth() : Math.min(bitmap.getWidth(), reqWidth);
bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
return new BitmapDrawable(context.getResources(), bitmap);
} catch (Exception e) {
return null;
}
}

@Nullable
public Drawable getDrawable(String filePath, int width, int height) {
File file = filePath.startsWith("/") ? new File(filePath) : new File(themeDir, filePath);
var wae = WppCore.getPrivString("folder_wae", null);

if (wae != null && !filePath.startsWith("/")) {
var uriFile = chacheUris.get(filePath);
if (uriFile == null) {
var themes = DocumentFile.fromTreeUri(context, Uri.parse(wae)).findFile("themes");
if (themes == null) return null;
var theme = themes.findFile(themeDir.getName());
if (theme == null) return null;
uriFile = theme.findFile(file.getName());
if (uriFile == null) return null;
chacheUris.put(filePath, uriFile);
}
String key = uriFile.getUri().toString();
long lastModified = uriFile.lastModified();

CachedDrawable cachedDrawable = drawableCache.get(key);
if (cachedDrawable != null && cachedDrawable.lastModified == lastModified) {
return cachedDrawable.drawable;
}
Drawable cachedDrawableFromFile = loadDrawableFromCache(key, lastModified);
if (cachedDrawableFromFile != null) {
cachedDrawable = new CachedDrawable(cachedDrawableFromFile, lastModified);
drawableCache.put(key, cachedDrawable);
return cachedDrawableFromFile;
}
Drawable drawable = loadDrawableFromUri(uriFile.getUri(), width, height);
saveDrawableToCache(key, (BitmapDrawable) drawable, lastModified);
cachedDrawable = new CachedDrawable(drawable, lastModified);
drawableCache.put(key, cachedDrawable);
return drawable;
}


if (!file.exists() || !file.canRead()) {
return null; // Return transparent drawable
return null;
}
String key = file.getAbsolutePath();
long lastModified = file.lastModified();

// Check if the cached drawable exists and is up-to-date
CachedDrawable cachedDrawable = drawableCache.get(key);
if (cachedDrawable != null && cachedDrawable.lastModified == lastModified) {
return cachedDrawable.drawable;
}

// Try loading the drawable from cache
Drawable cachedDrawableFromFile = loadDrawableFromCache(key, lastModified);
if (cachedDrawableFromFile != null) {
cachedDrawable = new CachedDrawable(cachedDrawableFromFile, lastModified);
drawableCache.put(key, cachedDrawable);
return cachedDrawableFromFile;
}

// Load drawable from original file
Drawable drawable = loadDrawableFromFile(key, width, height);
// Save the drawable to cache directory
saveDrawableToCache(key, (BitmapDrawable) drawable, lastModified);

// Update the cache
cachedDrawable = new CachedDrawable(drawable, lastModified);
drawableCache.put(key, cachedDrawable);

return drawable;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public static class string {
public static int grant_permission;
public static int expiration;
public static int deleted_a_message_in_group;
public static int allow;
public static int invalid_folder;
public static int uri_permission;
}

public static class array {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,6 @@
<string name="list_animations_home_screen_sum">Add animations when swiping the chat list</string>
<string name="stamp_copied_messages">Remove Stamp from Copied Messages</string>
<string name="stamp_copied_messages_sum">Removes name and date when copying more than one message</string>
<string name="invalid_folder">The invalid folder has been selected. Please choose the WaEnhancer folder</string>
<string name="uri_permission">To use custom themes you need to select the \"WaEnhancer\" folder in \"Download\"</string>
</resources>

0 comments on commit d0380da

Please sign in to comment.