Request root access from separate thread and don't use global Shell

This should help prevent some of the ANR's reported through Google Play
This commit is contained in:
Alexander Bakker 2022-12-04 16:55:19 +01:00
parent ac51996896
commit 69f0bb4fbc
8 changed files with 78 additions and 23 deletions

View file

@ -39,8 +39,8 @@ public abstract class AegisApplicationBase extends Application {
private VaultManager _vaultManager;
static {
// to access other app's internal storage directory, run libsu commands inside the global mount namespace
Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
// Enable verbose libsu logging in debug builds
Shell.enableVerboseLogging = BuildConfig.DEBUG;
}
@Override

View file

@ -16,6 +16,7 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.util.JsonUtils;
import com.beemdevelopment.aegis.util.PreferenceParser;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
@ -67,8 +68,9 @@ public class AuthyImporter extends DatabaseImporter {
}
@Override
public State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException {
public State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException {
SuFile path = getAppPath();
path.setShell(shell);
JSONArray array;
JSONArray authyArray;

View file

@ -8,6 +8,7 @@ import androidx.annotation.StringRes;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.util.UUIDMap;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
@ -70,8 +71,10 @@ public abstract class DatabaseImporter {
return read(stream, false);
}
public State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException {
public State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException {
SuFile file = getAppPath();
file.setShell(shell);
try (InputStream stream = SuFileInputStream.open(file)) {
return read(stream, true);
} catch (IOException e) {

View file

@ -13,6 +13,7 @@ import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
import java.io.InputStream;
@ -31,7 +32,8 @@ public class GoogleAuthImporter extends DatabaseImporter {
@Override
protected SuFile getAppPath() throws PackageManager.NameNotFoundException {
return getAppPath(_pkgName, _subPath);
SuFile file = getAppPath(_pkgName, _subPath);
return file;
}
@Override
@ -55,8 +57,10 @@ public class GoogleAuthImporter extends DatabaseImporter {
}
@Override
public DatabaseImporter.State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException {
public DatabaseImporter.State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException {
SuFile path = getAppPath();
path.setShell(shell);
final Context context = requireContext();
SqlImporterHelper helper = new SqlImporterHelper(context);
List<Entry> entries = helper.read(Entry.class, path, "accounts");

View file

@ -11,6 +11,7 @@ import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
import java.io.InputStream;
@ -40,8 +41,10 @@ public class MicrosoftAuthImporter extends DatabaseImporter {
}
@Override
public DatabaseImporter.State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException {
public DatabaseImporter.State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException {
SuFile path = getAppPath();
path.setShell(shell);
SqlImporterHelper helper = new SqlImporterHelper(requireContext());
List<Entry> entries = helper.read(Entry.class, path, "accounts");
return new State(entries);

View file

@ -20,12 +20,12 @@ import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.ui.models.ImportEntry;
import com.beemdevelopment.aegis.ui.tasks.RootShellTask;
import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter;
import com.beemdevelopment.aegis.util.UUIDMap;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultRepository;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.topjohnwu.superuser.Shell;
import java.io.File;
import java.io.FileInputStream;
@ -118,25 +118,36 @@ public class ImportEntriesActivity extends AegisActivity {
}
private void startImportApp(@NonNull DatabaseImporter importer) {
// obtain the global root shell and close it immediately after we're done
// TODO: find a way to use SuFileInputStream with Shell.newInstance()
try (Shell shell = Shell.getShell()) {
if (!shell.isRoot()) {
RootShellTask task = new RootShellTask(this, shell -> {
if (isFinishing()) {
return;
}
if (shell == null || !shell.isRoot()) {
Toast.makeText(this, R.string.root_error, Toast.LENGTH_SHORT).show();
finish();
return;
}
DatabaseImporter.State state = importer.readFromApp();
processImporterState(state);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
Toast.makeText(this, R.string.app_lookup_error, Toast.LENGTH_SHORT).show();
finish();
} catch (IOException | DatabaseImporterException e) {
e.printStackTrace();
Dialogs.showErrorDialog(this, R.string.reading_file_error, e, (dialog, which) -> finish());
}
try {
DatabaseImporter.State state = importer.readFromApp(shell);
processImporterState(state);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
Toast.makeText(this, R.string.app_lookup_error, Toast.LENGTH_SHORT).show();
finish();
} catch (DatabaseImporterException e) {
e.printStackTrace();
Dialogs.showErrorDialog(this, R.string.reading_file_error, e, (dialog, which) -> finish());
} finally {
try {
shell.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
task.execute(this);
}
private void processImporterState(DatabaseImporter.State state) {

View file

@ -0,0 +1,31 @@
package com.beemdevelopment.aegis.ui.tasks;
import android.content.Context;
import com.beemdevelopment.aegis.R;
import com.topjohnwu.superuser.Shell;
public class RootShellTask extends ProgressDialogTask<Object, Shell> {
private final Callback _cb;
public RootShellTask(Context context, Callback cb) {
super(context, context.getString(R.string.requesting_root_access));
_cb = cb;
}
@Override
protected Shell doInBackground(Object... params) {
// To access other app's internal storage directory, run libsu commands inside the global mount namespace
return Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER).build();
}
@Override
protected void onPostExecute(Shell shell) {
super.onPostExecute(shell);
_cb.onTaskFinished(shell);
}
public interface Callback {
void onTaskFinished(Shell shell);
}
}

View file

@ -171,6 +171,7 @@
<string name="encrypting_vault">Encrypting the vault</string>
<string name="exporting_vault">Exporting the vault</string>
<string name="reading_file">Reading file</string>
<string name="requesting_root_access">Requesting root access</string>
<string name="analyzing_qr">Analyzing QR code</string>
<string name="analyzing_qr_multiple">Analyzing QR code %d/%d (%s)</string>
<plurals name="added_new_entries">
@ -237,7 +238,7 @@
<string name="reading_file_error">An error occurred while trying to read the file</string>
<string name="app_lookup_error">Error: App is not installed</string>
<string name="app_version_error">The version of %s that\'s installed is not supported. Recent versions have started encrypting some of the files in the internal storage directory, making Aegis unable to access the information it needs for the import. Attempting to import is likely to result in an error. Would you like to continue anyway?</string>
<string name="root_error">Error: unable to obtain root access</string>
<string name="root_error">Error: Unable to obtain root access</string>
<plurals name="imported_entries_count">
<item quantity="one">Imported %d entry</item>
<item quantity="other">Imported %d entries</item>