From b58a6014d8a49bd17af182ea05862d8bb205959b Mon Sep 17 00:00:00 2001
From: relan <email@hidden>
Date: Sun, 15 Oct 2017 21:52:28 +0300
Subject: [PATCH] Add FileUtil class

Exact copy of android.os.FileUtils functions (from AOSP 7.1.2), only Slog
is replaced with Log.

Use the new FileUtil class to delete directories

Stop using reflection.

Add FileUtils.setPermissions() methods

Those functions can set different permissions for owner and everyone else.
For example FileUtils.setPermissions(path, 7, 1) will mean Unix mode 0711:
read, write, execute for owner; only execute for group and others.

There is no need to set group permissions specifically. When a new app is
being installed, Android creates a new Unix user and group for it. Since
groups aren't shared, they don't actually participate in access rights
checking: when we access our file, "owner" permissions are in effect; when
another app accesses our file, "other" permissions are used.

Use the new FileUtil.setPermissions() methods

---
 app/build.gradle                                   |   1 -
 .../robv/android/xposed/installer/XposedApp.java   |  28 ++++-----
 .../installer/installation/FlashDirectly.java      |   2 +-
 .../android/xposed/installer/util/AssetUtil.java   |  13 ++--
 .../android/xposed/installer/util/FileUtil.java    |  69 +++++++++++++++++++++
 .../android/xposed/installer/util/ModuleUtil.java  |   5 +-
 7 files changed, 90 insertions(+), 28 deletions(-)
 delete mode 100644 app/libs/AndroidHiddenAPI.jar
 create mode 100644 app/src/main/java/de/robv/android/xposed/installer/util/FileUtil.java

diff --git a/app/build.gradle b/app/build.gradle
index d3d07e7..fad9889 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -33,5 +33,4 @@ dependencies {
     implementation 'eu.chainfire:libsuperuser:1.0.0.201608240809'
     implementation 'com.squareup.picasso:picasso:2.5.2'
     implementation 'de.psdev.licensesdialog:licensesdialog:1.8.1'
-    compileOnly fileTree(dir: 'libs', include: ['*.jar'])
 }
diff --git a/app/src/main/java/de/robv/android/xposed/installer/XposedApp.java b/app/src/main/java/de/robv/android/xposed/installer/XposedApp.java
index abb8211..1fe78c1 100644
--- a/app/src/main/java/de/robv/android/xposed/installer/XposedApp.java
+++ b/app/src/main/java/de/robv/android/xposed/installer/XposedApp.java
@@ -11,7 +11,6 @@ import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.preference.PreferenceManager;
 import android.support.v4.content.FileProvider;
@@ -20,10 +19,10 @@ import android.util.Log;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.lang.reflect.Method;
 
 import de.robv.android.xposed.installer.util.AssetUtil;
 import de.robv.android.xposed.installer.util.DownloadsUtil;
+import de.robv.android.xposed.installer.util.FileUtil;
 import de.robv.android.xposed.installer.util.InstallZipUtil;
 import de.robv.android.xposed.installer.util.InstallZipUtil.XposedProp;
 import de.robv.android.xposed.installer.util.NotificationUtil;
