Skip to content

Commit

Permalink
Open Audio Player in another unprivileged process
Browse files Browse the repository at this point in the history
Even if App Manager runs in the privileged mode, the Audio Player will run in a
separate unprivileged process, reducing attack surface for third-party malicious
apps as well as preventing the player from closing in case the system runs out
of resources (the latter is still in progress).

Note: The process retains all the permissions granted to App Manager. So, it's
NOT an effective process isolation technique, and various attack surface are
still present. In an ideal process isolation technique, the audio should be
played using an isolated service with limited permissions. Such technique will
be explored in the future.

Signed-off-by: Muntashir Al-Islam <[email protected]>
  • Loading branch information
MuntashirAkon committed Nov 18, 2024
1 parent 291fa8c commit b42162d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 50 deletions.
5 changes: 3 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1117,10 +1117,11 @@
<activity
android:name=".viewer.audio.AudioPlayerActivity"
android:excludeFromRecents="true"
android:exported="false"
android:exported="true"
android:label="@string/title_audio_player"
android:taskAffinity=".misc.music.AudioPlayerActivity"
android:theme="@style/AppTheme.TransparentBackground">
android:theme="@style/AppTheme.TransparentBackground"
android:process=":audio_player">
<intent-filter>
<action android:name="android.intent.action.SEND" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,20 @@
package io.github.muntashirakon.AppManager;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;

import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.CallSuper;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.biometric.BiometricPrompt;
import androidx.biometric.BiometricPrompt.AuthenticationResult;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;

import java.util.ArrayList;
Expand All @@ -44,7 +36,7 @@
import io.github.muntashirakon.AppManager.settings.SecurityAndOpsViewModel;
import io.github.muntashirakon.AppManager.utils.UIUtils;

public abstract class BaseActivity extends AppCompatActivity {
public abstract class BaseActivity extends PerProcessActivity {
public static final String TAG = BaseActivity.class.getSimpleName();

private static final HashMap<String, Boolean> ASKED_PERMISSIONS = new HashMap<String, Boolean>() {{
Expand Down Expand Up @@ -75,7 +67,6 @@ public abstract class BaseActivity extends AppCompatActivity {

@Override
protected final void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
if (Ops.isAuthenticated()) {
Log.d(TAG, "Already authenticated.");
Expand Down Expand Up @@ -160,20 +151,6 @@ public void onAuthenticationFailed() {

protected abstract void onAuthenticated(@Nullable Bundle savedInstanceState);

public boolean getTransparentBackground() {
return false;
}

@CallSuper
@SuppressLint("RestrictedApi")
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (menu instanceof MenuBuilder) {
((MenuBuilder) menu).setOptionalIconsVisible(true);
}
return super.onCreateOptionsMenu(menu);
}

@CallSuper
@Override
protected void onStart() {
Expand All @@ -196,24 +173,6 @@ protected void onStop() {
super.onStop();
}

protected void clearBackStack() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(0);
fragmentManager.popBackStackImmediate(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}

protected void removeCurrentFragment(@IdRes int id) {
Fragment fragment = getSupportFragmentManager().findFragmentById(id);
if (fragment != null) {
getSupportFragmentManager()
.beginTransaction()
.remove(fragment)
.commit();
}
}

private void authenticate() {
// Check KeyStore
if (KeyStoreManager.hasKeyStorePassword()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-3.0-or-later

package io.github.muntashirakon.AppManager;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.Menu;

import androidx.activity.EdgeToEdge;
import androidx.annotation.CallSuper;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;

public class PerProcessActivity extends AppCompatActivity {
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
}

public boolean getTransparentBackground() {
return false;
}

@CallSuper
@SuppressLint("RestrictedApi")
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (menu instanceof MenuBuilder) {
((MenuBuilder) menu).setOptionalIconsVisible(true);
}
return super.onCreateOptionsMenu(menu);
}

protected void clearBackStack() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(0);
fragmentManager.popBackStackImmediate(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}

protected void removeCurrentFragment(@IdRes int id) {
Fragment fragment = getSupportFragmentManager().findFragmentById(id);
if (fragment != null) {
getSupportFragmentManager()
.beginTransaction()
.remove(fragment)
.commit();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import java.util.LinkedHashSet;
import java.util.Locale;

import io.github.muntashirakon.AppManager.BaseActivity;
import io.github.muntashirakon.AppManager.PerProcessActivity;
import io.github.muntashirakon.AppManager.settings.Prefs;
import io.github.muntashirakon.AppManager.utils.LangUtils;

Expand Down Expand Up @@ -116,8 +116,8 @@ public static void applyConfigurationChangesToActivities() {
private static class ActivityAppearanceCallback implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
if (activity instanceof BaseActivity) {
boolean transparentBackground = ((BaseActivity) activity).getTransparentBackground();
if (activity instanceof PerProcessActivity) {
boolean transparentBackground = ((PerProcessActivity) activity).getTransparentBackground();
activity.setTheme(transparentBackground
? Prefs.Appearance.getTransparentAppTheme()
: Prefs.Appearance.getAppTheme());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@

import java.util.List;

import io.github.muntashirakon.AppManager.BaseActivity;
import io.github.muntashirakon.AppManager.PerProcessActivity;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.intercept.IntentCompat;

public class AudioPlayerActivity extends BaseActivity {
public class AudioPlayerActivity extends PerProcessActivity {
@Override
public boolean getTransparentBackground() {
return true;
}

@Override
protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_player);
List<Uri> uriList = IntentCompat.getDataUris(getIntent());
if (uriList == null) {
Expand Down

0 comments on commit b42162d

Please sign in to comment.