fix: add warnings for incompatible or unsupported IDE build configurations

This commit is contained in:
Akash Yadav
2023-10-08 12:52:04 +05:30
parent 18e320f729
commit 044599f6e2
13 changed files with 350 additions and 51 deletions
+1
View File
@@ -19,6 +19,7 @@
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".app.IDEApplication"
@@ -25,6 +25,7 @@ import androidx.core.util.Pair;
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity;
import com.itsaky.androidide.R;
import com.itsaky.androidide.app.IDEActivity;
import com.itsaky.androidide.app.IDEBuildConfigProvider;
import com.itsaky.androidide.databinding.ActivityAboutBinding;
import com.itsaky.androidide.databinding.LayoutAboutItemsBinding;
import java.util.ArrayList;
@@ -32,7 +33,6 @@ import java.util.List;
public class AboutActivity extends IDEActivity {
public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android";
private ActivityAboutBinding binding;
@Override
@@ -109,7 +109,7 @@ public class AboutActivity extends IDEActivity {
}
private String getFooter() {
final String arch = android.os.Build.SUPPORTED_ABIS[0];
final String arch = IDEBuildConfigProvider.getInstance().getBuildFlavor();
try {
final String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
return getString(R.string.about_footer, version, arch);
@@ -17,6 +17,7 @@
package com.itsaky.androidide.activities
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Rect
import android.os.Bundle
@@ -33,7 +34,7 @@ import androidx.transition.doOnEnd
import com.blankj.utilcode.util.SizeUtils
import com.google.android.material.transition.MaterialSharedAxis
import com.itsaky.androidide.activities.editor.EditorActivityKt
import com.itsaky.androidide.app.BaseApplication
import com.itsaky.androidide.app.IDEBuildConfigProvider
import com.itsaky.androidide.app.LimitlessIDEActivity
import com.itsaky.androidide.databinding.ActivityMainBinding
import com.itsaky.androidide.preferences.internal.NO_OPENED_PROJECT
@@ -88,8 +89,7 @@ class MainActivity : LimitlessIDEActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!BaseApplication.isAbiSupported()) {
showDeviceNotSupported()
if (!checkDeviceSupported()) {
return
}
@@ -229,13 +229,38 @@ class MainActivity : LimitlessIDEActivity() {
startActivity(Intent(this, TerminalActivity::class.java))
}
private fun showDeviceNotSupported() {
val builder = DialogUtils.newMaterialDialogBuilder(this)
builder.setTitle(string.title_device_not_supported)
builder.setMessage(string.msg_device_not_supported)
builder.setCancelable(false)
builder.setPositiveButton(android.R.string.ok) { _, _ -> 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() {
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>
get() {
val flavors = mutableListOf<String>()
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<String>
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)
}
}
@@ -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)
) :
SwitchPreference(setValue = ::launchAppAfterInstall::set, getValue = ::launchAppAfterInstall::get)
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>
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()
}
}
}
@@ -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;
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>
/**
* The supported CPU architectures of the device.
*/
val supportedAbis: Array<String>
/**
* @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
}
}
}
@@ -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;
}
@@ -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(
@@ -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
@@ -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 {
@@ -53,6 +53,7 @@
<!-- Project Builder -->
<string name="title_device_not_supported">Device Not Supported</string>
<string name="msg_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</string>
<string name="build">Build</string>
<string name="build_output">Build output</string>
<string name="lint_tasks">Lint tasks</string>
@@ -610,4 +611,7 @@
<string name="err_cannot_determine_package">Cannot run app. Unable to determine package name.</string>
<string name="msg_launch_failure_no_app_module">Cannot run application. No application modules found in project.</string>
<string name="title_launch_app">Launch app</string>
<string name="msg_64bit_on_32bit_device">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.</string>
<string name="title_32bit_on_64bit_device">Configuration Warning</string>
<string name="msg_32bit_on_64bit_device">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.</string>
</resources>