@@ -124,26 +123,21 @@ public class XposedApp extends Application implements ActivityLifecycleCallbacks
     }
 
     private void createDirectories() {
-        FileUtils.setPermissions(BASE_DIR, 00711, -1, -1);
-        mkdirAndChmod("conf", 00771);
-        mkdirAndChmod("log", 00777);
+        FileUtil.setPermissions(BASE_DIR, 7, 1);
+        mkdirAndChmod("conf", 7, 1);
+        mkdirAndChmod("log", 7, 7);
 
         if (Build.VERSION.SDK_INT >= 24) {
-            try {
-                Method deleteDir = FileUtils.class.getDeclaredMethod("deleteContentsAndDir", File.class);
-                deleteDir.invoke(null, new File(BASE_DIR_LEGACY, "bin"));
-                deleteDir.invoke(null, new File(BASE_DIR_LEGACY, "conf"));
-                deleteDir.invoke(null, new File(BASE_DIR_LEGACY, "log"));
-            } catch (ReflectiveOperationException e) {
-                Log.w(XposedApp.TAG, "Failed to delete obsolete directories", e);
-            }
+            FileUtil.deleteContentsAndDir(new File(BASE_DIR_LEGACY, "bin"));
+            FileUtil.deleteContentsAndDir(new File(BASE_DIR_LEGACY, "conf"));
+            FileUtil.deleteContentsAndDir(new File(BASE_DIR_LEGACY, "log"));
         }
     }
 
