diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ede1c015a..8d1fccd84 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ tools:ignore="ScopedStorage" /> + finishAffinity() } - builder.create().show() + private fun checkDeviceSupported(): Boolean { + val configProvider = IDEBuildConfigProvider.getInstance() + + val supported = if ( + configProvider.isArm64v8aBuild() + && !configProvider.isArm64v8aDevice() + && configProvider.isArmeabiv7aDevice() + ) { + // IDE = 64-bit + // Device = 32-bit + // NOT SUPPORTED + DialogUtils.show64bitOn32bit(this) + false + + } else if ( + configProvider.isArmeabiv7aBuild() + && configProvider.isArm64v8aDevice() + ) { + // IDE = 32-bit + // Device = 64-bit + // SUPPORTED, but warn the user + DialogUtils.show32bitOn64bit(this) + true + + } else configProvider.supportsBuildFlavor() + + if (!supported) { + DialogUtils.showDeviceNotSupported(this) + } + + + return supported } private fun openLastProject() { diff --git a/app/src/main/java/com/itsaky/androidide/app/IDEBuildConfigProviderImpl.kt b/app/src/main/java/com/itsaky/androidide/app/IDEBuildConfigProviderImpl.kt new file mode 100644 index 000000000..f24c379a9 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/app/IDEBuildConfigProviderImpl.kt @@ -0,0 +1,69 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.app + +import android.os.Build +import com.google.auto.service.AutoService +import com.itsaky.androidide.BuildConfig + +/** + * @author Akash Yadav + */ +@AutoService(IDEBuildConfigProvider::class) +@Suppress("UNUSED") +open class IDEBuildConfigProviderImpl : IDEBuildConfigProvider { + + override val buildFlavor: String + get() = BuildConfig.FLAVOR + + override val supportedBuildFlavors: Array + get() { + val flavors = mutableListOf() + if (supportedAbis.contains(BuildConfig.FLAVOR_ARM64_V8A)) { + flavors.add(BuildConfig.FLAVOR_ARM64_V8A) + } + if (supportedAbis.contains(BuildConfig.FLAVOR_ARMEABI_V7A)) { + flavors.add(BuildConfig.FLAVOR_ARMEABI_V7A) + } + + return flavors.toTypedArray() + } + + override val supportedAbis: Array + get() = Build.SUPPORTED_ABIS + + override fun isArm64v8aBuild(): Boolean { + return buildFlavor == BuildConfig.FLAVOR_ARM64_V8A + } + + override fun isArm64v8aDevice(): Boolean { + return Build.SUPPORTED_64_BIT_ABIS.contains(BuildConfig.FLAVOR_ARM64_V8A) + } + + override fun isArmeabiv7aDevice(): Boolean { + return Build.SUPPORTED_32_BIT_ABIS.contains(BuildConfig.FLAVOR_ARMEABI_V7A) + } + + override fun isArmeabiv7aBuild(): Boolean { + return buildFlavor == BuildConfig.FLAVOR_ARMEABI_V7A + } + + override fun supportsBuildFlavor(): Boolean { + return supportedBuildFlavors.contains(buildFlavor) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt index 3e4f0385e..b8dc136c4 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt @@ -24,7 +24,7 @@ import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputLayout import com.itsaky.androidide.R -import com.itsaky.androidide.app.BaseApplication +import com.itsaky.androidide.app.IDEBuildConfigProvider import com.itsaky.androidide.preferences.internal.CUSTOM_GRADLE_INSTALLATION import com.itsaky.androidide.preferences.internal.GRADLE_CLEAR_CACHE import com.itsaky.androidide.preferences.internal.GRADLE_COMMANDS @@ -74,7 +74,7 @@ private class GradleOptions( addPreference(GradleCommands()) addPreference(GradleDistrubution()) addPreference(GradleClearCache()) - if (BaseApplication.isAarch64() && VERSION.SDK_INT == VERSION_CODES.R) { + if (IDEBuildConfigProvider.getInstance().isArm64v8aBuild() && VERSION.SDK_INT == VERSION_CODES.R) { addPreference(TagPointersFix()) } } @@ -218,4 +218,5 @@ private class LaunchAppAfterInstall( override val title: Int = R.string.idepref_launchAppAfterInstall_title, override val summary: Int? = R.string.idepref_launchAppAfterInstall_summary, override val icon: Int? = drawable.ic_open_external -) : SwitchPreference(setValue = ::launchAppAfterInstall::set, getValue = ::launchAppAfterInstall::get) \ No newline at end of file +) : + SwitchPreference(setValue = ::launchAppAfterInstall::set, getValue = ::launchAppAfterInstall::get) \ No newline at end of file diff --git a/app/src/test/java/com/itsaky/androidide/app/IDEBuildConfigProviderImplTest.kt b/app/src/test/java/com/itsaky/androidide/app/IDEBuildConfigProviderImplTest.kt new file mode 100644 index 000000000..d273c087d --- /dev/null +++ b/app/src/test/java/com/itsaky/androidide/app/IDEBuildConfigProviderImplTest.kt @@ -0,0 +1,111 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.app + +import com.google.common.truth.Truth.assertThat +import com.itsaky.androidide.BuildConfig +import org.junit.Test + +/** + * @author Akash Yadav + */ +class IDEBuildConfigProviderImplTest { + + @Test + fun `test build config provider`() { + object : IDEBuildConfigProviderImpl() { + var flavor = fun () = BuildConfig.FLAVOR_ARM64_V8A + var abis = fun () = arrayOf(BuildConfig.FLAVOR_ARM64_V8A, BuildConfig.FLAVOR_ARMEABI_V7A) + + override val buildFlavor: String + get() = flavor() + override val supportedAbis: Array + get() = abis() + }.apply { + + // ======================= AARCH64 variant on an AARCH64 device ================== + assertThat(buildFlavor).isEqualTo(BuildConfig.FLAVOR_ARM64_V8A) + + assertThat(supportedAbis.toList()).containsExactly( + BuildConfig.FLAVOR_ARM64_V8A, + BuildConfig.FLAVOR_ARMEABI_V7A + ) + assertThat(supportedBuildFlavors.toList()).containsExactly( + BuildConfig.FLAVOR_ARM64_V8A, + BuildConfig.FLAVOR_ARMEABI_V7A + ) + + assertThat(isArm64v8aBuild()).isTrue() + assertThat(isArmeabiv7aBuild()).isFalse() + assertThat(supportsBuildFlavor()).isTrue() + + // ======================= ARM32 variant on an ARM32 device ================== + flavor = fun () = BuildConfig.FLAVOR_ARMEABI_V7A + abis = fun () = arrayOf(BuildConfig.FLAVOR_ARMEABI_V7A) + + assertThat(buildFlavor).isEqualTo(BuildConfig.FLAVOR_ARMEABI_V7A) + + assertThat(supportedAbis.toList()).containsExactly( + BuildConfig.FLAVOR_ARMEABI_V7A + ) + assertThat(supportedBuildFlavors.toList()).containsExactly( + BuildConfig.FLAVOR_ARMEABI_V7A + ) + + assertThat(isArm64v8aBuild()).isFalse() + assertThat(isArmeabiv7aBuild()).isTrue() + assertThat(supportsBuildFlavor()).isTrue() + + // ======================= ARM32 variant on an AARCH64 device ================== + flavor = fun () = BuildConfig.FLAVOR_ARMEABI_V7A + abis = fun () = arrayOf(BuildConfig.FLAVOR_ARM64_V8A, BuildConfig.FLAVOR_ARMEABI_V7A) + + assertThat(buildFlavor).isEqualTo(BuildConfig.FLAVOR_ARMEABI_V7A) + + assertThat(supportedAbis.toList()).containsExactly( + BuildConfig.FLAVOR_ARM64_V8A, + BuildConfig.FLAVOR_ARMEABI_V7A + ) + assertThat(supportedBuildFlavors.toList()).containsExactly( + BuildConfig.FLAVOR_ARM64_V8A, + BuildConfig.FLAVOR_ARMEABI_V7A + ) + + assertThat(isArm64v8aBuild()).isFalse() + assertThat(isArmeabiv7aBuild()).isTrue() + assertThat(supportsBuildFlavor()).isTrue() + + // ======================= AARCH64 variant on an ARM32 device ================== + flavor = fun () = BuildConfig.FLAVOR_ARM64_V8A + abis = fun () = arrayOf(BuildConfig.FLAVOR_ARMEABI_V7A) + + assertThat(buildFlavor).isEqualTo(BuildConfig.FLAVOR_ARM64_V8A) + + assertThat(supportedAbis.toList()).containsExactly( + BuildConfig.FLAVOR_ARMEABI_V7A + ) + assertThat(supportedBuildFlavors.toList()).containsExactly( + BuildConfig.FLAVOR_ARMEABI_V7A + ) + + assertThat(isArm64v8aBuild()).isTrue() + assertThat(isArmeabiv7aBuild()).isFalse() + assertThat(supportsBuildFlavor()).isFalse() + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java b/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java index d818acea3..7a34c79c1 100644 --- a/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java +++ b/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java @@ -21,8 +21,6 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Intent; import android.net.Uri; -import android.os.Build; -import androidx.annotation.Nullable; import androidx.core.app.NotificationManagerCompat; import com.blankj.utilcode.util.ThrowableUtils; import com.itsaky.androidide.buildinfo.BuildInfo; @@ -35,8 +33,6 @@ import com.itsaky.androidide.utils.FlashbarUtilsKt; import com.itsaky.androidide.utils.JavaCharacter; import com.itsaky.androidide.utils.VMUtils; import java.io.File; -import java.util.Arrays; -import kotlin.collections.ArraysKt; public class BaseApplication extends Application { @@ -46,8 +42,6 @@ public class BaseApplication extends Application { public static final String SPONSOR_URL = BuildInfo.PROJECT_SITE + "/donate"; public static final String DOCS_URL = BuildInfo.PROJECT_SITE + "/docs"; public static final String EMAIL = "contact@androidide.com"; - private static final String AARCH64 = "arm64-v8a"; - private static final String ARM = "armeabi-v7a"; private static BaseApplication instance; private PreferenceManager mPrefsManager; @@ -55,28 +49,6 @@ public class BaseApplication extends Application { return instance; } - public static boolean isAbiSupported() { - return Arrays.asList(Build.SUPPORTED_ABIS).contains(getArch()); - } - - public static boolean isAarch64() { - return ArraysKt.contains(Build.SUPPORTED_64_BIT_ABIS, AARCH64); - } - - public static boolean isArmv7a() { - return ArraysKt.contains(Build.SUPPORTED_32_BIT_ABIS, ARM); - } - - @Nullable - public static String getArch() { - if (isAarch64()) { - return AARCH64; - } else if (isArmv7a()) { - return ARM; - } - return null; - } - @Override public void onCreate() { instance = this; @@ -106,10 +78,6 @@ public class BaseApplication extends Application { ThrowableUtils.getFullStackTrace(th)); } - public final File getTempProjectDir() { - return Environment.mkdirIfNotExits(new File(Environment.TMP_DIR, "tempProject")); - } - public PreferenceManager getPrefManager() { return mPrefsManager; } diff --git a/common/src/main/java/com/itsaky/androidide/app/IDEBuildConfigProvider.kt b/common/src/main/java/com/itsaky/androidide/app/IDEBuildConfigProvider.kt new file mode 100644 index 000000000..161c3d041 --- /dev/null +++ b/common/src/main/java/com/itsaky/androidide/app/IDEBuildConfigProvider.kt @@ -0,0 +1,83 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.app + +import com.itsaky.androidide.utils.ServiceLoader + +/** + * [IDEBuildConfigProvider] provides information about the build variant of the IDE itself. + * + * @author Akash Yadav + */ +interface IDEBuildConfigProvider { + + /** + * The current product flavor of the IDE. + */ + val buildFlavor: String + + /** + * The build flavors which can be installed on the current device. + */ + val supportedBuildFlavors: Array + + /** + * The supported CPU architectures of the device. + */ + val supportedAbis: Array + + /** + * @return Whether this build is the 64-bit variant of the IDE. + */ + fun isArm64v8aBuild(): Boolean + + /** + * @return Whether the device supports `arm64-v8a` instructions. + */ + fun isArm64v8aDevice() : Boolean + + /** + * @return Whether this build is the 32-bit variant of the IDE. + */ + fun isArmeabiv7aBuild(): Boolean + + /** + * @return Whether the device supports `armeabi-v7a` instructions. + */ + fun isArmeabiv7aDevice() : Boolean + + /** + * @return Whether the IDE can be run on the device's CPU arch. + */ + fun supportsBuildFlavor() : Boolean + + companion object { + + private val _instance by lazy { + ServiceLoader.load(IDEBuildConfigProvider::class.java).findFirstOrThrow() + } + + /** + * Get instance of the [IDEBuildConfigProvider] implementation. + */ + @JvmStatic + fun getInstance(): IDEBuildConfigProvider { + return _instance + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java b/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java index 01d95a79a..6e6f9d21e 100755 --- a/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java +++ b/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java @@ -22,6 +22,7 @@ import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; import com.itsaky.androidide.app.BaseApplication; +import com.itsaky.androidide.app.IDEBuildConfigProvider; import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.ILogger; import java.io.File; @@ -37,12 +38,14 @@ import org.jetbrains.annotations.Contract; public class ToolsManager { private static final ILogger LOG = ILogger.newInstance("ToolsManager"); - public static String ARCH_SPECIFIC_ASSET_DATA_DIR = "data/" + BaseApplication.getArch(); + + public static String ARCH_SPECIFIC_ASSET_DATA_DIR = + "data/" + IDEBuildConfigProvider.getInstance().getBuildFlavor(); public static String COMMON_ASSET_DATA_DIR = "data/common"; public static void init(@NonNull BaseApplication app, Runnable onFinish) { - if (BaseApplication.getArch() == null) { + if (!IDEBuildConfigProvider.getInstance().supportsBuildFlavor()) { LOG.error("Device not supported"); return; } diff --git a/common/src/main/java/com/itsaky/androidide/utils/DialogUtils.kt b/common/src/main/java/com/itsaky/androidide/utils/DialogUtils.kt index 77f1d6e94..8dd111f88 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/DialogUtils.kt +++ b/common/src/main/java/com/itsaky/androidide/utils/DialogUtils.kt @@ -16,11 +16,13 @@ */ package com.itsaky.androidide.utils +import android.app.Activity import android.content.Context import android.content.DialogInterface.OnClickListener import android.view.LayoutInflater import android.view.View import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.itsaky.androidide.common.R import com.itsaky.androidide.common.databinding.LayoutDialogProgressBinding import com.itsaky.androidide.resources.R.string import com.itsaky.androidide.resources.R.style @@ -34,6 +36,36 @@ import java.util.concurrent.atomic.AtomicInteger */ object DialogUtils { + @JvmStatic + fun showDeviceNotSupported(context: Activity) { + val builder = newMaterialDialogBuilder(context) + builder.setTitle(R.string.title_device_not_supported) + builder.setMessage(R.string.title_device_not_supported) + builder.setCancelable(false) + builder.setPositiveButton(android.R.string.ok) { _, _ -> context.finishAffinity() } + builder.create().show() + } + + @JvmStatic + fun show64bitOn32bit(context: Activity) { + val builder = newMaterialDialogBuilder(context) + builder.setTitle(R.string.title_device_not_supported) + builder.setMessage(R.string.msg_64bit_on_32bit_device) + builder.setCancelable(false) + builder.setPositiveButton(android.R.string.ok) { _, _ -> context.finishAffinity() } + builder.create().show() + } + + @JvmStatic + fun show32bitOn64bit(context: Activity) { + val builder = newMaterialDialogBuilder(context) + builder.setTitle(R.string.title_32bit_on_64bit_device) + builder.setMessage(R.string.msg_32bit_on_64bit_device) + builder.setCancelable(false) + builder.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } + builder.create().show() + } + @JvmStatic @JvmOverloads fun newProgressDialog( diff --git a/common/src/main/java/com/itsaky/androidide/utils/Environment.java b/common/src/main/java/com/itsaky/androidide/utils/Environment.java index 12dcaa5f3..3790f689a 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/Environment.java +++ b/common/src/main/java/com/itsaky/androidide/utils/Environment.java @@ -22,6 +22,7 @@ import androidx.annotation.NonNull; import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.itsaky.androidide.app.BaseApplication; +import com.itsaky.androidide.app.IDEBuildConfigProvider; import java.io.File; import java.io.StringReader; import java.util.ArrayList; @@ -218,7 +219,7 @@ public final class Environment { ENV_VARS.put("TERMUX_PKG_NO_MIRROR_SELECT", "true"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R - && BaseApplication.isAarch64() + && IDEBuildConfigProvider.getInstance().isArm64v8aBuild() && LIB_HOOK.exists() && BaseApplication.getBaseInstance().getPrefManager().shouldUseLdPreload()) { // Required for JDK 11 diff --git a/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt b/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt index 88fedd137..93b27ec8c 100644 --- a/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt +++ b/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt @@ -22,6 +22,7 @@ import android.os.Build import android.telephony.TelephonyManager import com.blankj.utilcode.util.DeviceUtils import com.itsaky.androidide.app.BaseApplication +import com.itsaky.androidide.app.IDEBuildConfigProvider import com.itsaky.androidide.buildinfo.BuildInfo import java.math.BigInteger import java.security.MessageDigest @@ -58,7 +59,7 @@ object AndroidIDEStats { } val cpuArch by lazy { - BaseApplication.getArch()!! + IDEBuildConfigProvider.getInstance().buildFlavor } val statData by lazy { diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index 7027734bb..02977093b 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -53,6 +53,7 @@ Device Not Supported Unfortunately, your device is not supported at the moment. We are sorry for the inconvenience.\n\nSupported CPU ABIs: aarch64 and armeabi-v7a + Build Build output Lint tasks @@ -610,4 +611,7 @@ Cannot run app. Unable to determine package name. Cannot run application. No application modules found in project. Launch app + You are using 64-bit variant of AndroidIDE on a 32-bit-only device. This configuration is not supported. Please install the 32-bit variant of AndroidIDE. + Configuration Warning + You are using 32-bit variant of AndroidIDE on a 64-bit device. This configuration may work, but it\'ll result in reduced performance and possible security vulnerabilities. Please avoid using this configuration whenever possible.