diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c9634c8341e..90182bfc3ac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -147,7 +147,9 @@
tools:ignore="ProtectedPermissions" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/InstallerDialogHelper.java b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/InstallerDialogHelper.java
index 7797732eb81..50565b07166 100644
--- a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/InstallerDialogHelper.java
+++ b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/InstallerDialogHelper.java
@@ -3,6 +3,7 @@
package io.github.muntashirakon.AppManager.apk.installer;
import android.content.Context;
+import android.content.pm.PackageInstaller;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.Button;
@@ -146,6 +147,22 @@ public void showInstallConfirmationDialog(@StringRes int installButtonRes,
mMessage.setText(R.string.install_app_message);
mFragmentContainer.setVisibility(View.GONE);
}
+ public void showSessionConfirmationDialog(@StringRes int installButtonRes,
+ @NonNull OnClickButtonsListener onClickButtonsListener) {
+ // Buttons
+ mNeutralBtn.setVisibility(View.GONE);
+ mPositiveBtn.setVisibility(View.VISIBLE);
+ mPositiveBtn.setText(installButtonRes);
+ mPositiveBtn.setOnClickListener(v -> onClickButtonsListener.triggerInstall());
+ mNegativeBtn.setVisibility(View.VISIBLE);
+ mNegativeBtn.setText(R.string.cancel);
+ mNegativeBtn.setOnClickListener(v -> onClickButtonsListener.triggerCancel());
+ // Body
+ mLayout.setVisibility(View.GONE);
+ mMessage.setVisibility(View.VISIBLE);
+ mMessage.setText(R.string.install_app_message);
+ mFragmentContainer.setVisibility(View.GONE);
+ }
public void showApkChooserDialog(@StringRes int installButtonRes, Fragment fragment,
@NonNull OnClickButtonsListener onClickButtonsListener,
diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerActivity.java b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerActivity.java
index 91638af4907..93b0ec9ac3a 100644
--- a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerActivity.java
+++ b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerActivity.java
@@ -2,6 +2,8 @@
package io.github.muntashirakon.AppManager.apk.installer;
+import static io.github.muntashirakon.AppManager.apk.installer.PackageInstallerCompat.ACTION_CONFIRM_INSTALL;
+import static io.github.muntashirakon.AppManager.apk.installer.PackageInstallerCompat.ACTION_CONFIRM_PRE_APPROVAL;
import static io.github.muntashirakon.AppManager.apk.installer.PackageInstallerCompat.STATUS_FAILURE_ABORTED;
import static io.github.muntashirakon.AppManager.apk.installer.PackageInstallerCompat.STATUS_FAILURE_BLOCKED;
import static io.github.muntashirakon.AppManager.apk.installer.PackageInstallerCompat.STATUS_FAILURE_CONFLICT;
@@ -23,12 +25,14 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageInstaller;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandleHidden;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -43,6 +47,7 @@
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.PackageInfoCompat;
+import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import java.util.LinkedList;
@@ -116,7 +121,7 @@ public static Intent getLaunchableInstance(@NonNull Context context, @NonNull St
private static final String EXTRA_APK_FILE_LINK = "link";
public static final String EXTRA_INSTALL_EXISTING = "install_existing";
- public static final String EXTRA_PACKAGE_NAME = "pkg";
+ public static final String EXTRA_PACKAGE_NAME = PackageInstaller.EXTRA_PACKAGE_NAME;
public static final String ACTION_PACKAGE_INSTALLED = BuildConfig.APPLICATION_ID + ".action.PACKAGE_INSTALLED";
private int mSessionId = -1;
@@ -197,6 +202,11 @@ protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
onNewIntent(intent);
return;
}
+ if (ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()) ||
+ ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
+ onSessionIntent(intent);
+ return;
+ }
mModel = new ViewModelProvider(this).get(PackageInstallerViewModel.class);
if (!bindService(
new Intent(this, PackageInstallerService.class), mServiceConnection, BIND_AUTO_CREATE)) {
@@ -376,12 +386,55 @@ private void launchInstallerService() {
goToNext();
}
}
+ private void onSessionIntent(@NonNull Intent intent) {
+ mSessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
+ IPackageInstaller installer;
+ PackageInstaller.SessionInfo sessionInfo;
+ PackageInstallerCompat installerCompat = PackageInstallerCompat.getNewInstance();
+ try {
+ installer = PackageManagerCompat.getPackageInstaller();
+ sessionInfo = installer.getSessionInfo(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to getPackageInstaller", e);
+ throw new RuntimeException(e);
+ }
+
+ InstallerDialogHelper.OnClickButtonsListener onClickButtonsListener = new InstallerDialogHelper.OnClickButtonsListener() {
+ @Override
+ public void triggerInstall() {
+ installerCompat.setPermissionsResult(mSessionId, true);
+ }
+
+ @Override
+ public void triggerCancel() {
+ installerCompat.setPermissionsResult(mSessionId, false);
+ }
+ };
+
+ mModel = new ViewModelProvider(this).get(PackageInstallerViewModel.class);
+ if (!bindService(
+ new Intent(this, PackageInstallerService.class), mServiceConnection, BIND_AUTO_CREATE)) {
+ throw new RuntimeException("Unable to bind PackageInstallerService");
+ }
+
+ // Init fragment
+ mInstallerDialogFragment = new InstallerDialogFragment();
+ mInstallerDialogFragment.setCancelable(false);
+ mInstallerDialogFragment.onCreateDialog(null);
+ mInstallerDialogFragment.showNow(getSupportFragmentManager(), InstallerDialogFragment.TAG);
+ if (ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
+ mDialogHelper.showSessionConfirmationDialog(R.string.install, onClickButtonsListener);
+ } else if (ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())) {
+ mDialogHelper.showSessionConfirmationDialog(R.string.install, onClickButtonsListener);
+ }
+ }
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "New intent called: %s", intent);
setIntent(intent);
+
// Check for action first
if (ACTION_PACKAGE_INSTALLED.equals(intent.getAction())) {
mSessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java
index 13c62649167..111b1be5ff0 100644
--- a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java
+++ b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java
@@ -35,10 +35,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
import androidx.annotation.WorkerThread;
import androidx.core.app.PendingIntentCompat;
import androidx.core.content.ContextCompat;
+import org.jetbrains.annotations.Contract;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -84,6 +87,9 @@ public final class PackageInstallerCompat {
// For rootless installer to prevent PackageInstallerService from hanging
public static final String ACTION_INSTALL_INTERACTION_BEGIN = BuildConfig.APPLICATION_ID + ".action.INSTALL_INTERACTION_BEGIN";
public static final String ACTION_INSTALL_INTERACTION_END = BuildConfig.APPLICATION_ID + ".action.INSTALL_INTERACTION_END";
+ public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
+ public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
+ public static final String EXTRA_UNINSTALL_ALL_USERS = "android.intent.extra.UNINSTALL_ALL_USERS";
@IntDef({
STATUS_SUCCESS,
@@ -797,6 +803,30 @@ private boolean commit(int userId) {
}
return mFinalStatus == PackageInstaller.STATUS_SUCCESS;
}
+ public void setPermissionsResult(int sessionId, boolean accepted) {
+ try {
+ mPackageInstaller = PackageManagerCompat.getPackageInstaller();
+ mPackageInstaller.setPermissionsResult(sessionId, accepted);
+ } catch (RemoteException e) {
+ callFinish(STATUS_FAILURE_SESSION_COMMIT);
+ Log.e(TAG, "SetPermissionsResult: failed to set permission result for given session", e);
+ }
+
+ }
+
+ @RequiresPermission("android.permission.PACKAGE_VERIFICATION_AGENT")
+ public IPackageInstallerSession openSession(final int sessionId) {
+
+ try {
+ mPackageInstaller = PackageManagerCompat.getPackageInstaller();
+ return Refine.unsafeCast(new PackageInstallerHidden.Session(IPackageInstallerSession.Stub.asInterface(
+ new ProxyBinder(mPackageInstaller.openSession(sessionId).asBinder()))));
+ } catch (RemoteException e) {
+ callFinish(STATUS_FAILURE_SESSION_CREATE);
+ Log.e(TAG, "OpenSession: Failed to open install session.", e);
+ return null;
+ }
+ }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean openSession(@UserIdInt int userId, @InstallFlags int installFlags,
diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageUninstallerActivity.java b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageUninstallerActivity.java
new file mode 100644
index 00000000000..fb4b01637c5
--- /dev/null
+++ b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageUninstallerActivity.java
@@ -0,0 +1,115 @@
+package io.github.muntashirakon.AppManager.apk.installer;
+
+import static io.github.muntashirakon.AppManager.utils.UIUtils.displayLongToast;
+
+import android.Manifest;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandleHidden;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+import androidx.core.content.ContextCompat;
+
+import java.util.ArrayList;
+
+import io.github.muntashirakon.AppManager.BaseActivity;
+import io.github.muntashirakon.AppManager.R;
+import io.github.muntashirakon.AppManager.compat.ActivityManagerCompat;
+import io.github.muntashirakon.AppManager.compat.PackageManagerCompat;
+import io.github.muntashirakon.AppManager.self.SelfPermissions;
+import io.github.muntashirakon.AppManager.utils.ThreadUtils;
+import io.github.muntashirakon.AppManager.utils.UIUtils;
+import io.github.muntashirakon.dialog.ScrollableDialogBuilder;
+
+public class PackageUninstallerActivity extends BaseActivity {
+ public static final String TAG = PackageUninstallerActivity.class.getSimpleName();
+ private String mPackageName;
+ private ApplicationInfo mApplicationInfo;
+ private BaseActivity mActivity;
+ private String mAppLabel;
+ private int mUserId;
+ private boolean isUninstallFromAllUsers;
+
+ @Override
+ public boolean getTransparentBackground() {
+ return true;
+ }
+
+
+ @RequiresPermission(android.Manifest.permission.REQUEST_DELETE_PACKAGES)
+ @Override
+ protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
+ this.mPackageName = getIntent().getDataString().replaceFirst("package:", "");
+ this.isUninstallFromAllUsers = getIntent().getBooleanExtra(PackageInstallerCompat.EXTRA_UNINSTALL_ALL_USERS, false);
+ this.mActivity = this;
+ this.mUserId = UserHandleHidden.myUserId();
+ try {
+ this.mApplicationInfo = PackageManagerCompat.getApplicationInfo(mPackageName, ApplicationInfo.FLAG_INSTALLED, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ this.mAppLabel = mApplicationInfo.loadLabel(getPackageManager()).toString();
+ uninstallDialog();
+ }
+
+ private void uninstallDialog() {
+ if (mUserId != UserHandleHidden.myUserId() && !SelfPermissions.checkSelfOrRemotePermission(Manifest.permission.DELETE_PACKAGES)) {
+ // Could be for work profile
+ try {
+ Intent uninstallIntent = new Intent(Intent.ACTION_DELETE);
+ uninstallIntent.setData(Uri.parse("package:" + mPackageName));
+ ActivityManagerCompat.startActivity(uninstallIntent, mUserId);
+ // TODO: 19/8/24 Watch for uninstallation
+ } catch (Throwable th) {
+ UIUtils.displayLongToast("Error: " + th.getLocalizedMessage());
+ }
+ return;
+ }
+ final boolean isSystemApp = (mApplicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ ScrollableDialogBuilder builder = new ScrollableDialogBuilder(mActivity,
+ isSystemApp ? R.string.uninstall_system_app_message : R.string.uninstall_app_message)
+ .setTitle(mAppLabel)
+ // FIXME: 16/6/23 Does it even work without INSTALL_PACKAGES?
+ .setCheckboxLabel(R.string.keep_data_and_app_signing_signatures)
+ .setPositiveButton(R.string.uninstall, (dialog, which, keepData) -> ThreadUtils.postOnBackgroundThread(() -> {
+ PackageInstallerCompat installer = PackageInstallerCompat.getNewInstance();
+ installer.setAppLabel(mAppLabel);
+ boolean uninstalled = installer.uninstall(mPackageName, mUserId, keepData);
+ ThreadUtils.postOnMainThread(() -> {
+ if (uninstalled) {
+ displayLongToast(R.string.uninstalled_successfully, mAppLabel);
+ mActivity.finish();
+ } else {
+ displayLongToast(R.string.failed_to_uninstall, mAppLabel);
+ }
+ });
+ }))
+ .setNegativeButton(R.string.cancel, (dialog, which, keepData) -> {
+ if (dialog != null) dialog.cancel();
+ });
+ if ((mApplicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ builder.setNeutralButton(R.string.uninstall_updates, (dialog, which, keepData) ->
+ ThreadUtils.postOnBackgroundThread(() -> {
+ PackageInstallerCompat installer = PackageInstallerCompat.getNewInstance();
+ installer.setAppLabel(mAppLabel);
+ boolean isSuccessful = installer.uninstall(mPackageName, UserHandleHidden.USER_ALL, keepData);
+ if (isSuccessful) {
+ ThreadUtils.postOnMainThread(() -> displayLongToast(R.string.update_uninstalled_successfully, mAppLabel));
+ } else {
+ ThreadUtils.postOnMainThread(() -> displayLongToast(R.string.failed_to_uninstall_updates, mAppLabel));
+ }
+ }));
+ }
+ builder.show();
+ }
+}