Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/platform_emulator_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Platform Emulator Release

on:
push:
branches:
- 'master'
- 'AppManager-*'
paths-ignore:
- 'fastlane/**'
- 'scripts/**'
- '*.md'
pull_request:
branches: [ master ]
paths-ignore:
- 'fastlane/**'
- 'scripts/**'
- '*.md'
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone the repository
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '21'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Get app version
run: echo "APP_VERSION=v$(cat ./app/build.gradle | grep -m1 versionName | awk -F \" '{print $2}')" >> $GITHUB_ENV
- name: Get app name
run: echo "APP_NAME=AppManager_${{ env.APP_VERSION }}-PLATFORM#${{ github.run_number }}" >> $GITHUB_ENV
- name: Inject run number
run: sed -i -e 's|versionName "\([0-9\.]\+\)"|versionName "\1-${{ github.run_number }}"|' ./app/build.gradle
- name: Build with Gradle
run: ./gradlew packagePlatformReleaseUniversalApk -Pandroid.injected.signing.store.file=$PWD/app/dev_keystore.jks -Pandroid.injected.signing.store.password='kJCp!Bda#PBdN2RLK%yMK@hatq&69E' -Pandroid.injected.signing.key.alias=key1 -Pandroid.injected.signing.key.password='kJCp!Bda#PBdN2RLK%yMK@hatq&69E'
- name: Rename the APK file
run: mv ./app/build/outputs/apk_from_bundle/platformRelease/app-platform-release-universal.apk ./${{ env.APP_NAME }}.apk
- name: Store generated APK file
uses: actions/upload-artifact@v4
with:
name: ${{ env.APP_NAME }}
path: ./${{ env.APP_NAME }}.apk
36 changes: 36 additions & 0 deletions Android.bp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
android_app_import {
name: "AppManager",
owner: "Muntashirakon",
apk: "app/build/outputs/apk_from_bundle/platformRelease/app-platform-release-universal-unsigned.apk",
certificate: "platform",
dex_preopt: {
enabled: false,
},
privileged: true,
presigned: false,
overrides: ["PackageInstaller"],
required: [
"io.github.muntashirakon.AppManager_sysconfig.xml",
"default-permissions_io.github.muntashirakon.AppManager.xml",
"privapp-permissions_io.github.muntashirakon.AppManager.xml"
],
}


prebuilt_etc {
name: "io.github.muntashirakon.AppManager_sysconfig.xml",
src: "platform/sysconfig.xml",
sub_dir: "sysconfig",
}

prebuilt_etc {
name: "privapp-permissions_io.github.muntashirakon.AppManager.xml",
src: "platform/permissions.xml",
sub_dir: "permissions",
}

prebuilt_etc {
name: "default-permissions_io.github.muntashirakon.AppManager.xml",
src: "platform/default-permissions.xml",
sub_dir: "default-permissions"
}
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.apk
.cxx
*~
/platform
25 changes: 22 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,40 @@ android {
keyPassword 'kJCp!Bda#PBdN2RLK%yMK@hatq&69E'
keyAlias 'key0'
}
platform { // Platform signing key used by eng rom images https://github.com/LineageOS/android_build/blob/lineage-23.2/target/product/security/platform.pk8
storeFile file('dev_keystore.jks')
storePassword 'kJCp!Bda#PBdN2RLK%yMK@hatq&69E'
keyPassword 'kJCp!Bda#PBdN2RLK%yMK@hatq&69E'
keyAlias 'key1'
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
resValue "string", "app_name", "App Manager"
}
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
signingConfig signingConfigs.debug
resValue "string", "app_name", "AM Debug"
// Value must be changed manually for platform debug
// TODO 11FEB26 make debug signingConfig selection automatic.
signingConfig = signingConfigs.platform;
}
}
flavorDimensions "version"
productFlavors {
standard {
dimension "version"
}
platform {
dimension "version"
applicationIdSuffix ".platform"
versionNameSuffix "-PLATFORM"
}
}

