diff --git a/.github/workflows/platform_emulator_release.yml b/.github/workflows/platform_emulator_release.yml new file mode 100644 index 00000000000..bae43fa5495 --- /dev/null +++ b/.github/workflows/platform_emulator_release.yml @@ -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 \ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 00000000000..7bd35e3c73c --- /dev/null +++ b/Android.bp @@ -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" +} \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore index c2fdceef700..27cc6bff3c3 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -2,3 +2,4 @@ *.apk .cxx *~ +/platform \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index c341aed556e..411ed3be518 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/dev_keystore.jks b/app/dev_keystore.jks index c2abe7bb892..9466e5438f2 100644 Binary files a/app/dev_keystore.jks and b/app/dev_keystore.jks differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c2e07c4609d..e9c05983fc4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -152,7 +152,17 @@ + + + + + + + + + + + + + @@ -728,7 +746,12 @@ android:label="@string/interceptor" android:taskAffinity="" android:windowSoftInputMode="stateUnchanged"> - + + + + + + diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/AppManager.java b/app/src/main/java/io/github/muntashirakon/AppManager/AppManager.java index 79a3f945971..85923903509 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/AppManager.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/AppManager.java @@ -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; @@ -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"); } } 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..7bf4431a0f2 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 @@ -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, 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 50c17dabefa..48cd9025404 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,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; @@ -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; @@ -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; @@ -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.unsafeCast(getWindow()).addSystemFlags(1 << 19); + } + mModel = new ViewModelProvider(this).get(PackageInstallerViewModel.class); if (!bindService( new Intent(this, PackageInstallerService.class), mServiceConnection, BIND_AUTO_CREATE)) { @@ -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.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)); 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 c536eadcf36..88f98cd1bdf 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 @@ -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, @@ -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, diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/self/life/BuildExpiryChecker.java b/app/src/main/java/io/github/muntashirakon/AppManager/self/life/BuildExpiryChecker.java index 671a9b8d8f4..86d2f9481ec 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/self/life/BuildExpiryChecker.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/self/life/BuildExpiryChecker.java @@ -20,9 +20,10 @@ import io.github.muntashirakon.AppManager.BuildConfig; import io.github.muntashirakon.AppManager.R; +import io.github.muntashirakon.AppManager.settings.Ops; public final class BuildExpiryChecker { - @IntDef({BUILD_TYPE_DEBUG, BUILD_TYPE_ALPHA, BUILD_TYPE_BETA, BUILD_TYPE_RC, BUILD_TYPE_STABLE}) + @IntDef({BUILD_TYPE_DEBUG, BUILD_TYPE_ALPHA, BUILD_TYPE_BETA, BUILD_TYPE_RC, BUILD_TYPE_STABLE, BUILD_TYPE_PLATFORM}) @Retention(RetentionPolicy.SOURCE) private @interface BuildType {} @@ -31,6 +32,7 @@ public final class BuildExpiryChecker { private static final int BUILD_TYPE_BETA = 2; private static final int BUILD_TYPE_RC = 3; private static final int BUILD_TYPE_STABLE = 4; + private static final int BUILD_TYPE_PLATFORM = 5; private static final long[] TIME_SPAN_MILLIS = new long[]{ 2L * 30 * 24 * 60 * 60_000, // 2 months @@ -38,6 +40,7 @@ public final class BuildExpiryChecker { 6L * 30 * 24 * 60 * 60_000, // 6 months 6L * 30 * 24 * 60 * 60_000, // 6 months 18L * 30 * 24 * 60 * 60_000, // 18 months + 18L * 30 * 24 * 60 * 60_000, // 18 months }; private static final long[] WARNING_PERIOD_MILLIS = new long[]{ @@ -46,6 +49,7 @@ public final class BuildExpiryChecker { 30L * 24 * 60 * 60_000, // 1 month 30L * 24 * 60 * 60_000, // 1 month 3L * 30 * 24 * 60 * 60_000, // 3 months + 3L * 30 * 24 * 60 * 60_000, // 3 months }; @NonNull @@ -69,7 +73,8 @@ public static AlertDialog getBuildExpiredDialog(@NonNull FragmentActivity activi activity.startActivity(intent); activity.finishAndRemoveTask(); }); - if (getBuildType() == BUILD_TYPE_STABLE) { + // Platform + if (getBuildType() == BUILD_TYPE_STABLE || getBuildType() == BUILD_TYPE_PLATFORM) { builder.setNeutralButton(R.string.action_continue, continueClickListener); } return builder.create(); @@ -107,6 +112,9 @@ private static Uri getUpdateUri() { if (getBuildType() == BUILD_TYPE_DEBUG) { return Uri.parse("https://github.com/MuntashirAkon/AppManager/actions/workflows/debug_build.yml"); } + if (getBuildType() == BUILD_TYPE_PLATFORM) { // Rom Builders Should Set this value //TODO: 8FEB26 Link to relevant AM Docs page + return Uri.parse(System.getProperty("ro.appmanager.update.url", "https://tharow.net/AppManager")); + } // TODO: 3/12/22 For Stable builds, check F-Droid too return Uri.parse("https://github.com/MuntashirAkon/AppManager/releases"); } @@ -116,6 +124,9 @@ private static int getBuildType() { if (BuildConfig.DEBUG) { return BUILD_TYPE_DEBUG; } + if (Ops.isPlatform()) { + return BUILD_TYPE_PLATFORM; + } String[] versionParts = BuildConfig.VERSION_NAME.split("-"); if (versionParts.length == 1) { return BUILD_TYPE_STABLE; diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java b/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java index 8793357e9ba..f3e4ec14c3a 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java @@ -33,13 +33,6 @@ public static MainPreferences getInstance(@Nullable String key, boolean dualPane return preferences; } - private static final List MODE_NAMES = Arrays.asList( - Ops.MODE_AUTO, - Ops.MODE_ROOT, - Ops.MODE_ADB_OVER_TCP, - Ops.MODE_ADB_WIFI, - Ops.MODE_NO_ROOT); - private FragmentActivity mActivity; private Preference mModePref; private Preference mLocalePref; @@ -76,7 +69,7 @@ public void onStart() { super.onStart(); if (mModePref != null) { mModePref.setSummary(getString(R.string.mode_of_op_with_inferred_mode_of_op, - mModes[MODE_NAMES.indexOf(Ops.getMode())], Ops.getInferredMode(mActivity))); + mModes[Ops.MODE_NAMES.indexOf(Ops.getMode())], Ops.getInferredMode(mActivity))); } if (mLocalePref != null) { mLocalePref.setSummary(getLanguageName()); diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java b/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java index 2b2fbefde23..d24b4212fe9 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java @@ -41,13 +41,6 @@ import io.github.muntashirakon.widget.TextInputTextView; public class ModeOfOpsPreference extends Fragment { - private static final List MODE_NAMES = Arrays.asList( - Ops.MODE_AUTO, - Ops.MODE_ROOT, - Ops.MODE_ADB_OVER_TCP, - Ops.MODE_ADB_WIFI, - Ops.MODE_NO_ROOT); - private MaterialTextView mInferredModeView; private MaterialTextView mRemoteServerStatusView; private MaterialTextView mRemoteServicesStatusView; @@ -113,7 +106,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Utils.isTv(requireContext())) { disabledItems = Collections.singletonList(Ops.MODE_ADB_WIFI); } else disabledItems = null; - changeModeView.setOnClickListener(v -> new SearchableSingleChoiceDialogBuilder<>(requireActivity(), MODE_NAMES, mModes) + changeModeView.setOnClickListener(v -> new SearchableSingleChoiceDialogBuilder<>(requireActivity(), Ops.MODE_NAMES, mModes) .setTitle(R.string.pref_mode_of_operations) .setSelection(mCurrentMode) .addDisabledItems(disabledItems) @@ -210,7 +203,7 @@ private void updateViews() { TextViewCompat.setCompoundDrawableTintList(mModeOfOpsView, mColorActive); mModeOfOpsView.setTextColor(mColorActive); mModeOfOpsView.setCompoundDrawablesRelativeWithIntrinsicBounds(mIconProgress, 0, 0, 0); - mModeOfOpsView.setText(getString(R.string.status_connecting_via_mode, mModes[MODE_NAMES.indexOf(mCurrentMode)])); + mModeOfOpsView.setText(getString(R.string.status_connecting_via_mode, mModes[Ops.MODE_NAMES.indexOf(mCurrentMode)])); } else { int uid = Users.getSelfOrRemoteUid(); boolean goodMode = !badInferredMode(mCurrentMode, uid); @@ -223,14 +216,14 @@ private void updateViews() { CharSequence mode; if (serverActive && uid != Process.myUid()) { mode = "remote service"; - } else mode = mModes[MODE_NAMES.indexOf(mCurrentMode)]; + } else mode = mModes[Ops.MODE_NAMES.indexOf(mCurrentMode)]; mModeOfOpsView.setText(getString(R.string.status_connected_via_mode, mode)); } else { mInferredModeView.setTextColor(mColorError); TextViewCompat.setCompoundDrawableTintList(mModeOfOpsView, mColorError); mModeOfOpsView.setTextColor(mColorError); mModeOfOpsView.setCompoundDrawablesRelativeWithIntrinsicBounds(mIconInactive, 0, 0, 0); - mModeOfOpsView.setText(getString(R.string.status_not_connected_via_mode, mModes[MODE_NAMES.indexOf(mCurrentMode)])); + mModeOfOpsView.setText(getString(R.string.status_not_connected_via_mode, mModes[Ops.MODE_NAMES.indexOf(mCurrentMode)])); } } // Server diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java b/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java index 86e5e48944a..5943b58e37b 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java @@ -29,11 +29,14 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import io.github.muntashirakon.AppManager.BuildConfig; import io.github.muntashirakon.AppManager.R; import io.github.muntashirakon.AppManager.adb.AdbConnectionManager; import io.github.muntashirakon.AppManager.adb.AdbPairingService; @@ -66,7 +69,7 @@ public class Ops { public static final String TAG = Ops.class.getSimpleName(); - @StringDef({MODE_AUTO, MODE_ROOT, MODE_ADB_OVER_TCP, MODE_ADB_WIFI, MODE_NO_ROOT}) + @StringDef({MODE_AUTO, MODE_ROOT, MODE_ADB_OVER_TCP, MODE_ADB_WIFI, MODE_NO_ROOT, MODE_PLATFORM}) @Retention(RetentionPolicy.SOURCE) public @interface Mode { } @@ -76,6 +79,15 @@ public class Ops { public static final String MODE_ADB_OVER_TCP = "adb_tcp"; public static final String MODE_ADB_WIFI = "adb_wifi"; public static final String MODE_NO_ROOT = "no-root"; + public static final String MODE_PLATFORM = "platform"; + + public static final List MODE_NAMES = Arrays.asList( + Ops.MODE_AUTO, + Ops.MODE_ROOT, + Ops.MODE_ADB_OVER_TCP, + Ops.MODE_ADB_WIFI, + Ops.MODE_NO_ROOT, + Ops.MODE_PLATFORM); @IntDef({ STATUS_SUCCESS, @@ -166,6 +178,12 @@ public static boolean isAdb() { return sIsAdb; } + @AnyThread + public static boolean isPlatform() { + //noinspection ConstantValue + return "platform".equals(BuildConfig.FLAVOR); + } + /** * Whether the current App Manager session is authenticated by the user. It does two things: *
    @@ -260,6 +278,9 @@ public static int init(@NonNull Context context, boolean force) { } return STATUS_SUCCESS; } + if (MODE_PLATFORM.equals(mode)) { + return initPermissionsWithSuccess(); + } if (!force && isAMServiceUpAndRunning(context, mode)) { // An instance of AMService is already running return sIsAdb ? STATUS_SUCCESS : initPermissionsWithSuccess(); @@ -325,6 +346,9 @@ public static boolean hasRoot() { @NoOps // Although we've used Ops checks, its overall usage does not affect anything private static void autoDetectRootSystemOrAdbAndPersist(@NonNull Context context) { sIsRoot = sDirectRoot; + if (isPlatform()) { + setMode(MODE_PLATFORM); + } if (sDirectRoot) { // Root permission was granted setMode(MODE_ROOT); diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/utils/Utils.java b/app/src/main/java/io/github/muntashirakon/AppManager/utils/Utils.java index 06a9120675e..6e9d16d0fa6 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/utils/Utils.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/utils/Utils.java @@ -53,6 +53,7 @@ import io.github.muntashirakon.AppManager.apk.signing.SignerInfo; import io.github.muntashirakon.AppManager.compat.PackageManagerCompat; import io.github.muntashirakon.AppManager.misc.OsEnvironment; +import io.github.muntashirakon.AppManager.self.SelfPermissions; public class Utils { public static final String TERMUX_LOGIN_PATH = OsEnvironment.getDataDirectoryRaw() + "/data/com.termux/files/usr/bin/login"; diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 4b6287b254a..b3f944a2640 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -36,6 +36,7 @@ @string/adb_over_tcp @string/wireless_debugging @string/no_root + @string/platform @string/type_boolean diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83b4d6306e3..33f48f66ea2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1558,4 +1558,5 @@ Selected %d out of %d items ADB local server port Waiting for Wi-Fi + Platform diff --git a/app/src/platform/AndroidManifest.xml b/app/src/platform/AndroidManifest.xml new file mode 100644 index 00000000000..89c3180b69b --- /dev/null +++ b/app/src/platform/AndroidManifest.xml @@ -0,0 +1,334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/platform/java/io/github/muntashirakon/AppManager/apk/installer/PackageUninstallerActivity.java b/app/src/platform/java/io/github/muntashirakon/AppManager/apk/installer/PackageUninstallerActivity.java new file mode 100644 index 00000000000..ce40b7e70e4 --- /dev/null +++ b/app/src/platform/java/io/github/muntashirakon/AppManager/apk/installer/PackageUninstallerActivity.java @@ -0,0 +1,98 @@ +package io.github.muntashirakon.AppManager.apk.installer; + +import static io.github.muntashirakon.AppManager.utils.UIUtils.displayLongToast; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.UserHandleHidden; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; + +import java.util.Objects; + +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; + + @Override + public boolean getTransparentBackground() { + return true; + } + + + @RequiresPermission(anyOf = {Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.DELETE_PACKAGES}) + @Override + protected void onAuthenticated(@Nullable Bundle savedInstanceState) { + this.mPackageName = Objects.requireNonNull(getIntent().getData()).getHost(); + boolean 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 | PackageManager.NameNotFoundException e) { + Log.e(TAG, "Uninstaller: onAuthenticated: getApplicationInfo", e); + throw new RuntimeException(e); + } + this.mAppLabel = mApplicationInfo.loadLabel(getPackageManager()).toString(); + uninstallDialog(isUninstallFromAllUsers); + } + + private void uninstallDialog(boolean isUninstallFromAllUsers) { + 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) + .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(); + finish(); + }); + 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(); + } +} \ No newline at end of file diff --git a/hiddenapi/src/main/java/android/content/pm/PackageInstallerHidden.java b/hiddenapi/src/main/java/android/content/pm/PackageInstallerHidden.java index 84ef335cfe9..272e390939c 100644 --- a/hiddenapi/src/main/java/android/content/pm/PackageInstallerHidden.java +++ b/hiddenapi/src/main/java/android/content/pm/PackageInstallerHidden.java @@ -54,4 +54,10 @@ public static class SessionParams { @RequiresApi(Build.VERSION_CODES.P) public String installerPackageName; } + + public static class SessionInfo { + public String getResolvedBaseApkPath() { + return HiddenUtil.throwUOE(); + } + } } diff --git a/hiddenapi/src/main/java/android/view/WindowHidden.java b/hiddenapi/src/main/java/android/view/WindowHidden.java new file mode 100644 index 00000000000..04e6e45cb87 --- /dev/null +++ b/hiddenapi/src/main/java/android/view/WindowHidden.java @@ -0,0 +1,11 @@ +package android.view; + +import dev.rikka.tools.refine.RefineAs; +import misc.utils.HiddenUtil; + +@RefineAs(Window.class) +public class WindowHidden { + public void addSystemFlags(int flags) { + HiddenUtil.throwUOE(flags); + } +} diff --git a/platform/default-permissions.xml b/platform/default-permissions.xml new file mode 100644 index 00000000000..d27fd8823df --- /dev/null +++ b/platform/default-permissions.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/permissions.xml b/platform/permissions.xml new file mode 100644 index 00000000000..fe3e23233bb --- /dev/null +++ b/platform/permissions.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/sysconfig.xml b/platform/sysconfig.xml new file mode 100644 index 00000000000..8645eb15a8c --- /dev/null +++ b/platform/sysconfig.xml @@ -0,0 +1,10 @@ + + + + + + + +