mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Merged branch 'tooling-api' into 'main'
This commit is contained in:
@@ -2,7 +2,9 @@ name: Android CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
- tooling-api
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
Generated
+2
@@ -11,6 +11,8 @@
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<module name="AndroidIDE.build-tools.builder-model" target="11" />
|
||||
<module name="AndroidIDE.build-tools.project" target="11" />
|
||||
<module name="AndroidIDE.build-tools.tooling-api-plugin" target="11" />
|
||||
<module name="AndroidIDE.google-java-format" target="1.8" />
|
||||
<module name="AndroidIDE.gradle-tooling" target="1.8" />
|
||||
|
||||
Generated
+2
@@ -86,6 +86,8 @@
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/test.xml" value="0.20512820512820512" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/menu/menu_editor.xml" value="0.20512820512820512" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/menu/menu_editor_nav.xml" value="0.20512820512820512" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.1445" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.1445" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/common/src/main/res/layout/layout_color_picker.xml" value="0.264" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/xml-inflater/src/main/res/drawable/empy_drawable.xml" value="0.147" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/xml-inflater/src/main/res/drawable/states.xml" value="0.147" />
|
||||
|
||||
@@ -56,8 +56,34 @@ interface ActionItem {
|
||||
*/
|
||||
fun postExec(data: ActionData, result: Any) = Unit
|
||||
|
||||
/**
|
||||
* Return the show as action flags for the menu item.
|
||||
*
|
||||
* @return The show as action flags.
|
||||
*/
|
||||
fun getShowAsActionFlags(data: ActionData): Int = -1
|
||||
|
||||
/**
|
||||
* Checks if the given [ActionData] has instances of the given [types].
|
||||
*
|
||||
* @param data The data to check.
|
||||
* @param types The type of objects to look for.
|
||||
* @return `true` if the [data] has the given [types], `false` otherwise.
|
||||
*/
|
||||
fun hasRequiredData(data: ActionData, vararg types: Class<*>): Boolean {
|
||||
for (type in types) {
|
||||
data.get(type) ?: return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Marks this action item as invisible. */
|
||||
fun markInvisible() {
|
||||
visible = false
|
||||
enabled = false
|
||||
}
|
||||
|
||||
/** Location where an action item will be shown. */
|
||||
enum class Location(val id: String) {
|
||||
|
||||
|
||||
+15
-14
@@ -24,7 +24,7 @@ import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* Default implementation for the [ActionsRegistry]
|
||||
@@ -148,21 +148,22 @@ class DefaultActionsRegistry : ActionsRegistry() {
|
||||
|
||||
private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) {
|
||||
val start = System.currentTimeMillis()
|
||||
CompletableFuture.supplyAsync { action.execAction(data) }.whenComplete { result, error ->
|
||||
if (result == null || (result is Boolean && !result) || error != null) {
|
||||
log.error(
|
||||
"An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms",
|
||||
error)
|
||||
} else {
|
||||
log.info(
|
||||
"Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms")
|
||||
}
|
||||
CompletableFuture.supplyAsync { action.execAction(data) }
|
||||
.whenComplete { result, error ->
|
||||
if (result == null || (result is Boolean && !result) || error != null) {
|
||||
log.error(
|
||||
"An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms",
|
||||
error)
|
||||
} else {
|
||||
log.info(
|
||||
"Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms")
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
action.postExec(data, result ?: false)
|
||||
notifyActionExec(action, result ?: false)
|
||||
ThreadUtils.runOnUiThread {
|
||||
action.postExec(data, result ?: false)
|
||||
notifyActionExec(action, result ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyActionExec(action: ActionItem, result: Any) {
|
||||
|
||||
+39
-34
@@ -4,60 +4,60 @@ apply plugin: 'kotlin-android'
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
|
||||
defaultConfig {
|
||||
applicationId rootProject.ext.packageName
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
|
||||
|
||||
multiDexEnabled true
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
|
||||
signingConfigs {
|
||||
|
||||
|
||||
debug {
|
||||
storeFile file("dev.keystore")
|
||||
storeFile file ("dev.keystore")
|
||||
keyAlias "androidide"
|
||||
storePassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
}
|
||||
|
||||
|
||||
release {
|
||||
storeFile file("dev.keystore")
|
||||
storeFile file ("dev.keystore")
|
||||
keyAlias "androidide"
|
||||
storePassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ android {
|
||||
excludes += ['META-INF/eclipse.inf', 'about_files/LICENSE-2.0.txt', 'plugin.xml', 'plugin.properties', 'about.mappings', 'about.properties', 'about.ini', 'modeling32.png']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lint {
|
||||
abortOnError false
|
||||
disable 'VectorPath', 'NestedWeights', 'ContentDescription', 'SmallSp'
|
||||
@@ -74,10 +74,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
|
||||
// Annotation processors
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1'
|
||||
|
||||
|
||||
implementation common.editor
|
||||
implementation common.utilcode
|
||||
implementation common.file_picker
|
||||
@@ -87,9 +87,9 @@ dependencies {
|
||||
implementation common.xml2json
|
||||
implementation google.gson
|
||||
implementation google.guava
|
||||
|
||||
|
||||
implementation common.jsoup
|
||||
|
||||
|
||||
// AndroidX
|
||||
implementation androidx.annotation
|
||||
implementation androidx.appcompat
|
||||
@@ -103,24 +103,29 @@ dependencies {
|
||||
implementation androidx.vectors
|
||||
implementation androidx.animated_vectors
|
||||
implementation google.material
|
||||
|
||||
|
||||
// Kotlin
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
|
||||
// Local projects here
|
||||
implementation project(':treeview')
|
||||
implementation project(':emulatorview')
|
||||
implementation project(':xml-inflater')
|
||||
implementation project(':lexers')
|
||||
implementation project(':common')
|
||||
implementation project(':actions')
|
||||
|
||||
implementation project(':lsp:api')
|
||||
implementation project(':lsp:java')
|
||||
implementation project(':lsp:xml')
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation project (':build-tools:builder-api')
|
||||
implementation project (':treeview')
|
||||
implementation project (':emulatorview')
|
||||
implementation project (':xml-inflater')
|
||||
implementation project (':lexers')
|
||||
implementation project (':common')
|
||||
implementation project (':actions')
|
||||
|
||||
implementation project (':lsp:api')
|
||||
implementation project (':lsp:java')
|
||||
implementation project (':lsp:xml')
|
||||
|
||||
implementation fileTree (dir: 'libs', include: ['*.jar'])
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
// This is to build the tooling-api-impl project before the app is built
|
||||
// So we always copy the latest JAR file to assets
|
||||
compileOnly project (path: ':build-tools:tooling-api-impl')
|
||||
}
|
||||
@@ -68,6 +68,9 @@
|
||||
android:configChanges="orientation|screenSize|uiMode"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<service
|
||||
android:name=".services.GradleBuildService"
|
||||
android:exported="false" />
|
||||
<provider
|
||||
android:name=".filepicker.IDEDocumentsProvider"
|
||||
android:authorities="com.itsaky.androidide.documents"
|
||||
@@ -87,16 +90,6 @@
|
||||
<action android:name="com.itsaky.androidide.logs.APPEND_LOG" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/secondaryColor" />
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
android:value="@string/cms_channel_id_devs" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -39,18 +39,15 @@ import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.models.NewProjectDetails;
|
||||
import com.itsaky.androidide.models.ProjectTemplate;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.ProjectCreatorCallable;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.ProjectFinder;
|
||||
import com.itsaky.androidide.utils.TransformUtils;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
|
||||
@@ -68,11 +65,213 @@ public class MainActivity extends StudioActivity
|
||||
private int currentTemplateIndex = 0;
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
createBinding = binding.createLayout;
|
||||
createLayoutBinding = createBinding.createProjectContent;
|
||||
return binding.getRoot();
|
||||
public void onClick(View p1) {
|
||||
if (p1.getId() == binding.createProject.getId()) {
|
||||
showCreateProject();
|
||||
} else if (p1.getId() == createBinding.createprojectClose.getId()) {
|
||||
hideCreateProject();
|
||||
} else if (p1.getId() == createLayoutBinding.nextCard.getId()) {
|
||||
currentTemplateIndex++;
|
||||
if (currentTemplateIndex >= mTemplates.size()) {
|
||||
currentTemplateIndex = 0;
|
||||
}
|
||||
showTemplate(currentTemplateIndex);
|
||||
} else if (p1.getId() == createLayoutBinding.previousCard.getId()) {
|
||||
currentTemplateIndex--;
|
||||
if (currentTemplateIndex < 0) {
|
||||
currentTemplateIndex = mTemplates.size() - 1;
|
||||
}
|
||||
showTemplate(currentTemplateIndex);
|
||||
} else if (p1.getId() == createLayoutBinding.createprojectCreate.getId()) {
|
||||
createNewProject();
|
||||
} else if (p1.getId() == binding.gotoPreferences.getId()) {
|
||||
gotoSettings();
|
||||
} else if (p1.getId() == binding.openProject.getId()) {
|
||||
pickProject();
|
||||
} else if (p1.getId() == binding.openTerminal.getId()) {
|
||||
startActivity(new Intent(this, TerminalActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
private void showCreateProject() {
|
||||
MaterialContainerTransform transform =
|
||||
TransformUtils.createContainerTransformFor(
|
||||
binding.createProject, binding.createNewCard, binding.realContainer);
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), transform);
|
||||
binding.createNewCard.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideCreateProject() {
|
||||
MaterialContainerTransform transform =
|
||||
TransformUtils.createContainerTransformFor(
|
||||
binding.createNewCard, binding.createProject, binding.realContainer);
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), transform);
|
||||
binding.createNewCard.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showTemplate(int index) {
|
||||
if (index < 0 || index >= mTemplates.size()) {
|
||||
return;
|
||||
}
|
||||
final ProjectTemplate template = mTemplates.get(index);
|
||||
createLayoutBinding.createprojectTemplateLabel.setText(template.getName());
|
||||
createLayoutBinding.createprojectTemplateDescription.setText(template.getDescription());
|
||||
createLayoutBinding.createprojectTemplateImage.setImageResource(template.getImageId());
|
||||
}
|
||||
|
||||
private void createNewProject() {
|
||||
final String appName =
|
||||
Objects.requireNonNull(createLayoutBinding.createprojectTextAppName.getEditText())
|
||||
.getText()
|
||||
.toString()
|
||||
.trim();
|
||||
final String packageName =
|
||||
Objects.requireNonNull(
|
||||
createLayoutBinding.createprojectTextPackageName.getEditText())
|
||||
.getText()
|
||||
.toString()
|
||||
.trim();
|
||||
final int minSdk = getMinSdk();
|
||||
final int targetSdk = getTargetSdk();
|
||||
|
||||
if (!isValid(appName, packageName, minSdk, targetSdk)) {
|
||||
getApp().toast(R.string.invalid_values, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
createProject(appName, packageName, minSdk, targetSdk);
|
||||
}
|
||||
|
||||
private void gotoSettings() {
|
||||
startActivity(new Intent(this, PreferencesActivity.class));
|
||||
}
|
||||
|
||||
private void pickProject() {
|
||||
UnicornFilePicker.from(this)
|
||||
.addConfigBuilder()
|
||||
.addItemDivider(false)
|
||||
.selectMultipleFiles(false)
|
||||
.setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath())
|
||||
.showHiddenFiles(true)
|
||||
.showOnlyDirectory(true)
|
||||
.theme(R.style.AppTheme_FilePicker)
|
||||
.build()
|
||||
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
|
||||
}
|
||||
|
||||
private int getMinSdk() {
|
||||
try {
|
||||
return Integer.parseInt(
|
||||
Objects.requireNonNull(
|
||||
createLayoutBinding.createprojectTextMinSdk.getEditText())
|
||||
.getText()
|
||||
.toString());
|
||||
} catch (Exception e) {
|
||||
getApp().toast(e.getMessage(), Toaster.Type.ERROR);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getTargetSdk() {
|
||||
try {
|
||||
return Integer.parseInt(
|
||||
Objects.requireNonNull(
|
||||
createLayoutBinding.createprojectTextTargetSdk.getEditText())
|
||||
.getText()
|
||||
.toString());
|
||||
} catch (Exception e) {
|
||||
getApp().toast(e.getMessage(), Toaster.Type.ERROR);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private boolean isValid(String appName, String packageName, int minSdk, int targetSdk) {
|
||||
return appName != null
|
||||
&& appName.length() > 0
|
||||
&& packageName != null
|
||||
&& packageName.length() > 0
|
||||
&& minSdk > 1
|
||||
&& minSdk < 99
|
||||
&& targetSdk > 1
|
||||
&& targetSdk < 99;
|
||||
}
|
||||
|
||||
private void createProject(String appName, String packageName, int minSdk, int targetSdk) {
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
new ProjectCreatorCallable(
|
||||
mTemplates.get(currentTemplateIndex),
|
||||
new NewProjectDetails(appName, packageName, minSdk, targetSdk),
|
||||
this),
|
||||
r -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeBegin() {
|
||||
if (mProgressSheet == null) {
|
||||
createProgressSheet();
|
||||
}
|
||||
|
||||
setMessage(R.string.msg_begin_project_write);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProcessTask(String taskName) {
|
||||
setMessage(taskName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(File root) {
|
||||
if (mProgressSheet == null) {
|
||||
createProgressSheet();
|
||||
}
|
||||
|
||||
if (mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
getApp().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS);
|
||||
|
||||
openProject(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(String reason) {
|
||||
if (mProgressSheet == null) {
|
||||
createProgressSheet();
|
||||
}
|
||||
|
||||
if (mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
getApp().toast(reason, Toaster.Type.ERROR);
|
||||
}
|
||||
|
||||
private void openProject(File root) {
|
||||
final var intent = new Intent(this, EditorActivity.class);
|
||||
intent.putExtra(EditorActivity.EXTRA_PROJECT_PATH, root.getAbsolutePath());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void createProgressSheet() {
|
||||
mProgressSheet = new ProgressSheet();
|
||||
mProgressSheet.setShowShadow(false);
|
||||
}
|
||||
|
||||
private void setMessage(int msg) {
|
||||
setMessage(getString(msg));
|
||||
}
|
||||
|
||||
private void setMessage(String msg) {
|
||||
mProgressSheet.setMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (binding.createNewCard.getVisibility() == View.VISIBLE) {
|
||||
hideCreateProject();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,20 +326,6 @@ public class MainActivity extends StudioActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void askProjectOpenPermission(File root) {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_confirm_open_project);
|
||||
builder.setMessage(getString(R.string.msg_confirm_open_project, root.getAbsolutePath()));
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(R.string.yes, (d, w) -> openProject(root));
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void gotoSettings() {
|
||||
startActivity(new Intent(this, PreferencesActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageGranted() {}
|
||||
|
||||
@@ -151,120 +336,11 @@ public class MainActivity extends StudioActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View p1) {
|
||||
if (p1.getId() == binding.createProject.getId()) {
|
||||
showCreateProject();
|
||||
} else if (p1.getId() == createBinding.createprojectClose.getId()) {
|
||||
hideCreateProject();
|
||||
} else if (p1.getId() == createLayoutBinding.nextCard.getId()) {
|
||||
currentTemplateIndex++;
|
||||
if (currentTemplateIndex >= mTemplates.size()) {
|
||||
currentTemplateIndex = 0;
|
||||
}
|
||||
showTemplate(currentTemplateIndex);
|
||||
} else if (p1.getId() == createLayoutBinding.previousCard.getId()) {
|
||||
currentTemplateIndex--;
|
||||
if (currentTemplateIndex < 0) {
|
||||
currentTemplateIndex = mTemplates.size() - 1;
|
||||
}
|
||||
showTemplate(currentTemplateIndex);
|
||||
} else if (p1.getId() == createLayoutBinding.createprojectCreate.getId()) {
|
||||
createNewProject();
|
||||
} else if (p1.getId() == binding.gotoPreferences.getId()) {
|
||||
gotoSettings();
|
||||
} else if (p1.getId() == binding.openProject.getId()) {
|
||||
pickProject();
|
||||
} else if (p1.getId() == binding.openTerminal.getId()) {
|
||||
startActivity(new Intent(this, TerminalActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeBegin() {
|
||||
if (mProgressSheet == null) {
|
||||
createProgressSheet();
|
||||
}
|
||||
|
||||
setMessage(R.string.msg_begin_project_write);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProcessTask(String taskName) {
|
||||
setMessage(taskName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(File root) {
|
||||
if (mProgressSheet == null) {
|
||||
createProgressSheet();
|
||||
}
|
||||
|
||||
if (mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
getApp().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS);
|
||||
|
||||
openProject(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(String reason) {
|
||||
if (mProgressSheet == null) {
|
||||
createProgressSheet();
|
||||
}
|
||||
|
||||
if (mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
getApp().toast(reason, Toaster.Type.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE
|
||||
&& resultCode == RESULT_OK) {
|
||||
ArrayList<String> files = data.getStringArrayListExtra("filePaths");
|
||||
if (files != null) {
|
||||
if (files.size() == 1) {
|
||||
File choseDir = new File(files.get(0));
|
||||
if (choseDir.exists() && choseDir.isDirectory()) {
|
||||
openProject(choseDir);
|
||||
} else {
|
||||
getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
|
||||
}
|
||||
} else {
|
||||
getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (binding.createNewCard.getVisibility() == View.VISIBLE) {
|
||||
hideCreateProject();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (mProgressSheet != null && mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void showTemplate(int index) {
|
||||
if (index < 0 || index >= mTemplates.size()) {
|
||||
return;
|
||||
}
|
||||
final ProjectTemplate template = mTemplates.get(index);
|
||||
createLayoutBinding.createprojectTemplateLabel.setText(template.getName());
|
||||
createLayoutBinding.createprojectTemplateDescription.setText(template.getDescription());
|
||||
createLayoutBinding.createprojectTemplateImage.setImageResource(template.getImageId());
|
||||
protected View bindLayout() {
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
createBinding = binding.createLayout;
|
||||
createLayoutBinding = createBinding.createProjectContent;
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
/** Must be called in/after onViewCreated */
|
||||
@@ -310,147 +386,42 @@ public class MainActivity extends StudioActivity
|
||||
mTemplates.add(libgdx);
|
||||
}
|
||||
|
||||
private void createProgressSheet() {
|
||||
mProgressSheet = new ProgressSheet();
|
||||
mProgressSheet.setShowShadow(false);
|
||||
private void askProjectOpenPermission(File root) {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_confirm_open_project);
|
||||
builder.setMessage(getString(R.string.msg_confirm_open_project, root.getAbsolutePath()));
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(R.string.yes, (d, w) -> openProject(root));
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void setMessage(int msg) {
|
||||
setMessage(getString(msg));
|
||||
}
|
||||
|
||||
private void setMessage(String msg) {
|
||||
mProgressSheet.setMessage(msg);
|
||||
}
|
||||
|
||||
private void openProject(File root) {
|
||||
setMessage(R.string.msg_opening_project);
|
||||
mProgressSheet.setCancelable(false);
|
||||
mProgressSheet.show(getSupportFragmentManager(), "opening_project");
|
||||
|
||||
new TaskExecutor().executeAsync(new ProjectReader(root), this::openEditor);
|
||||
}
|
||||
|
||||
private void pickProject() {
|
||||
UnicornFilePicker.from(this)
|
||||
.addConfigBuilder()
|
||||
.addItemDivider(false)
|
||||
.selectMultipleFiles(false)
|
||||
.setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath())
|
||||
.showHiddenFiles(true)
|
||||
.showOnlyDirectory(true)
|
||||
.theme(R.style.AppTheme_FilePicker)
|
||||
.build()
|
||||
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
|
||||
}
|
||||
|
||||
private void openEditor(AndroidProject project) {
|
||||
if (project == null) {
|
||||
getApp().toast(R.string.msg_invalid_project, Toaster.Type.ERROR);
|
||||
} else {
|
||||
startActivity(
|
||||
new Intent(this, EditorActivity.class)
|
||||
.putExtra(EditorActivity.EXTRA_PROJECT, project));
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE
|
||||
&& resultCode == RESULT_OK) {
|
||||
ArrayList<String> files = data.getStringArrayListExtra("filePaths");
|
||||
if (files != null) {
|
||||
if (files.size() == 1) {
|
||||
File choseDir = new File(files.get(0));
|
||||
if (choseDir.exists() && choseDir.isDirectory()) {
|
||||
openProject(choseDir);
|
||||
} else {
|
||||
getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
|
||||
}
|
||||
} else {
|
||||
getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createNewProject() {
|
||||
final String appName =
|
||||
Objects.requireNonNull(createLayoutBinding.createprojectTextAppName.getEditText())
|
||||
.getText()
|
||||
.toString()
|
||||
.trim();
|
||||
final String packageName =
|
||||
Objects.requireNonNull(
|
||||
createLayoutBinding.createprojectTextPackageName.getEditText())
|
||||
.getText()
|
||||
.toString()
|
||||
.trim();
|
||||
final int minSdk = getMinSdk();
|
||||
final int targetSdk = getTargetSdk();
|
||||
|
||||
if (!isValid(appName, packageName, minSdk, targetSdk)) {
|
||||
getApp().toast(R.string.invalid_values, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
createProject(appName, packageName, minSdk, targetSdk);
|
||||
}
|
||||
|
||||
private void createProject(String appName, String packageName, int minSdk, int targetSdk) {
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
new ProjectCreatorCallable(
|
||||
mTemplates.get(currentTemplateIndex),
|
||||
new NewProjectDetails(appName, packageName, minSdk, targetSdk),
|
||||
this),
|
||||
r -> {});
|
||||
}
|
||||
|
||||
private boolean isValid(String appName, String packageName, int minSdk, int targetSdk) {
|
||||
return appName != null
|
||||
&& appName.length() > 0
|
||||
&& packageName != null
|
||||
&& packageName.length() > 0
|
||||
&& minSdk > 1
|
||||
&& minSdk < 99
|
||||
&& targetSdk > 1
|
||||
&& targetSdk < 99;
|
||||
}
|
||||
|
||||
private int getMinSdk() {
|
||||
try {
|
||||
return Integer.parseInt(
|
||||
Objects.requireNonNull(
|
||||
createLayoutBinding.createprojectTextMinSdk.getEditText())
|
||||
.getText()
|
||||
.toString());
|
||||
} catch (Exception e) {
|
||||
getApp().toast(e.getMessage(), Toaster.Type.ERROR);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getTargetSdk() {
|
||||
try {
|
||||
return Integer.parseInt(
|
||||
Objects.requireNonNull(
|
||||
createLayoutBinding.createprojectTextTargetSdk.getEditText())
|
||||
.getText()
|
||||
.toString());
|
||||
} catch (Exception e) {
|
||||
getApp().toast(e.getMessage(), Toaster.Type.ERROR);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void showCreateProject() {
|
||||
MaterialContainerTransform transform =
|
||||
TransformUtils.createContainerTransformFor(
|
||||
binding.createProject, binding.createNewCard, binding.realContainer);
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), transform);
|
||||
binding.createNewCard.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideCreateProject() {
|
||||
MaterialContainerTransform transform =
|
||||
TransformUtils.createContainerTransformFor(
|
||||
binding.createNewCard, binding.createProject, binding.realContainer);
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), transform);
|
||||
binding.createNewCard.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private static final class ProjectReader implements Callable<AndroidProject> {
|
||||
|
||||
private final File root;
|
||||
|
||||
public ProjectReader(File root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidProject call() throws Exception {
|
||||
return ProjectFinder.fromFolder(root);
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (mProgressSheet != null && mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,25 +33,14 @@ import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
|
||||
public class SplashActivity extends StudioActivity {
|
||||
private ActivitySplashBinding binding;
|
||||
|
||||
private static final Handler mHandler = new Handler();
|
||||
private final Runnable mRunnable =
|
||||
new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ConstantsBridge.SPLASH_TO_MAIN = true;
|
||||
startActivity(new Intent(SplashActivity.this, MainActivity.class));
|
||||
finish();
|
||||
}
|
||||
() -> {
|
||||
ConstantsBridge.SPLASH_TO_MAIN = true;
|
||||
startActivity(new Intent(SplashActivity.this, MainActivity.class));
|
||||
finish();
|
||||
};
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivitySplashBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
private ActivitySplashBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -59,6 +48,12 @@ public class SplashActivity extends StudioActivity {
|
||||
binding.splashText.setText(getString(R.string.msg_checking_storage_permissions));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageAlreadyGranted() {
|
||||
super.onStorageAlreadyGranted();
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageGranted() {
|
||||
super.onStorageGranted();
|
||||
@@ -66,9 +61,9 @@ public class SplashActivity extends StudioActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageAlreadyGranted() {
|
||||
super.onStorageAlreadyGranted();
|
||||
proceed();
|
||||
protected View bindLayout() {
|
||||
binding = ActivitySplashBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void proceed() {
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class BaseBuildAction : EditorActivityAction() {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
val context = getActivity(data) ?: return
|
||||
context.invalidateOptionsMenu()
|
||||
@@ -51,5 +51,5 @@ abstract class BaseBuildAction : EditorActivityAction() {
|
||||
fun shouldPrepare() = visible && enabled
|
||||
|
||||
private fun isBuildInProgress(activity: EditorActivity): Boolean =
|
||||
activity.buildServiceHandler.isBuilding
|
||||
activity.buildService == null || activity.buildService.isBuildInProgress
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ abstract class EditorActivityAction : ActionItem {
|
||||
|
||||
override var requiresUIThread: Boolean = false
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
if (!hasRequiredData(data, Context::class.java)) {
|
||||
markInvisible()
|
||||
}
|
||||
}
|
||||
|
||||
fun getActivity(data: ActionData): EditorActivity? {
|
||||
val context = data.get(Context::class.java) ?: return null
|
||||
return context as EditorActivity
|
||||
|
||||
@@ -36,7 +36,7 @@ class AssembleDebugAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.buildServiceHandler.assembleDebug(true)
|
||||
activity.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class AssembleReleaseAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.buildServiceHandler.service.assembleRelease()
|
||||
activity.assembleRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class BuildAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.buildServiceHandler.service.build()
|
||||
activity.build()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+22
-3
@@ -22,12 +22,15 @@ import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class StopDaemonsAction() : EditorActivityAction() {
|
||||
class CancelBuildAction() : EditorActivityAction() {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.stop_gradle_daemons)
|
||||
label = context.getString(R.string.title_cancel_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_stop_daemons)
|
||||
}
|
||||
|
||||
@@ -41,7 +44,23 @@ class StopDaemonsAction() : EditorActivityAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.stopAllDaemons()
|
||||
log.info("Sending build cancellation request...")
|
||||
context.buildService.cancelCurrentBuild().whenComplete { result, error ->
|
||||
if (error != null) {
|
||||
log.error("Failed to send build cancellation request", error)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
if (!result.wasEnqueued) {
|
||||
log.warn(
|
||||
"Unable to enqueue cancellation request",
|
||||
result.failureReason,
|
||||
result.failureReason!!.message)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
log.info("Build cancellation request was successfully enqueued...")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class CleanAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.clean()
|
||||
context.clean()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class CreateAABAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.bundle()
|
||||
context.bundle()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class QuickRunAction() : BaseBuildAction() {
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.buildServiceHandler.assembleDebug(true)
|
||||
context.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class RebuildAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.cleanAndRebuild()
|
||||
context.cleanAndRebuild()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
|
||||
@@ -54,11 +55,19 @@ class SaveFileAction() : EditorRelatedAction() {
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
|
||||
return try {
|
||||
// Cannot use context.saveAll() because this.execAction is called on non-UI thread
|
||||
// and saveAll call will result in UI actions
|
||||
context.saveAllResult()
|
||||
val result = context.saveAllResult()
|
||||
|
||||
if (result.xmlSaved) {
|
||||
ProjectManager.generateSources(context.buildService)
|
||||
}
|
||||
|
||||
if (result.gradleSaved) {
|
||||
context.notifySyncNeeded()
|
||||
}
|
||||
true
|
||||
} catch (error: Throwable) {
|
||||
log.error("Failed to save file", error)
|
||||
|
||||
@@ -35,7 +35,7 @@ class LintAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.lint()
|
||||
context.lint()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class LintDebugAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.lintDebug()
|
||||
context.lintDebug()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class LintReleaseAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.lintRelease()
|
||||
context.lintRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.adapters;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutProjectsItemBinding;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class ProjectsListAdapter extends RecyclerView.Adapter<ProjectsListAdapter.VH> {
|
||||
private ArrayList<AndroidProject> mProjects;
|
||||
private OnProjectClickListener listener;
|
||||
|
||||
private final Comparator<AndroidProject> PROJECTS_SORTER =
|
||||
new Comparator<AndroidProject>() {
|
||||
|
||||
@Override
|
||||
public int compare(AndroidProject p1, AndroidProject p2) {
|
||||
return p1.getAppName().compareTo(p2.getAppName());
|
||||
}
|
||||
};
|
||||
|
||||
public ProjectsListAdapter(
|
||||
ArrayList<AndroidProject> mProjects, OnProjectClickListener listener) {
|
||||
this.mProjects = mProjects;
|
||||
this.listener = listener;
|
||||
Collections.sort(this.mProjects, PROJECTS_SORTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectsListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
|
||||
return new VH(
|
||||
LayoutProjectsItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ProjectsListAdapter.VH holder, int p2) {
|
||||
final AndroidProject project = mProjects.get(p2);
|
||||
holder.binding.projecsitemName.setText(project.getAppName());
|
||||
holder.binding.projecsitemPkgName.setText(project.getPackageName());
|
||||
holder.binding.deleteMsg.setText(
|
||||
holder.binding
|
||||
.deleteMsg
|
||||
.getContext()
|
||||
.getString(R.string.msg_confirm_delete, project.getAppName()));
|
||||
if (!TextUtils.isEmpty(project.getIconPath()))
|
||||
Glide.with(holder.itemView.getContext().getApplicationContext())
|
||||
.load(new File(project.getIconPath()))
|
||||
.into(holder.binding.projecsitemIcon);
|
||||
else holder.binding.projecsitemIcon.setImageResource(R.drawable.ic_android);
|
||||
|
||||
holder.binding.projectsitemClickableContainer.setOnClickListener(
|
||||
v -> sendClickEvent(project));
|
||||
holder.binding.projectsitemClickableContainer.setOnLongClickListener(
|
||||
v -> holder.toggleDelete());
|
||||
holder.binding.delete.setOnClickListener(v -> holder.showConfirmDelete());
|
||||
holder.binding.deleteCancel.setOnClickListener(v -> holder.hideAllDelete());
|
||||
holder.binding.deleteOk.setOnClickListener(v -> deleteProject(project, p2));
|
||||
}
|
||||
|
||||
private void deleteProject(AndroidProject project, int index) {
|
||||
index = index >= mProjects.size() ? mProjects.size() - 1 : index;
|
||||
if (index < 0) return;
|
||||
mProjects.remove(index);
|
||||
notifyItemRemoved(index);
|
||||
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
() -> FileUtils.delete(project.getProjectPath()),
|
||||
__ -> {
|
||||
StudioApp.getInstance()
|
||||
.toast(
|
||||
__
|
||||
? R.string.msg_project_deleted
|
||||
: R.string.msg_project_delete_failed,
|
||||
Toaster.Type.SUCCESS);
|
||||
});
|
||||
}
|
||||
|
||||
private void sendClickEvent(AndroidProject project) {
|
||||
if (listener != null) listener.onProjectClick(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mProjects.size();
|
||||
}
|
||||
|
||||
public class VH extends RecyclerView.ViewHolder {
|
||||
LayoutProjectsItemBinding binding;
|
||||
|
||||
public VH(LayoutProjectsItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
public void showConfirmDelete() {
|
||||
binding.confirmDeleteContainer.setEnabled(true);
|
||||
TransitionManager.beginDelayedTransition(
|
||||
binding.getRoot(), new ChangeBounds().setDuration(85));
|
||||
binding.confirmDeleteContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideAllDelete() {
|
||||
hideDelete();
|
||||
hideConfirmDelete();
|
||||
}
|
||||
|
||||
public boolean toggleDelete() {
|
||||
binding.delete.setEnabled(!binding.delete.isEnabled());
|
||||
TransitionManager.beginDelayedTransition(
|
||||
binding.getRoot(), new ChangeBounds().setDuration(85));
|
||||
binding.delete.setVisibility(binding.delete.isEnabled() ? View.VISIBLE : View.GONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void hideDelete() {
|
||||
binding.delete.setEnabled(false);
|
||||
TransitionManager.beginDelayedTransition(
|
||||
binding.getRoot(), new ChangeBounds().setDuration(85));
|
||||
binding.delete.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void hideConfirmDelete() {
|
||||
binding.confirmDeleteContainer.setEnabled(false);
|
||||
TransitionManager.beginDelayedTransition(
|
||||
binding.getRoot(), new ChangeBounds().setDuration(85));
|
||||
binding.confirmDeleteContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnProjectClickListener {
|
||||
public void onProjectClick(AndroidProject project);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import androidx.annotation.NonNull;
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
import com.itsaky.androidide.CrashHandlerActivity;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.project.ProjectResourceTable;
|
||||
import com.itsaky.androidide.projects.ProjectResourceTable;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.apiinfo.ApiInfo;
|
||||
import com.itsaky.attrinfo.AttrInfo;
|
||||
@@ -66,16 +66,6 @@ public class StudioApp extends BaseApplication {
|
||||
getApiInformation();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ILanguageServer getJavaLanguageServer() {
|
||||
return mJavaLanguageServer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ILanguageServer getXMLLanguageServer() {
|
||||
return mXMLLanguageServer;
|
||||
}
|
||||
|
||||
private void handleCrash(Thread thread, Throwable th) {
|
||||
writeException(th);
|
||||
|
||||
@@ -95,6 +85,33 @@ public class StudioApp extends BaseApplication {
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads API version information from api-versions.xml */
|
||||
public CompletableFuture<SDKInfo> getApiInformation() {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
if (sdkInfo == null) {
|
||||
try {
|
||||
sdkInfo = new SDKInfo(StudioApp.this);
|
||||
((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(R.string.err_init_sdkinfo), th);
|
||||
}
|
||||
}
|
||||
|
||||
return sdkInfo;
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ILanguageServer getJavaLanguageServer() {
|
||||
return mJavaLanguageServer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ILanguageServer getXMLLanguageServer() {
|
||||
return mXMLLanguageServer;
|
||||
}
|
||||
|
||||
public CompletableFuture<LayoutInflaterConfiguration> createInflaterConfig(
|
||||
ILayoutInflater.ContextProvider contextProvider, Set<File> resDirs) {
|
||||
|
||||
@@ -119,27 +136,6 @@ public class StudioApp extends BaseApplication {
|
||||
return mResTable == null ? mResTable = new ProjectResourceTable() : mResTable;
|
||||
}
|
||||
|
||||
/** Reads API version information from api-versions.xml */
|
||||
public CompletableFuture<SDKInfo> getApiInformation() {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
if (sdkInfo == null) {
|
||||
try {
|
||||
sdkInfo = new SDKInfo(StudioApp.this);
|
||||
((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(R.string.err_init_sdkinfo), th);
|
||||
}
|
||||
}
|
||||
|
||||
return sdkInfo;
|
||||
});
|
||||
}
|
||||
|
||||
public void stopAllDaemons() {
|
||||
newShell(null).bgAppend("gradle --stop");
|
||||
}
|
||||
|
||||
public ApiInfo apiInfo() {
|
||||
return sdkInfo.getApiInfo();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.FileTreeCallable;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
@@ -63,19 +63,27 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
private LayoutEditorFileTreeBinding binding;
|
||||
private AndroidTreeView mFileTreeView;
|
||||
private FileActionListener mFileActionListener;
|
||||
private AndroidProject mProject;
|
||||
private TreeNode mRoot;
|
||||
private String mTreeState;
|
||||
|
||||
public FileTreeFragment() {}
|
||||
|
||||
@NonNull
|
||||
public static FileTreeFragment newInstance(AndroidProject project) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable("project", project);
|
||||
FileTreeFragment frag = new FileTreeFragment();
|
||||
frag.setArguments(bundle);
|
||||
return frag;
|
||||
public static FileTreeFragment newInstance() {
|
||||
return new FileTreeFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mFileActionListener = (FileActionListener) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
saveTreeState();
|
||||
outState.putString(KEY_STORED_TREE_STATE, mTreeState);
|
||||
}
|
||||
|
||||
public void saveTreeState() {
|
||||
@@ -87,49 +95,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void tryRestoreState() {
|
||||
tryRestoreState(mTreeState);
|
||||
}
|
||||
|
||||
private void tryRestoreState(String state) {
|
||||
if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
|
||||
|
||||
LOG.debug("Restoring tree view state:", "'" + state + "'");
|
||||
|
||||
mFileTreeView.collapseAll();
|
||||
final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
|
||||
final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
|
||||
restoreNodeState(mRoot, openNodes);
|
||||
} else {
|
||||
LOG.error(
|
||||
"Unable to restore tree state",
|
||||
"treeState=" + state,
|
||||
"treeView=" + mFileTreeView);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) {
|
||||
final var children = root.getChildren();
|
||||
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
|
||||
final var node = children.get(i);
|
||||
if (openNodes.contains(node.getPath())) {
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
restoreNodeState(node, openNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mFileActionListener = (FileActionListener) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -140,7 +105,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mProject = requireArguments().getParcelable("project");
|
||||
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
|
||||
mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null);
|
||||
@@ -188,11 +152,28 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
saveTreeState();
|
||||
outState.putString(KEY_STORED_TREE_STATE, mTreeState);
|
||||
public void collapseNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.collapseNode(node);
|
||||
}
|
||||
|
||||
public void expandNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.expandNode(node);
|
||||
}
|
||||
|
||||
private void setLoading(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).setLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void listNode(@NonNull TreeNode node, Runnable whenDone) {
|
||||
@@ -222,6 +203,12 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
__ -> finalWhenDone.run());
|
||||
}
|
||||
|
||||
private void updateChevron(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
private void getNodeFromFiles(File[] files, TreeNode parent) {
|
||||
Arrays.sort(files, new FileTreeCallable.SortFileName());
|
||||
Arrays.sort(files, new FileTreeCallable.SortFolder());
|
||||
@@ -232,18 +219,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void setLoading(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).setLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChevron(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(TreeNode node, Object value) {
|
||||
if (mFileActionListener != null) {
|
||||
@@ -252,32 +227,15 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
return true;
|
||||
}
|
||||
|
||||
public void expandNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.expandNode(node);
|
||||
}
|
||||
|
||||
public void collapseNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.collapseNode(node);
|
||||
}
|
||||
|
||||
public void listProjectFiles() {
|
||||
if (mProject == null) {
|
||||
final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath ();
|
||||
if (projectDirPath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final File gradleProps = Environment.GRADLE_PROPS;
|
||||
final File gradleHome = Environment.GRADLE_USER_HOME;
|
||||
File projectDir = new File(mProject.getProjectPath());
|
||||
File projectDir = new File(projectDirPath);
|
||||
mRoot = TreeNode.root(projectDir);
|
||||
if (gradleHome.exists() && gradleHome.isDirectory()) {
|
||||
if (!gradleProps.exists()) {
|
||||
@@ -342,6 +300,43 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
return binding.fileTreeLoadingProgress;
|
||||
}
|
||||
|
||||
private void tryRestoreState() {
|
||||
tryRestoreState(mTreeState);
|
||||
}
|
||||
|
||||
private void tryRestoreState(String state) {
|
||||
if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
|
||||
|
||||
LOG.debug("Restoring tree view state:", "'" + state + "'");
|
||||
|
||||
mFileTreeView.collapseAll();
|
||||
final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
|
||||
final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
|
||||
restoreNodeState(mRoot, openNodes);
|
||||
} else {
|
||||
LOG.error(
|
||||
"Unable to restore tree state",
|
||||
"treeState=" + state,
|
||||
"treeView=" + mFileTreeView);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) {
|
||||
final var children = root.getChildren();
|
||||
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
|
||||
final var node = children.get(i);
|
||||
if (openNodes.contains(node.getPath())) {
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
restoreNodeState(node, openNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface FileActionListener {
|
||||
CodeEditorView openFile(File file);
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.preferences;
|
||||
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_AUTO_SAVE;
|
||||
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_DRAW_HEX;
|
||||
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_LINE_BREAK;
|
||||
@@ -44,6 +43,7 @@ import androidx.preference.SwitchPreference;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.databinding.LayoutTextSizeSliderBinding;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
/*
|
||||
* 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.handlers;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.project.IDEModule;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
import com.itsaky.androidide.services.builder.BuildListener;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
import com.itsaky.androidide.tasks.gradle.build.ApkGeneratingTask;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BuildServiceHandler extends IDEHandler implements BuildListener {
|
||||
|
||||
private IDEService service;
|
||||
private boolean shouldInstallApk = false;
|
||||
|
||||
public BuildServiceHandler(Provider provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
public IDEService getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public boolean isBuilding() {
|
||||
return getService().isBuilding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
Objects.requireNonNull(this.activity());
|
||||
|
||||
AndroidProject project = provider.provideAndroidProject();
|
||||
Objects.requireNonNull(project);
|
||||
Objects.requireNonNull(project.getProjectPath());
|
||||
|
||||
this.service = new IDEService(new File(project.getProjectPath()));
|
||||
this.service.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.service.stopAllDaemons();
|
||||
this.service.exit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildModified() {
|
||||
// Does nothing currently
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProjectLoaded(IDEProject project, Optional<IDEModule> appModule) {
|
||||
project.iconPath = provider.provideAndroidProject().getIconPath();
|
||||
activity().setIDEProject(project);
|
||||
|
||||
if (appModule.isPresent()) {
|
||||
IDEModule app = appModule.get();
|
||||
activity().setStatus(activity().getString(R.string.msg_starting_completion));
|
||||
|
||||
final List<String> paths =
|
||||
app.dependencies.stream()
|
||||
.filter(this::isClasspathValid)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
provider.provideAndroidProject().setClassPaths(paths);
|
||||
|
||||
File androidJar =
|
||||
new File(
|
||||
Environment.ANDROID_HOME,
|
||||
String.format("platforms/%s/android.jar", app.compileSdkVersion));
|
||||
if (androidJar.exists()) {
|
||||
Environment.setBootClasspath(androidJar);
|
||||
provider.provideAndroidProject().addClasspath(androidJar.getAbsolutePath());
|
||||
activity().updateServices();
|
||||
} else {
|
||||
activity().setStatus("android.jar not found!");
|
||||
}
|
||||
} else {
|
||||
activity().setStatus("Cannot get :app module...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartingGradleDaemon(GradleTask task) {
|
||||
activity().setStatus(activity().getString(R.string.msg_starting_daemon));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunTask(GradleTask task, String name) {
|
||||
if (task != null && task.canOutput()) {
|
||||
activity().setStatus(service.typeString(task.getTaskID()) + " " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildSuccessful(GradleTask task, String msg) {
|
||||
appendOutput(task, msg);
|
||||
analyzeCurrentFile();
|
||||
if (task == null || activity() == null) {
|
||||
return;
|
||||
}
|
||||
if (task.canOutput()) {
|
||||
activity().setStatus(msg);
|
||||
}
|
||||
|
||||
// If this task generates APK, ask user if he/she wants to install them
|
||||
if (shouldInstallApk && task instanceof ApkGeneratingTask) {
|
||||
final IDEProject project = activity().provideIDEProject();
|
||||
final Optional<IDEModule> app =
|
||||
project.getModuleByPath(":app"); // TODO Handle multiple application modules
|
||||
if (app.isPresent()) {
|
||||
final String path = app.get().projectDir;
|
||||
if (path != null) {
|
||||
final File buildDir = new File(path, "build");
|
||||
installApks(((ApkGeneratingTask) task).getApks(buildDir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity()
|
||||
.getApp()
|
||||
.getPrefManager()
|
||||
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
|
||||
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildFailed(GradleTask task, String msg) {
|
||||
appendOutput(task, msg);
|
||||
analyzeCurrentFile();
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
if (task.canOutput()) {
|
||||
activity().setStatus(msg);
|
||||
}
|
||||
|
||||
activity()
|
||||
.getApp()
|
||||
.getPrefManager()
|
||||
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
|
||||
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFiles() {
|
||||
activity().saveAll(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendOutput(GradleTask task, CharSequence text) {
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
if (task != null && !task.canOutput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
activity().appendBuildOut(text.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareBuild() {
|
||||
boolean isFirstBuild =
|
||||
activity()
|
||||
.getApp()
|
||||
.getPrefManager()
|
||||
.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true);
|
||||
activity()
|
||||
.setStatus(
|
||||
activity()
|
||||
.getString(
|
||||
isFirstBuild
|
||||
? R.string.preparing_first
|
||||
: R.string.preparing));
|
||||
if (isFirstBuild) {
|
||||
activity().showFirstBuildNotice();
|
||||
}
|
||||
activity().getBinding().buildProgressIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void setShouldInstallApk(boolean install) {
|
||||
this.shouldInstallApk = install;
|
||||
}
|
||||
|
||||
public void assembleDebug(boolean shouldInstallApk) {
|
||||
setShouldInstallApk(shouldInstallApk);
|
||||
getService().assembleDebug();
|
||||
}
|
||||
|
||||
private boolean isClasspathValid(String path) {
|
||||
if (path == null || path.trim().length() <= 0 || path.trim().equals("/")) {
|
||||
return false;
|
||||
}
|
||||
File file = new File(path);
|
||||
return file.isFile()
|
||||
&& file.getName().endsWith(".jar")
|
||||
// Release R.jar shouldn't be included in classpath
|
||||
&& !file.getAbsolutePath().endsWith("/release/R.jar");
|
||||
}
|
||||
|
||||
private void installApks(final Set<File> apks) {
|
||||
if (activity() == null || apks == null || apks.isEmpty()) {
|
||||
LOG.error("Cannot install APK. Activity=" + activity(), "apks=" + apks);
|
||||
return;
|
||||
}
|
||||
|
||||
if (apks.size() == 1) {
|
||||
activity().install(apks.iterator().next());
|
||||
} else {
|
||||
LOG.info("Multiple APKs found. Let the user select...");
|
||||
final List<File> files = new ArrayList<>(apks);
|
||||
final MaterialAlertDialogBuilder builder =
|
||||
DialogUtils.newMaterialDialogBuilder(activity());
|
||||
builder.setTitle(activity().getString(R.string.title_install_apks));
|
||||
builder.setItems(
|
||||
getNames(files),
|
||||
(d, w) -> {
|
||||
d.dismiss();
|
||||
activity().install(files.get(w));
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
|
||||
private CharSequence[] getNames(final Collection<File> apks) {
|
||||
final String[] names = new String[apks.size()];
|
||||
|
||||
int i = 0;
|
||||
for (File apk : apks) {
|
||||
names[i] = apk.getName();
|
||||
++i;
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private void analyzeCurrentFile() {
|
||||
final var editorView = activity().getCurrentEditor();
|
||||
if (editorView != null) {
|
||||
final var editor = editorView.getEditor();
|
||||
if (editor != null) {
|
||||
editor.analyze();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.handlers
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.text.Layout.Alignment.ALIGN_CENTER
|
||||
import android.text.Layout.Alignment.ALIGN_NORMAL
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.AlignmentSpan
|
||||
import android.view.View
|
||||
import android.webkit.URLUtil
|
||||
import androidx.core.view.GravityCompat
|
||||
import com.itsaky.androidide.EditorActivity
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.managers.PreferenceManager
|
||||
import com.itsaky.androidide.services.GradleBuildService
|
||||
import com.itsaky.androidide.tooling.events.ProgressEvent
|
||||
import com.itsaky.androidide.tooling.events.StatusEvent
|
||||
import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent
|
||||
import com.itsaky.androidide.tooling.events.download.FileDownloadOperationDescriptor
|
||||
import com.itsaky.androidide.tooling.events.task.TaskProgressEvent
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.StudioUtils
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Handles events received from [GradleBuildService] updates [EditorActivity].
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class EditorEventListener : GradleBuildService.EventListener {
|
||||
|
||||
private var activityReference: WeakReference<EditorActivity?> = WeakReference(null)
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
fun setActivity(activity: EditorActivity) {
|
||||
this.activityReference = WeakReference(activity)
|
||||
}
|
||||
|
||||
override fun prepareBuild() {
|
||||
val isFirstBuild =
|
||||
activity()
|
||||
.app.prefManager.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true)
|
||||
activity()
|
||||
.setStatus(
|
||||
activity()
|
||||
.getString(if (isFirstBuild) string.preparing_first else string.preparing))
|
||||
|
||||
if (isFirstBuild) {
|
||||
activity().showFirstBuildNotice()
|
||||
}
|
||||
|
||||
activity().binding.buildProgressIndicator.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onBuildSuccessful(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
appendOutputSeparator()
|
||||
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onProgressEvent(event: ProgressEvent) {
|
||||
if (event is FileDownloadFinishEvent) {
|
||||
activity().setStatus("")
|
||||
} else if (event is TaskProgressEvent) {
|
||||
activity()
|
||||
.setStatus(activity().getString(string.msg_running_task, event.descriptor.taskPath))
|
||||
} else if (event is StatusEvent && event.descriptor is FileDownloadOperationDescriptor) {
|
||||
val total = StudioUtils.bytesToSizeString(event.total)
|
||||
val progress = StudioUtils.bytesToSizeString(event.progress)
|
||||
val fileName =
|
||||
URLUtil.guessFileName(
|
||||
(event.descriptor as FileDownloadOperationDescriptor).uri.path, null, null)
|
||||
|
||||
val status = SpannableStringBuilder()
|
||||
status.append(
|
||||
"[$progress/$total]",
|
||||
AlignmentSpan.Standard(ALIGN_NORMAL),
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
status.append(
|
||||
" Download $fileName",
|
||||
AlignmentSpan.Standard(ALIGN_CENTER),
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
activity().setStatus(status, GravityCompat.START)
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendOutputSeparator() {
|
||||
activity().appendBuildOut("\n\n")
|
||||
}
|
||||
|
||||
override fun onBuildFailed(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
appendOutputSeparator()
|
||||
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onOutput(line: String?) {
|
||||
activity().appendBuildOut(line)
|
||||
|
||||
// TODO This can be handled better when ProgressEvents are received from Tooling API server
|
||||
if (line!!.contains("BUILD SUCCESSFUL") || line.contains("BUILD FAILED")) {
|
||||
activity().setStatus(line)
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeCurrentFile() {
|
||||
val editorView = activity().currentEditor
|
||||
if (editorView != null) {
|
||||
val editor = editorView.editor
|
||||
editor?.analyze()
|
||||
}
|
||||
}
|
||||
|
||||
fun activity(): EditorActivity {
|
||||
return activityReference.get()
|
||||
?: throw IllegalStateException("Activity reference has been destroyed!")
|
||||
}
|
||||
|
||||
private fun installApks(apks: Set<File>?) {
|
||||
if (apks == null || apks.isEmpty()) {
|
||||
IDEHandler.LOG.error("Cannot install APKs: $apks")
|
||||
return
|
||||
}
|
||||
|
||||
if (apks.size == 1) {
|
||||
activity().install(apks.iterator().next())
|
||||
} else {
|
||||
IDEHandler.LOG.info("Multiple APKs found. Let the user select...")
|
||||
val files: List<File> = ArrayList(apks)
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(activity())
|
||||
builder.setTitle(activity().getString(string.title_install_apks))
|
||||
builder.setItems(getNames(files)) { d: DialogInterface, w: Int ->
|
||||
d.dismiss()
|
||||
activity().install(files[w])
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNames(apks: Collection<File>): Array<String?> {
|
||||
val names = arrayOfNulls<String>(apks.size)
|
||||
for ((i, apk) in apks.withIndex()) {
|
||||
names[i] = apk.name
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import com.itsaky.androidide.databinding.LayoutCreateFileJavaBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutDialogTextInputBinding;
|
||||
import com.itsaky.androidide.fragments.sheets.OptionsListFragment;
|
||||
import com.itsaky.androidide.models.SheetOption;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ProjectWriter;
|
||||
@@ -93,34 +94,27 @@ public class FileOptionsHandler extends IDEHandler
|
||||
if (forceUnknownType) {
|
||||
createNewEmptyFile(f);
|
||||
} else {
|
||||
final var projectDir = ProjectManager.INSTANCE.getProjectDirPath ();
|
||||
Objects.requireNonNull(projectDir);
|
||||
|
||||
final boolean isJava =
|
||||
Pattern.compile(
|
||||
Pattern.quote(androidProject().getProjectPath())
|
||||
+ JAVA_PATH_REGEX)
|
||||
Pattern.compile(Pattern.quote(projectDir) + JAVA_PATH_REGEX)
|
||||
.matcher(f.getAbsolutePath())
|
||||
.find();
|
||||
final boolean isRes =
|
||||
Pattern.compile(
|
||||
Pattern.quote(androidProject().getProjectPath())
|
||||
+ RES_PATH_REGEX)
|
||||
Pattern.compile(Pattern.quote(projectDir) + RES_PATH_REGEX)
|
||||
.matcher(f.getAbsolutePath())
|
||||
.find();
|
||||
final boolean isLayoutRes =
|
||||
Pattern.compile(
|
||||
Pattern.quote(androidProject().getProjectPath())
|
||||
+ LAYOUT_RES_PATH_REGEX)
|
||||
Pattern.compile(Pattern.quote(projectDir) + LAYOUT_RES_PATH_REGEX)
|
||||
.matcher(f.getAbsolutePath())
|
||||
.find();
|
||||
final boolean isMenuRes =
|
||||
Pattern.compile(
|
||||
Pattern.quote(androidProject().getProjectPath())
|
||||
+ MENU_RES_PATH_REGEX)
|
||||
Pattern.compile(Pattern.quote(projectDir) + MENU_RES_PATH_REGEX)
|
||||
.matcher(f.getAbsolutePath())
|
||||
.find();
|
||||
final boolean isDrawableRes =
|
||||
Pattern.compile(
|
||||
Pattern.quote(androidProject().getProjectPath())
|
||||
+ DRAWABLE_RES_PATH_REGEX)
|
||||
Pattern.compile(Pattern.quote(projectDir) + DRAWABLE_RES_PATH_REGEX)
|
||||
.matcher(f.getAbsolutePath())
|
||||
.find();
|
||||
if (isJava) {
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
package com.itsaky.androidide.handlers;
|
||||
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
/**
|
||||
@@ -36,22 +34,14 @@ public abstract class IDEHandler {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
protected EditorActivity activity() {
|
||||
return provider.provideEditorActivity();
|
||||
}
|
||||
|
||||
protected AndroidProject androidProject() {
|
||||
return provider.provideAndroidProject();
|
||||
}
|
||||
|
||||
protected IDEProject ideProject() {
|
||||
return provider.provideIDEProject();
|
||||
}
|
||||
|
||||
public abstract void start();
|
||||
|
||||
public abstract void stop();
|
||||
|
||||
protected EditorActivity activity() {
|
||||
return provider.provideEditorActivity();
|
||||
}
|
||||
|
||||
/** An interface to communicate between a handler and its client */
|
||||
public interface Provider {
|
||||
|
||||
@@ -61,19 +51,5 @@ public abstract class IDEHandler {
|
||||
* @throws NullPointerException is this is required
|
||||
*/
|
||||
EditorActivity provideEditorActivity();
|
||||
|
||||
/**
|
||||
* Called by handler to get a reference of the current {@link AndroidProject}
|
||||
*
|
||||
* @throws NullPointerException is this is required
|
||||
*/
|
||||
AndroidProject provideAndroidProject();
|
||||
|
||||
/**
|
||||
* Called by handler to get a reference of the current {@link IDEProject}
|
||||
*
|
||||
* @throws NullPointerException is this is required
|
||||
*/
|
||||
IDEProject provideIDEProject();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.androidide.language.xml;
|
||||
|
||||
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.OPERATOR;
|
||||
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.TEXT_SELECTED;
|
||||
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forComment;
|
||||
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get;
|
||||
import static io.github.rosemoe.sora.widget.schemes.EditorColorScheme.LITERAL;
|
||||
|
||||
@@ -25,8 +25,8 @@ import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.language.CommonCompletionProvider;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageClient;
|
||||
|
||||
@@ -17,15 +17,26 @@
|
||||
*/
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import static com.itsaky.androidide.utils.ILogger.newInstance;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.JSONUtility;
|
||||
import com.itsaky.androidide.utils.ListingFileRedirect;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ApkMetadata {
|
||||
|
||||
private static final ILogger LOG = newInstance("ApkMetadata");
|
||||
|
||||
@SerializedName("version")
|
||||
private int version;
|
||||
|
||||
@@ -44,12 +55,92 @@ public class ApkMetadata {
|
||||
@SerializedName("elementType")
|
||||
private String elementType;
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
@Nullable
|
||||
public static File findApkFile(@NonNull File listingFIle) {
|
||||
try {
|
||||
|
||||
// This sometimes might be a redirect to the listing file
|
||||
// So we have to handle this case too.
|
||||
final var redirectedFile = ListingFileRedirect.maybeExtractRedirectedFile(listingFIle);
|
||||
if (redirectedFile != null) {
|
||||
listingFIle = redirectedFile;
|
||||
}
|
||||
|
||||
final var dir = listingFIle.getParentFile();
|
||||
final var metadata =
|
||||
JSONUtility.gson.fromJson(new FileReader(listingFIle), ApkMetadata.class);
|
||||
if (!isValid(metadata)) {
|
||||
LOG.warn("Invalid APK metadata:", metadata);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Element element : metadata.getElements()) {
|
||||
if (element == null || element.getOutputFile() == null) {
|
||||
LOG.warn("No output file specified in APK metadata element:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
final File apk = new File(dir, element.getOutputFile());
|
||||
if (apk.exists() && apk.isFile()) {
|
||||
LOG.info("Found apk in metadata:", apk);
|
||||
return apk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.error("Metadata file not found...", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
private static boolean isValid(ApkMetadata metadata) {
|
||||
|
||||
// Null checks
|
||||
if (metadata == null
|
||||
|| metadata.getArtifactType() == null
|
||||
|| metadata.getArtifactType().getType() == null
|
||||
|| metadata.getElements() == null) {
|
||||
LOG.warn("APK metadata null check failed. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
final ArtifactType type = metadata.getArtifactType();
|
||||
final List<Element> elements = metadata.getElements();
|
||||
|
||||
if (!type.getType().equals(ArtifactType.TYPE_APK)) {
|
||||
LOG.warn("Artifact is not of type APK. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elements.isEmpty()) {
|
||||
LOG.warn("No output elements found for metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean atLeastOneApk = false;
|
||||
for (Element element : elements) {
|
||||
if (element == null
|
||||
|| element.getOutputFile() == null
|
||||
|| !element.getOutputFile().endsWith(".apk")) {
|
||||
LOG.warn("Skipping output element because file is not APK:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
atLeastOneApk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("Output metadata validation succeeded");
|
||||
return atLeastOneApk;
|
||||
}
|
||||
|
||||
public List<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public ArtifactType getArtifactType() {
|
||||
@@ -60,6 +151,18 @@ public class ApkMetadata {
|
||||
this.artifactType = artifactType;
|
||||
}
|
||||
|
||||
public void setElements(List<Element> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
@@ -76,14 +179,6 @@ public class ApkMetadata {
|
||||
this.variantName = variantName;
|
||||
}
|
||||
|
||||
public List<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public void setElements(List<Element> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public String getElementType() {
|
||||
return elementType;
|
||||
}
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.project;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AndroidProject implements Parcelable {
|
||||
|
||||
private String appName;
|
||||
private String packageName;
|
||||
private String iconPath;
|
||||
private String projectPath;
|
||||
private String mainModule;
|
||||
private String mainSourcePath;
|
||||
|
||||
private List<String> modulePaths = new ArrayList<>();
|
||||
private List<String> sourcePaths = new ArrayList<>();
|
||||
private List<String> classPaths = new ArrayList<>();
|
||||
|
||||
public AndroidProject() {
|
||||
addSource(StudioApp.getInstance().getLogSenderDir().getAbsolutePath());
|
||||
}
|
||||
|
||||
protected AndroidProject(Parcel in) {
|
||||
appName = in.readString();
|
||||
packageName = in.readString();
|
||||
iconPath = in.readString();
|
||||
projectPath = in.readString();
|
||||
mainModule = in.readString();
|
||||
mainSourcePath = in.readString();
|
||||
in.readList(modulePaths, String.class.getClassLoader());
|
||||
in.readList(sourcePaths, String.class.getClassLoader());
|
||||
in.readList(classPaths, String.class.getClassLoader());
|
||||
}
|
||||
|
||||
public File getBuildDir() {
|
||||
return new File(getMainModulePath(), "build");
|
||||
}
|
||||
|
||||
public AndroidProject setMainModule(String mainModule) {
|
||||
this.mainModule = mainModule;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getMainModule() {
|
||||
return mainModule;
|
||||
}
|
||||
|
||||
public String getMainModulePath() {
|
||||
return new File(getProjectPath(), getMainModule()).getAbsolutePath();
|
||||
}
|
||||
|
||||
public AndroidProject setMainSourcePath(String mainModulePath) {
|
||||
this.mainSourcePath = mainModulePath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getMainSourcePath() {
|
||||
return mainSourcePath;
|
||||
}
|
||||
|
||||
public AndroidProject setModulePaths(List<String> modulePaths) {
|
||||
this.modulePaths = modulePaths;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getModulePaths() {
|
||||
if (modulePaths == null) modulePaths = new ArrayList<>();
|
||||
return modulePaths;
|
||||
}
|
||||
|
||||
public AndroidProject addModule(String path) {
|
||||
if (!getModulePaths().contains(path)) getModulePaths().add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AndroidProject setSourcePaths(List<String> sourcePaths) {
|
||||
this.sourcePaths = sourcePaths;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getSourcePaths() {
|
||||
if (sourcePaths == null) sourcePaths = new ArrayList<>();
|
||||
return sourcePaths;
|
||||
}
|
||||
|
||||
public AndroidProject addSource(String path) {
|
||||
if (!getSourcePaths().contains(path)) getSourcePaths().add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AndroidProject setClassPaths(List<String> classPaths) {
|
||||
this.classPaths = classPaths;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getClassPaths() {
|
||||
if (classPaths == null) classPaths = new ArrayList<>();
|
||||
return classPaths;
|
||||
}
|
||||
|
||||
public AndroidProject addClasspath(String path) {
|
||||
if (!getClassPaths().contains(path)) getClassPaths().add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AndroidProject setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public AndroidProject setPackageName(String packageName) {
|
||||
this.packageName = packageName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public AndroidProject setIconPath(String iconPath) {
|
||||
this.iconPath = iconPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getIconPath() {
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
public AndroidProject setProjectPath(String projectPath) {
|
||||
this.projectPath = projectPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getProjectPath() {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel p1, int p2) {
|
||||
p1.writeString(appName);
|
||||
p1.writeString(packageName);
|
||||
p1.writeString(iconPath);
|
||||
p1.writeString(projectPath);
|
||||
p1.writeString(mainModule);
|
||||
p1.writeString(mainSourcePath);
|
||||
p1.writeList(modulePaths);
|
||||
p1.writeList(sourcePaths);
|
||||
p1.writeList(classPaths);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "["
|
||||
+ "\nappName="
|
||||
+ getAppName()
|
||||
+ "\npackageName="
|
||||
+ getPackageName()
|
||||
+ "\niconPath="
|
||||
+ getIconPath()
|
||||
+ "\nprojectPath="
|
||||
+ getProjectPath()
|
||||
+ "\nmainModule="
|
||||
+ getMainModule()
|
||||
+ "\nmainModulePath="
|
||||
+ getMainModulePath()
|
||||
+ "\nmainModuleSourcePath="
|
||||
+ getMainSourcePath()
|
||||
+ "\nmodules="
|
||||
+ getModulePaths()
|
||||
+ "\nsources="
|
||||
+ getSourcePaths()
|
||||
+ "\nclasspaths="
|
||||
+ getClassPaths()
|
||||
+ "\n]";
|
||||
}
|
||||
|
||||
public static final Creator<AndroidProject> CREATOR =
|
||||
new Creator<AndroidProject>() {
|
||||
|
||||
@Override
|
||||
public AndroidProject createFromParcel(Parcel p1) {
|
||||
return new AndroidProject(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidProject[] newArray(int p1) {
|
||||
return new AndroidProject[p1];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.project;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents a module (subproject) of a root project.
|
||||
*
|
||||
* <p>In Android projects, a module can either be an application project, or a library project.
|
||||
*/
|
||||
public class IDEModule extends IDEProject {
|
||||
|
||||
/** Build tools version of this project */
|
||||
@SerializedName("buildToolsVersion")
|
||||
public String buildToolsVersion;
|
||||
|
||||
/**
|
||||
* Compile SDK version of this project
|
||||
*
|
||||
* <p>Representation: 'android-31', 'android-30', 'android-29', etc
|
||||
*/
|
||||
@SerializedName("compileSdkVersion")
|
||||
public String compileSdkVersion;
|
||||
|
||||
/** Minimum SDK version of this project */
|
||||
@SerializedName("minSdk")
|
||||
public SDK minSdk;
|
||||
|
||||
/** Target SDK version of this project */
|
||||
@SerializedName("targetSdk")
|
||||
public SDK targetSdk;
|
||||
|
||||
/** Version code of this project */
|
||||
@SerializedName("versionCode")
|
||||
public int versionCode = 0;
|
||||
|
||||
/** Version name of this project */
|
||||
@SerializedName("versionName")
|
||||
public String versionName = "Not defined";
|
||||
|
||||
/** Is this project an Android library project? */
|
||||
@SerializedName("isLibrary")
|
||||
public boolean isLibrary = true;
|
||||
|
||||
/** Does this module has viewBinding enabled? */
|
||||
@SerializedName("viewBindingEnabled")
|
||||
public boolean viewBindingEnabled;
|
||||
|
||||
/** Library projects don't have applicationId, application projects do. */
|
||||
@SerializedName("applicationId")
|
||||
public String applicationId;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.project;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** A model that contains data of a project currently opened in AndroidIDE */
|
||||
public class IDEProject {
|
||||
|
||||
/** Name of this project */
|
||||
@SerializedName("name")
|
||||
public String name = "";
|
||||
|
||||
/** Name of this project which should be displayed to user */
|
||||
@SerializedName("displayName")
|
||||
public String displayName = "";
|
||||
|
||||
/** Description of this project. Maybe null. */
|
||||
@SerializedName("description")
|
||||
public String description = "";
|
||||
|
||||
/**
|
||||
* Path of this project. This is NOT the directory path of this project.
|
||||
*
|
||||
* <p>For example, ':app or ':module:subModule'
|
||||
*/
|
||||
@SerializedName("path")
|
||||
public String path = "";
|
||||
|
||||
/** Path of the directory containing this project */
|
||||
@SerializedName("projectDir")
|
||||
public String projectDir;
|
||||
|
||||
/** File path of the application's icon We get this manually by parsing AndroidManifest.xml */
|
||||
public String iconPath;
|
||||
|
||||
/** Subprojects of this project */
|
||||
@SerializedName("modules")
|
||||
public List<IDEModule> modules = new ArrayList<>();
|
||||
|
||||
/** Tasks included in this project */
|
||||
@SerializedName("tasks")
|
||||
public List<IDETask> tasks = new ArrayList<>();
|
||||
|
||||
/** Path of jar file of dependencies of this project */
|
||||
@SerializedName("dependencies")
|
||||
public List<String> dependencies = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Get the module by its path
|
||||
*
|
||||
* @param path Path of the module
|
||||
* @return An Optional containing the IDEModule or Optional.empty()
|
||||
*/
|
||||
public Optional<IDEModule> getModuleByPath(String path) {
|
||||
if (modules != null && modules.size() > 0) {
|
||||
for (IDEModule module : modules) {
|
||||
if (module == null) continue;
|
||||
if (module.path.trim().equals(path.trim())) return Optional.of(module);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** Stores information about a SDK. Like, • Codename • API string • API level */
|
||||
public static class SDK {
|
||||
|
||||
@SerializedName("codename")
|
||||
public String codename;
|
||||
|
||||
@SerializedName("apiString")
|
||||
public String apiString;
|
||||
|
||||
@SerializedName("apiLevel")
|
||||
public int apiLevel;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.project;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/** Represents a Gradle task */
|
||||
public class IDETask {
|
||||
|
||||
/** Name of this task */
|
||||
@SerializedName("name")
|
||||
public String name;
|
||||
|
||||
/** Description of this task */
|
||||
@SerializedName("description")
|
||||
public String description;
|
||||
|
||||
/** Name of the group that this task belongs to */
|
||||
@SerializedName("group")
|
||||
public String group;
|
||||
|
||||
/**
|
||||
* Path of this task
|
||||
*
|
||||
* <p>For example, ':app:assembleDebug', ':app:build'
|
||||
*/
|
||||
@SerializedName("path")
|
||||
public String path;
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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.projects
|
||||
|
||||
import com.android.builder.model.v2.ide.LibraryType.ANDROID_LIBRARY
|
||||
import com.android.builder.model.v2.ide.LibraryType.JAVA_LIBRARY
|
||||
import com.android.builder.model.v2.ide.LibraryType.PROJECT
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.builder.BuildService
|
||||
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
|
||||
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeModule
|
||||
import com.itsaky.lsp.java.models.JavaServerConfiguration
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Manages projects in AndroidIDE.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object ProjectManager {
|
||||
private val log = com.itsaky.androidide.utils.ILogger.newInstance(javaClass.simpleName)
|
||||
var rootProject: IdeGradleProject? = null
|
||||
var projectPath: String? = null
|
||||
|
||||
fun getProjectDir(): File? =
|
||||
if (getProjectDirPath() == null) null else File(getProjectDirPath()!!)
|
||||
|
||||
fun getProjectDirPath(): String? =
|
||||
if (rootProject == null) projectPath else rootProject!!.projectDir!!.absolutePath
|
||||
|
||||
fun generateSources(builder: BuildService?) {
|
||||
if (builder == null) {
|
||||
log.warn("Cannot generate sources. BuildService is null.")
|
||||
return
|
||||
}
|
||||
|
||||
val app =
|
||||
getApplicationModule()
|
||||
?: kotlin.run {
|
||||
log.error("Cannot generate resources...")
|
||||
return
|
||||
}
|
||||
|
||||
val debug = app.variants.firstOrNull { it.name == "debug" }
|
||||
if (debug == null) {
|
||||
log.warn("No debug variant found in application project ${app.name}")
|
||||
return
|
||||
}
|
||||
|
||||
val mainArtifact = app.variants.first { it.name == "debug" }.mainArtifact
|
||||
val genResourcesTask = mainArtifact.resGenTaskName
|
||||
val genSourcesTask = mainArtifact.sourceGenTaskName
|
||||
builder
|
||||
.executeProjectTasks(
|
||||
app.projectPath!!,
|
||||
genResourcesTask ?: "",
|
||||
genSourcesTask,
|
||||
|
||||
// If view binding is enabled, generate the view binding classes too
|
||||
if (app.viewBindingOptions != null && app.viewBindingOptions!!.isEnabled)
|
||||
"dataBindingGenBaseClassesDebug"
|
||||
else "")
|
||||
.whenComplete { result, err ->
|
||||
if (!result.isSuccessful || err != null) {
|
||||
log.warn(
|
||||
"Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
|
||||
err ?: "")
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
notifyProjectUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
fun getApplicationModule(): IdeAndroidModule? {
|
||||
if (rootProject == null) {
|
||||
log.error(
|
||||
"No root project instance is set. Is the project initialization process finished?")
|
||||
return null
|
||||
}
|
||||
|
||||
val app = rootProject!!.findFirstAndroidModule()
|
||||
if (app == null) {
|
||||
log.error("No application module found in root project.")
|
||||
return null
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
fun getApplicationResDirectories(): List<File>? {
|
||||
return collectResDirectories(getApplicationModule() ?: return null)
|
||||
}
|
||||
|
||||
fun collectResDirectories(android: IdeAndroidModule): List<File> {
|
||||
val main = android.mainSourceSet
|
||||
if (main == null) {
|
||||
log.error("No main source set found in application module: ${android.name}")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val dirs = mutableListOf<File>()
|
||||
if (main.sourceProvider.resDirectories != null) {
|
||||
dirs.addAll(main.sourceProvider.resDirectories!!)
|
||||
}
|
||||
|
||||
val dependencies =
|
||||
collectProjectDependencies(rootProject!!, android)
|
||||
.filterIsInstance(IdeAndroidModule::class.java)
|
||||
|
||||
for (dependency in dependencies) {
|
||||
dirs.addAll(collectResDirectories(dependency))
|
||||
}
|
||||
|
||||
return dirs
|
||||
}
|
||||
|
||||
fun notifyProjectUpdate() {
|
||||
val server = StudioApp.getInstance().javaLanguageServer
|
||||
val sourceDirs = collectApplicationSourceDirs()
|
||||
val classPaths = collectApplicationClassPaths()
|
||||
val configuration = JavaServerConfiguration(classPaths, sourceDirs)
|
||||
server.configurationChanged(configuration)
|
||||
}
|
||||
|
||||
private fun collectApplicationClassPaths(): Set<Path> {
|
||||
val app = getApplicationModule() ?: return emptySet()
|
||||
|
||||
val libraries = app.variantDependencies["debug"]!!.libraries
|
||||
val paths = mutableSetOf<Path>()
|
||||
|
||||
for (value in libraries.values) {
|
||||
if (value.type == ANDROID_LIBRARY) {
|
||||
paths.addAll(value.androidLibraryData!!.compileJarFiles.map { it.toPath() })
|
||||
} else if (value.type == JAVA_LIBRARY) {
|
||||
paths.add(value.artifact!!.toPath())
|
||||
} else {
|
||||
val project =
|
||||
rootProject!!.findByPath(value.projectInfo!!.projectPath)!! as IdeModule
|
||||
paths.add(project.getGeneratedJar("debug").toPath())
|
||||
|
||||
if (project is IdeAndroidModule && project.projectPath != app.projectPath) {
|
||||
// R.jar and maybe other JARs
|
||||
paths.addAll(
|
||||
project.variants
|
||||
.first { it.name == "debug" }
|
||||
.mainArtifact.classesFolders
|
||||
.filter { it.name.endsWith(".jar") }
|
||||
.map { it.toPath() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
private fun collectApplicationSourceDirs(): Set<Path> {
|
||||
val app = getApplicationModule() ?: return emptySet()
|
||||
val sources = app.mainSourceSet ?: return emptySet()
|
||||
val javaDirs = sources.sourceProvider.javaDirectories
|
||||
val sourcePaths = javaDirs.toMutableSet()
|
||||
val projectSources = collectSourceDirs(collectProjectDependencies(rootProject!!, app))
|
||||
sourcePaths.addAll(projectSources)
|
||||
return sourcePaths.map { it.toPath() }.toSet()
|
||||
}
|
||||
|
||||
fun collectProjectDependencies(
|
||||
project: IdeGradleProject,
|
||||
app: IdeAndroidModule
|
||||
): List<IdeModule> {
|
||||
|
||||
return app.variantDependencies["debug"]!!
|
||||
.libraries.values
|
||||
.filter { it.type == PROJECT }
|
||||
.mapNotNull { project.findByPath(it.projectInfo!!.projectPath) }
|
||||
.filterIsInstance(IdeModule::class.java)
|
||||
}
|
||||
|
||||
fun collectSourceDirs(projects: List<IdeModule>): Set<File> {
|
||||
val sources = mutableSetOf<File>()
|
||||
for (project in projects) {
|
||||
if (project is IdeJavaModule) {
|
||||
sources.addAll(collectSources(project))
|
||||
} else if (project is IdeAndroidModule) {
|
||||
sources.addAll(collectSources(project))
|
||||
}
|
||||
}
|
||||
|
||||
return sources
|
||||
}
|
||||
|
||||
fun collectSourceDirs(vararg projects: IdeModule): List<File> {
|
||||
val sources = mutableListOf<File>()
|
||||
for (project in projects) {
|
||||
if (project is IdeJavaModule) {
|
||||
sources.addAll(collectSources(project))
|
||||
} else if (project is IdeAndroidModule) {
|
||||
sources.addAll(collectSources(project))
|
||||
}
|
||||
}
|
||||
|
||||
return sources
|
||||
}
|
||||
|
||||
fun collectSources(java: IdeJavaModule): List<File> {
|
||||
val sources = mutableListOf<File>()
|
||||
for (root in java.contentRoots) {
|
||||
sources.addAll(root.sourceDirectories.map { it.directory })
|
||||
}
|
||||
|
||||
return sources
|
||||
}
|
||||
|
||||
fun collectSources(android: IdeAndroidModule): List<File> {
|
||||
if (android.mainSourceSet == null) {
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
// src/main/java
|
||||
val sources = android.mainSourceSet!!.sourceProvider.javaDirectories.toMutableList()
|
||||
|
||||
// src/main/kotlin
|
||||
sources.addAll(android.mainSourceSet!!.sourceProvider.kotlinDirectories)
|
||||
|
||||
// build/generated/**
|
||||
// AIDL, ViewBinding, Renderscript, BuildConfig i.e every generated source sources
|
||||
sources.addAll(
|
||||
android.variants.first { it.name == "debug" }.mainArtifact.generatedSourceFolders)
|
||||
return sources
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.project;
|
||||
package com.itsaky.androidide.projects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -0,0 +1,566 @@
|
||||
/*
|
||||
* 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.services;
|
||||
|
||||
import static com.itsaky.androidide.managers.ToolsManager.getCommonAsset;
|
||||
import static com.itsaky.androidide.utils.ILogger.newInstance;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.blankj.utilcode.util.ResourceUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.blankj.utilcode.util.ZipUtils;
|
||||
import com.itsaky.androidide.BuildConfig;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.BaseApplication;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.builder.BuildService;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.shell.CommonProcessExecutor;
|
||||
import com.itsaky.androidide.shell.ProcessStreamsHolder;
|
||||
import com.itsaky.androidide.tooling.api.IToolingApiClient;
|
||||
import com.itsaky.androidide.tooling.api.IToolingApiServer;
|
||||
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage;
|
||||
import com.itsaky.androidide.tooling.api.messages.TaskExecutionMessage;
|
||||
import com.itsaky.androidide.tooling.api.messages.result.BuildCancellationRequestResult;
|
||||
import com.itsaky.androidide.tooling.api.messages.result.BuildResult;
|
||||
import com.itsaky.androidide.tooling.api.messages.result.GradleWrapperCheckResult;
|
||||
import com.itsaky.androidide.tooling.api.messages.result.InitializeResult;
|
||||
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult;
|
||||
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher;
|
||||
import com.itsaky.androidide.tooling.events.ProgressEvent;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.InputStreamLineReader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
/**
|
||||
* A foreground service that handles interaction with the Gradle Tooling API.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class GradleBuildService extends Service implements BuildService, IToolingApiClient {
|
||||
|
||||
private static final ILogger LOG = newInstance("GradleBuildService");
|
||||
private static final int NOTIFICATION_ID = R.string.app_name;
|
||||
private static final ILogger SERVER_System_err = newInstance("ToolingApiErrorStream");
|
||||
private final ILogger SERVER_LOGGER = newInstance("ToolingApiServer");
|
||||
private final IBinder mBinder = new GradleServiceBinder();
|
||||
private boolean isToolingServerStarted = false;
|
||||
private boolean isBuildInProgress = false;
|
||||
private Thread toolingServerThread;
|
||||
private NotificationManager notificationManager;
|
||||
private IToolingApiServer server;
|
||||
private EventListener eventListener;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
showNotification();
|
||||
}
|
||||
|
||||
private void showNotification() {
|
||||
final var ticker = getString(R.string.title_gradle_service_notification_ticker);
|
||||
final var title = getString(R.string.title_gradle_service_notification);
|
||||
final var message = getString(R.string.msg_gradle_service_notification);
|
||||
|
||||
final var launch =
|
||||
getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID);
|
||||
|
||||
final var intent =
|
||||
PendingIntent.getActivity(this, 0, launch, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
final var notification =
|
||||
new Notification.Builder(this, BaseApplication.NOTIFICATION_GRADLE_BUILD_SERVICE)
|
||||
.setSmallIcon(R.mipmap.ic_launcher_round)
|
||||
.setTicker(ticker)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setContentIntent(intent)
|
||||
.build();
|
||||
|
||||
LOG.info("Showing notification to user...");
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// No point in restarting the service if it really gets killed.
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LOG.info("Service is being destroyed.", "Dismissing the shown notification...");
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
|
||||
if (server != null) {
|
||||
server.cancelCurrentBuild();
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
if (toolingServerThread != null) {
|
||||
toolingServerThread.interrupt();
|
||||
}
|
||||
|
||||
isToolingServerStarted = false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logMessage(@NonNull LogLine line) {
|
||||
SERVER_LOGGER.log(ILogger.priority(line.priorityChar), line.formattedTagAndMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOutput(@NonNull String line) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onOutput(line);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareBuild() {
|
||||
if (eventListener != null) {
|
||||
eventListener.prepareBuild();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildSuccessful(@NonNull BuildResult result) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onBuildSuccessful(result.getTasks());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildFailed(@NonNull BuildResult result) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onBuildFailed(result.getTasks());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressEvent(@NonNull ProgressEvent event) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onProgressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<List<String>> getBuildArguments() {
|
||||
final PreferenceManager prefs = StudioApp.getInstance().getPrefManager();
|
||||
final var extraArgs = new ArrayList<String>();
|
||||
|
||||
extraArgs.add("--init-script");
|
||||
extraArgs.add(Environment.INIT_SCRIPT.getAbsolutePath());
|
||||
|
||||
// Override AAPT2 binary
|
||||
// The one downloaded from Maven is not built for Android
|
||||
extraArgs.add("-Pandroid.aapt2FromMavenOverride=" + Environment.AAPT2.getAbsolutePath());
|
||||
|
||||
if (prefs.isStackTraceEnabled()) {
|
||||
extraArgs.add("--stacktrace");
|
||||
}
|
||||
if (prefs.isGradleInfoEnabled()) {
|
||||
extraArgs.add("--info");
|
||||
}
|
||||
if (prefs.isGradleDebugEnabled()) {
|
||||
extraArgs.add("--debug");
|
||||
}
|
||||
if (prefs.isGradleScanEnabled()) {
|
||||
extraArgs.add("--scan");
|
||||
}
|
||||
if (prefs.isGradleWarningEnabled()) {
|
||||
extraArgs.add("--warning-mode");
|
||||
extraArgs.add("all");
|
||||
}
|
||||
if (prefs.isGradleBuildCacheEnabled()) {
|
||||
extraArgs.add("--build-cache");
|
||||
}
|
||||
if (prefs.isGradleOfflineModeEnabled()) {
|
||||
extraArgs.add("--offline");
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(extraArgs);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<GradleWrapperCheckResult> checkGradleWrapperAvailability() {
|
||||
return isGradleWrapperAvailable()
|
||||
? CompletableFuture.completedFuture(new GradleWrapperCheckResult(true))
|
||||
: installWrapper();
|
||||
}
|
||||
|
||||
public boolean isGradleWrapperAvailable() {
|
||||
final var projectDir = ProjectManager.INSTANCE.getProjectDirPath();
|
||||
if (projectDir == null || TextUtils.isEmpty(projectDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var projectRoot = Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDir());
|
||||
if (!projectRoot.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final File gradlew = new File(projectRoot, "gradlew");
|
||||
final File gradleWrapperJar = new File(projectRoot, "gradle/wrapper/gradle-wrapper.jar");
|
||||
final File gradleWrapperProps =
|
||||
new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties");
|
||||
|
||||
return gradlew.exists() && gradleWrapperJar.exists() && gradleWrapperProps.exists();
|
||||
}
|
||||
|
||||
private CompletableFuture<GradleWrapperCheckResult> installWrapper() {
|
||||
if (eventListener != null) {
|
||||
eventListener.onOutput("-------------------- NOTE --------------------");
|
||||
eventListener.onOutput(getString(R.string.msg_installing_gradlew));
|
||||
eventListener.onOutput("----------------------------------------------");
|
||||
}
|
||||
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
boolean isAvailable = false;
|
||||
final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip");
|
||||
if (ResourceUtils.copyFileFromAssets(
|
||||
getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) {
|
||||
try {
|
||||
final List<File> files =
|
||||
ZipUtils.unzipFile(
|
||||
extracted, ProjectManager.INSTANCE.getProjectDir());
|
||||
if (files != null && !files.isEmpty()) {
|
||||
isAvailable = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("An error occurred while extracting Gradle wrapper", e);
|
||||
}
|
||||
} else {
|
||||
LOG.error("Unable to extract gradle-plugin.zip from IDE resources.");
|
||||
}
|
||||
|
||||
return new GradleWrapperCheckResult(isAvailable);
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<InitializeResult> initializeProject(@NonNull String rootDir) {
|
||||
checkServerStarted();
|
||||
ensureTmpdir();
|
||||
|
||||
if (isBuildInProgress) {
|
||||
logBuildInProgress();
|
||||
return failedFutureForBuildInProgress();
|
||||
}
|
||||
|
||||
isBuildInProgress = true;
|
||||
return server.initialize(new InitializeProjectMessage(rootDir))
|
||||
.thenApply(this::markBuildAsFinished);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<TaskExecutionResult> executeTasks(@NonNull String... tasks) {
|
||||
checkServerStarted();
|
||||
ensureTmpdir();
|
||||
|
||||
if (isBuildInProgress) {
|
||||
logBuildInProgress();
|
||||
return failedFutureForBuildInProgress();
|
||||
}
|
||||
|
||||
isBuildInProgress = true;
|
||||
return server.executeTasks(new TaskExecutionMessage(":", Arrays.asList(tasks)))
|
||||
.thenApply(this::markBuildAsFinished);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<TaskExecutionResult> executeProjectTasks(
|
||||
@NonNull String projectPath, @NonNull String... tasks) {
|
||||
checkServerStarted();
|
||||
ensureTmpdir();
|
||||
|
||||
if (isBuildInProgress) {
|
||||
logBuildInProgress();
|
||||
return failedFutureForBuildInProgress();
|
||||
}
|
||||
|
||||
isBuildInProgress = true;
|
||||
return server.executeTasks(new TaskExecutionMessage(projectPath, Arrays.asList(tasks)))
|
||||
.thenApply(this::markBuildAsFinished);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<BuildCancellationRequestResult> cancelCurrentBuild() {
|
||||
return server.cancelCurrentBuild();
|
||||
}
|
||||
|
||||
private void checkServerStarted() {
|
||||
if (!isToolingServerStarted) {
|
||||
throw new IllegalStateException("Tooling API server has not been started");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTmpdir() {
|
||||
Environment.mkdirIfNotExits(Environment.TMP_DIR);
|
||||
}
|
||||
|
||||
private void logBuildInProgress() {
|
||||
LOG.warn("A build is already in progress!");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private <T> CompletableFuture<T> failedFutureForBuildInProgress() {
|
||||
final var future = new CompletableFuture<T>();
|
||||
future.completeExceptionally(
|
||||
new CompletionException(new IllegalStateException("A build is already running!")));
|
||||
return future;
|
||||
}
|
||||
|
||||
protected <T> T markBuildAsFinished(T t) {
|
||||
isBuildInProgress = false;
|
||||
return t;
|
||||
}
|
||||
|
||||
public boolean isBuildInProgress() {
|
||||
return isBuildInProgress;
|
||||
}
|
||||
|
||||
public void startToolingServer(@Nullable OnServerStartListener listener) {
|
||||
if (toolingServerThread != null && toolingServerThread.isAlive()) {
|
||||
throw new IllegalStateException("Tooling API server is already started!");
|
||||
}
|
||||
|
||||
toolingServerThread = new Thread(new ToolingServerRunner(listener));
|
||||
toolingServerThread.start();
|
||||
}
|
||||
|
||||
public GradleBuildService setEventListener(EventListener eventListener) {
|
||||
this.eventListener = wrap(eventListener);
|
||||
return this;
|
||||
}
|
||||
|
||||
private EventListener wrap(EventListener listener) {
|
||||
if (listener == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EventListener() {
|
||||
@Override
|
||||
public void prepareBuild() {
|
||||
ThreadUtils.runOnUiThread(listener::prepareBuild);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildSuccessful(@NonNull List<String> tasks) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onBuildSuccessful(tasks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressEvent(@NonNull ProgressEvent event) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onProgressEvent(event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildFailed(@NonNull List<String> tasks) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onBuildFailed(tasks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutput(String line) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onOutput(line));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void onServerExited(int exitCode) {
|
||||
LOG.warn("Tooling API process terminated with exit code:", exitCode);
|
||||
stopForeground(true);
|
||||
}
|
||||
|
||||
private void startServerOutputReader(InputStream err) {
|
||||
new Thread(new InputStreamLineReader(err, this::onServerOutput)).start();
|
||||
}
|
||||
|
||||
protected void onServerOutput(String line) {
|
||||
SERVER_System_err.error(line);
|
||||
}
|
||||
|
||||
public class GradleServiceBinder extends Binder {
|
||||
public GradleBuildService getService() {
|
||||
return GradleBuildService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private class ToolingServerRunner implements Runnable {
|
||||
|
||||
@Nullable private final OnServerStartListener listener;
|
||||
|
||||
public ToolingServerRunner(@Nullable OnServerStartListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
LOG.info("Starting tooling API server...");
|
||||
final var serverStreams = new ProcessStreamsHolder();
|
||||
final var executor = new CommonProcessExecutor();
|
||||
executor.execAsync(
|
||||
serverStreams,
|
||||
GradleBuildService.this::onServerExited,
|
||||
false,
|
||||
|
||||
// The 'java' binary executable
|
||||
Environment.JAVA.getAbsolutePath(),
|
||||
|
||||
// Allow reflective access to private members of classes in the following
|
||||
// packages:
|
||||
// - java.lang
|
||||
// - java.io
|
||||
// - java.util
|
||||
//
|
||||
// If any of the model classes in 'tooling-api-model' module send/receive
|
||||
// objects from the JDK, their package name must be declared here with
|
||||
// '--add-opens' to prevent InaccessibleObjectException.
|
||||
// For example, some of the model classes has members of type java.io.File.
|
||||
// When sending/receiving these type of objects using LSP4J, members of
|
||||
// these objects are reflectively accessed by Gson. If we do no specify
|
||||
// '--add-opens' for 'java.io' (for java.io.File) package, JVM will throw an
|
||||
// InaccessibleObjectException.
|
||||
"--add-opens",
|
||||
"java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens",
|
||||
"java.base/java.util=ALL-UNNAMED",
|
||||
"--add-opens",
|
||||
"java.base/java.io=ALL-UNNAMED",
|
||||
|
||||
// The JAR file to run
|
||||
"-jar",
|
||||
Environment.TOOLING_API_JAR.getAbsolutePath());
|
||||
|
||||
final var launcher =
|
||||
ToolingApiLauncher.createClientLauncher(
|
||||
GradleBuildService.this, serverStreams.in, serverStreams.out);
|
||||
final var future = launcher.startListening();
|
||||
|
||||
GradleBuildService.this.startServerOutputReader(serverStreams.err);
|
||||
GradleBuildService.this.server = launcher.getRemoteProxy();
|
||||
GradleBuildService.this.isToolingServerStarted = true;
|
||||
|
||||
if (listener != null) {
|
||||
listener.onServerStarted();
|
||||
}
|
||||
|
||||
// Wait(block) until the process terminates
|
||||
try {
|
||||
future.get();
|
||||
} catch (Throwable err) {
|
||||
if (!(err instanceof CancellationException)
|
||||
&& !(err instanceof InterruptedException)) {
|
||||
LOG.error(
|
||||
"An error occurred while waiting for tooling API server to terminate");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to start tooling API server", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback to listen for Tooling API server start event. */
|
||||
public interface OnServerStartListener {
|
||||
|
||||
/** Called when the tooling API server has been successfully started. */
|
||||
void onServerStarted();
|
||||
}
|
||||
|
||||
/** Handles events received from a Gradle build. */
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Called just before a build is started.
|
||||
*
|
||||
* @see IToolingApiClient#prepareBuild()
|
||||
*/
|
||||
void prepareBuild();
|
||||
|
||||
/**
|
||||
* Called when a build is successful.
|
||||
*
|
||||
* @param tasks The tasks that were run.
|
||||
* @see IToolingApiClient#onBuildSuccessful(BuildResult)
|
||||
*/
|
||||
void onBuildSuccessful(@NonNull List<String> tasks);
|
||||
|
||||
/**
|
||||
* Called when a progress event is received from the Tooling API server.
|
||||
*
|
||||
* @param event The event model describing the event.
|
||||
*/
|
||||
void onProgressEvent(@NonNull ProgressEvent event);
|
||||
|
||||
/**
|
||||
* Called when a build fails.
|
||||
*
|
||||
* @param tasks The tasks that were run.
|
||||
* @see IToolingApiClient#onBuildFailed(BuildResult)
|
||||
*/
|
||||
void onBuildFailed(@NonNull List<String> tasks);
|
||||
|
||||
/**
|
||||
* Called when the output line is received.
|
||||
*
|
||||
* @param line The line of the build output.
|
||||
*/
|
||||
void onOutput(String line);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.services.builder;
|
||||
|
||||
import com.itsaky.androidide.project.IDEModule;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/** A client should implement this interface to get callbacks about a build */
|
||||
public interface BuildListener {
|
||||
/** Called to notify the client about the modification of build/generated files */
|
||||
void onBuildModified();
|
||||
|
||||
/**
|
||||
* Called when the project has been loaded and its data has been read
|
||||
*
|
||||
* @param project The loaded project
|
||||
* @param appModule The main application module
|
||||
*/
|
||||
void onProjectLoaded(IDEProject project, Optional<IDEModule> appModule);
|
||||
|
||||
/**
|
||||
* Called when a Gradle Daemon is being started
|
||||
*
|
||||
* @param task Task that is currently running
|
||||
*/
|
||||
void onStartingGradleDaemon(GradleTask task);
|
||||
|
||||
/**
|
||||
* Called when a task is started
|
||||
*
|
||||
* @param task Task that is currently running
|
||||
* @param name Name of this task that should be shown to user
|
||||
*/
|
||||
void onRunTask(GradleTask task, String name);
|
||||
|
||||
/**
|
||||
* Notifys about a successful build
|
||||
*
|
||||
* @param task Task that succeeded
|
||||
* @param msg Successful message
|
||||
*/
|
||||
void onBuildSuccessful(GradleTask task, String msg);
|
||||
|
||||
/**
|
||||
* Notifys about a failed build
|
||||
*
|
||||
* @param task Task that failed
|
||||
* @param msg Failure message
|
||||
*/
|
||||
void onBuildFailed(GradleTask task, String msg);
|
||||
|
||||
/** Called to notify client to save any opened files */
|
||||
void saveFiles();
|
||||
|
||||
/**
|
||||
* Notifys the client to append the build output that should be shown to user
|
||||
*
|
||||
* @param task Task that is currently running
|
||||
* @param text Output to append
|
||||
*/
|
||||
void appendOutput(GradleTask task, CharSequence text);
|
||||
|
||||
/** Called to notify the client to prepare for a build */
|
||||
void prepareBuild();
|
||||
}
|
||||
@@ -1,493 +0,0 @@
|
||||
/*
|
||||
* 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.services.builder;
|
||||
|
||||
import static com.itsaky.androidide.managers.ToolsManager.getCommonAsset;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.ResourceUtils;
|
||||
import com.blankj.utilcode.util.ZipUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.project.IDEModule;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
import com.itsaky.androidide.shell.IProcessExecutor;
|
||||
import com.itsaky.androidide.shell.IProcessExitListener;
|
||||
import com.itsaky.androidide.shell.ProcessExecutorFactory;
|
||||
import com.itsaky.androidide.shell.ProcessStreamsHolder;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
import com.itsaky.androidide.tasks.gradle.BaseGradleTasks;
|
||||
import com.itsaky.androidide.tasks.gradle.resources.UpdateResourceClassesTask;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.InputStreamLineReader;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class IDEService {
|
||||
|
||||
public static final int TASK_SHOW_DEPENDENCIES = GradleTask.TASK_SHOW_DEPENDENCIES;
|
||||
public static final int TASK_ASSEMBLE_DEBUG = GradleTask.TASK_ASSEMBLE_DEBUG;
|
||||
public static final int TASK_ASSEMBLE_RELEASE = GradleTask.TASK_ASSEMBLE_RELEASE;
|
||||
public static final int TASK_BUILD = GradleTask.TASK_BUILD;
|
||||
public static final int TASK_BUNDLE = GradleTask.TASK_BUNDLE;
|
||||
public static final int TASK_CLEAN = GradleTask.TASK_CLEAN;
|
||||
public static final int TASK_CLEAN_BUILD = GradleTask.TASK_CLEAN_BUILD;
|
||||
public static final int TASK_DEX = GradleTask.TASK_DEX;
|
||||
public static final int TASK_LINT = GradleTask.TASK_LINT;
|
||||
public static final int TASK_LINT_DEBUG = GradleTask.TASK_LINT_DEBUG;
|
||||
public static final int TASK_LINT_RELEASE = GradleTask.TASK_LINT_RELEASE;
|
||||
public static final ILogger LOG = ILogger.newInstance("IDEService");
|
||||
private final File projectRoot;
|
||||
private final IProcessExecutor processExecutor;
|
||||
private BuildListener listener;
|
||||
private GradleTask currentTask;
|
||||
private IDEProject mIDEProject;
|
||||
private IDEModule mAppModule;
|
||||
private final InputStreamLineReader.OnReadListener mOutputReadListener = this::onBuildOutput;
|
||||
private boolean isBuilding = false;
|
||||
private final IProcessExitListener mProcessExitListener =
|
||||
new IProcessExitListener() {
|
||||
@Override
|
||||
public void onExit(int code) {
|
||||
isBuilding = false;
|
||||
if (listener != null) {
|
||||
final String msg = getString(R.string.msg_gradle_terminated, code);
|
||||
if (code == 0) {
|
||||
onBuildSuccessful(currentTask, msg);
|
||||
} else {
|
||||
onBuildFailed(currentTask, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public IDEService(File projectRoot) {
|
||||
this.projectRoot = projectRoot;
|
||||
this.processExecutor = ProcessExecutorFactory.commonExecutor();
|
||||
}
|
||||
|
||||
public IDEService setListener(final BuildListener listener) {
|
||||
this.listener = listener == null ? null : new MainThreadBuildListener(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void onBuildOutput(String line) {
|
||||
if (listener == null || line == null) return;
|
||||
line = line.trim();
|
||||
|
||||
String PROJECT_INITIALIZED = ">>> PROJECT INITIALIZED <<<";
|
||||
if (line.contains(PROJECT_INITIALIZED)) {
|
||||
readIdeProject();
|
||||
return;
|
||||
}
|
||||
|
||||
String text = line;
|
||||
|
||||
final var app = StudioApp.getInstance();
|
||||
// Try to shorten common file paths
|
||||
if (line.contains(app.getRootDir().getAbsolutePath())) {
|
||||
text = line.replace(app.getRootDir().getAbsolutePath(), "HOME");
|
||||
}
|
||||
if (line.contains(
|
||||
Objects.requireNonNull(app.getRootDir().getParentFile()).getAbsolutePath())) {
|
||||
text = line.replace(app.getRootDir().getParentFile().getAbsolutePath(), "ROOT");
|
||||
}
|
||||
|
||||
listener.appendOutput(currentTask, text);
|
||||
|
||||
String RUN_TASK = "> Task";
|
||||
String STARTING_DAEMON = "Starting a ";
|
||||
if (line.startsWith(RUN_TASK)) {
|
||||
listener.onRunTask(currentTask, line.trim());
|
||||
} else if (line.startsWith(STARTING_DAEMON)) {
|
||||
listener.onStartingGradleDaemon(currentTask);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onBuildSuccessful(GradleTask task, String msg) {
|
||||
if (currentTask != null && currentTask.getTaskID() != TASK_SHOW_DEPENDENCIES) {
|
||||
listener.onBuildSuccessful(task, msg.trim());
|
||||
}
|
||||
appendOutputSeparator();
|
||||
|
||||
if (currentTask != null && currentTask.affectsGeneratedSources()) {
|
||||
notifyExternalSourceChange();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onBuildFailed(GradleTask task, @NonNull String msg) {
|
||||
isBuilding = false;
|
||||
listener.onBuildFailed(task, msg.trim());
|
||||
appendOutputSeparator();
|
||||
}
|
||||
|
||||
private void readIdeProject() {
|
||||
// Parsing project data is meaningless if there is no one listening...
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BuildListener local = listener;
|
||||
new Thread(
|
||||
() -> {
|
||||
mIDEProject =
|
||||
new Gson()
|
||||
.fromJson(
|
||||
FileIOUtils.readFile2String(
|
||||
Environment.PROJECT_DATA_FILE),
|
||||
IDEProject.class);
|
||||
if (mIDEProject != null) {
|
||||
Optional<IDEModule> appModule = mIDEProject.getModuleByPath(":app");
|
||||
appModule.ifPresent(ideModule -> mAppModule = ideModule);
|
||||
local.onProjectLoaded(mIDEProject, appModule);
|
||||
} else {
|
||||
StudioApp.getInstance()
|
||||
.toast(
|
||||
com.itsaky.androidide.R.string
|
||||
.msg_init_project_failed,
|
||||
Toaster.Type.ERROR);
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
private void appendOutputSeparator() {
|
||||
listener.appendOutput(currentTask, "\n\n");
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
// Delete the tmp directory. It is not needed anymore...
|
||||
FileUtils.delete(Environment.TMP_DIR);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String currentTime() {
|
||||
String pattern = "HH:mm:ss";
|
||||
DateFormat df = new SimpleDateFormat(pattern, Locale.US);
|
||||
Date today = Calendar.getInstance().getTime();
|
||||
return df.format(today);
|
||||
}
|
||||
|
||||
private String getString(@StringRes int id) {
|
||||
return StudioApp.getInstance().getString(id);
|
||||
}
|
||||
|
||||
private String getString(@StringRes int id, Object... format) {
|
||||
return StudioApp.getInstance().getString(id, format);
|
||||
}
|
||||
|
||||
private void notifyExternalSourceChange() {
|
||||
// TODO Watch for file changes using a file observer
|
||||
// instead of depending on the build
|
||||
if (listener != null) {
|
||||
listener.onBuildModified();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String[] getArguments(List<String> tasks) {
|
||||
final PreferenceManager prefs = StudioApp.getInstance().getPrefManager();
|
||||
final List<String> args = new ArrayList<>();
|
||||
|
||||
// 'bash' shell is always preferred
|
||||
if (Environment.SHELL.exists()) {
|
||||
args.add(Environment.SHELL.getAbsolutePath());
|
||||
} else {
|
||||
args.add(Environment.BUSYBOX.getAbsolutePath());
|
||||
args.add("sh");
|
||||
}
|
||||
|
||||
args.add(new File(projectRoot, "gradlew").getAbsolutePath());
|
||||
args.addAll(asAppTasks(tasks));
|
||||
args.add("--init-script");
|
||||
args.add(Environment.INIT_SCRIPT.getAbsolutePath());
|
||||
|
||||
if (prefs.isStackTraceEnabled()) {
|
||||
args.add("--stacktrace");
|
||||
}
|
||||
if (prefs.isGradleInfoEnabled()) {
|
||||
args.add("--info");
|
||||
}
|
||||
if (prefs.isGradleDebugEnabled()) {
|
||||
args.add("--debug");
|
||||
}
|
||||
if (prefs.isGradleScanEnabled()) {
|
||||
args.add("--scan");
|
||||
}
|
||||
if (prefs.isGradleWarningEnabled()) {
|
||||
args.add("--warning-mode");
|
||||
args.add("all");
|
||||
}
|
||||
if (prefs.isGradleBuildCacheEnabled()) {
|
||||
args.add("--build-cache");
|
||||
}
|
||||
if (prefs.isGradleOfflineModeEnabled()) {
|
||||
args.add("--offline");
|
||||
}
|
||||
|
||||
return args.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private Collection<? extends String> asAppTasks(@NonNull List<String> tasks) {
|
||||
return tasks.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(this::asAppModuleTask)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private String asAppModuleTask(@NonNull String name) {
|
||||
return name.startsWith(":app:") ? name : ":app:" + name;
|
||||
}
|
||||
|
||||
public void execTask(GradleTask task) {
|
||||
execTask(task, false);
|
||||
}
|
||||
|
||||
public void execTask(GradleTask task, boolean stopIfRunning) {
|
||||
if (stopIfRunning && isBuilding()) {
|
||||
stopAllDaemons();
|
||||
} else if (isBuilding()) {
|
||||
return;
|
||||
}
|
||||
|
||||
execTask(
|
||||
task,
|
||||
isGradleWrapperAvailable()
|
||||
? CompletableFuture.completedFuture(Boolean.TRUE)
|
||||
: installWrapper());
|
||||
}
|
||||
|
||||
private void execTask(GradleTask task, CompletableFuture<Boolean> installing) {
|
||||
|
||||
final Runnable taskRunner =
|
||||
() -> {
|
||||
if (installing != null) {
|
||||
Boolean result;
|
||||
String failure = "";
|
||||
try {
|
||||
result = installing.get();
|
||||
} catch (Throwable th) {
|
||||
result = Boolean.FALSE;
|
||||
failure = th.getMessage();
|
||||
}
|
||||
|
||||
if (result.equals(Boolean.FALSE)) {
|
||||
if (listener != null) {
|
||||
listener.appendOutput(
|
||||
task, "-------------------- ERROR --------------------");
|
||||
listener.appendOutput(
|
||||
task,
|
||||
getString(
|
||||
R.string.msg_gradlew_installation_failed, failure));
|
||||
listener.appendOutput(
|
||||
task, "-----------------------------------------------");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
|
||||
final ProcessStreamsHolder holder = new ProcessStreamsHolder();
|
||||
|
||||
listener.saveFiles();
|
||||
|
||||
currentTask = task;
|
||||
listener.appendOutput(
|
||||
task,
|
||||
getString(R.string.msg_task_begin, currentTime(), task.getName()));
|
||||
try {
|
||||
|
||||
Environment.mkdirIfNotExits(Environment.TMP_DIR);
|
||||
|
||||
processExecutor.execAsync(
|
||||
holder,
|
||||
mProcessExitListener,
|
||||
projectRoot.getAbsolutePath(),
|
||||
true,
|
||||
getArguments(task.getTasks()));
|
||||
isBuilding = true;
|
||||
|
||||
startReading(holder.in);
|
||||
|
||||
listener.prepareBuild();
|
||||
} catch (Throwable e) {
|
||||
listener.onBuildFailed(task, e.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Thread thread = new Thread(taskRunner, "GradleTaskExecutor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void startReading(InputStream in) {
|
||||
final Thread reader =
|
||||
new Thread(
|
||||
new InputStreamLineReader(in, mOutputReadListener),
|
||||
"GradleProcessOutputReader");
|
||||
reader.setDaemon(true);
|
||||
reader.start();
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> installWrapper() {
|
||||
if (listener != null) {
|
||||
listener.appendOutput(null, "-------------------- NOTE --------------------");
|
||||
listener.appendOutput(null, getString(R.string.msg_installing_gradlew));
|
||||
listener.appendOutput(null, "----------------------------------------------");
|
||||
}
|
||||
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip");
|
||||
if (ResourceUtils.copyFileFromAssets(
|
||||
getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) {
|
||||
try {
|
||||
final List<File> files = ZipUtils.unzipFile(extracted, projectRoot);
|
||||
if (files != null && !files.isEmpty()) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return Boolean.FALSE;
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isGradleWrapperAvailable() {
|
||||
if (projectRoot == null || !projectRoot.exists()) {
|
||||
return false;
|
||||
}
|
||||
final File gradlew = new File(projectRoot, "gradlew");
|
||||
final File gradleWrapperJar = new File(projectRoot, "gradle/wrapper/gradle-wrapper.jar");
|
||||
final File gradleWrapperProps =
|
||||
new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties");
|
||||
|
||||
return gradlew.exists() && gradleWrapperJar.exists() && gradleWrapperProps.exists();
|
||||
}
|
||||
|
||||
public void assembleDebug() {
|
||||
execTask(BaseGradleTasks.ASSEMBLE_DEBUG);
|
||||
}
|
||||
|
||||
public void assembleRelease() {
|
||||
execTask(BaseGradleTasks.ASSEMBLE_RELEASE);
|
||||
}
|
||||
|
||||
public void build() {
|
||||
execTask(BaseGradleTasks.BUILD);
|
||||
}
|
||||
|
||||
public void bundle() {
|
||||
execTask(BaseGradleTasks.BUNDLE);
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
execTask(BaseGradleTasks.CLEAN);
|
||||
}
|
||||
|
||||
public void cleanAndRebuild() {
|
||||
execTask(BaseGradleTasks.CLEAN_BUILD);
|
||||
}
|
||||
|
||||
public void stopAllDaemons() {
|
||||
StudioApp.getInstance()
|
||||
.newShell(null)
|
||||
.bgAppend(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"cd '%s' && sh gradlew --stop",
|
||||
projectRoot.getAbsolutePath()));
|
||||
if (isBuilding()) {
|
||||
if (listener != null)
|
||||
listener.onBuildFailed(currentTask, getString(R.string.msg_daemons_stopped));
|
||||
isBuilding = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void lint() {
|
||||
execTask(BaseGradleTasks.LINT);
|
||||
}
|
||||
|
||||
public void lintDebug() {
|
||||
execTask(BaseGradleTasks.LINT_DEBUG);
|
||||
}
|
||||
|
||||
public void lintRelease() {
|
||||
execTask(BaseGradleTasks.LINT_RELEASE);
|
||||
}
|
||||
|
||||
public void updateResourceClasses() {
|
||||
execTask(
|
||||
new UpdateResourceClassesTask(mAppModule != null && mAppModule.viewBindingEnabled));
|
||||
}
|
||||
|
||||
public boolean isBuilding() {
|
||||
return isBuilding;
|
||||
}
|
||||
|
||||
public String typeString(int type) {
|
||||
switch (type) {
|
||||
case TASK_ASSEMBLE_DEBUG:
|
||||
return getString(R.string.build_debug);
|
||||
case TASK_ASSEMBLE_RELEASE:
|
||||
return getString(R.string.build_release);
|
||||
case TASK_BUILD:
|
||||
return getString(R.string.build);
|
||||
case TASK_CLEAN_BUILD:
|
||||
return getString(R.string.clean_amp_build);
|
||||
case TASK_CLEAN:
|
||||
return getString(R.string.clean_project);
|
||||
case TASK_BUNDLE:
|
||||
return getString(R.string.create_aab);
|
||||
case TASK_DEX:
|
||||
return getString(R.string.compiling);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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.services.builder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.itsaky.androidide.project.IDEModule;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
class MainThreadBuildListener implements BuildListener {
|
||||
|
||||
private final BuildListener listener;
|
||||
|
||||
public MainThreadBuildListener(@NonNull final BuildListener listener) {
|
||||
Objects.requireNonNull(listener, "Build listener cannot be null");
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildModified() {
|
||||
ThreadUtils.runOnUiThread(listener::onBuildModified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProjectLoaded(IDEProject project, Optional<IDEModule> appModule) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onProjectLoaded(project, appModule));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartingGradleDaemon(GradleTask task) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onStartingGradleDaemon(task));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunTask(GradleTask task, String name) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onRunTask(task, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildSuccessful(GradleTask task, String msg) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onBuildSuccessful(task, msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildFailed(GradleTask task, String msg) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.onBuildFailed(task, msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFiles() {
|
||||
ThreadUtils.runOnUiThread(listener::saveFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendOutput(GradleTask task, CharSequence text) {
|
||||
ThreadUtils.runOnUiThread(() -> listener.appendOutput(task, text));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareBuild() {
|
||||
ThreadUtils.runOnUiThread(listener::prepareBuild);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks;
|
||||
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
|
||||
public abstract class BaseGradleTask implements GradleTask {
|
||||
|
||||
@Override
|
||||
public boolean affectsGeneratedSources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSaveFiles() {
|
||||
return getType() == Type.BUILD
|
||||
|| getType() == Type.LINT
|
||||
|| getTaskID() == IDEService.TASK_DEX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj != null
|
||||
&& obj instanceof GradleTask
|
||||
&& getTaskID() == ((GradleTask) obj).getTaskID()
|
||||
&& getType() == ((GradleTask) obj).getType();
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GradleTask {
|
||||
|
||||
public String getName();
|
||||
|
||||
public String getCommands();
|
||||
|
||||
public List<String> getTasks();
|
||||
|
||||
public int getTaskID();
|
||||
|
||||
public boolean affectsGeneratedSources();
|
||||
|
||||
public boolean shouldSaveFiles();
|
||||
|
||||
public boolean canOutput();
|
||||
|
||||
public Type getType();
|
||||
|
||||
public static enum Type {
|
||||
ANDROIDIDE_PLUGIN,
|
||||
BUILD,
|
||||
LINT,
|
||||
HELP,
|
||||
OTHER
|
||||
}
|
||||
|
||||
public static final int TASK_SHOW_DEPENDENCIES = 0;
|
||||
public static final int TASK_ASSEMBLE_DEBUG = 1;
|
||||
public static final int TASK_ASSEMBLE_RELEASE = 2;
|
||||
public static final int TASK_BUILD = 3;
|
||||
public static final int TASK_BUNDLE = 4;
|
||||
public static final int TASK_CLEAN = 5;
|
||||
public static final int TASK_CLEAN_BUILD = 6;
|
||||
public static final int TASK_COMPILE_JAVA = 7;
|
||||
public static final int TASK_DEX = 8;
|
||||
public static final int TASK_LINT = 9;
|
||||
public static final int TASK_LINT_DEBUG = 10;
|
||||
public static final int TASK_LINT_RELEASE = 11;
|
||||
public static final int TASK_TASKS = 12;
|
||||
public static final int TASK_INIT_PROJECT = 13;
|
||||
public static final int TASK_PROCESS_DEBUG_RESOURCES = 14;
|
||||
public static final int DATABINDING_GEN_BASE_CLASSES_DEBUG = 15;
|
||||
public static final int TASK_UPDATE_RESOURCE_CLASSES = 16;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle;
|
||||
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.tasks.gradle.build.AssembleDebug;
|
||||
import com.itsaky.androidide.tasks.gradle.build.AssembleRelease;
|
||||
import com.itsaky.androidide.tasks.gradle.build.Build;
|
||||
import com.itsaky.androidide.tasks.gradle.build.Bundle;
|
||||
import com.itsaky.androidide.tasks.gradle.build.Clean;
|
||||
import com.itsaky.androidide.tasks.gradle.build.CleanBuild;
|
||||
import com.itsaky.androidide.tasks.gradle.lint.Lint;
|
||||
import com.itsaky.androidide.tasks.gradle.lint.LintDebug;
|
||||
import com.itsaky.androidide.tasks.gradle.lint.LintRelease;
|
||||
import com.itsaky.androidide.tasks.gradle.plugin.InitializeIDEProject;
|
||||
import com.itsaky.androidide.tasks.gradle.resources.ProcessDebugResources;
|
||||
|
||||
public class BaseGradleTasks {
|
||||
|
||||
public static final BaseGradleTask BUILD = new Build(),
|
||||
ASSEMBLE_DEBUG = new AssembleDebug(),
|
||||
ASSEMBLE_RELEASE = new AssembleRelease(),
|
||||
BUNDLE = new Bundle(),
|
||||
CLEAN = new Clean(),
|
||||
CLEAN_BUILD = new CleanBuild(),
|
||||
LINT = new Lint(),
|
||||
LINT_DEBUG = new LintDebug(),
|
||||
LINT_RELEASE = new LintRelease(),
|
||||
PROCESS_DEBUG_RESOURCES = new ProcessDebugResources(),
|
||||
INITIALIZE_IDE_PROJECT = new InitializeIDEProject();
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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.tasks.gradle.build;
|
||||
|
||||
import static com.itsaky.androidide.models.ApkMetadata.ArtifactType;
|
||||
import static com.itsaky.androidide.models.ApkMetadata.Element;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.models.ApkMetadata;
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.utils.JSONUtility;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class ApkGeneratingTask extends BaseGradleTask {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ApkGeneratingTask");
|
||||
|
||||
/**
|
||||
* Get the directories where APKs are generated. These directories must contain an
|
||||
* output-metadata.json file
|
||||
*/
|
||||
protected abstract Set<File> provideApkDirectories(File buildDir);
|
||||
|
||||
protected Set<File> directories(File buildDir) {
|
||||
if (buildDir == null || !buildDir.exists() || !buildDir.isDirectory()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
return provideApkDirectories(buildDir);
|
||||
}
|
||||
|
||||
/** Get generated APKs */
|
||||
public Set<File> getApks(File buildDir) {
|
||||
final Set<File> result = new HashSet<>();
|
||||
final Set<File> dirs = directories(buildDir);
|
||||
if (dirs == null || dirs.isEmpty()) {
|
||||
LOG.error("No apk directories found. buildDir=" + buildDir);
|
||||
return result;
|
||||
}
|
||||
|
||||
for (File dir : dirs) {
|
||||
final File jsonData = new File(dir, "output-metadata.json");
|
||||
if (!jsonData.exists() || !FileUtils.isUtf8(jsonData)) {
|
||||
LOG.error(
|
||||
"No output-metadata.json file found in APK output directory"
|
||||
+ " or the file is empty. APK directory:",
|
||||
dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
final String contents = FileIOUtils.readFile2String(jsonData);
|
||||
|
||||
try {
|
||||
final ApkMetadata metadata = JSONUtility.gson.fromJson(contents, ApkMetadata.class);
|
||||
if (!isValid(metadata)) {
|
||||
LOG.warn("Invalid APK metadata:", metadata);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Element element : metadata.getElements()) {
|
||||
if (element == null || element.getOutputFile() == null) {
|
||||
LOG.warn("No output file specified in APK metadata element:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
final File apk = new File(dir, element.getOutputFile());
|
||||
if (apk.exists() && apk.isFile()) {
|
||||
LOG.info("APK generated:", apk);
|
||||
result.add(apk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable th) {
|
||||
LOG.error(
|
||||
StudioApp.getInstance()
|
||||
.getString(
|
||||
com.itsaky.androidide.R.string
|
||||
.err_cannot_parse_apk_metadata),
|
||||
th);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isValid(ApkMetadata metadata) {
|
||||
|
||||
// Null checks
|
||||
if (metadata == null
|
||||
|| metadata.getArtifactType() == null
|
||||
|| metadata.getArtifactType().getType() == null
|
||||
|| metadata.getElements() == null) {
|
||||
LOG.warn("APK metadata null check failed. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
final ArtifactType type = metadata.getArtifactType();
|
||||
final List<Element> elements = metadata.getElements();
|
||||
|
||||
if (!type.getType().equals(ArtifactType.TYPE_APK)) {
|
||||
LOG.warn("Artifact is not of type APK. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elements.isEmpty()) {
|
||||
LOG.warn("No output elements found for metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean atLeastOneApk = false;
|
||||
for (Element element : elements) {
|
||||
if (element == null
|
||||
|| element.getOutputFile() == null
|
||||
|| !element.getOutputFile().endsWith(".apk")) {
|
||||
LOG.warn("Skipping output element because file is not APK:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
atLeastOneApk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("Output metadata validation succeeded");
|
||||
return atLeastOneApk;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* 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.tasks.gradle.build;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class AssembleDebug extends ApkGeneratingTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.build_debug);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<File> provideApkDirectories(File buildDir) {
|
||||
final Set<File> dirs = new HashSet<>();
|
||||
dirs.add(new File(buildDir, "outputs/apk/debug"));
|
||||
return dirs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "assembleDebug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Collections.singletonList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_ASSEMBLE_DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.BUILD;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.build;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class AssembleRelease extends ApkGeneratingTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.build_release);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<File> provideApkDirectories(File buildDir) {
|
||||
final Set<File> dirs = new HashSet<>();
|
||||
dirs.add(new File(buildDir, "outputs/apk/release"));
|
||||
return dirs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "assembleRelease";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_ASSEMBLE_RELEASE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return GradleTask.Type.BUILD;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.build;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class Build extends ApkGeneratingTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.build);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<File> provideApkDirectories(File buildDir) {
|
||||
final Set<File> dirs = new HashSet<>();
|
||||
dirs.add(new File(buildDir, "outputs/apk/debug"));
|
||||
dirs.add(new File(buildDir, "outputs/apk/release"));
|
||||
return dirs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "build";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_BUILD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return GradleTask.Type.BUILD;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.build;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Bundle extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.create_aab);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "bundle";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_BUNDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return GradleTask.Type.BUILD;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.build;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Clean extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.clean_project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "clean";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_CLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return GradleTask.Type.BUILD;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.build;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class CleanBuild extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.clean_amp_build);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "clean build";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList("clean", "build");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_CLEAN_BUILD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return GradleTask.Type.BUILD;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.lint;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Lint extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.lint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "lint";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList("lint");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_LINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.LINT;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.lint;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class LintDebug extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.lint_debug);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "lintDebug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Collections.singletonList("lintDebug");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_LINT_DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.LINT;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.lint;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class LintRelease extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StudioApp.getInstance().getString(R.string.lint_release);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "lintRelease";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList("lintRelease");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return IDEService.TASK_LINT_RELEASE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.LINT;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.plugin;
|
||||
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class InitializeIDEProject extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "initializeIDEProject";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "initializeIDEProject";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return TASK_INIT_PROJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return Type.ANDROIDIDE_PLUGIN;
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.resources;
|
||||
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DatabindingGenBaseClassesDebug extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public boolean affectsGeneratedSources() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Generate databinding classes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "databindingGenBaseClassesDebug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return DATABINDING_GEN_BASE_CLASSES_DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.BUILD;
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.resources;
|
||||
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ProcessDebugResources extends BaseGradleTask {
|
||||
|
||||
@Override
|
||||
public boolean affectsGeneratedSources() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "processDebugResources";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
return "processDebugResources";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
return Arrays.asList(getCommands());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return TASK_PROCESS_DEBUG_RESOURCES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return Type.BUILD;
|
||||
}
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.tasks.gradle.resources;
|
||||
|
||||
import com.itsaky.androidide.tasks.BaseGradleTask;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class UpdateResourceClassesTask extends BaseGradleTask {
|
||||
|
||||
private final boolean viewBinding;
|
||||
|
||||
public UpdateResourceClassesTask(boolean viewBinding) {
|
||||
this.viewBinding = viewBinding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean affectsGeneratedSources() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Update resource classes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommands() {
|
||||
String command = "";
|
||||
if (viewBinding) {
|
||||
command = new DatabindingGenBaseClassesDebug().getCommands();
|
||||
command += " ";
|
||||
}
|
||||
command += new ProcessDebugResources().getCommands();
|
||||
return command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTasks() {
|
||||
List<String> tasks = new ArrayList<>();
|
||||
if (viewBinding) {
|
||||
tasks.addAll(new DatabindingGenBaseClassesDebug().getTasks());
|
||||
}
|
||||
tasks.addAll(new ProcessDebugResources().getTasks());
|
||||
return tasks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskID() {
|
||||
return TASK_UPDATE_RESOURCE_CLASSES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradleTask.Type getType() {
|
||||
return Type.BUILD;
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,9 @@ package com.itsaky.androidide.utils
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.actions.build.CancelBuildAction
|
||||
import com.itsaky.androidide.actions.build.QuickRunAction
|
||||
import com.itsaky.androidide.actions.build.RunActionMenu
|
||||
import com.itsaky.androidide.actions.build.StopDaemonsAction
|
||||
import com.itsaky.androidide.actions.editor.CopyAction
|
||||
import com.itsaky.androidide.actions.editor.CutAction
|
||||
import com.itsaky.androidide.actions.editor.ExpandSelectionAction
|
||||
@@ -78,7 +78,7 @@ class EditorActivityActions {
|
||||
registry.registerAction(FindActionMenu(context))
|
||||
registry.registerAction(FileTreeAction(context))
|
||||
registry.registerAction(DaemonStatusAction(context))
|
||||
registry.registerAction(StopDaemonsAction(context))
|
||||
registry.registerAction(CancelBuildAction(context))
|
||||
|
||||
// editor text actions
|
||||
registry.registerAction(ExpandSelectionAction(context))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/************************************************************************************
|
||||
/*
|
||||
* 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
|
||||
@@ -16,14 +14,21 @@
|
||||
* 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.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher;
|
||||
|
||||
public class JSONUtility {
|
||||
public static final Gson gson = new Gson();
|
||||
public static final Gson toolingGson;
|
||||
public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
static {
|
||||
final var builder = new GsonBuilder();
|
||||
ToolingApiLauncher.configureGson(builder);
|
||||
toolingGson = builder.create();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
import java.util.*
|
||||
|
||||
class ListingFileRedirect {
|
||||
|
||||
companion object {
|
||||
/** Redirect file will have this marker as the first line as comment. */
|
||||
private const val REDIRECT_MARKER = "#- File Locator -"
|
||||
|
||||
/** Property name in a [Properties] for the metadata file location. */
|
||||
private const val REDIRECT_PROPERTY_NAME = "listingFile"
|
||||
|
||||
@JvmStatic
|
||||
fun maybeExtractRedirectedFile(redirectFile: File): File? {
|
||||
val fileContent = redirectFile.readText()
|
||||
return if (fileContent.startsWith(REDIRECT_MARKER)) {
|
||||
val fileLocator = Properties().also { it.load(StringReader(fileContent)) }
|
||||
val file = File(fileLocator.getProperty(REDIRECT_PROPERTY_NAME))
|
||||
if (!file.isAbsolute) redirectFile.parentFile!!.resolve(file) else file
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/************************************************************************************
|
||||
* 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.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import fr.arnaudguyon.xmltojsonlib.XmlToJson;
|
||||
|
||||
public class ProjectFinder {
|
||||
|
||||
public static AndroidProject fromFolder(File file) {
|
||||
if (!file.isDirectory() || file.isHidden()) return null;
|
||||
File settings = new File(file, "settings.gradle");
|
||||
if (!settings.exists()) return null;
|
||||
String content = FileUtil.readFile(settings.getAbsolutePath());
|
||||
if (TextUtils.isEmpty(content)) return null;
|
||||
|
||||
AndroidProject project = new AndroidProject();
|
||||
for (File module : file.listFiles()) {
|
||||
if (module.isDirectory() && !module.isHidden()) {
|
||||
try {
|
||||
proceedModule(file, module, project);
|
||||
} catch (JSONException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.setAppName(file.getName());
|
||||
project.setProjectPath(file.getAbsolutePath());
|
||||
if (project.getMainModule() == null
|
||||
&& project.getModulePaths().size() > 0
|
||||
&& project.getModulePaths().get(0) != null) {
|
||||
File f = new File(project.getModulePaths().get(0));
|
||||
if (!f.exists() || !f.isDirectory()) return null;
|
||||
project.setMainModule(f.getName());
|
||||
project.setMainSourcePath(new File(f, "src/main/java").getAbsolutePath());
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
private static void proceedModule(File file, File module, AndroidProject project)
|
||||
throws JSONException {
|
||||
final Pattern appPattern =
|
||||
Pattern.compile("apply\\s+plugin\\s*:\\s*(['\"])com.android.application(['\"])");
|
||||
File projectGradle = new File(file, "build.gradle");
|
||||
File moduleGradle = new File(module, "build.gradle");
|
||||
File manifest = new File(module, "src/main/AndroidManifest.xml");
|
||||
File res = new File(module, "src/main/res");
|
||||
if (!module.exists()
|
||||
|| !projectGradle.exists()
|
||||
|| !moduleGradle.exists()
|
||||
|| !manifest.exists()) return;
|
||||
if (!module.isDirectory()
|
||||
|| !moduleGradle.isFile()
|
||||
|| !projectGradle.isFile()
|
||||
|| !manifest.isFile()) return;
|
||||
|
||||
File java = new File(module, "src/main/java");
|
||||
if (java.exists() && java.isDirectory())
|
||||
project.addSource(java.getAbsolutePath()).addModule(module.getAbsolutePath());
|
||||
|
||||
File libsFolder = new File(module, "libs");
|
||||
if (libsFolder.exists() && libsFolder.isDirectory()) {
|
||||
File[] libs = libsFolder.listFiles();
|
||||
if (libs != null && libs.length > 0) {
|
||||
for (File lib : libs)
|
||||
if (lib.getName().endsWith(".jar")) project.addClasspath(lib.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
project.addSource(
|
||||
new File(module, "build/generated/source/buildConfig/debug").getAbsolutePath());
|
||||
project.addSource(
|
||||
new File(module, "build/generated/data_binding_base_class_source_out/debug/out")
|
||||
.getAbsolutePath());
|
||||
|
||||
JSONObject obj =
|
||||
new XmlToJson.Builder(FileUtil.readFile(manifest.getAbsolutePath()))
|
||||
.build()
|
||||
.toJson();
|
||||
if (!obj.has("manifest")) return;
|
||||
String iconPath = null;
|
||||
if (obj.has("manifest")
|
||||
&& obj.getJSONObject("manifest").has("application")
|
||||
&& obj.getJSONObject("manifest").getJSONObject("application").has("android:icon")) {
|
||||
String iconRes =
|
||||
obj.getJSONObject("manifest")
|
||||
.getJSONObject("application")
|
||||
.getString("android:icon");
|
||||
if (iconRes.startsWith("@drawable")) {
|
||||
final String iconName = iconRes.replace("@drawable/", "");
|
||||
File[] drawables = listDrawables(res);
|
||||
for (File drawable : drawables) {
|
||||
File[] icons = listFilesNotEndingWith(drawable, ".xml");
|
||||
for (File icon : icons) {
|
||||
String nameWithoutExtension =
|
||||
icon.getName().substring(0, icon.getName().lastIndexOf("."));
|
||||
if (nameWithoutExtension.equals(iconName)) {
|
||||
iconPath = icon.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (iconRes.startsWith("@drawable")) {
|
||||
final String iconName = iconRes.replace("@drawable/", "");
|
||||
File[] drawables = listMipmaps(res);
|
||||
for (File drawable : drawables) {
|
||||
File[] icons = listFilesNotEndingWith(drawable, ".xml");
|
||||
for (File icon : icons) {
|
||||
String nameWithoutExtension =
|
||||
icon.getName().substring(0, icon.getName().lastIndexOf("."));
|
||||
if (nameWithoutExtension.equals(iconName)) {
|
||||
iconPath = icon.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final String packageName = obj.getJSONObject("manifest").getString("package");
|
||||
final String mainModuleName = module.getName();
|
||||
|
||||
boolean isMainModule = false;
|
||||
if (project.getMainModule() == null) {
|
||||
String content = FileIOUtils.readFile2String(moduleGradle);
|
||||
if (content != null && appPattern.matcher(content).find()) {
|
||||
isMainModule = true;
|
||||
project.setMainModule(mainModuleName);
|
||||
project.setMainSourcePath(new File(module, "src/main/java").getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (iconPath != null) {
|
||||
project.setIconPath(iconPath);
|
||||
}
|
||||
|
||||
if (packageName != null && isMainModule) {
|
||||
project.setPackageName(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
private static File[] listDrawables(File file) {
|
||||
return listFolderStartingWith(file, "drawable");
|
||||
}
|
||||
|
||||
private static File[] listMipmaps(File file) {
|
||||
return listFolderStartingWith(file, "mipmap");
|
||||
}
|
||||
|
||||
private static File[] listFolderStartingWith(File file, final String prefix) {
|
||||
return file.listFiles(
|
||||
new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File p1) {
|
||||
return p1.isDirectory() && p1.getName().startsWith(prefix);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static File[] listFilesNotEndingWith(File file, final String suffix) {
|
||||
return file.listFiles(
|
||||
new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File p1) {
|
||||
return p1.isFile() && !p1.getName().endsWith(suffix);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,6 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -37,8 +34,6 @@ import java.util.Objects;
|
||||
public class EditorViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<List<File>> mFiles = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<AndroidProject> mProject = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<IDEProject> mIDEProject = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Boolean> mFilesModified = new MutableLiveData<>(false);
|
||||
|
||||
/**
|
||||
@@ -47,44 +42,6 @@ public class EditorViewModel extends ViewModel {
|
||||
*/
|
||||
private final MutableLiveData<Pair<Integer, File>> mCurrentFile = new MutableLiveData<>(null);
|
||||
|
||||
/**
|
||||
* Get the current {@link AndroidProject}.
|
||||
*
|
||||
* @return The android project.
|
||||
*/
|
||||
@Nullable
|
||||
public AndroidProject getAndroidProject() {
|
||||
return mProject.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the android project currently opened in the editor activity.
|
||||
*
|
||||
* @param project The project that is opened.
|
||||
*/
|
||||
public void setAndroidProject(final AndroidProject project) {
|
||||
this.mProject.setValue(project);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link IDEProject} opened in the editor activity.
|
||||
*
|
||||
* @return The ide project.
|
||||
*/
|
||||
@Nullable
|
||||
public IDEProject getIDEProject() {
|
||||
return this.mIDEProject.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IDEProject that is currently opened in the editor activity.
|
||||
*
|
||||
* @param project The project that is opened.
|
||||
*/
|
||||
public void setIDEProject(final IDEProject project) {
|
||||
this.mIDEProject.setValue(project);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given file to the list of opened files.
|
||||
*
|
||||
@@ -112,6 +69,10 @@ public class EditorViewModel extends ViewModel {
|
||||
setCurrentFile(-1, null);
|
||||
}
|
||||
|
||||
public void setCurrentFile(final int index, @Nullable final File file) {
|
||||
mCurrentFile.setValue(Pair.create(index, file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the opened file at the given index.
|
||||
*
|
||||
@@ -167,10 +128,6 @@ public class EditorViewModel extends ViewModel {
|
||||
return mCurrentFile.getValue().second;
|
||||
}
|
||||
|
||||
public void setCurrentFile(final int index, @Nullable final File file) {
|
||||
mCurrentFile.setValue(Pair.create(index, file));
|
||||
}
|
||||
|
||||
public void setFilesModified(boolean modified) {
|
||||
this.mFilesModified.setValue(modified);
|
||||
}
|
||||
|
||||
@@ -62,11 +62,6 @@ import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
import io.github.rosemoe.sora.widget.component.Magnifier;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
@@ -78,6 +73,11 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
import io.github.rosemoe.sora.widget.component.Magnifier;
|
||||
|
||||
/**
|
||||
* A view that handles opened code editors.
|
||||
*
|
||||
|
||||
@@ -1,101 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/editor_drawerLayout"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/realContainer">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/editor_drawerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/editor_appBarLayout"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/realContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/editor_appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/editor_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primaryDarkColor"
|
||||
android:elevation="10dp" />
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/primaryDarkColor"
|
||||
android:elevation="10dp"
|
||||
android:id="@+id/editor_toolbar"/>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/build_progress_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:indicatorColor="@color/secondaryColor"/>
|
||||
|
||||
<com.itsaky.androidide.views.MaterialBanner
|
||||
android:id="@+id/syncBanner"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:visibility="gone"/>
|
||||
app:indicatorColor="@color/secondaryColor" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/file_tab_height"
|
||||
android:background="@color/primaryDarkColor"
|
||||
app:tabGravity="start"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="@color/secondaryColor"
|
||||
app:tabIndicatorFullWidth="false"
|
||||
app:tabIndicatorGravity="bottom"
|
||||
app:tabIndicatorHeight="4dp"
|
||||
app:tabMode="scrollable"
|
||||
app:tabSelectedTextColor="@color/secondaryColor"
|
||||
app:tabTextAppearance="@style/AppTheme.TabLayout.Title"
|
||||
app:tabTextColor="@color/secondaryTextColor_light" />
|
||||
<com.itsaky.androidide.views.MaterialBanner
|
||||
android:id="@+id/syncBanner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/file_tab_height"
|
||||
android:background="@color/primaryDarkColor"
|
||||
app:tabGravity="start"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="@color/secondaryColor"
|
||||
app:tabIndicatorFullWidth="false"
|
||||
app:tabIndicatorGravity="bottom"
|
||||
app:tabIndicatorHeight="4dp"
|
||||
app:tabMode="scrollable"
|
||||
app:tabSelectedTextColor="@color/secondaryColor"
|
||||
app:tabTextAppearance="@style/AppTheme.TabLayout.Title"
|
||||
app:tabTextColor="@color/secondaryTextColor_light" />
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/editor_container"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/content_background"
|
||||
android:layout_marginBottom="@dimen/editor_sheet_peek_height"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/editor_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="@dimen/editor_sheet_peek_height"
|
||||
android:background="@color/content_background"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
<include
|
||||
android:id="@+id/bottom_sheet"
|
||||
layout="@layout/layout_editor_bottom_sheet" />
|
||||
|
||||
<include
|
||||
android:id="@+id/bottom_sheet"
|
||||
layout="@layout/layout_editor_bottom_sheet"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/diagnosticInfo"
|
||||
layout="@layout/layout_diagnostic_info"/>
|
||||
layout="@layout/layout_diagnostic_info" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/startNav"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:background="@color/primaryColor"
|
||||
app:headerLayout="@layout/layout_nav_header"
|
||||
app:menu="@menu/menu_editor_nav">
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</com.google.android.material.navigation.NavigationView>
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/startNav"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:background="@color/primaryColor"
|
||||
app:headerLayout="@layout/layout_nav_header"
|
||||
app:menu="@menu/menu_editor_nav">
|
||||
|
||||
</com.google.android.material.navigation.NavigationView>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:background="@color/primaryColor">
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:background="@color/primaryColor"
|
||||
android:layout_gravity="end">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/editor_frameLayout"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"/>
|
||||
|
||||
</com.google.android.material.navigation.NavigationView>
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.google.android.material.navigation.NavigationView>
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="8dp"
|
||||
android:orientation="vertical"
|
||||
app:behavior_hideable="false"
|
||||
app:behavior_peekHeight="@dimen/editor_sheet_peek_height"
|
||||
app:layout_behavior="com.itsaky.androidide.utils.EditorBottomSheetBehavior">
|
||||
@@ -29,26 +28,28 @@
|
||||
android:id="@+id/text_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@android:color/black">
|
||||
android:background="@android:color/black"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusText"
|
||||
android:layout_height="20dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:gravity="center"
|
||||
android:textSize="14sp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:singleLine="true" />
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/swipe_hint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="18dp"
|
||||
android:text="@string/msg_swipe_up"
|
||||
android:gravity="center"
|
||||
android:text="@string/msg_swipe_up"
|
||||
android:textColor="@color/secondaryTextColor"
|
||||
android:textSize="11sp"/>
|
||||
android:textSize="11sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -56,22 +57,22 @@
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:background="@android:color/black"
|
||||
android:layout_below="@id/text_container"
|
||||
android:background="@android:color/black"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorColor="@color/secondaryColor"
|
||||
app:tabIndicatorGravity="stretch"
|
||||
app:tabIndicatorHeight="36dp"
|
||||
app:tabTextAppearance="@style/AppTheme.TabLayout.Title"
|
||||
app:tabSelectedTextColor="@color/primaryTextColor"
|
||||
app:tabMode="scrollable"
|
||||
app:tabGravity="fill"/>
|
||||
app:tabSelectedTextColor="@color/primaryTextColor"
|
||||
app:tabTextAppearance="@style/AppTheme.TabLayout.Title" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/content_background"
|
||||
android:layout_below="@id/tabs"/>
|
||||
android:layout_below="@id/tabs"
|
||||
android:background="@color/content_background" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/clear_fab"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
@@ -87,7 +87,7 @@
|
||||
<string name="preparing_first">Vorbereiten. Der erste Build kann bis zu 10 bis 15 Minuten dauern!</string>
|
||||
<string name="msg_gradle_terminated">Der Gradle-Prozess wurde mit dem Exit-Code %1$d beendet</string>
|
||||
<string name="msg_gradlew_installation_failed">Gradle Wrapper konnte nicht installiert werden: %s</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper ist nicht verfügbar!\nStandard-Wrapper wird installiert. Gradle v7.3 wird bei Bedarf heruntergeladen.</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper ist nicht verfügbar!\nStandard-Wrapper wird installiert. Gradle v7.4 wird bei Bedarf heruntergeladen.</string>
|
||||
<string name="msg_getting_daemom_status">Abrufen des Daemon-Status, bitte warten…</string>
|
||||
<string name="msg_daemons_stopped">Gradle-Dämonen gestoppt</string>
|
||||
<string name="gradle_daemon_status">Gradle-Daemon-Status</string>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<string name="preparing_first">Préparation en cours, la première compilation peut prendre de 10–15 minutes !</string>
|
||||
<string name="msg_gradle_terminated">Résultat de la compilation : %1$d</string>
|
||||
<string name="msg_gradlew_installation_failed">Impossible d\'installer Gradle Wrapper: %s</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper n\'est pas disponible\nInstallation de celui par défaut. Gradle v7.3 sera téléchargé si nécessaire.</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper n\'est pas disponible\nInstallation de celui par défaut. Gradle v7.4 sera téléchargé si nécessaire.</string>
|
||||
<string name="msg_getting_daemom_status">Récupération du status du Daemon, veuillez patienter…</string>
|
||||
<string name="msg_daemons_stopped">Gradle Daemons arrêté</string>
|
||||
<string name="gradle_daemon_status">Statut du Daemon Gradle</string>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<string name="preparing_first">तैयार कर रहे हैं। पहले निर्माण में 10–15 मिनट तक लग सकते हैं!</string>
|
||||
<string name="msg_gradle_terminated">ग्रैडल प्रक्रिया एग्जिट कोड %1$d के साथ बाहर निकली</string>
|
||||
<string name="msg_gradlew_installation_failed">ग्रैडल रैपर इंस्टॉल करने में विफल: %s</string>
|
||||
<string name="msg_installing_gradlew">ग्रैडल रैपर उपलब्ध नहीं है!\nडिफ़ॉल्ट इंस्टाल किया जा रहा है। यदि आवश्यक हो तो ग्रैडल v7.3 डाउनलोड किया जाएगा।</string>
|
||||
<string name="msg_installing_gradlew">ग्रैडल रैपर उपलब्ध नहीं है!\nडिफ़ॉल्ट इंस्टाल किया जा रहा है। यदि आवश्यक हो तो ग्रैडल v7.4 डाउनलोड किया जाएगा।</string>
|
||||
<string name="msg_getting_daemom_status">डेमॉन की स्थिति, कृपया प्रतीक्षा करें…</string>
|
||||
<string name="msg_daemons_stopped">ग्रैडल डेमन्स रुक गए</string>
|
||||
<string name="gradle_daemon_status">ग्रैडल डेमॉन की स्थिति</string>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<string name="preparing_first">Menyiapkan. Build pertama mungkin memakan waktu sekitar 10–15 menit!</string>
|
||||
<string name="msg_gradle_terminated">Proses Gradle berhenti dengan kode exit %1$d</string>
|
||||
<string name="msg_gradlew_installation_failed">Gagal untuk menginstal Gradle Wrapper: %s</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper tidak tersedia!\nMenginstal Gradle bawaan. Gradle v7.3 akan diunduh jika diperlukan.</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper tidak tersedia!\nMenginstal Gradle bawaan. Gradle v7.4 akan diunduh jika diperlukan.</string>
|
||||
<string name="msg_getting_daemom_status">Mendapatkan status Daemon, mohon tunggu…</string>
|
||||
<string name="msg_daemons_stopped">Gradle Daemon Dihentikan</string>
|
||||
<string name="gradle_daemon_status">Status Gradle Daemon</string>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<string name="preparing_first">Preparando. A primeira compilação pode levar de 10 a 15 minutos!</string>
|
||||
<string name="msg_gradle_terminated">Processo Gradle encerrado com código de saída %1$d</string>
|
||||
<string name="msg_gradlew_installation_failed">Falha ao instalar Gradle Wrapper: %s</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper não está disponível!\nInstalando um padrão. Gradle v7.3 será baixado se necessário.</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper não está disponível!\nInstalando um padrão. Gradle v7.4 será baixado se necessário.</string>
|
||||
<string name="msg_getting_daemom_status">Recebendo o status de Daemon, aguarde…</string>
|
||||
<string name="msg_daemons_stopped">Gradle Daemons Parado</string>
|
||||
<string name="gradle_daemon_status">Status do daemon Gradle</string>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<string name="preparing_first">Выполняется подготовка. Первая сборка может занять 10–15 минут!</string>
|
||||
<string name="msg_gradle_terminated">Процесс Gradle завершился с кодом %1$d</string>
|
||||
<string name="msg_gradlew_installation_failed">Ошибка установки Gradle Wrapper: %s</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper недоступен!\nУстановка версии Gradle по умолчанию. Gradle v7.3 будет скачан, если это необходимо.</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper недоступен!\nУстановка версии Gradle по умолчанию. Gradle v7.4 будет скачан, если это необходимо.</string>
|
||||
<string name="msg_getting_daemom_status">Получение статуса Daemon, пожалуйста, подождите…</string>
|
||||
<string name="msg_daemons_stopped">Daemon\'ы Gradle остановлены</string>
|
||||
<string name="gradle_daemon_status">Статус Gradle Daemon</string>
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
<string name="preparing_first">准备中。 第一次编译可能耗费 10–15 分钟!</string>
|
||||
<string name="msg_gradle_terminated">Gradle进程以代码 %1$d退出</string>
|
||||
<string name="msg_gradlew_installation_failed">安装Gradle Wrapper失败: %s</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper不可用!\n正在安装默认版本。 如有需要,Gradle v7.3将会被下载</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper不可用!\n正在安装默认版本。 如有需要,Gradle v7.4将会被下载</string>
|
||||
<string name="msg_getting_daemom_status">正在获取Gradle守护进程状态,请稍等…</string>
|
||||
<string name="msg_daemons_stopped">Gradle守护进程已停止</string>
|
||||
<string name="gradle_daemon_status">Gradle守护进程状态</string>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<string name="preparing_first">Preparing. First build may take up to 10–15 minutes!</string>
|
||||
<string name="msg_gradle_terminated">Gradle process exited with exit code %1$d</string>
|
||||
<string name="msg_gradlew_installation_failed">Failed to install Gradle Wrapper: %s</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper is not available!\nInstalling default one. Gradle v7.3 will be downloaded if necessary.</string>
|
||||
<string name="msg_installing_gradlew">Gradle Wrapper is not available!\nInstalling default one. Gradle v7.4 will be downloaded if necessary.</string>
|
||||
<string name="msg_getting_daemom_status">Getting Daemon status, please wait…</string>
|
||||
<string name="msg_daemons_stopped">Gradle Daemons Stopped</string>
|
||||
<string name="gradle_daemon_status">Gradle daemon status</string>
|
||||
@@ -357,6 +357,17 @@
|
||||
<string name="msg_editor_find_case_sensitive">Case sensitive</string>
|
||||
<string name="msg_editor_find_regex">Regex</string>
|
||||
<string name="msg_replacing">Replacing...</string>
|
||||
|
||||
<string name="title_gradle_service_notification_ticker">Gradle build service started</string>
|
||||
<string name="title_gradle_service_notification">Gradle build service</string>
|
||||
<string name="msg_gradle_service_notification">AndroidIDE\'s Gradle build service is running in the background.</string>
|
||||
|
||||
<string name="title_cancel_build">Cancel build</string>
|
||||
<string name="msg_initializing_project">Initializing project</string>
|
||||
<string name="msg_project_initialized">Project initialized</string>
|
||||
<string name="msg_project_initialization_failed">Project initialization failed</string>
|
||||
<string name="msg_project_not_initialized">Project is not initialized</string>
|
||||
<string name="msg_running_task">Running task: \'%1$s\'</string>
|
||||
</resources>
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
build/
|
||||
/build
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project (path: ':build-tools:tooling-api')
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral ()
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.builder
|
||||
|
||||
import com.itsaky.androidide.tooling.api.messages.result.BuildCancellationRequestResult
|
||||
import com.itsaky.androidide.tooling.api.messages.result.InitializeResult
|
||||
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* A build service provides API to initialize project, execute builds, query a build, cancel running
|
||||
* builds, etc.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface BuildService {
|
||||
|
||||
/**
|
||||
* Initialize the project.
|
||||
*
|
||||
* @param rootDir The root directory of the project to initialize.
|
||||
* @return A [CompletableFuture] which returns an [InitializeResult] when the project
|
||||
* initialization process finishes.
|
||||
*/
|
||||
fun initializeProject(rootDir: String): CompletableFuture<InitializeResult>
|
||||
|
||||
/**
|
||||
* Execute the given tasks.
|
||||
*
|
||||
* @param tasks The tasks to execute. If the fully qualified path of the task is not specified,
|
||||
* then it will be executed in the root project directory.
|
||||
* @return A [CompletableFuture] which returns a list of [TaskExecutionResult]. The result
|
||||
* contains a list of tasks that were executed and the result of the whole execution.
|
||||
* @see BuildService.executeProjectTasks
|
||||
*/
|
||||
fun executeTasks(vararg tasks: String): CompletableFuture<TaskExecutionResult>
|
||||
|
||||
/**
|
||||
* Execute the given tasks of the given project.
|
||||
*
|
||||
* @param projectPath The path of the project. All the tasks will be executed in this project.
|
||||
* @param tasks The tasks to execute. These may or may not be fully qualified names of the
|
||||
* project. If the task name is not fully qualified, then it will be executed in the given
|
||||
* project path. For example, if the project path is ':app' and the task is 'assembleDebug',
|
||||
* then, ':app:assembleDebug' task will be executed.
|
||||
* @return A [CompletableFuture] which returns a list of [TaskExecutionResult]. The result
|
||||
* contains a list of tasks that were executed and the result of the whole execution.
|
||||
*/
|
||||
fun executeProjectTasks(
|
||||
projectPath: String,
|
||||
vararg tasks: String
|
||||
): CompletableFuture<TaskExecutionResult>
|
||||
|
||||
/** Cancel any running build. */
|
||||
fun cancelCurrentBuild(): CompletableFuture<BuildCancellationRequestResult>
|
||||
}
|
||||
Executable → Regular
+53
-42
@@ -17,35 +17,46 @@
|
||||
*/
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class LogLine {
|
||||
|
||||
// It makes things easier in LogLanguageImpl
|
||||
public static final int INFO = SchemeAndroidIDE.LOG_INFO;
|
||||
public static final int DEBUG = SchemeAndroidIDE.LOG_DEBUG;
|
||||
public static final int ERROR = SchemeAndroidIDE.LOG_ERROR;
|
||||
public static final int WARNING = SchemeAndroidIDE.LOG_WARNING;
|
||||
public static int INFO;
|
||||
public static int DEBUG;
|
||||
public static int ERROR;
|
||||
public static int WARNING;
|
||||
|
||||
static {
|
||||
try {
|
||||
final var klass =
|
||||
Class.forName("com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE");
|
||||
INFO = klass.getField("LOG_INFO").getInt(null);
|
||||
DEBUG = klass.getField("LOG_DEBUG").getInt(null);
|
||||
ERROR = klass.getField("LOG_ERROR").getInt(null);
|
||||
WARNING = klass.getField("LOG_WARNING").getInt(null);
|
||||
} catch (Throwable err) {
|
||||
INFO = 0;
|
||||
DEBUG = 1;
|
||||
ERROR = 2;
|
||||
WARNING = 3;
|
||||
}
|
||||
}
|
||||
|
||||
public String unformatted;
|
||||
public String date;
|
||||
public String time;
|
||||
public String pid;
|
||||
public String tid;
|
||||
public String priorityChar;
|
||||
public String tag;
|
||||
public String message;
|
||||
public char priorityChar;
|
||||
public int priority;
|
||||
public boolean formatted;
|
||||
|
||||
public LogLine(String priorityChar, String tag, String message) {
|
||||
// For JSONRpc
|
||||
@SuppressWarnings("unused")
|
||||
private LogLine() {}
|
||||
|
||||
public LogLine(char priorityChar, String tag, String message) {
|
||||
this("", "", "", "", priorityChar, tag, message);
|
||||
}
|
||||
|
||||
@@ -54,7 +65,7 @@ public class LogLine {
|
||||
String time,
|
||||
String pid,
|
||||
String tid,
|
||||
String priorityChar,
|
||||
char priorityChar,
|
||||
String tag,
|
||||
String message) {
|
||||
this(date, time, pid, tid, priorityChar, tag, message, true);
|
||||
@@ -65,8 +76,8 @@ public class LogLine {
|
||||
String time,
|
||||
String pid,
|
||||
String tid,
|
||||
String priorityChar,
|
||||
@NonNull String tag,
|
||||
char priorityChar,
|
||||
String tag,
|
||||
String message,
|
||||
boolean formatted) {
|
||||
this.date = date;
|
||||
@@ -84,11 +95,8 @@ public class LogLine {
|
||||
}
|
||||
}
|
||||
|
||||
private int parsePriority(String s) {
|
||||
if (s == null || s.trim().length() > 1) {
|
||||
return DEBUG;
|
||||
}
|
||||
char c = s.toLowerCase(Locale.US).charAt(0);
|
||||
private int parsePriority(char s) {
|
||||
final var c = Character.toLowerCase(s);
|
||||
if (c == 'w') {
|
||||
return WARNING;
|
||||
} else if (c == 'e') {
|
||||
@@ -105,8 +113,7 @@ public class LogLine {
|
||||
this.formatted = false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static LogLine forLogString(@NonNull final String log) {
|
||||
public static LogLine forLogString(final String log) {
|
||||
try {
|
||||
final var split = log.split("\\s", 7);
|
||||
return new LogLine(
|
||||
@@ -114,7 +121,7 @@ public class LogLine {
|
||||
split[1], // time
|
||||
split[2], // process id
|
||||
split[3], // thread id
|
||||
split[4], // priority
|
||||
split[4].charAt(0), // priority
|
||||
split[5], // tag
|
||||
split[6] // message
|
||||
);
|
||||
@@ -123,35 +130,27 @@ public class LogLine {
|
||||
}
|
||||
}
|
||||
|
||||
public int getColor(Context ctx) {
|
||||
int id = R.color.log_normal;
|
||||
public int getColor() {
|
||||
int id = 0xffffffff;
|
||||
if (priority == WARNING) {
|
||||
id = R.color.log_warning;
|
||||
id = 0xffff7043;
|
||||
} else if (priority == ERROR) {
|
||||
id = R.color.log_error;
|
||||
id = 0xffc50e29;
|
||||
} else if (priority == INFO) {
|
||||
id = R.color.log_info;
|
||||
id = 0xff4caf50;
|
||||
}
|
||||
return ContextCompat.getColor(ctx, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.formatted
|
||||
? String.format(
|
||||
"%-6s %-13s %-6s %-6s %-2s %-35s %s",
|
||||
date, time, pid, tid, priorityChar, trimIfNeeded (tag, 35), message)
|
||||
date, time, pid, tid, priorityChar, trimIfNeeded(tag, 35), message)
|
||||
: this.unformatted;
|
||||
}
|
||||
|
||||
public String toSimpleString() {
|
||||
return this.formatted
|
||||
? String.format("%-25s %-2s %s", trimIfNeeded(tag, 25), priorityChar, message)
|
||||
: this.unformatted;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String trimIfNeeded(String tag, int maxLength) {
|
||||
final var sb = new StringBuilder(tag);
|
||||
final var length = tag.length();
|
||||
@@ -166,4 +165,16 @@ public class LogLine {
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String toSimpleString() {
|
||||
return this.formatted
|
||||
? String.format("%-25s %-2s %s", trimIfNeeded(tag, 25), priorityChar, message)
|
||||
: this.unformatted;
|
||||
}
|
||||
|
||||
public String formattedTagAndMessage() {
|
||||
return this.formatted
|
||||
? String.format("%-25s %s", trimIfNeeded(tag, 25), message)
|
||||
: this.unformatted;
|
||||
}
|
||||
}
|
||||
@@ -103,18 +103,34 @@ public abstract class ILogger {
|
||||
}
|
||||
}
|
||||
|
||||
public static String priorityChar(int priority) {
|
||||
public static char priorityChar(int priority) {
|
||||
switch (priority) {
|
||||
case INFO:
|
||||
return "I";
|
||||
return 'I';
|
||||
case VERBOSE:
|
||||
return "V";
|
||||
return 'V';
|
||||
case ERROR:
|
||||
return "E";
|
||||
return 'E';
|
||||
case WARNING:
|
||||
return "W";
|
||||
return 'W';
|
||||
default:
|
||||
return "D";
|
||||
return 'D';
|
||||
}
|
||||
}
|
||||
|
||||
public static int priority(char priorityChar) {
|
||||
switch (priorityChar) {
|
||||
case 'I':
|
||||
return INFO;
|
||||
case 'V':
|
||||
return VERBOSE;
|
||||
case 'E':
|
||||
return ERROR;
|
||||
case 'W':
|
||||
return WARNING;
|
||||
case 'D':
|
||||
default:
|
||||
return DEBUG;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
|
||||
/**
|
||||
* {@link ILogger} implementation for the JVM.
|
||||
@@ -34,91 +26,23 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
public class JvmLogger extends ILogger {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger("main");
|
||||
|
||||
static {
|
||||
initLogger();
|
||||
}
|
||||
public static LogInterceptor interceptor;
|
||||
|
||||
protected JvmLogger(String tag) {
|
||||
super(tag);
|
||||
}
|
||||
|
||||
private static void initLogger() {
|
||||
LOG.addHandler(new ConsoleHandler());
|
||||
LOG.setLevel(Level.FINEST);
|
||||
for (var h : LOG.getHandlers()) {
|
||||
h.setLevel(Level.FINEST);
|
||||
h.setFormatter(new LogFormat());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLog(int priority, String message) {
|
||||
switch (priority) {
|
||||
case ERROR:
|
||||
LOG.log(Level.SEVERE, message);
|
||||
break;
|
||||
case WARNING:
|
||||
LOG.log(Level.WARNING, message);
|
||||
break;
|
||||
case VERBOSE:
|
||||
LOG.log(Level.FINEST, message);
|
||||
break;
|
||||
case INFO:
|
||||
LOG.log(Level.INFO, message);
|
||||
break;
|
||||
case DEBUG:
|
||||
default:
|
||||
LOG.log(Level.FINE, message);
|
||||
break;
|
||||
final var log = new LogLine(priorityChar(priority), TAG, message);
|
||||
if (interceptor != null) {
|
||||
interceptor.onLog(log);
|
||||
} else {
|
||||
System.err.println(log.toSimpleString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class LogFormat extends Formatter {
|
||||
|
||||
private final String format = "%1$tT.%1$tL\t%4$s\t%2$s\t%5$s%6$s%n";
|
||||
private final Date date = new Date();
|
||||
|
||||
@Override
|
||||
public synchronized String format(LogRecord record) {
|
||||
date.setTime(record.getMillis());
|
||||
String source;
|
||||
if (record.getSourceClassName() != null) {
|
||||
source = last(record.getSourceClassName());
|
||||
if (record.getSourceMethodName() != null) {
|
||||
source += " " + record.getSourceMethodName();
|
||||
}
|
||||
} else {
|
||||
source = record.getLoggerName();
|
||||
}
|
||||
var message = formatMessage(record);
|
||||
var throwable = "";
|
||||
if (record.getThrown() != null) {
|
||||
var sw = new StringWriter();
|
||||
var pw = new PrintWriter(sw);
|
||||
pw.println();
|
||||
record.getThrown().printStackTrace(pw);
|
||||
pw.close();
|
||||
throwable = sw.toString();
|
||||
}
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
format,
|
||||
date,
|
||||
source,
|
||||
record.getLoggerName(),
|
||||
record.getLevel().getLocalizedName(),
|
||||
message,
|
||||
throwable);
|
||||
}
|
||||
|
||||
private String last(String className) {
|
||||
var dot = className.lastIndexOf('.');
|
||||
if (dot == -1) {
|
||||
return className;
|
||||
}
|
||||
return className.substring(dot + 1);
|
||||
}
|
||||
public interface LogInterceptor {
|
||||
void onLog(LogLine line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
build/
|
||||
/build
|
||||
+11
-10
@@ -15,15 +15,16 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.gradle.plugin
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
}
|
||||
|
||||
class IDEModule extends IDEProject {
|
||||
def buildToolsVersion = 0
|
||||
def compileSdkVersion = 0
|
||||
def minSdk = 0
|
||||
def targetSdk = 0
|
||||
def versionCode = 0
|
||||
def versionName = "Not defined"
|
||||
def isLibrary = true
|
||||
def viewBindingEnabled = false
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
}
|
||||
+5
-8
@@ -15,12 +15,9 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.gradle.plugin
|
||||
package com.itsaky.androidide.tooling.events
|
||||
|
||||
class IDEAppModule extends IDEModule {
|
||||
def applicationId = "Not defined"
|
||||
|
||||
public IDEAppModule() {
|
||||
isLibrary = false
|
||||
}
|
||||
}
|
||||
/** @author Akash Yadav */
|
||||
interface FinishEvent {
|
||||
val result: OperationResult
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.tooling.events
|
||||
|
||||
/**
|
||||
* Describes an operation for which an event has occurred.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class OperationDescriptor {
|
||||
abstract val name: String
|
||||
abstract val displayName: String
|
||||
abstract val parent: OperationDescriptor?
|
||||
|
||||
private val gsonType = javaClass.name
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user