-    private void mkdirAndChmod(String dir, int permissions) {
-        dir = BASE_DIR + dir;
-        new File(dir).mkdir();
-        FileUtils.setPermissions(dir, permissions, -1, -1);
+    private void mkdirAndChmod(String name, int owner, int other) {
+        File dir = new File(BASE_DIR + name);
+        dir.mkdir();
+        FileUtil.setPermissions(dir, owner, other);
     }
 
     public void reloadXposedProp() {
diff --git a/app/src/main/java/de/robv/android/xposed/installer/installation/FlashDirectly.java b/app/src/main/java/de/robv/android/xposed/installer/installation/FlashDirectly.java
index e230d3f..fad7f66 100644
--- a/app/src/main/java/de/robv/android/xposed/installer/installation/FlashDirectly.java
+++ b/app/src/main/java/de/robv/android/xposed/installer/installation/FlashDirectly.java
@@ -46,7 +46,7 @@ public class FlashDirectly extends Flashable {
         ZipEntry entry = zip.getEntry("META-INF/com/google/android/update-binary");
         File updateBinaryFile = new File(XposedApp.getInstance().getCacheDir(), "update-binary");
         try {
-            AssetUtil.writeStreamToFile(zip.getInputStream(entry), updateBinaryFile, 0700);
+            AssetUtil.writeStreamToFile(zip.getInputStream(entry), updateBinaryFile, 7, 0);
         } catch (IOException e) {
             Log.e(XposedApp.TAG, "Could not extract update-binary", e);
             triggerError(callback, FlashCallback.ERROR_INVALID_ZIP);
diff --git a/app/src/main/java/de/robv/android/xposed/installer/util/AssetUtil.java b/app/src/main/java/de/robv/android/xposed/installer/util/AssetUtil.java
index 63b2bc8..5b71de5 100644
--- a/app/src/main/java/de/robv/android/xposed/installer/util/AssetUtil.java
+++ b/app/src/main/java/de/robv/android/xposed/installer/util/AssetUtil.java
@@ -2,7 +2,6 @@ package de.robv.android.xposed.installer.util;
 
 import android.content.res.AssetManager;
 import android.os.Build;
-import android.os.FileUtils;
 import android.util.Log;
 
 import java.io.File;
@@ -26,12 +25,13 @@ public class AssetUtil {
         }
     }
 
-    public static File writeAssetToFile(AssetManager assets, String assetName, File targetFile, int mode) {
+    public static File writeAssetToFile(AssetManager assets, String assetName,
+            File targetFile, int owner, int other) {
         try {
             if (assets == null)
                 assets = XposedApp.getInstance().getAssets();
             InputStream in = assets.open(assetName);
-            writeStreamToFile(in, targetFile, mode);;
+            writeStreamToFile(in, targetFile, owner, other);
             return targetFile;
         } catch (IOException e) {
             Log.e(XposedApp.TAG, "could not extract asset", e);
@@ -42,7 +42,8 @@ public class AssetUtil {
         }
     }
 
-    public static void writeStreamToFile(InputStream in, File targetFile, int mode) throws IOException {
+    public static void writeStreamToFile(InputStream in, File targetFile,
+            int owner, int other) throws IOException {
         FileOutputStream out = new FileOutputStream(targetFile);
 
         byte[] buffer = new byte[1024];
@@ -53,7 +54,7 @@ public class AssetUtil {
         in.close();
         out.close();
 
-        FileUtils.setPermissions(targetFile.getAbsolutePath(), mode, -1, -1);
+        FileUtil.setPermissions(targetFile.getAbsolutePath(), owner, other);
     }
 
     public synchronized static void extractBusybox() {
@@ -61,7 +62,7 @@ public class AssetUtil {
             return;
 
         AssetManager assets = null;
-        writeAssetToFile(assets, getBinariesFolder() + "busybox-xposed", BUSYBOX_FILE, 00700);
+        writeAssetToFile(assets, getBinariesFolder() + "busybox-xposed", BUSYBOX_FILE, 7, 0);
     }
 
     public synchronized static void removeBusybox() {
diff --git a/app/src/main/java/de/robv/android/xposed/installer/util/FileUtil.java b/app/src/main/java/de/robv/android/xposed/installer/util/FileUtil.java
new file mode 100644
index 0000000..aacb87f
--- /dev/null
+++ b/app/src/main/java/de/robv/android/xposed/installer/util/FileUtil.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.robv.android.xposed.installer.util;
+
+import android.util.Log;
+
+import java.io.File;
+
+import de.robv.android.xposed.installer.XposedApp;
+
+public class FileUtil {
+    public static boolean setPermissions(String path, int owner, int other) {
+        return setPermissions(new File(path), owner, other);
+    }
+
+    public static boolean setPermissions(File path, int owner, int other) {
+        if (!setPermissions(path, other, false) || !setPermissions(path, owner, true)) {
+            Log.w(XposedApp.TAG, "Failed to set permissions " +
+                    owner + "/" + other + " for " + path.getPath());
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean setPermissions(File path, int perm, boolean ownerOnly) {
+        return path.setReadable((perm & 4) != 0, ownerOnly)
+            && path.setWritable((perm & 2) != 0, ownerOnly)
+            && path.setExecutable((perm & 1) != 0, ownerOnly);
+    }
+
+    public static boolean deleteContentsAndDir(File dir) {
+        if (deleteContents(dir)) {
+            return dir.delete();
+        } else {
+            return false;
+        }
+    }
+
+    public static boolean deleteContents(File dir) {
+        File[] files = dir.listFiles();
+        boolean success = true;
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    success &= deleteContents(file);
+                }
+                if (!file.delete()) {
+                    Log.w(XposedApp.TAG, "Failed to delete " + file);
+                    success = false;
+                }
+            }
+        }
+        return success;
+    }
+}
diff --git a/app/src/main/java/de/robv/android/xposed/installer/util/ModuleUtil.java b/app/src/main/java/de/robv/android/xposed/installer/util/ModuleUtil.java
index 9aa8e1d..e38788b 100644
--- a/app/src/main/java/de/robv/android/xposed/installer/util/ModuleUtil.java
+++ b/app/src/main/java/de/robv/android/xposed/installer/util/ModuleUtil.java
@@ -9,7 +9,6 @@ import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
-import android.os.FileUtils;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -223,8 +222,8 @@ public final class ModuleUtil {
             modulesList.close();
             enabledModulesList.close();
 
-            FileUtils.setPermissions(MODULES_LIST_FILE, 00664, -1, -1);
-            FileUtils.setPermissions(XposedApp.ENABLED_MODULES_LIST_FILE, 00664, -1, -1);
+            FileUtil.setPermissions(MODULES_LIST_FILE, 6, 4);
+            FileUtil.setPermissions(XposedApp.ENABLED_MODULES_LIST_FILE, 6, 4);
 
             if (showToast)
                 showToast(R.string.xposed_module_list_updated);
-- 
2.14.1

