Skip to content

Commit a00d297

Browse files
committed
[Installer] Add option to temporarily disable apk verification during installation
Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
1 parent 11b1b5b commit a00d297

11 files changed

Lines changed: 94 additions & 6 deletions

File tree

app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/InstallerOptions.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public static InstallerOptions getDefault() {
4343
private int mPackageSource;
4444
private int mInstallScenario;
4545
private boolean mRequestUpdateOwnership;
46+
private boolean mDisableApkVerification;
4647
private boolean mSignApkFiles;
4748
private boolean mForceDexOpt;
4849
private boolean mBlockTrackers;
@@ -63,6 +64,7 @@ private InstallerOptions() {
6364
: PackageManager.INSTALL_SCENARIO_FAST;
6465
}
6566
mRequestUpdateOwnership = Prefs.Installer.requestUpdateOwnership();
67+
mDisableApkVerification = Prefs.Installer.isDisableApkVerification();
6668
mSignApkFiles = Prefs.Installer.canSignApk();
6769
mForceDexOpt = Prefs.Installer.forceDexOpt();
6870
mBlockTrackers = Prefs.Installer.blockTrackers();
@@ -78,6 +80,7 @@ protected InstallerOptions(@NonNull Parcel in) {
7880
mPackageSource = in.readInt();
7981
mInstallScenario = in.readInt();
8082
mRequestUpdateOwnership = ParcelCompat.readBoolean(in);
83+
mDisableApkVerification = ParcelCompat.readBoolean(in);
8184
mSignApkFiles = ParcelCompat.readBoolean(in);
8285
mForceDexOpt = ParcelCompat.readBoolean(in);
8386
mBlockTrackers = ParcelCompat.readBoolean(in);
@@ -93,6 +96,7 @@ public void copy(@NonNull InstallerOptions options) {
9396
mPackageSource = options.mPackageSource;
9497
mInstallScenario = options.mInstallScenario;
9598
mRequestUpdateOwnership = options.mRequestUpdateOwnership;
99+
mDisableApkVerification = options.mDisableApkVerification;
96100
mSignApkFiles = options.mSignApkFiles;
97101
mForceDexOpt = options.mForceDexOpt;
98102
mBlockTrackers = options.mBlockTrackers;
@@ -109,6 +113,7 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
109113
dest.writeInt(mPackageSource);
110114
dest.writeInt(mInstallScenario);
111115
ParcelCompat.writeBoolean(dest, mRequestUpdateOwnership);
116+
ParcelCompat.writeBoolean(dest, mDisableApkVerification);
112117
ParcelCompat.writeBoolean(dest, mSignApkFiles);
113118
ParcelCompat.writeBoolean(dest, mForceDexOpt);
114119
ParcelCompat.writeBoolean(dest, mBlockTrackers);
@@ -125,6 +130,7 @@ protected InstallerOptions(@NonNull JSONObject jsonObject) throws JSONException
125130
mPackageSource = jsonObject.getInt("package_source");
126131
mInstallScenario = jsonObject.getInt("install_scenario");
127132
mRequestUpdateOwnership = jsonObject.getBoolean("request_update_ownership");
133+
mDisableApkVerification = jsonObject.getBoolean("disable_apk_verification");
128134
mSignApkFiles = jsonObject.getBoolean("sign_apk_files");
129135
mForceDexOpt = jsonObject.getBoolean("force_dex_opt");
130136
mBlockTrackers = jsonObject.getBoolean("block_trackers");
@@ -143,6 +149,7 @@ public JSONObject serializeToJson() throws JSONException {
143149
jsonObject.put("package_source", mPackageSource);
144150
jsonObject.put("install_scenario", mInstallScenario);
145151
jsonObject.put("request_update_ownership", mRequestUpdateOwnership);
152+
jsonObject.put("disable_apk_verification", mDisableApkVerification);
146153
jsonObject.put("sign_apk_files", mSignApkFiles);
147154
jsonObject.put("force_dex_opt", mForceDexOpt);
148155
jsonObject.put("block_trackers", mBlockTrackers);
@@ -245,6 +252,14 @@ public void requestUpdateOwnership(boolean update) {
245252
mRequestUpdateOwnership = update;
246253
}
247254

255+
public boolean isDisableApkVerification() {
256+
return mDisableApkVerification;
257+
}
258+
259+
public void setDisableApkVerification(boolean disableApkVerification) {
260+
mDisableApkVerification = disableApkVerification;
261+
}
262+
248263
public boolean isSignApkFiles() {
249264
return mSignApkFiles;
250265
}

app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/InstallerOptionsFragment.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
130130
mPackageSourceSpinner = mDialogView.findViewById(R.id.package_source);
131131
mInstallerAppLayout = mDialogView.findViewById(R.id.installer);
132132
mInstallerAppField = Objects.requireNonNull(mInstallerAppLayout.getEditText());
133+
MaterialSwitch disableVerificationSwitch = mDialogView.findViewById(R.id.action_disable_verification);
133134
MaterialSwitch setOriginSwitch = mDialogView.findViewById(R.id.action_set_origin);
134135
MaterialSwitch reqUpdateOwnershipSwitch = mDialogView.findViewById(R.id.action_update_ownership);
135136
MaterialSwitch signApkSwitch = mDialogView.findViewById(R.id.action_sign_apk);
@@ -144,6 +145,9 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
144145
initInstallLocationSpinner();
145146
initPackageSourceSpinner();
146147
initInstallerAppSpinner();
148+
disableVerificationSwitch.setEnabled(SelfPermissions.isSystemOrRootOrShell());
149+
disableVerificationSwitch.setChecked(mOptions.isDisableApkVerification());
150+
disableVerificationSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> mOptions.setDisableApkVerification(isChecked));
147151
setOriginSwitch.setChecked(mOptions.isSetOriginatingPackage());
148152
setOriginSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> mOptions.setSetOriginatingPackage(isChecked));
149153
reqUpdateOwnershipSwitch.setVisibility(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ? View.VISIBLE : View.GONE);

app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import android.os.Process;
3232
import android.os.RemoteException;
3333
import android.os.UserHandleHidden;
34+
import android.provider.Settings;
3435

3536
import androidx.annotation.IntDef;
3637
import androidx.annotation.NonNull;
@@ -70,6 +71,7 @@
7071
import io.github.muntashirakon.AppManager.users.Users;
7172
import io.github.muntashirakon.AppManager.utils.BroadcastUtils;
7273
import io.github.muntashirakon.AppManager.utils.ContextUtils;
74+
import io.github.muntashirakon.AppManager.utils.ExUtils;
7375
import io.github.muntashirakon.AppManager.utils.FileUtils;
7476
import io.github.muntashirakon.AppManager.utils.HuaweiUtils;
7577
import io.github.muntashirakon.AppManager.utils.MiuiUtils;
@@ -472,6 +474,8 @@ public final class PackageInstallerCompat {
472474
@RequiresApi(Build.VERSION_CODES.P)
473475
public static final int DELETE_CHATTY = 0x80000000;
474476

477+
public static final String SETTINGS_VERIFIER_VERIFY_ADB_INSTALLS = "verifier_verify_adb_installs";
478+
475479
public interface OnInstallListener {
476480
@WorkerThread
477481
void onStartInstall(int sessionId, String packageName);
@@ -577,11 +581,14 @@ public void onReceive(Context context, @NonNull Intent intent) {
577581
private PackageInstaller.Session mSession;
578582
// MIUI-added: Multiple attempts may be required
579583
int mAttempts = 1;
580-
private final Context mContext = ContextUtils.getContext();
584+
private final Context mContext;
581585
private final boolean mHasInstallPackagePermission;
586+
private int mLastVerifyAdbInstallsResult;
582587

583588
private PackageInstallerCompat() {
589+
mContext = ContextUtils.getContext();
584590
mHasInstallPackagePermission = SelfPermissions.checkSelfOrRemotePermission(Manifest.permission.INSTALL_PACKAGES);
591+
mLastVerifyAdbInstallsResult = -1;
585592
}
586593

587594
public void setOnInstallListener(@Nullable OnInstallListener onInstallListener) {
@@ -641,7 +648,7 @@ public boolean install(@NonNull ApkFile apkFile, @NonNull List<String> selectedS
641648
if (!openSession(userId, installFlags, options.getInstallerName(),
642649
options.getInstallLocation(), originatingPackage, originatingUri,
643650
options.getInstallScenario(), options.getPackageSource(),
644-
options.requestUpdateOwnership())) {
651+
options.requestUpdateOwnership(), options.isDisableApkVerification())) {
645652
return false;
646653
}
647654
List<ApkFile.Entry> selectedEntries = new ArrayList<>();
@@ -682,6 +689,7 @@ public boolean install(@NonNull ApkFile apkFile, @NonNull List<String> selectedS
682689
return commit(userId);
683690
} finally {
684691
unregisterReceiver();
692+
restoreVerifySettings();
685693
}
686694
}
687695

@@ -718,7 +726,7 @@ public boolean install(@NonNull Path[] apkFiles, @NonNull String packageName, @N
718726
if (!openSession(userId, installFlags, options.getInstallerName(),
719727
options.getInstallLocation(), originatingPackage, originatingUri,
720728
options.getInstallScenario(), options.getPackageSource(),
721-
options.requestUpdateOwnership())) {
729+
options.requestUpdateOwnership(), options.isDisableApkVerification())) {
722730
return false;
723731
}
724732
long totalSize = 0;
@@ -745,6 +753,7 @@ public boolean install(@NonNull Path[] apkFiles, @NonNull String packageName, @N
745753
return commit(userId);
746754
} finally {
747755
unregisterReceiver();
756+
restoreVerifySettings();
748757
}
749758
}
750759

@@ -810,7 +819,7 @@ private boolean openSession(@UserIdInt int userId, @InstallFlags int installFlag
810819
String installerName, int installLocation,
811820
@Nullable String originatingPackage, @Nullable Uri originatingUri,
812821
int installScenario, int packageSource,
813-
boolean requestUpdateOwnership) {
822+
boolean requestUpdateOwnership, boolean disableVerification) {
814823
// Changing package installer in stock Huawei with UID 2000 does not work
815824
boolean canChangeInstaller = mHasInstallPackagePermission && (!HuaweiUtils.isStockHuawei() || Users.getSelfOrRemoteUid() != Ops.SHELL_UID);
816825
String requestedInstallerPackageName = canChangeInstaller ? installerName : null;
@@ -827,6 +836,24 @@ private boolean openSession(@UserIdInt int userId, @InstallFlags int installFlag
827836
cleanOldSessions();
828837
// Create install session
829838
SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
839+
if (disableVerification) {
840+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
841+
&& SelfPermissions.isSystemOrRootOrShell()) {
842+
// This disables verification for this UID temporarily
843+
ExUtils.exceptionAsIgnored(() ->
844+
mPackageInstaller.disableVerificationForUid(Users.getSelfOrRemoteUid()));
845+
}
846+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
847+
installFlags |= INSTALL_DISABLE_VERIFICATION;
848+
}
849+
// In addition, we may also want to use the traditional methods
850+
if (SelfPermissions.isShell()) {
851+
mLastVerifyAdbInstallsResult = Settings.Global.getInt(mContext.getContentResolver(), SETTINGS_VERIFIER_VERIFY_ADB_INSTALLS, 1);
852+
if (mLastVerifyAdbInstallsResult != 0) {
853+
Settings.Global.putInt(mContext.getContentResolver(), SETTINGS_VERIFIER_VERIFY_ADB_INSTALLS, 0);
854+
}
855+
}
856+
}
830857
Refine.<PackageInstallerHidden.SessionParams>unsafeCast(sessionParams).installFlags |= installFlags;
831858
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
832859
Refine.<PackageInstallerHidden.SessionParams>unsafeCast(sessionParams).installerPackageName = requestedInstallerPackageName;
@@ -890,9 +917,19 @@ private boolean openSession(@UserIdInt int userId, @InstallFlags int installFlag
890917
return true;
891918
}
892919

920+
private void restoreVerifySettings() {
921+
if (mLastVerifyAdbInstallsResult == 1) {
922+
int val = Settings.Global.getInt(mContext.getContentResolver(), SETTINGS_VERIFIER_VERIFY_ADB_INSTALLS, 1);
923+
if (val != 1) {
924+
// Restore value
925+
Settings.Global.putInt(mContext.getContentResolver(), SETTINGS_VERIFIER_VERIFY_ADB_INSTALLS, 1);
926+
}
927+
}
928+
}
929+
893930
@InstallFlags
894931
private static int getInstallFlags(@UserIdInt int userId) {
895-
int flags = INSTALL_ALLOW_TEST | INSTALL_REPLACE_EXISTING;
932+
int flags = INSTALL_FROM_ADB | INSTALL_ALLOW_TEST | INSTALL_REPLACE_EXISTING;
896933
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
897934
flags |= INSTALL_FULL_APP;
898935
}

app/src/main/java/io/github/muntashirakon/AppManager/self/SelfPermissions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ public static boolean checkCrossUserPermission(@UserIdInt int userId, boolean re
241241
|| checkSelfOrRemotePermission(ManifestCompat.permission.INTERACT_ACROSS_USERS, callingUid);
242242
}
243243

244+
public static boolean isShell() {
245+
return Users.getSelfOrRemoteUid() == Ops.SHELL_UID;
246+
}
247+
244248
public static boolean isSystem() {
245249
return Users.getSelfOrRemoteUid() == Ops.SYSTEM_UID;
246250
}

app/src/main/java/io/github/muntashirakon/AppManager/settings/InstallerPreferences.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
157157
.show();
158158
return true;
159159
});
160+
// Disable verification
161+
SwitchPreferenceCompat disableVerification = Objects.requireNonNull(findPreference("installer_disable_verification"));
162+
disableVerification.setEnabled(SelfPermissions.isSystemOrRootOrShell());
163+
disableVerification.setChecked(Prefs.Installer.isDisableApkVerification());
160164
// Update ownership
161165
SwitchPreferenceCompat updateOwnership = Objects.requireNonNull(findPreference("installer_update_ownership"));
162166
updateOwnership.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE);

app/src/main/java/io/github/muntashirakon/AppManager/settings/Prefs.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,10 @@ public static boolean requestUpdateOwnership() {
440440
// Shell default is false
441441
return AppPref.getBoolean(AppPref.PrefKey.PREF_INSTALLER_UPDATE_OWNERSHIP_BOOL);
442442
}
443+
444+
public static boolean isDisableApkVerification() {
445+
return AppPref.getBoolean(AppPref.PrefKey.PREF_INSTALLER_DISABLE_VERIFICATION_BOOL);
446+
}
443447
}
444448

