diff --git a/src/main/java/dk/jens/backup/AppInfo.java b/src/main/java/dk/jens/backup/AppInfo.java index 88f0739451..8edffad20f 100644 --- a/src/main/java/dk/jens/backup/AppInfo.java +++ b/src/main/java/dk/jens/backup/AppInfo.java @@ -8,7 +8,7 @@ public class AppInfo implements Comparable, Parcelable { LogFile logInfo; - String label, packageName, versionName, sourceDir, dataDir; + String label, packageName, versionName, sourceDir, dataDir, deviceProtectedDataDir; int versionCode, backupMode; private boolean system, installed, checked, disabled; public Bitmap icon; @@ -17,7 +17,7 @@ public class AppInfo public static final int MODE_DATA = 2; public static final int MODE_BOTH = 3; - public AppInfo(String packageName, String label, String versionName, int versionCode, String sourceDir, String dataDir, boolean system, boolean installed) + public AppInfo(String packageName, String label, String versionName, int versionCode, String sourceDir, String dataDir, String deviceProtectedDataDir, boolean system, boolean installed) { this.label = label; this.packageName = packageName; @@ -25,6 +25,7 @@ public AppInfo(String packageName, String label, String versionName, int version this.versionCode = versionCode; this.sourceDir = sourceDir; this.dataDir = dataDir; + this.deviceProtectedDataDir = deviceProtectedDataDir; this.system = system; this.installed = installed; this.backupMode = MODE_UNSET; @@ -53,6 +54,10 @@ public String getDataDir() { return dataDir; } + + public String getDeviceProtectedDataDir() { + return deviceProtectedDataDir; + } public int getBackupMode() { return backupMode; @@ -128,6 +133,7 @@ public void writeToParcel(Parcel out, int flags) out.writeString(versionName); out.writeString(sourceDir); out.writeString(dataDir); + out.writeString(deviceProtectedDataDir); out.writeInt(versionCode); out.writeInt(backupMode); out.writeBooleanArray(new boolean[] {system, installed, checked}); @@ -152,6 +158,7 @@ protected AppInfo(Parcel in) versionName = in.readString(); sourceDir = in.readString(); dataDir = in.readString(); + deviceProtectedDataDir = in.readString(); versionCode = in.readInt(); backupMode = in.readInt(); boolean[] bools = new boolean[4]; diff --git a/src/main/java/dk/jens/backup/AppInfoHelper.java b/src/main/java/dk/jens/backup/AppInfoHelper.java index e0a3ec8bad..b33b52f463 100644 --- a/src/main/java/dk/jens/backup/AppInfoHelper.java +++ b/src/main/java/dk/jens/backup/AppInfoHelper.java @@ -79,10 +79,12 @@ public static ArrayList getPackageInfo(Context context, // package at least on cm14 if(pinfo.packageName.equals("android") && dataDir == null) dataDir = "/data/system"; + //determine the directory where device-protected data will be stored for android N and above + String deviceProtectedDataDir = pinfo.applicationInfo.deviceProtectedDataDir; AppInfo appInfo = new AppInfo(pinfo.packageName, pinfo.applicationInfo.loadLabel(pm).toString(), pinfo.versionName, pinfo.versionCode, - pinfo.applicationInfo.sourceDir, dataDir, isSystem, + pinfo.applicationInfo.sourceDir, dataDir, deviceProtectedDataDir, isSystem, true); File subdir = new File(backupDir, pinfo.packageName); if(subdir.exists()) @@ -115,7 +117,7 @@ public static void addUninstalledBackups(File backupDir, ArrayList list LogFile logInfo = new LogFile(new File(backupDir.getAbsolutePath() + "/" + folder), folder); if(logInfo.getLastBackupMillis() > 0) { - AppInfo appInfo = new AppInfo(logInfo.getPackageName(), logInfo.getLabel(), logInfo.getVersionName(), logInfo.getVersionCode(), logInfo.getSourceDir(), logInfo.getDataDir(), logInfo.isSystem(), false); + AppInfo appInfo = new AppInfo(logInfo.getPackageName(), logInfo.getLabel(), logInfo.getVersionName(), logInfo.getVersionCode(), logInfo.getSourceDir(), logInfo.getDataDir(), logInfo.getDeviceProtectedDataDir(), logInfo.isSystem(), false); appInfo.setLogInfo(logInfo); list.add(appInfo); } diff --git a/src/main/java/dk/jens/backup/AppInfoSpecial.java b/src/main/java/dk/jens/backup/AppInfoSpecial.java index 8c2ed9ec50..73ca14f6ee 100644 --- a/src/main/java/dk/jens/backup/AppInfoSpecial.java +++ b/src/main/java/dk/jens/backup/AppInfoSpecial.java @@ -9,7 +9,7 @@ public class AppInfoSpecial extends AppInfo String[] files; public AppInfoSpecial(String packageName, String label, String versionName, int versionCode) { - super(packageName, label, versionName, versionCode, "", "", true, true); + super(packageName, label, versionName, versionCode, "", "", "", true, true); } public String[] getFilesList() { diff --git a/src/main/java/dk/jens/backup/BackupRestoreHelper.java b/src/main/java/dk/jens/backup/BackupRestoreHelper.java index 85c6879b06..1adb258c85 100644 --- a/src/main/java/dk/jens/backup/BackupRestoreHelper.java +++ b/src/main/java/dk/jens/backup/BackupRestoreHelper.java @@ -41,7 +41,7 @@ else if(backupMode != AppInfo.MODE_DATA && appInfo.getSourceDir().length() > 0) } else { - ret = shellCommands.doBackup(context, backupSubDir, appInfo.getLabel(), appInfo.getDataDir(), appInfo.getSourceDir(), backupMode); + ret = shellCommands.doBackup(context, backupSubDir, appInfo.getLabel(), appInfo.getDataDir(), appInfo.getDeviceProtectedDataDir(), appInfo.getSourceDir(), backupMode); appInfo.setBackupMode(backupMode); } @@ -94,7 +94,7 @@ else if(!appInfo.isSpecial()) } else { - restoreRet = shellCommands.doRestore(context, backupSubDir, appInfo.getLabel(), appInfo.getPackageName(), appInfo.getLogInfo().getDataDir()); + restoreRet = shellCommands.doRestore(context, backupSubDir, appInfo.getLabel(), appInfo.getPackageName(), appInfo.getLogInfo().getDataDir(), appInfo.getLogInfo().getDeviceProtectedDataDir()); permRet = shellCommands.setPermissions(dataDir); } diff --git a/src/main/java/dk/jens/backup/LogFile.java b/src/main/java/dk/jens/backup/LogFile.java index 44ed4f00fd..63b54e6ba0 100644 --- a/src/main/java/dk/jens/backup/LogFile.java +++ b/src/main/java/dk/jens/backup/LogFile.java @@ -18,7 +18,7 @@ public class LogFile implements Parcelable { final static String TAG = OAndBackup.TAG; - String label, packageName, versionName, sourceDir, dataDir; + String label, packageName, versionName, sourceDir, dataDir, deviceProtectedDataDir; int versionCode, backupMode; long lastBackupMillis; boolean encrypted, system; @@ -34,6 +34,11 @@ public LogFile(File backupSubDir, String packageName) this.versionName = jsonObject.getString("versionName"); this.sourceDir = jsonObject.getString("sourceDir"); this.dataDir = jsonObject.getString("dataDir"); + //check that the value exists in case backups have come from an old version of oandbackup + if(jsonObject.has("deviceProtectedDataDir")) + this.deviceProtectedDataDir = jsonObject.getString("deviceProtectedDataDir"); + else + deviceProtectedDataDir = null; this.lastBackupMillis = jsonObject.getLong("lastBackupMillis"); this.versionCode = jsonObject.getInt("versionCode"); this.encrypted = jsonObject.optBoolean("isEncrypted"); @@ -79,6 +84,9 @@ public String getDataDir() { return dataDir; } + public String getDeviceProtectedDataDir() { + return deviceProtectedDataDir; + } public long getLastBackupMillis() { return lastBackupMillis; @@ -119,6 +127,7 @@ public static void writeLogFile(File backupSubDir, AppInfo appInfo, int backupMo jsonObject.put("packageName", appInfo.getPackageName()); jsonObject.put("sourceDir", sourceDir); jsonObject.put("dataDir", appInfo.getDataDir()); + jsonObject.put("deviceProtectedDataDir", appInfo.getDeviceProtectedDataDir()); jsonObject.put("lastBackupMillis", System.currentTimeMillis()); jsonObject.put("isEncrypted", encrypted); jsonObject.put("isSystem", appInfo.isSystem()); diff --git a/src/main/java/dk/jens/backup/ShellCommands.java b/src/main/java/dk/jens/backup/ShellCommands.java index 68ffee32ac..85183171f5 100644 --- a/src/main/java/dk/jens/backup/ShellCommands.java +++ b/src/main/java/dk/jens/backup/ShellCommands.java @@ -3,6 +3,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.os.Build; import android.text.TextUtils; import android.util.Log; @@ -26,12 +27,12 @@ public class ShellCommands implements CommandHandler.UnexpectedExceptionListener { final static String TAG = OAndBackup.TAG; final static String EXTERNAL_FILES = "external_files"; + final static String DEVICE_PROTECTED_FILES = "device_protected_files"; CommandHandler commandHandler = new CommandHandler(); private final String oabUtils; private boolean legacyMode; - SharedPreferences prefs; String busybox; ArrayList users; @@ -81,7 +82,7 @@ public void onUnexpectedException(Throwable t) { errors += t.toString(); } - public int doBackup(Context context, File backupSubDir, String label, String packageData, String packageApk, int backupMode) + public int doBackup(Context context, File backupSubDir, String label, String packageData, String deviceProtectedPackageData, String packageApk, int backupMode) { String backupSubDirPath = swapBackupDirPath(backupSubDir.getAbsolutePath()); Log.i(TAG, "backup: " + label); @@ -96,6 +97,7 @@ public int doBackup(Context context, File backupSubDir, String label, String pac // -L because fat (which will often be used to store the backup files) // doesn't support symlinks String followSymlinks = prefs.getBoolean("followSymlinks", true) ? "L" : ""; + File backupSubDirDeviceProtectedFiles = null; switch(backupMode) { case AppInfo.MODE_APK: @@ -103,9 +105,18 @@ public int doBackup(Context context, File backupSubDir, String label, String pac break; case AppInfo.MODE_DATA: commands.add("cp -R" + followSymlinks + " " + packageData + " " + backupSubDirPath); + + backupSubDirDeviceProtectedFiles = new File(backupSubDir, DEVICE_PROTECTED_FILES); + if(backupSubDirDeviceProtectedFiles.exists() || backupSubDirDeviceProtectedFiles.mkdir()) + commands.add("cp -R" + followSymlinks + " " + deviceProtectedPackageData + " " + swapBackupDirPath(backupSubDir.getAbsolutePath() + "/" + DEVICE_PROTECTED_FILES)); break; default: // defaults to MODE_BOTH commands.add("cp -R" + followSymlinks + " " + packageData + " " + backupSubDirPath); + + backupSubDirDeviceProtectedFiles = new File(backupSubDir, DEVICE_PROTECTED_FILES); + if(backupSubDirDeviceProtectedFiles.exists() || backupSubDirDeviceProtectedFiles.mkdir()) + commands.add("cp -R" + followSymlinks + " " + deviceProtectedPackageData + " " + swapBackupDirPath(backupSubDir.getAbsolutePath() + "/" + DEVICE_PROTECTED_FILES)); + commands.add("cp " + packageApk + " " + backupSubDirPath); break; } @@ -169,6 +180,8 @@ public int doBackup(Context context, File backupSubDir, String label, String pac int zipret = compress(new File(backupSubDir, folder)); if(backupSubDirExternalFiles != null) zipret += compress(new File(backupSubDirExternalFiles, packageData.substring(packageData.lastIndexOf("/") + 1))); + if(backupSubDirDeviceProtectedFiles != null) + zipret += compress(new File(backupSubDirDeviceProtectedFiles, packageData.substring(packageData.lastIndexOf("/") + 1))); if(zipret != 0) ret += zipret; } @@ -177,7 +190,7 @@ public int doBackup(Context context, File backupSubDir, String label, String pac Crypto.cleanUpEncryptedFiles(backupSubDir, packageApk, packageData, backupMode, prefs.getBoolean("backupExternalFiles", false)); return ret; } - public int doRestore(Context context, File backupSubDir, String label, String packageName, String dataDir) + public int doRestore(Context context, File backupSubDir, String label, String packageName, String dataDir, String deviceProtectedDataDir) { String backupSubDirPath = swapBackupDirPath(backupSubDir.getAbsolutePath()); String dataDirName = dataDir.substring(dataDir.lastIndexOf("/") + 1); @@ -213,8 +226,36 @@ public int doRestore(Context context, File backupSubDir, String label, String pa // restored system apps will not necessarily have the data folder (which is otherwise handled by pm) } commands.add(restoreCommand); + + File deviceProtectedFiles = new File(backupSubDir, DEVICE_PROTECTED_FILES); + if(deviceProtectedDataDir != null && deviceProtectedFiles.exists()) + { + + Compression.unzip(new File(deviceProtectedFiles, dataDirName + ".zip"), deviceProtectedFiles); + restoreCommand = busybox + " cp -r " + deviceProtectedFiles + "/" + dataDirName + "/* " + deviceProtectedDataDir + "\n"; + + try { + PackageManager packageManager = context.getPackageManager(); + String user = String.valueOf(packageManager.getApplicationInfo(dataDirName, PackageManager.GET_META_DATA).uid); + restoreCommand = restoreCommand + " chown -R " + user + ":" + user + " " + deviceProtectedDataDir + "\n"; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + restoreCommand = restoreCommand + " chmod -R 777 " + deviceProtectedDataDir + "\n"; + if(!(new File(deviceProtectedDataDir).exists())) + { + restoreCommand = "mkdir " + deviceProtectedDataDir + "\n" + restoreCommand; + // restored system apps will not necessarily have the data folder (which is otherwise handled by pm) + } + commands.add(restoreCommand); + + } + + if(Build.VERSION.SDK_INT >= 23) { commands.add("restorecon -R " + dataDir + " || true"); + commands.add("restorecon -R " + deviceProtectedDataDir + " || true"); } int ret = commandHandler.runCmd("su", commands, line -> {}, line -> writeErrorLog(label, line), @@ -237,6 +278,7 @@ public int doRestore(Context context, File backupSubDir, String label, String pa { deleteBackup(new File(backupSubDir, dataDirName)); } + deleteBackup(new File(new File(backupSubDir, DEVICE_PROTECTED_FILES), dataDirName)); } } public int backupSpecial(File backupSubDir, String label, String... files) @@ -487,7 +529,7 @@ public int restoreUserApk(File backupDir, String label, String apk, String ownDa commands.add(busybox + " rm -r " + swapBackupDirPath(tempPath)); } else { commands.add(String.format("%s -r %s/%s", installCmd, - backupDir.getAbsolutePath(), apk)); + backupDir.getAbsolutePath(), apk)); } List err = new ArrayList<>(); int ret = commandHandler.runCmd("su", commands, line -> {},