lint {
checkReleaseBuilds false
abortOnError false
Expand Down
Binary file modified app/dev_keystore.jks
Binary file not shown.
25 changes: 24 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,17 @@
<uses-permission
android:name="android.permission.WRITE_SECURE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.PACKAGE_VERIFICATION_AGENT"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
<uses-permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="com.termux.permission.RUN_COMMAND" />

<application
Expand Down Expand Up @@ -600,6 +610,14 @@
android:launchMode="singleInstance"
android:taskAffinity=""
android:theme="@style/AppTheme.TransparentBackground">
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PRE_APPROVAL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />

Expand Down Expand Up @@ -728,7 +746,12 @@
android:label="@string/interceptor"
android:taskAffinity=""
android:windowSoftInputMode="stateUnchanged">

<intent-filter>
<action android:name="android.intent.action.DELETE" />
<action android:name="android.intent.action.UNINSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
<!-- matching any given mime type -->
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.lsposed.hiddenapibypass.HiddenApiBypass;

import java.security.Security;
import java.util.Objects;

import dalvik.system.ZipPathValidator;
import io.github.muntashirakon.AppManager.misc.AMExceptionHandler;
Expand Down Expand Up @@ -48,7 +49,8 @@ public void onCreate() {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !Utils.isRoboUnitTest()) {
//noinspection ConstantValue Don't use the HiddenApiBypass in Platform as we are whilelisted and it caused infighting if enabled
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !Utils.isRoboUnitTest() && !Objects.equals(BuildConfig.FLAVOR, "platform")) {
HiddenApiBypass.addHiddenApiExemptions("L");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,26 @@ 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) {
mTitleBuilder.setTitle(R.string.confirm_installation)
.setStartIcon(R.drawable.ic_get_app)
.setSubtitle(null)
.setEndIcon(null, null);
// 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,17 +25,21 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstallerSession;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstallerHidden;
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;
import android.text.style.RelativeSizeSpan;
import android.view.View;
import android.view.WindowHidden;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
Expand All @@ -45,10 +51,12 @@
import androidx.core.content.pm.PackageInfoCompat;
import androidx.lifecycle.ViewModelProvider;

import java.io.File;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;

import dev.rikka.tools.refine.Refine;
import io.github.muntashirakon.AppManager.BaseActivity;
import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.R;
Expand Down Expand Up @@ -109,11 +117,14 @@ public static Intent getLaunchableInstance(@NonNull Context context, ApkSource a
@NonNull
public static Intent getLaunchableInstance(@NonNull Context context, @NonNull String packageName) {
Intent intent = new Intent(context, PackageInstallerActivity.class);
intent.setData(Uri.parse("package:" + packageName));
intent.putExtra(EXTRA_INSTALL_EXISTING, true);
intent.putExtra(EXTRA_PACKAGE_NAME, packageName);
return intent;
}

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 ACTION_PACKAGE_INSTALLED = BuildConfig.APPLICATION_ID + ".action.PACKAGE_INSTALLED";

private int mSessionId = -1;
Expand Down Expand Up @@ -194,6 +205,11 @@ protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
onNewIntent(intent);
return;
}
// Take a page out of the official installer hide all other overlays that aren't the system
if (SelfPermissions.checkSelfPermission("android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS")) {
Refine.<WindowHidden>unsafeCast(getWindow()).addSystemFlags(1 << 19);
}

mModel = new ViewModelProvider(this).get(PackageInstallerViewModel.class);
if (!bindService(
new Intent(this, PackageInstallerService.class), mServiceConnection, BIND_AUTO_CREATE)) {
Expand All @@ -203,7 +219,18 @@ protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
mApkQueue.addAll(ApkQueueItem.fromIntent(intent, Utils.getRealReferrer(this)));

}
ApkSource apkSource = IntentCompat.getUnwrappedParcelableExtra(intent, EXTRA_APK_FILE_LINK, ApkSource.class);
ApkSource apkSource;
if (ACTION_CONFIRM_INSTALL.equals(intent.getAction()) || ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())) {
mSessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
try {
Uri uri = Uri.fromFile(new File(Refine.<PackageInstallerHidden.SessionInfo>unsafeCast(PackageManagerCompat.getPackageInstaller().getSessionInfo(mSessionId)).getResolvedBaseApkPath()));
apkSource = ApkSource.getApkSource(uri, null);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} else {
apkSource = IntentCompat.getUnwrappedParcelableExtra(intent, EXTRA_APK_FILE_LINK, ApkSource.class);
}
if (apkSource != null) {
synchronized (mApkQueue) {
mApkQueue.add(ApkQueueItem.fromApkSource(apkSource));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,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,
Expand Down Expand Up @@ -813,6 +816,15 @@ 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);
}
}

@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean openSession(@UserIdInt int userId, @InstallFlags int installFlags,
Expand Down
Loading
Loading