445449
public static final class LogViewer {

app/src/main/java/io/github/muntashirakon/AppManager/utils/AppPref.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public enum PrefKey {
9999
PREF_INSTALLER_BLOCK_TRACKERS_BOOL,
100100
PREF_INSTALLER_ALWAYS_ON_BACKGROUND_BOOL,
101101
PREF_INSTALLER_DEFAULT_PKG_SOURCE_INT,
102+
PREF_INSTALLER_DISABLE_VERIFICATION_BOOL,
102103
PREF_INSTALLER_DISPLAY_CHANGES_BOOL,
103104
PREF_INSTALLER_FORCE_DEX_OPT_BOOL,
104105
PREF_INSTALLER_INSTALL_LOCATION_INT,
@@ -381,6 +382,7 @@ public Object getDefaultValue(@NonNull PrefKey key) {
381382
case PREF_GLOBAL_BLOCKING_ENABLED_BOOL:
382383
case PREF_INSTALLER_ALWAYS_ON_BACKGROUND_BOOL:
383384
case PREF_INSTALLER_BLOCK_TRACKERS_BOOL:
385+
case PREF_INSTALLER_DISABLE_VERIFICATION_BOOL:
384386
case PREF_INSTALLER_FORCE_DEX_OPT_BOOL:
385387
case PREF_INSTALLER_SIGN_APK_BOOL:
386388
case PREF_BACKUP_ANDROID_KEYSTORE_BOOL:

app/src/main/res/layout/dialog_installer_options.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@
5757
android:layout_marginBottom="8dp"
5858
android:hint="@string/pref_default_package_source" />
5959

60+
<com.google.android.material.materialswitch.MaterialSwitch
61+
android:id="@+id/action_disable_verification"
62+
style="@style/Widget.AppTheme.MaterialSwitch"
63+
android:layout_width="match_parent"
64+
android:layout_height="wrap_content"
65+
android:text="@string/pref_disable_apk_verification" />
66+
6067
<com.google.android.material.materialswitch.MaterialSwitch
6168
android:id="@+id/action_update_ownership"
6269
style="@style/Widget.AppTheme.MaterialSwitch"

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,4 +1533,6 @@
15331533
<string name="package_source_local_file">Local file</string>
15341534
<string name="package_source_downloaded_file">Downloaded file</string>
15351535
<string name="package_source_other">Other</string>
1536+
<string name="pref_disable_apk_verification">Disable package verification</string>
1537+
<string name="pref_disable_apk_verification_description">Ask the installer to skip running the package verifier (e.g., Play Protect) during the installation.</string>
15361538
</resources>

app/src/main/res/xml/preferences_installer.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
app:title="@string/installer_app"
4646
tools:summary="App Manager" />
4747

48+
<SwitchPreferenceCompat
49+
app:iconSpaceReserved="false"
50+
app:key="installer_disable_verification"
51+
app:summary="@string/pref_disable_apk_verification_description"
52+
app:title="@string/pref_disable_apk_verification" />
53+
4854
<SwitchPreferenceCompat
4955
app:iconSpaceReserved="false"
5056
app:key="installer_update_ownership"

0 commit comments

Comments
 (0)