diff --git a/app/libs/api-messages.jar b/app/libs/api-messages.jar new file mode 100644 index 000000000..0265c5a40 Binary files /dev/null and b/app/libs/api-messages.jar differ diff --git a/app/libs/api-model.jar b/app/libs/api-model.jar new file mode 100644 index 000000000..2234c4539 Binary files /dev/null and b/app/libs/api-model.jar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc20f27db..d17e89651 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -49,10 +49,6 @@ android:name=".AboutActivity" android:configChanges="orientation|screenSize|uiMode"> - - diff --git a/app/src/main/assets/data/bin.zip b/app/src/main/assets/data/bin.zip index 3b34afa97..06cafc063 100644 Binary files a/app/src/main/assets/data/bin.zip and b/app/src/main/assets/data/bin.zip differ diff --git a/app/src/main/assets/data/gradle-api.zip b/app/src/main/assets/data/gradle-api.zip new file mode 100644 index 000000000..f9669c54a Binary files /dev/null and b/app/src/main/assets/data/gradle-api.zip differ diff --git a/app/src/main/assets/data/jls.zip b/app/src/main/assets/data/jls.zip index 9a01eb471..7a76fa90e 100644 Binary files a/app/src/main/assets/data/jls.zip and b/app/src/main/assets/data/jls.zip differ diff --git a/app/src/main/assets/data/lib/libc++_shared.so b/app/src/main/assets/data/lib/libc++_shared.so deleted file mode 100644 index 44ceeb217..000000000 Binary files a/app/src/main/assets/data/lib/libc++_shared.so and /dev/null differ diff --git a/app/src/main/assets/data/lib/libgcc.so b/app/src/main/assets/data/lib/libgcc.so deleted file mode 100644 index b3ecce967..000000000 Binary files a/app/src/main/assets/data/lib/libgcc.so and /dev/null differ diff --git a/app/src/main/assets/data/lib/libgcc_real.so b/app/src/main/assets/data/lib/libgcc_real.so deleted file mode 100644 index d96dc99d3..000000000 Binary files a/app/src/main/assets/data/lib/libgcc_real.so and /dev/null differ diff --git a/app/src/main/assets/data/lib/libhook.so b/app/src/main/assets/data/lib/libhook.so deleted file mode 100644 index 23ead2db5..000000000 Binary files a/app/src/main/assets/data/lib/libhook.so and /dev/null differ diff --git a/app/src/main/assets/data/lib/libpthread.so b/app/src/main/assets/data/lib/libpthread.so deleted file mode 100644 index 98a0f7ba7..000000000 Binary files a/app/src/main/assets/data/lib/libpthread.so and /dev/null differ diff --git a/app/src/main/assets/data/lib/librt.so b/app/src/main/assets/data/lib/librt.so deleted file mode 100644 index 98a0f7ba7..000000000 Binary files a/app/src/main/assets/data/lib/librt.so and /dev/null differ diff --git a/app/src/main/assets/data/lib/libz.so.1 b/app/src/main/assets/data/lib/libz.so.1 deleted file mode 100644 index 368b545c1..000000000 Binary files a/app/src/main/assets/data/lib/libz.so.1 and /dev/null differ diff --git a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java index 43a9477bf..5bc505539 100644 --- a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java @@ -15,11 +15,11 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.databinding.ActivityDownloadBinding; import com.itsaky.androidide.fragments.sheets.ProgressSheet; +import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.callables.ListDirectoryCallable; import com.itsaky.androidide.utils.FileUtil; -import com.itsaky.androidide.utils.PreferenceManager; import com.itsaky.toaster.Toaster; import java.io.File; import java.io.FileFilter; @@ -84,7 +84,7 @@ public class DownloadActivity extends StudioActivity { } } - private final String DONE = "'Installed successfully.'"; + private final String DONE = "DONE"; private void installAll() { showProgress(); getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true); diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index 7e70aa60b..c092b86ac 100644 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -1,11 +1,8 @@ package com.itsaky.androidide; import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; @@ -39,9 +36,9 @@ import com.blankj.utilcode.util.ThrowableUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.navigation.NavigationView; import com.google.android.material.tabs.TabLayout; -import com.google.android.material.transition.MaterialArcMotion; import com.google.android.material.transition.MaterialContainerTransform; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.itsaky.androidide.adapters.DiagnosticsAdapter; @@ -60,13 +57,17 @@ import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.fragments.sheets.TextSheetFragment; import com.itsaky.androidide.interfaces.DiagnosticClickListener; import com.itsaky.androidide.interfaces.JLSRequestor; +import com.itsaky.androidide.language.buildout.BuildOutputLanguage; import com.itsaky.androidide.language.java.server.JavaLanguageServer; import com.itsaky.androidide.language.logs.LogLanguageImpl; +import com.itsaky.androidide.managers.PreferenceManager; +import com.itsaky.androidide.managers.VersionedFileManager; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.androidide.models.LogLine; import com.itsaky.androidide.models.SearchResult; import com.itsaky.androidide.models.SheetOption; +import com.itsaky.androidide.models.build.OperationHierarchyNode; import com.itsaky.androidide.receivers.LogReceiver; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.shell.ShellServer; @@ -75,14 +76,19 @@ import com.itsaky.androidide.tasks.GradleTask; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.Logger; -import com.itsaky.androidide.utils.PreferenceManager; import com.itsaky.androidide.utils.ProjectWriter; import com.itsaky.androidide.utils.RecursiveFileSearcher; import com.itsaky.androidide.utils.Symbols; +import com.itsaky.androidide.utils.TransformUtils; import com.itsaky.androidide.utils.TypefaceUtils; -import com.itsaky.androidide.utils.VersionedFileManager; import com.itsaky.androidide.views.MaterialBanner; import com.itsaky.androidide.views.SymbolInputView; +import com.itsaky.gradle.tooling.api.model.IDEProject; +import com.itsaky.gradle.tooling.api.model.Module; +import com.itsaky.gradle.tooling.api.model.build.BuildProgressEvent; +import com.itsaky.gradle.tooling.api.model.build.OperationDescriptor; +import com.itsaky.gradle.tooling.api.model.build.ProgressType; +import com.itsaky.gradle.tooling.api.model.build.descriptors.TaskDescriptor; import com.itsaky.lsp.Diagnostic; import com.itsaky.lsp.DidChangeTextDocumentParams; import com.itsaky.lsp.DidChangeWatchedFilesParams; @@ -109,7 +115,6 @@ import com.itsaky.lsp.TextDocumentItem; import com.itsaky.lsp.TextDocumentPositionParams; import com.itsaky.toaster.Toaster; import com.unnamed.b.atv.model.TreeNode; -import io.github.rosemoe.editor.langs.EmptyLanguage; import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.widget.CodeEditor; import java.io.File; @@ -151,9 +156,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private OptionsListFragment mFileOptionsFragment; private ProgressSheet mSearchingProgress; private AlertDialog mFindInProjectDialog; - - private boolean recreateCompletionServices = false; + private final List operationTree = new ArrayList<>(); private final Map> diagnostics = new HashMap<>(); private final Stack pendingMessages = new Stack<>(); @@ -164,7 +168,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private final String JAVA_PATH_REGEX = "/.*/src/.*/java"; private static final Gson gson = new Gson(); - private final IDEService.BuildListener mBuildListener = this; private static final String TAG_FILE_OPTIONS_FRAGMENT = "file_options_fragment"; public static final String TAG = "EditorActivity"; @@ -172,19 +175,13 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private static final int ACTIONID_OTHERS = 101; private static final int ACTIONID_ALL = 102; + private static final int STD_TYPE_NORMAL = 0; + private static final int STD_TYPE_ERROR = 1; + private static final int STD_TYPE_SUCCESS = 2; + + public static final String EXTRA_PROJECT = "project"; + private LogReceiver mLogReceiver = new LogReceiver().setLogListener(line -> appendApkLog(line)); - private BroadcastReceiver mServiceStartReceiver = new BroadcastReceiver(){ - - @Override - public void onReceive(Context p1, Intent p2) { - unregisterReceiver(this); - getApp().storeBuildServiceInstance(mBuildListener); - if (getApp().isStopGradleDaemon() && getBuildService() != null) getBuildService().stopAllDaemons(); - getBuildService().setModule(mProject.getMainModulePath()); - preBuild(); - invalidateOptionsMenu(); - } - }; /** * MenuItem(s) that are related to the build process @@ -216,7 +213,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F super.onCreate(savedInstanceState); setSupportActionBar(mBinding.editorToolbar); - this.mProject = getIntent().getParcelableExtra("project"); + getProjectFromIntent(); mPagerAdapter = new EditorPagerAdapter(getSupportFragmentManager(), this.mProject); mFileTreeFragment = FileTreeFragment.newInstance(this.mProject).setFileActionListener(this); mDaemonStatusFragment = new TextSheetFragment().setTextSelectable(true); @@ -238,23 +235,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mBinding.fabView.setOnClickListener(v -> showViewOptions()); getApp().checkAndUpdateGradle(); - if (getBuildService() == null) { - registerServiceReceiver(); - getApp().startBuildService(); - } else { - if (getApp().isStopGradleDaemon() && getBuildService() != null) getBuildService().stopAllDaemons(); - getBuildService().setModule(mProject.getMainModulePath()); - getBuildService().setListener(this); - preBuild(); - invalidateOptionsMenu(); - } - + getApp().startBuildService(new File(mProject.getProjectPath())); + getApp().getBuildService().setListener(this); + KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged()); registerLogReceiver(); setupContainers(); setupSignatureText(); } + private void getProjectFromIntent() { + this.mProject = getIntent().getParcelableExtra(EXTRA_PROJECT); + getApp().getPrefManager().setOpenedProject(this.mProject.getProjectPath()); + } + private void setupSignatureText() { GradientDrawable gd = new GradientDrawable(); gd.setShape(GradientDrawable.RECTANGLE); @@ -328,7 +322,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } private void notifySyncNeeded() { - if(getBuildService() != null && !getBuildService().isBuilding()) { + if(getBuildService() != null && getBuildService().isProjectInitialized() && !getBuildService().isBuilding()) { getSyncBanner() .setNegative(android.R.string.cancel, null) .setPositive(android.R.string.ok, v -> { @@ -337,6 +331,35 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F .show(); } } + + private void closeProject(boolean manualFinish) { + if (getBuildService() != null && getBuildService().isProjectInitialized()) { + getBuildService().setListener(null); + getBuildService().exit(); + } + + getApp().getPrefManager().setOpenedProject(PreferenceManager.NO_OPENED_PROJECT); + + if(getApp().getJavaLanguageServer() != null) + getApp().getJavaLanguageServer().exit(); + + getApp().stopAllDaemons(); + + if(manualFinish) + finish(); + } + + private void confirmProjectClose() { + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.AppTheme_MaterialAlertDialog); + builder.setTitle(R.string.title_confirm_project_close); + builder.setMessage(R.string.msg_confirm_project_close); + builder.setNegativeButton(android.R.string.no, null); + builder.setPositiveButton(android.R.string.yes, (d, w) -> { + d.dismiss(); + closeProject(true); + }); + builder.show(); + } @Override public void onBackPressed() { @@ -359,7 +382,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } else if(mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) { mFileOptionsFragment.dismiss(); } else { - super.onBackPressed(); + confirmProjectClose(); } } @@ -375,14 +398,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F @Override protected void onDestroy() { - super.onDestroy(); - if (getBuildService() != null) { - getBuildService().setListener(null); - } - try { - unregisterReceiver(mServiceStartReceiver); - unregisterReceiver(mLogReceiver); + closeProject(false); + try { + unregisterReceiver(mLogReceiver); } catch (Throwable th) {} + super.onDestroy(); } @Override @@ -391,7 +411,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F for (int id : BUILD_IDS) { MenuItem item = menu.findItem(id); if (item != null) { - boolean enabled = getBuildService() != null && !getBuildService().isBuilding(); + boolean enabled = getBuildService() != null && getBuildService().isProjectInitialized() && !getBuildService().isBuilding(); item.setEnabled(enabled); item.getIcon().setAlpha(enabled ? 255 : 76); } @@ -604,19 +624,28 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F getLogLanguage().addLine(line); appendLogOut(line.toString()); } + + @Override + public void stdOut(GradleTask task, CharSequence text) { + if(text == null) text = ""; + appendBuildOut(text.toString(), STD_TYPE_NORMAL); + } @Override - public void appendOutput(GradleTask task, CharSequence text) { - if(text == null) - return; - if(task != null && !task.canOutput()) { - return; - } - appendBuildOut(text.toString()); + public void stdErr(GradleTask task, CharSequence text) { + if(text == null) text = ""; + appendBuildOut(text.toString(), STD_TYPE_ERROR); } + @Override + public void stdSuccess(GradleTask task, CharSequence text) { + if(text == null) text = ""; + appendBuildOut(text.toString(), STD_TYPE_SUCCESS); + } + @Override - public void showPreparing() { + public void prepare() { + operationTree.clear(); boolean isFirstBuild = getApp().getPrefManager().getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true); setStatus(getString(isFirstBuild ? R.string.preparing_first : R.string.preparing)); if(isFirstBuild) { @@ -625,20 +654,106 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } @Override - public void onStartingGradleDaemon(GradleTask task) { - setStatus(getString(R.string.msg_starting_daemon)); - getApp().setStopGradleDaemon(false); + public void onConnectionProgressChanged(String status) { + setStatus(status); } - @Override - public void onRunTask(String taskName, GradleTask task) { - if(task != null && task.canOutput()) { - setStatus(getBuildService().typeString(task.getTaskID()) + " " + taskName); - } - } + @Override + public void onBuildProgress(GradleTask gradleTask, BuildProgressEvent event) { + + OperationDescriptor descriptor = event.descriptor; + OperationDescriptor parent = descriptor.parent; + + OperationHierarchyNode node = createNode(event); + if(parent == null) { + // This is a root node, add it at level 0 in tree + if(operationTree.contains(node)) { + operationTree.set(operationTree.indexOf(node), node); + } else operationTree.add(node); + } else { + // A nested node must be added in the appropriate parent node + + // We first get the list of parents + // Last item in this list will always be the direct parent of current node + final List parents = new ArrayList<>(); + while(parent != null) { + parents.add(0, parent); + parent = parent.parent; + } + + List list = new ArrayList(operationTree); + OperationHierarchyNode direct = null; + final int last = parents.size() - 1; + for(int i=0;i(direct.children); + } + } + } + + if(event.descriptor != null) { + /** + * A descriptor describes a progress event + * Different descriptors are available for different events in package {@code com.itsaky.gradle.tooling.api.model.build.descriptors } + */ + if(event.descriptor instanceof TaskDescriptor) { + TaskDescriptor task = (TaskDescriptor) descriptor; + setStatus(String.format("> Task %s", task.taskPath)); + } + } + } + + private OperationHierarchyNode findNodeByLabelInList(String label, List list) { + if(list == null || label == null) return null; + for(int i=0;i dependencies) { - setStatus(getString(R.string.msg_starting_completion)); - mProject.setClassPaths(dependencies); + @Override + public void onProjectInitialized(IDEProject project, Module main) { + invalidateOptionsMenu(); + setStatus(getString(R.string.msg_starting_completion)); + mProject.setClassPaths(new ArrayList(main.dependencyJars)); createServices(); - } - - @Override - public void onGetDependenciesFailed() { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.AppTheme_MaterialAlertDialog); - builder.setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes, (p1, p2) -> { - p1.dismiss(); - if(getBuildService() != null) - getBuildService().showDependencies(); - }) - .setTitle(R.string.failed) - .setMessage(R.string.msg_first_prepare_failed) - .create().show(); - } + } @Override public void saveFiles() { @@ -697,10 +798,15 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } // Can be called from a different different, better to run on UI thread - private void appendBuildOut(final String str) { + private void appendBuildOut(final String str, int type) { runOnUiThread(() -> { String strFinal = str.endsWith("\n") ? str : str.concat("\n"); - getBuildView(true).getText().append(strFinal); + int line = getBuildView(true).getText().append(strFinal); + if(type == STD_TYPE_SUCCESS) { + ((BuildOutputLanguage) getBuildView().getEditorLanguage()).addSuccess(line); + } else if(type == STD_TYPE_ERROR) { + ((BuildOutputLanguage) getBuildView().getEditorLanguage()).addError(line); + } }); } @@ -822,20 +928,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } private MaterialContainerTransform createContainerTransformFor(View start, View end, View drawingView) { - MaterialContainerTransform transform = new MaterialContainerTransform(); - transform.setStartView(start); - transform.setEndView(end); - transform.addTarget(end); - transform.setDrawingViewId(drawingView.getId()); - transform.setAllContainerColors(ContextCompat.getColor(this, R.color.primaryDarkColor)); - transform.setElevationShadowEnabled(true); - transform.setPathMotion(new MaterialArcMotion()); - transform.setScrimColor(Color.TRANSPARENT); - return transform; - } - - private void preBuild() { - getBuildService().showDependencies(); + return TransformUtils.createContainerTransformFor(start, end, drawingView); } private void install(File apk) { @@ -855,12 +948,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F setStatus(getString(getApp().areCompletorsStarted() ? R.string.msg_service_started : R.string.msg_starting_completion_failed)); }); } - - private void registerServiceReceiver() { - IntentFilter filter = new IntentFilter(); - filter.addAction(IDEService.ACTION_CREATED); - registerReceiver(mServiceStartReceiver, filter); - } private void registerLogReceiver() { IntentFilter filter = new IntentFilter(); @@ -1268,7 +1355,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F startActivity(new Intent(this, PreferencesActivity.class)); } else if(id == R.id.editornav_share) { startActivity(IntentUtils.getShareTextIntent(getString(R.string.msg_share_app))); - } + } else if(id == R.id.editornav_close_project) { + confirmProjectClose(); + } + mBinding.getRoot().closeDrawer(GravityCompat.START); return false; } @@ -1342,7 +1432,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F buildView = new CodeEditor(this); buildView.setEditable(false); buildView.setDividerWidth(0); - buildView.setEditorLanguage(new EmptyLanguage()); + buildView.setEditorLanguage(new BuildOutputLanguage()); buildView.setOverScrollEnabled(false); buildView.setTextActionMode(CodeEditor.TextActionMode.ACTION_MODE); buildView.setWordwrap(false); @@ -1852,4 +1942,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F invalidateOptionsMenu(); } + + private static final Logger LOG = Logger.instance("EditorActivity"); } diff --git a/app/src/main/java/com/itsaky/androidide/MainActivity.java b/app/src/main/java/com/itsaky/androidide/MainActivity.java index 660ed7335..f2ae98f3d 100644 --- a/app/src/main/java/com/itsaky/androidide/MainActivity.java +++ b/app/src/main/java/com/itsaky/androidide/MainActivity.java @@ -4,100 +4,372 @@ import abhishekti7.unicorn.filepicker.UnicornFilePicker; import android.content.Intent; import android.os.Bundle; import android.view.View; -import com.blankj.utilcode.util.SizeUtils; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.itsaky.androidide.adapters.MainPagerAdapter; +import androidx.transition.Transition; +import androidx.transition.TransitionManager; +import com.google.android.material.transition.MaterialContainerTransform; import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.databinding.ActivityMainBinding; -import com.itsaky.androidide.fragments.MainFragment; -import com.itsaky.androidide.fragments.ProjectsFragment; +import com.itsaky.androidide.databinding.LayoutCreateProjectBinding; +import com.itsaky.androidide.databinding.LayoutCreateProjectContentBinding; import com.itsaky.androidide.fragments.sheets.ProgressSheet; -import com.itsaky.androidide.shell.ShellServer; -import com.itsaky.androidide.utils.FileUtil; -import com.itsaky.androidide.views.VerticalViewPager; +import com.itsaky.androidide.interfaces.ProjectWriterCallback; +import com.itsaky.androidide.managers.PreferenceManager; +import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.models.NewProjectDetails; +import com.itsaky.androidide.models.ProjectTemplate; +import com.itsaky.androidide.project.utils.ProjectFinder; +import com.itsaky.androidide.tasks.TaskExecutor; +import com.itsaky.androidide.tasks.callables.ProjectCreatorCallable; +import com.itsaky.androidide.utils.TransformUtils; import com.itsaky.toaster.Toaster; import java.io.File; import java.util.ArrayList; -import com.itsaky.androidide.utils.Environment; +import java.util.concurrent.Callable; + +public class MainActivity extends StudioActivity implements View.OnClickListener, ProjectWriterCallback { -public class MainActivity extends StudioActivity -{ private ActivityMainBinding binding; - - private MainFragment mMainFragment; - private ProjectsFragment mProjectsFragment; - private MainPagerAdapter mPagerAdapter; - - private int currentItem = 0; - - public static final String TAG = "MainActivity"; + private LayoutCreateProjectBinding createBinding; + private LayoutCreateProjectContentBinding createLayoutBinding; + + private ProgressSheet mProgressSheet; + + private ArrayList mTemplates = new ArrayList(); + + private int currentTemplateIndex = 0; @Override protected View bindLayout() { binding = ActivityMainBinding.inflate(getLayoutInflater()); + createBinding = binding.createLayout; + createLayoutBinding = createBinding.createProjectContent; return binding.getRoot(); } - + @Override - protected void onCreate(Bundle savedInstanceState) - { + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mPagerAdapter = new MainPagerAdapter(getSupportFragmentManager()) - .addFragment(getMainFragment()) - .addFragment(getProjectsFragment()); - binding.mainViewPager.setAdapter(mPagerAdapter); - binding.mainMoveDown.hide(); - binding.mainMoveDown.setOnClickListener(v -> showFirst()); - binding.settings.setOnClickListener(v -> gotoSettings()); - binding.mainViewPager.setOnPageChangeListener(new VerticalViewPager.OnPageChangeListener(){ - @Override - public void onPageScrolled(int p1, float p2, int p3) { - float elevation = SizeUtils.dp2px(8) * (currentItem == 0 ? p2 : -p2); - binding.activitymainTextView1.setElevation(elevation); - } + binding.createProject.setOnClickListener(this); + binding.openProject.setOnClickListener(this); + createBinding.createprojectClose.setOnClickListener(this); + createLayoutBinding.createprojectCreate.setOnClickListener(this); + createLayoutBinding.previousCard.setOnClickListener(this); + createLayoutBinding.nextCard.setOnClickListener(this); + binding.createNewCard.setVisibility(View.GONE); - @Override - public void onPageSelected(int p1) { - currentItem = p1; - if(p1 == 1) - binding.mainMoveDown.show(); - else binding.mainMoveDown.hide(); - } + currentTemplateIndex = 0; + createTemplates(); + createProgressSheet(); + showTemplate(currentTemplateIndex); - @Override - public void onPageScrollStateChanged(int p1) { - } - }); + binding.gotoPreferences.setOnClickListener(this); + + binding.getRoot().post(() -> { + PreferenceManager manager = getApp().getPrefManager(); + if(manager.wasProjectOpened()) { + String path = manager.getOpenedProject(); + if(path == null && path.trim().isEmpty()) { + getApp().toast(R.string.msg_opened_project_does_not_exist, Toaster.Type.INFO); + } else { + File root = new File(manager.getOpenedProject()); + if(!root.exists()) { + getApp().toast(R.string.msg_opened_project_does_not_exist, Toaster.Type.INFO); + } else { + openProject(root); + } + } + } + }); } - + private void gotoSettings() { startActivity(new Intent(this, PreferencesActivity.class)); } - - private void showFirst() { - if(binding.mainViewPager.getCurrentItem() != 0) - binding.mainViewPager.setCurrentItem(0, true); + + @Override + protected void onStorageGranted() { } @Override - protected void onStorageGranted() - { - getProjectsFragment().loadProjects(); - } - - @Override - protected void onStorageDenied() - { + protected void onStorageDenied() { getApp().toast(R.string.msg_storage_denied, Toaster.Type.ERROR); finishAffinity(); } - - private MainFragment getMainFragment() { - return mMainFragment == null ? mMainFragment = new MainFragment().setCreateProjectListener(() -> getProjectsFragment().loadProjects()).setOnCreateProjectVisibilityListener((isVisible) -> binding.mainViewPager.setScrollEnabled(!isVisible)) : mMainFragment; + + @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(); + } } - - private ProjectsFragment getProjectsFragment() { - return mProjectsFragment == null ? mProjectsFragment = new ProjectsFragment() : mProjectsFragment; + + @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 files = data.getStringArrayListExtra("filePaths"); + if (files != null) { + if (files.size() == 1) { + File choosenDir = new File(files.get(0)); + if (choosenDir.exists() && choosenDir.isDirectory()) { + openProject(choosenDir); + } else { + getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR); + choosenDir = null; + } + } 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()); + } + + /** + * Must be called in/after onViewCreated + */ + private void createTemplates() { + mTemplates = new ArrayList<>(); + + ProjectTemplate + empty = new ProjectTemplate() + .setId(0) + .setName(this, R.string.template_empty) + .setDescription(this, R.string.template_description_empty) + .setImageId(R.drawable.template_empty), + basic = new ProjectTemplate() + .setId(1) + .setName(this, R.string.template_basic) + .setDescription(this, R.string.template_description_basic) + .setImageId(R.drawable.template_basic), + drawer = new ProjectTemplate() + .setId(2) + .setName(this, R.string.template_navigation_drawer) + .setDescription(this, R.string.template_description_navigation_drawer) + .setImageId(R.drawable.template_navigation_drawer); + mTemplates.add(empty); + mTemplates.add(basic); + mTemplates.add(drawer); + + } + + private void createProgressSheet() { + mProgressSheet = new ProgressSheet(); + } + + 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), project -> openEditor(project, false)); + } + + private void pickProject() { + UnicornFilePicker.from(this) + .addConfigBuilder() + .addItemDivider(false) + .selectMultipleFiles(false) + .setRootDirectory(getApp().getProjectsDir().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, boolean finish) { +// if (mProgressSheet != null && mProgressSheet.isShowing()) { +// mProgressSheet.dismiss(); +// } + 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)); + if (finish) + finish(); + } + } + + private void createNewProject() { + final String appName = createLayoutBinding.createprojectTextAppName.getEditText().getText().toString().trim(); + final String packageName = 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 && minSdk < 99; + } + + private int getMinSdk() { + try { + return Integer.parseInt(createLayoutBinding.createprojectTextMinSdk.getEditText().getText().toString()); + } catch (Exception e) { getApp().toast(e.getMessage(), Toaster.Type.ERROR); } + return -1; + } + + private int getTargetSdk() { + try { + return Integer.parseInt(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 final class ProjectReader implements Callable { + + private final File root ; + + public ProjectReader(File root) { + this.root = root; + } + + @Override + public AndroidProject call() throws Exception { + return ProjectFinder.fromFolder(root); + } + } + + private final class OnEndTransitionListener implements Transition.TransitionListener { + + Callable c; + public OnEndTransitionListener(Callable c) { + this.c = c; + } + + @Override + public void onTransitionStart(Transition p1) { + } + + @Override + public void onTransitionEnd(Transition p1) { + try { + c.call(); + } catch (Exception e) {} + } + + @Override + public void onTransitionCancel(Transition p1) { + } + + @Override + public void onTransitionPause(Transition p1) { + } + + @Override + public void onTransitionResume(Transition p1) { + } } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java index fabe2f733..6c29f29f9 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java @@ -45,7 +45,7 @@ public class FileTreeViewHolder extends TreeNode.BaseNodeViewHolder { else icon = R.drawable.ic_file_unknown; - final boolean isGradle = file.getAbsolutePath().equals(Environment.GRADLE_PROPS_DIR.getAbsolutePath()); + final boolean isGradle = file.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()); chevron = binding.getRoot().findViewById(R.id.filetree_chevron); binding.filetreeName.setText(isGradle ? "GRADLE_HOME" : file.getName()); binding.filetreeIcon.setImageResource(icon); diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java b/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java index 459c08433..468200af7 100644 --- a/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java @@ -14,13 +14,16 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import com.itsaky.androidide.R; import com.itsaky.androidide.services.IDEService; +import com.itsaky.androidide.utils.Logger; public abstract class StudioActivity extends AppCompatActivity { private boolean toRequestStorage = true; public static final String TAG = "StudioActivity"; public static final int REQCODE_STORAGE = 1009; - + + protected static Logger LOG = Logger.instance("StudioActivity"); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index f0b7cdc67..d665270a5 100644 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -11,7 +11,6 @@ import android.text.TextUtils; import androidx.core.app.NotificationManagerCompat; import androidx.multidex.MultiDexApplication; import com.blankj.utilcode.util.EncodeUtils; -import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; import com.blankj.utilcode.util.SPStaticUtils; import com.blankj.utilcode.util.ThrowableUtils; @@ -22,14 +21,15 @@ import com.itsaky.androidide.interfaces.LanguageServerListener; import com.itsaky.androidide.language.java.manager.JavaCharacter; import com.itsaky.androidide.language.java.server.JavaLanguageServer; import com.itsaky.androidide.language.xml.completion.XMLCompletionService; +import com.itsaky.androidide.managers.PreferenceManager; +import com.itsaky.androidide.managers.ToolsManager; import com.itsaky.androidide.models.AndroidProject; -import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.services.MessagingService; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.FileUtil; -import com.itsaky.androidide.utils.PreferenceManager; +import com.itsaky.androidide.utils.Logger; import com.itsaky.androidide.utils.StudioUtils; import com.itsaky.lsp.LanguageClient; import com.itsaky.toaster.Toaster; @@ -39,8 +39,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import net.lingala.zip4j.ZipFile; -import com.blankj.utilcode.util.ZipUtils; public class StudioApp extends MultiDexApplication { @@ -73,6 +71,8 @@ public class StudioApp extends MultiDexApplication public static final String EMAIL = "contact@androidide.com"; private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + + private static final Logger LOG = Logger.instance("StudioApp"); @Override public void onCreate() { @@ -85,34 +85,15 @@ public class StudioApp extends MultiDexApplication mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); JavaCharacter.initMap(); - setupLibsIfNeeded(); - extractJlsIfNeeded(); - extractLogsenderIfNeeded(); - extractCleanerIfNeeded(); + ToolsManager.init(this, null); -// newShell(line -> handleLog(line)).bgAppend("androidide-cleaner && logcat -v threadtime"); - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannels(); } - + FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE); FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS); } - - private void extractCleanerIfNeeded() { - File cleanerZip = new File(Environment.TMP_DIR, "bin.zip"); - File cleaner = new File(Environment.BINDIR, "androidide-cleaner"); - if(!cleaner.exists()) { - ResourceUtils.copyFileFromAssets("data/bin.zip", cleanerZip.getAbsolutePath()); - try { - ZipUtils.unzipFile(cleanerZip, Environment.BINDIR); - } catch (Throwable e) {} - } - - if(cleaner.exists() && !cleaner.canExecute()) - cleaner.setExecutable(true); - } private void handleLog(CharSequence seq) { if(seq == null) @@ -165,67 +146,6 @@ public class StudioApp extends MultiDexApplication } - private void extractJlsIfNeeded() { - boolean gson = false, jls = false, proto = false; - final List files = FileUtils.listFilesInDir(Environment.JLS_HOME, true); - for(File f : files) { - gson = gson == true ? gson : f.getName().equals("gson.jar"); - jls = jls == true ? jls : f.getName().equals("jls.jar"); - proto = proto == true ? proto : f.getName().equals("protobuf.jar"); - } - - if(!(gson && jls && proto)) { - try { - extractJls(); - } catch (Throwable e) {} - } - } - - private void extractJls(){ - final File jlsZip = new File(Environment.JLS_HOME, "jls.zip"); - ResourceUtils.copyFileFromAssets("data/jls.zip", jlsZip.getAbsolutePath()); - try { - ZipFile file = new ZipFile(jlsZip, ConstantsBridge.JLS_ZIP_PASSWORD_HASH.toCharArray()); - file.extractAll(Environment.JLS_HOME.getAbsolutePath()); - FileUtils.delete(jlsZip); - } catch (Throwable th) {} - } - - private boolean setupLibsIfNeeded() { - File lib = Environment.LIBDIR; - if(lib != null && lib.exists() && lib.isDirectory()) { - boolean libhook, librt, libgcc, libgcc_real, libpthread, libz, libcpp_shared; - libhook = librt = libgcc = libgcc_real = libpthread = libz = libcpp_shared = false; - File[] list = lib.listFiles(); - if(list != null) { - for(File f : list) { - libhook = libhook == false ? f.getName().equals("libhook.so") : libhook; - librt = librt == false ? f.getName().equals("librt.so") : librt; - libpthread = libpthread == false ? f.getName().equals("libpthread.so") : libpthread; - libgcc = libgcc == false ? f.getName().equals("libgcc.so") : libgcc; - libgcc_real = libgcc_real = false ? f.getName().equals("libgcc_real.so") : libgcc_real; - libz = libz == false ? f.getName().equals("libz.so.1") : libz; - libcpp_shared = libcpp_shared == false ? f.getName().equals("libc++_shared.so") : libcpp_shared; - } - } else return extractLibs(lib); - return !(libhook && librt && libgcc && libgcc_real && libpthread && libz && libcpp_shared) ? extractLibs(lib) : true; - } else return extractLibs(lib); - } - - private boolean extractLibs(File lib) { - if(lib.exists()) - FileUtils.delete(lib.getParentFile()); - return lib.mkdirs() && ResourceUtils.copyFileFromAssets(getAssetsDataFile("lib"), lib.getAbsolutePath()); - } - - private void extractLogsenderIfNeeded() { - try { - final File logsenderZip = new File(Environment.JLS_HOME, "logsender.zip"); - ResourceUtils.copyFileFromAssets("data/logsender.zip", logsenderZip.getAbsolutePath()); - ZipUtils.unzipFile(logsenderZip, Environment.HOME); - } catch (IOException e) {} - } - private File getBusybox() { File parentFile = getRootDir().getParentFile(); File busyboxDir = new File(parentFile, "bin/busybox"); @@ -249,38 +169,28 @@ public class StudioApp extends MultiDexApplication return busyboxDir; } + + public void stopAllDaemons() { + newShell(null).bgAppend("gradle --stop"); + } - public void onGradleUpdated() { - IDEService service = getBuildService(); - if(service != null) { - service.onGradleUpdated(); - } + public void startBuildService(File rootProject) { + if(getBuildService() != null) { + getBuildService().exit(); + } + + buildService = new IDEService(rootProject); + buildService.start(); } - - public void startBuildService() { - if(!IDEService.isRunning()) { - startService(new Intent(this, IDEService.class)); - } - } - - public void storeBuildServiceInstance(IDEService.BuildListener listener) { - buildService = IDEService.getInstance(); - buildService.setListener(listener); - } - + public IDEService getBuildService() { return buildService; } public void createCompletionService(AndroidProject project, LanguageClient client) { this.mXmlCompletionService = new XMLCompletionService(); - - if(languageServer == null) { - this.languageServer = new JavaLanguageServer(project, client); - this.languageServer.startServer(); - } else { - this.languageServer.initialize(project); - } + this.languageServer = new JavaLanguageServer(project, client); + this.languageServer.startServer(); } public JavaLanguageServer getJavaLanguageServer() { @@ -500,11 +410,15 @@ public class StudioApp extends MultiDexApplication open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(open); } catch (Throwable th) { - Intent open = new Intent(); - open.setAction(Intent.ACTION_VIEW); - open.setData(Uri.parse(StudioApp.TELEGRAM_GROUP_URL)); - open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(open); + try { + Intent open = new Intent(); + open.setAction(Intent.ACTION_VIEW); + open.setData(Uri.parse(StudioApp.TELEGRAM_GROUP_URL)); + open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(open); + } catch (Throwable th2) { + toast(th2.getMessage(), Toaster.Type.ERROR); + } } } @@ -515,7 +429,9 @@ public class StudioApp extends MultiDexApplication open.setData(Uri.parse(StudioApp.SUGGESTIONS_URL)); open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(open); - } catch (Throwable th) {} + } catch (Throwable th) { + toast(th.getMessage(), Toaster.Type.ERROR); + } } public void openWebsite() { @@ -525,7 +441,9 @@ public class StudioApp extends MultiDexApplication open.setData(Uri.parse(StudioApp.WEBSITE)); open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(open); - } catch (Throwable th) {} + } catch (Throwable th) { + toast(th.getMessage(), Toaster.Type.ERROR); + } } public void emailUs() { @@ -535,6 +453,8 @@ public class StudioApp extends MultiDexApplication open.setData(Uri.parse("mailto:" + EMAIL)); open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(open); - } catch (Throwable th) {} + } catch (Throwable th) { + toast(th.getMessage(), Toaster.Type.ERROR); + } } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java index 7013ae075..fe264ee2d 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java @@ -15,12 +15,12 @@ import com.itsaky.androidide.language.java.JavaLanguage; import com.itsaky.androidide.language.java.JavaLanguageAnalyzer; import com.itsaky.androidide.language.xml.XMLLanguage; import com.itsaky.androidide.language.xml.lexer.XMLLexer; +import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.callables.ReadFileTask; -import com.itsaky.androidide.utils.PreferenceManager; import com.itsaky.androidide.utils.TypefaceUtils; import com.itsaky.lsp.Diagnostic; import com.itsaky.lsp.DidSaveTextDocumentParams; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java index d9a912826..1d7c9fd99 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java @@ -83,7 +83,7 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN } else if(f.isDirectory() && f.exists()) { if(node.isExpanded()) { collapseNode(node); - } else if(f.getAbsolutePath().equals(Environment.GRADLE_PROPS_DIR.getAbsolutePath()) && !node.isExpanded()) { + } else if(f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()) && !node.isExpanded()) { expandNode(node); } else { setLoading(node); @@ -164,7 +164,7 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN return; final File gradleProps = Environment.GRADLE_PROPS; - final File gradleHome = Environment.GRADLE_PROPS_DIR; + final File gradleHome = Environment.GRADLE_USER_HOME; final File root = StudioApp.getInstance().getRootDir().getParentFile(); File projectDir = new File(mProject.getProjectPath()); // projectDir = root; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/MainFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/MainFragment.java deleted file mode 100644 index b22b78ad7..000000000 --- a/app/src/main/java/com/itsaky/androidide/fragments/MainFragment.java +++ /dev/null @@ -1,340 +0,0 @@ -package com.itsaky.androidide.fragments; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewAnimationUtils; -import android.view.ViewGroup; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.transition.ChangeBounds; -import androidx.transition.Transition; -import androidx.transition.TransitionManager; -import com.itsaky.androidide.R; -import com.itsaky.androidide.databinding.LayoutCreateProjectBinding; -import com.itsaky.androidide.databinding.LayoutCreateProjectContentBinding; -import com.itsaky.androidide.databinding.LayoutMainBinding; -import com.itsaky.androidide.interfaces.ProjectWriterCallback; -import com.itsaky.androidide.models.ProjectTemplate; -import com.itsaky.androidide.views.transition.ArcMotionPlus; -import com.itsaky.toaster.Toaster; -import java.util.ArrayList; -import java.util.concurrent.Callable; -import com.itsaky.androidide.utils.ProjectWriter; -import com.itsaky.androidide.models.NewProjectDetails; -import com.itsaky.androidide.fragments.sheets.ProgressSheet; -import com.itsaky.androidide.tasks.TaskExecutor; -import com.itsaky.androidide.tasks.callables.ProjectCreatorCallable; - -public class MainFragment extends BaseFragment implements OnClickListener, ProjectWriterCallback -{ - private LayoutMainBinding binding; - private LayoutCreateProjectBinding createBinding; - private LayoutCreateProjectContentBinding createLayoutBinding; - - private OnCreateProjectListener createProjectListener; - private ProgressSheet mWritingProgressSheet; - - private ArrayList mTemplates = new ArrayList(); - private OnCreateProjectVisibilityListener visibilityChangeListener; - - public static final String TAG = "MainFragment"; - private int currentTemplateIndex = 0; - - public MainFragment setCreateProjectListener(OnCreateProjectListener createProjectListener) { - this.createProjectListener = createProjectListener; - return this; - } - - public MainFragment setOnCreateProjectVisibilityListener(OnCreateProjectVisibilityListener visibilityChangeListener) { - this.visibilityChangeListener = visibilityChangeListener; - return this; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = LayoutMainBinding.inflate(inflater, container, false); - createBinding = binding.createLayout; - createLayoutBinding = createBinding.createProjectContent; - return binding.getRoot(); - } - - @Override - public void onViewCreated(final View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - binding.createNew.setOnClickListener(this); - createBinding.createprojectClose.setOnClickListener(this); - createLayoutBinding.createprojectCreate.setOnClickListener(this); - createLayoutBinding.previousCard.setOnClickListener(this); - createLayoutBinding.nextCard.setOnClickListener(this); - binding.createNewCard.setVisibility(View.GONE); - - currentTemplateIndex = 0; - createTemplates(); - createProgressSheet(); - showTemplate(currentTemplateIndex); - } - - @Override - public void onClick(View p1) { - if(p1.getId() == binding.createNew.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(); - } - - @Override - public void beforeBegin() { - if(mWritingProgressSheet == null) - createProgressSheet(); - - setMessage(R.string.msg_begin_project_write); - } - - @Override - public void onProcessTask(String taskName) { - setMessage(taskName); - } - - @Override - public void onSuccess() { - if(mWritingProgressSheet != null) - createProgressSheet(); - - if(mWritingProgressSheet.isShowing()) - mWritingProgressSheet.dismiss(); - getStudioActivity().getApp().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS); - if(createProjectListener != null) - createProjectListener.onNewProjectCreated(); - } - - @Override - public void onFailed(String reason) { - if(mWritingProgressSheet != null) - createProgressSheet(); - - if(mWritingProgressSheet.isShowing()) - mWritingProgressSheet.dismiss(); - getStudioActivity().getApp().toast(reason, Toaster.Type.ERROR); - } - - 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()); - } - - /** - * Must be called in/after onViewCreated - */ - private void createTemplates() { - mTemplates = new ArrayList<>(); - - ProjectTemplate - empty = new ProjectTemplate() - .setId(0) - .setName(getContext(), R.string.template_empty) - .setDescription(getContext(), R.string.template_description_empty) - .setImageId(R.drawable.template_empty), - basic = new ProjectTemplate() - .setId(1) - .setName(getContext(), R.string.template_basic) - .setDescription(getContext(), R.string.template_description_basic) - .setImageId(R.drawable.template_basic), - drawer = new ProjectTemplate() - .setId(2) - .setName(getContext(), R.string.template_navigation_drawer) - .setDescription(getContext(), R.string.template_description_navigation_drawer) - .setImageId(R.drawable.template_navigation_drawer); -// navigation = new ProjectTemplate() -// .setId(3) -// .setName(getContext(), R.string.template_bottom_navigation) -// .setDescription(getContext(), R.string.template_description_bottom_navigation) -// .setImageId(R.drawable.template_bottom_navigation), -// kotlin = new ProjectTemplate() -// .setId(4) -// .setName(getContext(), R.string.template_basic_kotlin) -// .setDescription(getContext(), R.string.template_description_kotlin) -// .setImageId(R.drawable.template_kotlin); - - mTemplates.add(empty); - mTemplates.add(basic); - mTemplates.add(drawer); -// mTemplates.add(navigation); -// mTemplates.add(kotlin); - } - - private void createProgressSheet() { - mWritingProgressSheet = new ProgressSheet(); - } - - private void setMessage(int msg) { - if(getContext() != null) { - setMessage(getContext().getString(msg)); - } - } - - private void setMessage(String msg) { - mWritingProgressSheet.setMessage(msg); - } - - private void createNewProject() { - final String appName = createLayoutBinding.createprojectTextAppName.getEditText().getText().toString().trim(); - final String packageName = createLayoutBinding.createprojectTextPackageName.getEditText().getText().toString().trim(); - final int minSdk = getMinSdk(); - final int targetSdk = getTargetSdk(); - - if(!isValid(appName, packageName, minSdk, targetSdk)) { - getStudioActivity().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 && minSdk < 99; - } - - private int getMinSdk() - { - try - { - return Integer.parseInt(createLayoutBinding.createprojectTextMinSdk.getEditText().getText().toString()); - } catch (Exception e) - { getStudioActivity().getApp().toast(e.getMessage(), Toaster.Type.ERROR); } - return -1; - } - - private int getTargetSdk() - { - try - { - return Integer.parseInt(createLayoutBinding.createprojectTextTargetSdk.getEditText().getText().toString()); - } catch (Exception e) - { getStudioActivity().getApp().toast(e.getMessage(), Toaster.Type.ERROR); } - return -1; - } - - private void showCreateProject() - { - ChangeBounds bounds = new ChangeBounds(); - bounds.setPathMotion(new ArcMotionPlus().setArcAngle(90)); - bounds.setDuration(300); - bounds.addListener(new OnEndTransitionListener(() -> { - final int x = (int) binding.createNew.getX() + (binding.createNew.getWidth() / 2); - final int y = (int) binding.createNew.getY() + (binding.createNew.getHeight() / 2); - binding.createNewCard.setVisibility(View.VISIBLE); - int startRadius = binding.createNew.getWidth() / 2; - int endRadius = Math.max(binding.getRoot().getWidth(), binding.getRoot().getHeight()); - Animator anim = ViewAnimationUtils.createCircularReveal(binding.createNewCard, x, y, startRadius, endRadius); - anim.setDuration(600); - anim.start(); - if(visibilityChangeListener != null) - visibilityChangeListener.onVisibilityChanged(true); - return null; - })); - TransitionManager.beginDelayedTransition(binding.getRoot(), bounds); - - ConstraintLayout.LayoutParams p = (ConstraintLayout.LayoutParams) binding.createNew.getLayoutParams(); - p.topToTop = p.PARENT_ID; - p.rightToRight = p.PARENT_ID; - p.leftToLeft = p.UNSET; - binding.createNew.setLayoutParams(p); - } - - private void hideCreateProject() - { - int x = (int) binding.createNew.getX() + (binding.createNew.getWidth() / 2); - int y = (int) binding.createNew.getY() + (binding.createNew.getHeight() / 2); - int startRadius = Math.max(binding.getRoot().getWidth(), binding.getRoot().getHeight()); - int endRadius = binding.createNew.getWidth() / 2; - Animator anim = ViewAnimationUtils.createCircularReveal(binding.createNewCard, x, y, startRadius, endRadius); - anim.setDuration(500); - anim.addListener(new AnimatorListenerAdapter() - { - @Override - public void onAnimationEnd(Animator anim) - { - binding.createNewCard.setVisibility(View.GONE); - if(visibilityChangeListener != null) - visibilityChangeListener.onVisibilityChanged(false); - - ChangeBounds bounds = new ChangeBounds(); - bounds.setPathMotion(new ArcMotionPlus().setArcAngle(90)); - bounds.setDuration(300); - TransitionManager.beginDelayedTransition(binding.getRoot(), bounds); - - ConstraintLayout.LayoutParams p = (ConstraintLayout.LayoutParams) binding.createNew.getLayoutParams(); - p.topToTop = p.UNSET; - p.topToBottom = binding.greetingText.getId(); - p.rightToRight = p.PARENT_ID; - p.leftToLeft = p.PARENT_ID; - binding.createNew.setLayoutParams(p); - } - }); - anim.start(); - } - - private final class OnEndTransitionListener implements Transition.TransitionListener { - - Callable c; - public OnEndTransitionListener(Callable c) { - this.c = c; - } - - @Override - public void onTransitionStart(Transition p1) { - } - - @Override - public void onTransitionEnd(Transition p1) { - try { - c.call(); - } catch (Exception e) {} - } - - @Override - public void onTransitionCancel(Transition p1) { - } - - @Override - public void onTransitionPause(Transition p1) { - } - - @Override - public void onTransitionResume(Transition p1) { - } - } - - public interface OnCreateProjectVisibilityListener { - public void onVisibilityChanged(boolean isVisible); - } - - public static interface OnCreateProjectListener { - public void onNewProjectCreated(); - } -} diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java index 759bdbcac..f4c956351 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java @@ -1,13 +1,13 @@ package com.itsaky.androidide.fragments.preferences; -import androidx.preference.PreferenceFragmentCompat; +import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; -import android.os.Bundle; import android.view.ViewGroup; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceFragmentCompat; import com.itsaky.androidide.app.StudioApp; -import com.itsaky.androidide.utils.PreferenceManager; +import com.itsaky.androidide.managers.PreferenceManager; public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java index 9dd99de65..eca3dcba5 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java @@ -20,7 +20,7 @@ import java.io.File; import java.util.List; import java.util.Set; -import static com.itsaky.androidide.utils.PreferenceManager.*; +import static com.itsaky.androidide.managers.PreferenceManager.*; public class BuildPreferences extends BasePreferenceFragment implements Preference.OnPreferenceClickListener { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java index 47515d9a7..c40da0695 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java @@ -10,7 +10,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.itsaky.androidide.R; import com.itsaky.androidide.databinding.LayoutTextSizeSliderBinding; -import static com.itsaky.androidide.utils.PreferenceManager.*; +import static com.itsaky.androidide.managers.PreferenceManager.*; import com.itsaky.androidide.models.ConstantsBridge; import com.google.android.material.slider.LabelFormatter; import java.text.NumberFormat; diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java b/app/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java index 1642923b5..51ed7d62d 100644 --- a/app/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java @@ -1,8 +1,10 @@ package com.itsaky.androidide.interfaces; +import java.io.File; + public interface ProjectWriterCallback { public void beforeBegin(); public void onProcessTask(String taskName); - public void onSuccess(); + public void onSuccess(File rootDir); public void onFailed(String reason); } diff --git a/app/src/main/java/com/itsaky/androidide/language/buildout/BuildOutputLanguage.java b/app/src/main/java/com/itsaky/androidide/language/buildout/BuildOutputLanguage.java new file mode 100644 index 000000000..23ead11ac --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/language/buildout/BuildOutputLanguage.java @@ -0,0 +1,61 @@ +package com.itsaky.androidide.language.buildout; + +import io.github.rosemoe.editor.interfaces.CodeAnalyzer; +import io.github.rosemoe.editor.langs.EmptyLanguage; +import io.github.rosemoe.editor.text.TextAnalyzeResult; +import io.github.rosemoe.editor.text.TextAnalyzer; +import io.github.rosemoe.editor.widget.EditorColorScheme; +import java.util.ArrayList; +import java.util.List; + +public class BuildOutputLanguage extends EmptyLanguage { + + private final BuildAnalyzer analyzer = new BuildAnalyzer(); + + @Override + public CodeAnalyzer getAnalyzer() { + return analyzer; + } + + public void addError(int line) { + analyzer.addError(line); + } + + public void addSuccess(int line) { + analyzer.addSuccess(line); + } + + private class BuildAnalyzer implements CodeAnalyzer { + + private final List errorMap = new ArrayList<>(); + private final List successMap = new ArrayList<>(); + + public void addError(int line) { + errorMap.add(line); + } + + public void addSuccess(int line) { + successMap.add(line); + } + + @Override + public void analyze(CharSequence content, TextAnalyzeResult colors, TextAnalyzer.AnalyzeThread.Delegate delegate) { + int line = 0; + int index = 0; + colors.addNormalIfNull(); + while(index < content.length() && delegate.shouldAnalyze()) { + int id = EditorColorScheme.TEXT_NORMAL; + if(errorMap != null && errorMap.contains(line)) + id = EditorColorScheme.BUILD_OUT_ERROR; + else if(successMap != null && successMap.contains(line)) + id = EditorColorScheme.BUILD_OUT_SUCCESS; + colors.addIfNeeded(line, 0, id); + char c = content.charAt(index); + if(c == '\n') { + line++; + } + index++; + } + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java b/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java index 4f478997b..7d42a4a7a 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java @@ -81,7 +81,7 @@ public class JavaLanguageServer implements ShellServer.Callback { "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED " + "--add-opens jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED " + "-cp $JLS_HOME/lib/jls.jar:$JLS_HOME/lib/gson.jar:$JLS_HOME/lib/protobuf.jar " + - "org.javacs.Main --quiet"; + "org.javacs.Main"; private static final String CONTENT_LENGTH = "Content-Length: "; @@ -113,6 +113,7 @@ public class JavaLanguageServer implements ShellServer.Callback { onServerOut(temp); }); } + socket.close(); logger.i("Socket disconnected"); } catch (Throwable e) { logEx(e); @@ -167,6 +168,13 @@ public class JavaLanguageServer implements ShellServer.Callback { }); } + public void exit() { + write(Method.EXIT, ""); + try { + server.close(); + } catch (Throwable th) {} + } + public boolean isStarted() { return started; } @@ -287,8 +295,7 @@ public class JavaLanguageServer implements ShellServer.Callback { } private BufferedOutputStream os; - private BufferedOutputStream os1; - + private void writeOut(String data) { try { if(os == null) { @@ -300,24 +307,11 @@ public class JavaLanguageServer implements ShellServer.Callback { logger.e(ThrowableUtils.getFullStackTrace(th)); } } - - private void writeOut2(String data) { - try { - if(os1 == null) { - os1 = new BufferedOutputStream(new FileOutputStream(new File(FileUtil.getExternalStorageDir(), "ide_xlog/verbose.txt"), true)); - } - os1.write(data.getBytes()); - os1.flush(); - } catch (Throwable th) { - logger.e(ThrowableUtils.getFullStackTrace(th)); - } - } /** * This method is called on UI Thread, do not perform time consuming or networking tasks */ private void onServerOut(String line) { - writeOut2(line + "\n"); if(line.contains(CONTENT_LENGTH)) line = line.substring(0, line.lastIndexOf(CONTENT_LENGTH)); if(this.client == null) return; try { diff --git a/app/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java b/app/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java new file mode 100644 index 000000000..a65350b5b --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java @@ -0,0 +1,187 @@ +package com.itsaky.androidide.managers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import com.itsaky.androidide.utils.Environment; +import java.util.HashSet; +import java.util.Set; + +public class PreferenceManager { + + private SharedPreferences prefs; + private SharedPreferences.Editor editor; + + public static final int LOGSENDER_LATEST_VERSION = 102; + + public static final String KEY_FRAMEWORK_DOWNLOADED = "framework_downloaded"; + public static final String KEY_FRAMEWORK_INSTALLED = "framework_installed"; + public static final String KEY_IS_FIRST_PROJECT_BUILD = "project_isFirstBuild"; + + public static final String KEY_EDITORFLAG_WS_LEADING = "idepref_editor_wsLeading"; + public static final String KEY_EDITORFLAG_WS_TRAILING = "idepref_editor_wsTrailing"; + public static final String KEY_EDITORFLAG_WS_INNER = "idepref_editor_wsInner"; + public static final String KEY_EDITORFLAG_WS_EMPTY_LINE = "idepref_editor_wsEmptyLine"; + public static final String KEY_EDITORFLAG_LINE_BREAK = "idepref_editor_lineBreak"; + public static final String KEY_EDITOR_DRAW_HEX = "idepref_editor_drawHexColors"; + + public static final String KEY_GRADLE_CURRENT_DIR = "idepref_customGradle_currentFolder"; + public static final String KEY_GRADLE_FILENAMES = "idepref_customGradle_folderNames"; + public static final String KEY_GRADLECMD_STACKTRACRE = "idepref_gradleCmd_stacktrace"; + public static final String KEY_GRADLECMD_DEBUG = "idepref_gradleCmd_debug"; + public static final String KEY_GRADLECMD_SCAN = "idepref_gradleCmd_scan"; + public static final String KEY_GRADLECMD_INFO = "idepref_gradleCmd_info"; + public static final String KEY_GRADLECMD_WARNINGMODE = "idepref_gradleCmd_warningMode"; + + public static final String KEY_LOGSENDER_CURRENT_VERSION = "logsender_currentVersion"; + + public static final String KEY_LAST_OPENED_PROJECT = "ide_last_project"; + public static final String NO_OPENED_PROJECT = ""; + + @SuppressLint("CommitPrefEdits") + public PreferenceManager(Context ctx) { + this.prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(ctx); + this.editor = prefs.edit(); + } + + public PreferenceManager putInt(String key, int val) { + editor.putInt(key, val).apply(); + return this; + } + + public int getInt(String key) { + return prefs.getInt(key, 0); + } + + public int getInt(String key, int def) { + return prefs.getInt(key, def); + } + + public PreferenceManager putFloat(String key, float val) { + editor.putFloat(key, val).apply(); + return this; + } + + public float getFloat(String key) { + return prefs.getFloat(key, 0f); + } + + public float getFloat(String key, float def) { + return prefs.getFloat(key, def); + } + + public PreferenceManager putBoolean(String key, boolean value) { + editor.putBoolean(key, value).apply(); + return this; + } + + public boolean getBoolean(String key) { + return getBoolean(key, false); + } + + public boolean getBoolean(String key, boolean defaultValue) { + return prefs.getBoolean(key, defaultValue); + } + + public PreferenceManager putString(String key, String value) { + editor.putString(key, value).apply(); + return this; + } + + public String getString(String key) { + return prefs.getString(key, null); + } + + public String getString(String key, String defaultValue) { + return prefs.getString(key, defaultValue); + } + + public PreferenceManager setOpenedProject(String root) { + return putString(KEY_LAST_OPENED_PROJECT, root); + } + + public String getOpenedProject() { + return getString(KEY_LAST_OPENED_PROJECT, NO_OPENED_PROJECT); + } + + public boolean wasProjectOpened() { + return !getOpenedProject().equals(NO_OPENED_PROJECT); + } + + public boolean isFrameworkDownloaded() { + return getBoolean(KEY_FRAMEWORK_DOWNLOADED); + } + + public boolean isFrameworkInstalled() { + return isFrameworkDownloaded() && getBoolean(KEY_FRAMEWORK_INSTALLED); + } + + public boolean isStracktraceEnabled() { + return getBoolean(KEY_GRADLECMD_STACKTRACRE); + } + + public boolean isGradleInfoEnabled() { + return getBoolean(KEY_GRADLECMD_INFO, true); + } + + public boolean isGradleDebugEnabled() { + return getBoolean(KEY_GRADLECMD_DEBUG); + } + + public boolean isGradleScanEnabled() { + return getBoolean(KEY_GRADLECMD_SCAN); + } + + public boolean isGradleWarningEnabled() { + return getBoolean(KEY_GRADLECMD_WARNINGMODE); + } + + public PreferenceManager setGradleStacktraceEnabled(boolean enabled) { + return putBoolean(KEY_GRADLECMD_STACKTRACRE, enabled); + } + + public PreferenceManager setGradleInfoEnabled(boolean enabled) { + return putBoolean(KEY_GRADLECMD_INFO, enabled); + } + + public PreferenceManager setGradleDebugEnabled(boolean enabled) { + return putBoolean(KEY_GRADLECMD_DEBUG, enabled); + } + + public PreferenceManager setGradleScanEnabled(boolean enabled) { + return putBoolean(KEY_GRADLECMD_SCAN, enabled); + } + + public PreferenceManager setGradleWarningEnabled(boolean enabled) { + return putBoolean(KEY_GRADLECMD_WARNINGMODE, enabled); + } + + public PreferenceManager addGradleFolder(String name) { + Set folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet()); + if(folders == null) { + folders = new HashSet(); + } + if(!folders.contains(name)) { + folders.add(name); + } + editor.putStringSet(KEY_GRADLE_FILENAMES, folders).apply(); + return this; + } + + public Set getGradleFolderNames() { + Set folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet()); + if(folders != null && folders.size() > 0) { + return folders; + } else { + return addGradleFolder(Environment.DEFAULT_GRADLE_FOLDER).getGradleFolderNames(); + } + } + + public PreferenceManager setCurentGradleFolderName(String name) { + return addGradleFolder(name).putString(KEY_GRADLE_CURRENT_DIR, name); + } + + public String getCurrentGradleFolderName() { + return getString(KEY_GRADLE_CURRENT_DIR, Environment.DEFAULT_GRADLE_FOLDER); + } +} diff --git a/app/src/main/java/com/itsaky/androidide/managers/ToolsManager.java b/app/src/main/java/com/itsaky/androidide/managers/ToolsManager.java new file mode 100644 index 000000000..392eb051a --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/managers/ToolsManager.java @@ -0,0 +1,208 @@ +package com.itsaky.androidide.managers; + +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.itsaky.androidide.app.StudioApp; +import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.utils.Environment; +import com.itsaky.androidide.utils.Logger; +import java.io.File; +import java.io.IOException; +import java.util.List; +import net.lingala.zip4j.ZipFile; + +public class ToolsManager { + + private static PreferenceManager prefs; + + public static final int JLS_VERSION = 1; + public static final int LOGSENDER_VERSION = 1; + public static final int CLEANER_VERSION = 1; + public static final int GRADLE_API_VERSION = 2; + + public static final String KEY_JLS_VERSION = "tools_jlsVersion"; + public static final String KEY_LOGSENDER_VERSION = "tools_logsenderVersion"; + public static final String KEY_CLEANER_VERSION = "tools_cleanerVersion"; + public static final String KEY_GRADLE_API_VERSION = "tools_gradleApiVersion"; + + + public static void init(StudioApp app, Runnable onFinish) { + prefs = app.getPrefManager(); + extractJlsIfNeeded(); + extractLogsenderIfNeeded(); + extractCleanerIfNeeded(); + extractGradleApiIfNeeded(); + writeInitScript(app); + + if(onFinish != null) + onFinish.run(); + } + + private static void writeInitScript(StudioApp app) { + FileIOUtils.writeFileFromString(Environment.INIT_SCRIPT, INIT_SCRIPT); + } + + + private static void extractJlsIfNeeded() { + boolean gson = false, jls = false, proto = false; + final List files = FileUtils.listFilesInDir(Environment.JLS_HOME, true); + for(File f : files) { + gson = gson == true ? gson : f.getName().equals("gson.jar"); + jls = jls == true ? jls : f.getName().equals("jls.jar"); + proto = proto == true ? proto : f.getName().equals("protobuf.jar"); + } + + final boolean exists = gson && jls && proto; + final boolean isOld = JLS_VERSION > prefs.getInt(KEY_JLS_VERSION, 0); + if(!exists || isOld) { + try { + extractJls(); + } catch (Throwable e) {} + } + } + + private static void extractJls(){ + final File jlsZip = new File(Environment.JLS_HOME, "jls.zip"); + ResourceUtils.copyFileFromAssets("data/jls.zip", jlsZip.getAbsolutePath()); + try { + ZipFile file = new ZipFile(jlsZip, ConstantsBridge.JLS_ZIP_PASSWORD_HASH.toCharArray()); + file.extractAll(Environment.JLS_HOME.getAbsolutePath()); + FileUtils.delete(jlsZip); + prefs.putInt(KEY_JLS_VERSION, JLS_VERSION); + } catch (Throwable th) {} + } + + + + private static void extractLogsenderIfNeeded() { + try { + final boolean isOld = LOGSENDER_VERSION > prefs.getInt(KEY_LOGSENDER_VERSION, 0); + if(isOld) { + final File logsenderZip = new File(Environment.JLS_HOME, "logsender.zip"); + ResourceUtils.copyFileFromAssets("data/logsender.zip", logsenderZip.getAbsolutePath()); + ZipUtils.unzipFile(logsenderZip, Environment.HOME); + prefs.putInt(KEY_LOGSENDER_VERSION, LOGSENDER_VERSION); + } + } catch (IOException e) {} + } + + + + private static void extractCleanerIfNeeded() { + File cleanerZip = new File(Environment.TMP_DIR, "bin.zip"); + File cleaner = new File(Environment.BINDIR, "androidide-cleaner"); + final boolean isOld = CLEANER_VERSION > prefs.getInt(KEY_CLEANER_VERSION, 0); + if(!cleaner.exists() || isOld) { + ResourceUtils.copyFileFromAssets("data/bin.zip", cleanerZip.getAbsolutePath()); + try { + ZipUtils.unzipFile(cleanerZip, Environment.BINDIR); + prefs.putInt(KEY_CLEANER_VERSION, CLEANER_VERSION); + } catch (Throwable e) {} + } + + if(cleaner.exists() && !cleaner.canExecute()) + cleaner.setExecutable(true); + } + + + + private static void extractGradleApiIfNeeded() { + final File libs = Environment.GRADLE_API_LIBS_DIR; + final File gradleZip = new File(Environment.TMP_DIR, "gradle-api.zip"); + final boolean isOld = GRADLE_API_VERSION > prefs.getInt(KEY_GRADLE_API_VERSION, 0); + if(!libs.exists() || isOld) { + try { + ResourceUtils.copyFileFromAssets("data/gradle-api.zip", gradleZip.getAbsolutePath()); + ZipFile file = new ZipFile(gradleZip, ConstantsBridge.GRADLE_API_ZIP_PASSWORD_HASH.toCharArray()); + file.extractAll(Environment.GRADLE_API_DIR.getAbsolutePath()); + FileUtils.delete(gradleZip); + prefs.putInt(KEY_GRADLE_API_VERSION, GRADLE_API_VERSION); + } catch (Throwable th) { } + } + } + + private static final String INIT_SCRIPT = + "/**" + + "\n * This is the init script that AndroidIDE uses to configure builds." + + "\n * " + + "\n * DO NOT MODIFY" + + "\n * Any modification will be overwritten!" + + "\n *" + + "\n * If you\'re here, most probably you\'re trying to look into how AndroidIDE works" + + "\n * or maybe you\'re trying to modify AndroidIDE" + + "\n * Only thing I can say, is, FUCK YOU!" + + "\n */" + + "\n" + + "\nimport org.gradle.api.Plugin" + + "\nimport org.gradle.api.Project" + + "\n" + + "\ninitscript {" + + "\n repositories {" + + "\n maven { url \'https://maven.aliyun.com/repository/public/\' } " + + "\n maven { url \'https://maven.aliyun.com/repository/google/\' }" + + "\n maven { url \'https://maven.aliyun.com/repository/gradle-plugin/\' }" + + "\n maven { url \'https://clojars.org/repo/\'}" + + "\n maven { url \'https://jitpack.io\'}" + + "\n maven { url \'https://maven.google.com\' }" + + "\n google()" + + "\n mavenCentral()" + + "\n mavenLocal()" + + "\n jcenter()" + + "\n }" + + "\n " + + "\n dependencies {" + + "\n classpath \"com.android.tools.build:gradle:7.0.0\"" + + "\n }" + + "\n}" + + "\n" + + "\ngradle.projectsLoaded {" + + "\n rootProject.subprojects.forEach { sub ->" + + "\n sub.afterEvaluate {" + + "\n if(sub.getPluginManager().hasPlugin(\"com.android.application\")) {" + + "\n sub.getPluginManager().apply(AndroidIDEPlugin.class)" + + "\n }" + + "\n }" + + "\n }" + + "\n}" + + "\n" + + "\nclass AndroidIDEPlugin implements Plugin {" + + "\n " + + "\n def AAPT2_VERSION = \"4.0.1-6197926\"" + + "\n " + + "\n @Override" + + "\n void apply(Project project) {" + + "\n " + + "\n project.android {" + + "\n sourceSets {" + + "\n main.java.srcDirs += \"${System.getenv(\"HOME\")}/logsender\"" + + "\n }" + + "\n }" + + "\n " + + "\n project.configurations.matching { it.name == \'_internal_aapt2_binary\' }.all { config ->" + + "\n config.resolutionStrategy.eachDependency { details ->" + + "\n details.useVersion(\"$AAPT2_VERSION\")" + + "\n }" + + "\n }" + + "\n " + + "\n project.android.applicationVariants.all { variant ->" + + "\n project.tasks.create(name: \"printDependencies${variant.name.capitalize()}\", group: \"Android IDE\", description: \"[AndroidIDE] List all dependencies in project including those in subprojects for ${variant.name.capitalize()} build.\") {" + + "\n doLast {" + + "\n showDependencies(project, variant)" + + "\n }" + + "\n }" + + "\n }" + + "\n }" + + "\n}" + + "\n" + + "\ndef showDependencies(def project, def variant) {" + + "\n println \">> ${variant.name.capitalize()} dependencies <<\"" + + "\n variant.getCompileClasspath().each { dep ->" + + "\n println dep.absolutePath" + + "\n }" + + "\n println \"${System.getenv(\'ANDROID_HOME\')}/platforms/${project.android.compileSdkVersion}/android.jar\"" + + "\n}"; + + private static final Logger LOG = Logger.instance("ToolsManager"); +} diff --git a/app/src/main/java/com/itsaky/androidide/utils/VersionedFileManager.java b/app/src/main/java/com/itsaky/androidide/managers/VersionedFileManager.java similarity index 91% rename from app/src/main/java/com/itsaky/androidide/utils/VersionedFileManager.java rename to app/src/main/java/com/itsaky/androidide/managers/VersionedFileManager.java index 96833e091..c3c6d3d36 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/VersionedFileManager.java +++ b/app/src/main/java/com/itsaky/androidide/managers/VersionedFileManager.java @@ -1,21 +1,23 @@ -package com.itsaky.androidide.utils; +package com.itsaky.androidide.managers; + import java.io.File; import java.util.HashMap; public class VersionedFileManager { - private static final HashMap map = new HashMap<>(); + private static final HashMap map = new HashMap<>(); + public static int fileOpened(File file) { // Initial version of file should always be 1 map.put(file, 1); return 1; } - + public static int incrementVersion(File file) { if(!map.containsKey(file)) { map.put(file, 1); } - + int version = map.get(file); version++; map.put(file, version); diff --git a/app/src/main/java/com/itsaky/androidide/models/AndroidProject.java b/app/src/main/java/com/itsaky/androidide/models/AndroidProject.java index c6e262e21..5f7461796 100644 --- a/app/src/main/java/com/itsaky/androidide/models/AndroidProject.java +++ b/app/src/main/java/com/itsaky/androidide/models/AndroidProject.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; public class AndroidProject implements Parcelable { + private String appName; private String packageName; private String iconPath; diff --git a/app/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java b/app/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java index bc060b279..5ee04038f 100644 --- a/app/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java +++ b/app/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java @@ -15,4 +15,8 @@ public class ConstantsBridge { // Real password is admin@androidide.com // This will he further encrypted by StringFog at compile time public static final String JLS_ZIP_PASSWORD_HASH = "865dabea6f06fea1a3dd572e58a4a81c"; + + // Password for GradleApi zip + // Real password is gradle-api@admin@androidide + public static final String GRADLE_API_ZIP_PASSWORD_HASH = "b50b462f8e8c5789f32428d7e1967da2"; } diff --git a/app/src/main/java/com/itsaky/androidide/services/IDEService.java b/app/src/main/java/com/itsaky/androidide/services/IDEService.java index 393cffd4c..d8431b950 100644 --- a/app/src/main/java/com/itsaky/androidide/services/IDEService.java +++ b/app/src/main/java/com/itsaky/androidide/services/IDEService.java @@ -1,55 +1,76 @@ package com.itsaky.androidide.services; -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; +import androidx.annotation.StringRes; +import com.blankj.utilcode.util.CloseUtils; import com.blankj.utilcode.util.FileUtils; -import com.itsaky.androidide.R; +import com.google.gson.GsonBuilder; import com.itsaky.androidide.app.StudioApp; -import com.itsaky.androidide.models.gradle.Artifact; +import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.shell.ShellServer; +import com.itsaky.androidide.sockets.SocketConnection; import com.itsaky.androidide.tasks.GradleTask; import com.itsaky.androidide.tasks.gradle.BaseGradleTasks; import com.itsaky.androidide.tasks.gradle.build.AssembleDebug; import com.itsaky.androidide.utils.Environment; -import com.itsaky.androidide.utils.PreferenceManager; +import com.itsaky.androidide.utils.FileUtil; +import com.itsaky.gradle.tooling.api.GradleConnection; +import com.itsaky.gradle.tooling.api.messages.ErrorMessage; +import com.itsaky.gradle.tooling.api.messages.Message; +import com.itsaky.gradle.tooling.api.messages.Method; +import com.itsaky.gradle.tooling.api.messages.ResponseMessage; +import com.itsaky.gradle.tooling.api.messages.params.BuildParams; +import com.itsaky.gradle.tooling.api.messages.params.InitializeParams; +import com.itsaky.gradle.tooling.api.model.IDEProject; +import com.itsaky.gradle.tooling.api.model.Module; +import com.itsaky.gradle.tooling.api.model.build.BuildProgressEvent; +import com.itsaky.gradle.tooling.api.model.build.BuildResult; +import com.itsaky.gradle.tooling.api.model.build.OperationDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.FinishEventDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.ProjectConfigurationDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.StartEventDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.StatusEventDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.TaskDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.TestOutDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.TransformDescriptor; +import com.itsaky.gradle.tooling.api.model.build.descriptors.WorkItemDescriptor; +import com.itsaky.gradle.tooling.api.model.build.identifiers.BinaryPluginIdentifierDescription; +import com.itsaky.gradle.tooling.api.model.build.identifiers.PluginIdentifierDescription; +import com.itsaky.gradle.tooling.api.model.build.identifiers.ScriptPluginIdentifierDescription; +import com.itsaky.gradle.tooling.api.tools.RuntimeTypeAdapterFactory; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; -import org.json.JSONObject; +import java.util.stream.Collectors; + +public class IDEService extends SocketConnection implements ShellServer.Callback { -public class IDEService extends Service implements ShellServer.Callback { - - private static IDEService instance; private ShellServer shell; private BuildListener listener; - private File module; + private File rootProject; private GradleTask currentTask; - - private List dependencies; - + private StudioApp app; + + private IDEProject project; + private Module main; + + private boolean isConnected; + private boolean projectInitialized = false; private boolean isBuilding = false; - private boolean outputtingJars = false; - private boolean recreateShellOnDone = false; - - private static boolean isRunning = false; - - private final String RUN_TASK = "> Task"; - private final String STARTING_DAEMON = "Starting a "; - private final String BUILD_SUCCESS = "BUILD SUCCESSFUL"; - private final String BUILD_FAILED = "BUILD FAILED"; - private final String SHOW_DEPENDENCIES_START = ">> DEPENDENCIES START <<"; - private final String SHOW_DEPENDENCIES_END = ">> DEPENDENCIES END <<"; - - public static final String ACTION_CREATED = "BUILD_SERVICE_CREATED"; - + private boolean isRunning = false; + + private long buildStart = 0; + private int UNIVERSAL_ID = 0; + + public static final String SUCCESSFUL = "SUCCESSFUL"; + public static final String FAILED = "FAILED"; + public static final int TASK_SHOW_DEPENDENCIES = 0; public static final int TASK_ASSEMBLE_DEBUG = 1; public static final int TASK_ASSEMBLE_RELEASE = 2; @@ -62,351 +83,313 @@ public class IDEService extends Service implements ShellServer.Callback { public static final int TASK_LINT = 9; public static final int TASK_LINT_DEBUG = 10; public static final int TASK_LINT_RELEASE = 11; - - - public IDEService() {} - - public IDEService(String module) { - this(new File(module)); - } - - public IDEService(File module) { - this.module = module; + + + private static final String START_COMMAND = "java " + + "-cp %s" + + " com.itsaky.gradle.tooling.api.GradleApi"; + + static { + RuntimeTypeAdapterFactory descriptorFactory = RuntimeTypeAdapterFactory.of(OperationDescriptor.class) + .registerSubtype(OperationDescriptor.class) + .registerSubtype(TaskDescriptor.class) + .registerSubtype(FinishEventDescriptor.class) + .registerSubtype(ProjectConfigurationDescriptor.class) + .registerSubtype(StartEventDescriptor.class) + .registerSubtype(StatusEventDescriptor.class) + .registerSubtype(TestOutDescriptor.class) + .registerSubtype(TransformDescriptor.class) + .registerSubtype(WorkItemDescriptor.class); + RuntimeTypeAdapterFactory pluginFactory = RuntimeTypeAdapterFactory.of(PluginIdentifierDescription.class) + .registerSubtype(PluginIdentifierDescription.class) + .registerSubtype(BinaryPluginIdentifierDescription.class) + .registerSubtype(ScriptPluginIdentifierDescription.class); + GSON = new GsonBuilder() + .registerTypeAdapterFactory(descriptorFactory) + .registerTypeAdapterFactory(pluginFactory) + .create(); + } + + public IDEService(File rootProject) { + super(GradleConnection.SERVER_HOST, GradleConnection.SERVER_PORT); + this.app = StudioApp.getInstance(); + this.rootProject = rootProject; + + this.shell = app.newShell(this); + this.shell.bgAppend(String.format(START_COMMAND, getClasspaths())); + + isRunning = true; } - public void onGradleUpdated() { - if(shell == null) { - createShell(); - return; - } - if(!isBuilding()) { - createShell(); - } else { - recreateShellOnDone = true; - } - } - - public static IDEService getInstance() { - return instance; - } + private String getClasspaths() { + final List libs = FileUtils.listFilesInDir(Environment.GRADLE_API_LIBS_DIR, true); + StringBuilder sb = new StringBuilder(); + for (int i=0;i < libs.size();i++) { + if (i == libs.size() - 1) { + sb.append(libs.get(i)); + } else { + sb.append(libs.get(i) + File.pathSeparator); + } + } + + return sb.toString(); + } + + @Override + protected void onConnected() { + isConnected = true; + writeMessage(Method.PROJECT_INIT, new InitializeParams(Environment.GRADLE_DIR.getAbsolutePath(), rootProject.getAbsolutePath())); + } + + @Override + protected void onDisconnected() { + LOG.i("Disconnected from server..."); + } + + @Override + protected void onFailedToConnect(Throwable exception) { + LOG.e(ex(exception)); + } + + protected void onResponse(ResponseMessage message) { + if (message.result == null) return; + switch (message.method) { + case Method.API_CONNECTION_PROGRESS : + listener.onConnectionProgressChanged(message.result.getAsString()); + break; + case Method.PROJECT_INITIALIZED : + project = GSON.fromJson(message.result, IDEProject.class); + List applicationModules = project.modules.stream().filter(m -> !m.isLibrary).collect(Collectors.toList()); + if (applicationModules != null && applicationModules.size() > 0) { + // TODO: Handle multiple application modules + main = applicationModules.get(0); + projectInitialized = true; + listener.onProjectInitialized(project, main); + } else { + // TODO: Notify users about no application modules + } + break; + case Method.BUILD_STATUS_CHANGED : + BuildProgressEvent progress = GSON.fromJson(message.result, BuildProgressEvent.class); + listener.onBuildProgress(currentTask, progress); + break; + case Method.BUILD_STD_OUT : + listener.stdOut(currentTask, message.result.getAsString()); + break; + case Method.BUILD_STD_ERROR : + listener.stdErr(currentTask, message.result.getAsString()); + break; + case Method.BUILD_RESULT : + isBuilding = false; + BuildResult result = GSON.fromJson(message.result, BuildResult.class); + int seconds = (int) (System.currentTimeMillis() - buildStart) / 1000; + String msg = getString(com.itsaky.androidide.R.string.msg_final_build_result, result.isSuccess ? SUCCESSFUL : FAILED, currentTask.getName(), seconds); + + if(result.isSuccess) { + listener.stdSuccess(currentTask, getString(com.itsaky.androidide.R.string.msg_final_build_result_prefix)); + listener.stdSuccess(currentTask, msg); + listener.onBuildSuccessful(currentTask, msg); + } else { + listener.stdErr(currentTask, getString(com.itsaky.androidide.R.string.msg_final_build_result_prefix)); + listener.stdErr(currentTask, msg); + listener.onBuildFailed(currentTask, msg); + } + currentTask = null; + break; + case Method.API_ERROR : + ErrorMessage error = GSON.fromJson(message.result, ErrorMessage.class); + listener.stdErr(currentTask, String.format("========== AndroidIDE Error ==========\nError code: %d\nError message: %s\n======================================", error.code, error.message)); + break; + } + } + + @Override + protected void onResponse(String line) { + writeOut(line.trim() + "\n"); + onResponse(GSON.fromJson(line, ResponseMessage.class)); + } + + private BufferedOutputStream fos; + + private void writeOut(String line) { + try { + if (fos == null) + fos = new BufferedOutputStream(new FileOutputStream(new File(FileUtil.getExternalStorageDir(), "ide_xlog/gradle-api-out.txt"), true)); + fos.write(line.getBytes()); + fos.flush(); + } catch (Throwable th) {} + } public IDEService setListener(BuildListener listener) { this.listener = listener; return this; } - - public IDEService setModule(String path) { - return setModule(new File(path)); - } - - public IDEService setModule(File module) { - this.module = module; - this.shell.append(String.format("cd \"%s\"", module.getAbsolutePath())); - return this; - } - + @Override public void output(CharSequence charSequence) { - if(listener == null || charSequence == null) return; - final String line = charSequence - .toString() - .replace(":showDependenciesDebug", ":startCompletionServices") - .trim(); - - boolean shouldOutput = true; - if(line.contains(SHOW_DEPENDENCIES_START)) { - shouldOutput = false; - dependencies = new ArrayList<>(); - outputtingJars = true; - if(listener != null) - listener.onRunTask("> Task :app:startCompletionServices", currentTask); - return; - } else if(line.contains(SHOW_DEPENDENCIES_END)) { - shouldOutput = false; - outputtingJars = false; - if(listener != null - && dependencies != null) { - - listener.onGetDependencies(dependencies); - } - StudioApp.getInstance().getPrefManager().putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false); - return; - } - - if(outputtingJars) { - - JSONObject o = asObjectOrNull(line); - try { - if(o.has("type")) { - String type = o.getString("type"); - if(type.equals("ANDROID_JAR")) { - shouldOutput = false; - Environment.setBootClasspath(new File(o.getString("requested"))); - } - } else if(o.has("file")) { - shouldOutput = false; - String jar = o.getString("file"); - - dependencies.add(jar); - } - } catch (Throwable th) {} - } - if(shouldOutput) { - String text = line.replace(StudioApp.getInstance().getRootDir().getAbsolutePath(), "ANDROIDIDE_HOME"); - listener.appendOutput(currentTask, text); - } - if(charSequence.toString().contains(RUN_TASK)) { - listener.onRunTask(charSequence.toString().trim(), currentTask); - } else - if(charSequence.toString().startsWith(STARTING_DAEMON)) { - listener.onStartingGradleDaemon(currentTask); - } else - if(charSequence.toString().contains(BUILD_SUCCESS)) { - isBuilding = false; - outputtingJars = false; - if(recreateShellOnDone) - createShell(); - if(currentTask != null && currentTask.getTaskID() != TASK_SHOW_DEPENDENCIES) - listener.onBuildSuccessful(charSequence.toString().trim(), currentTask); - } else - if(charSequence.toString().contains(BUILD_FAILED)) { - isBuilding = false; - outputtingJars = false; - if(recreateShellOnDone) - createShell(); - if(currentTask != null) { - if(currentTask.getTaskID() == TASK_SHOW_DEPENDENCIES) - listener.onGetDependenciesFailed(); - else listener.onBuildFailed(line.trim(), currentTask); - } - } + writeOut(charSequence.toString().trim() + "\n"); } - private ArrayList dependenciesMapAsList(HashMap dependencies) { - ArrayList result = new ArrayList<>(); - for(Map.Entry entry : dependencies.entrySet()) { - result.add(Artifact.from(String.format("%s:%s", entry.getKey(), entry.getValue()))); - } - return result; - } - - @Override - public void onCreate() { - super.onCreate(); - this.instance = this; - createShell(); - sendBroadcast(new Intent().setAction(ACTION_CREATED)); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - isRunning = true; - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - - // Delete the tmp directory. This directory is not needed anymore... + public void exit() { + if (mSocket != null) { + CloseUtils.closeIOQuietly(mSocket); + } + // Delete the tmp directory. It is not needed anymore... FileUtils.delete(Environment.TMP_DIR); isRunning = false; - } - - @Override - public IBinder onBind(Intent p1) { - return null; - } - - private void createShell() { - if(shell != null) - shell.exit(); - shell = StudioApp.getInstance().newShell(this); - recreateShellOnDone = false; - } - - private JSONObject asObjectOrNull(String line) { - try { - if(line == null) return null; - line = line.trim(); - if(line.length() <= 0) return null; - return new JSONObject(line); - } catch (Throwable th) { - return null; - } - } - + } + 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 processCommands(String cmd) { - final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); - final StringBuilder sb = new StringBuilder(); - sb.append("gradle "); - sb.append(cmd); - if(prefs.isStracktraceEnabled()) { - sb.append(" --stacktrace"); + + private String getString(@StringRes int id) { + return app.getString(id); + } + + private String getString(@StringRes int id, Object... format) { + return app.getString(id, format); + } + + private List getArguments() { + final PreferenceManager prefs = app.getPrefManager(); + final List args = new ArrayList<>(); + + args.add("--init-script"); + args.add(Environment.INIT_SCRIPT.getAbsolutePath()); + + if (prefs.isStracktraceEnabled()) { + args.add("--stacktrace"); } - if(prefs.isGradleInfoEnabled()) { - sb.append(" --info"); + if (prefs.isGradleInfoEnabled()) { + args.add("--info"); } - if(prefs.isGradleDebugEnabled()) { - sb.append(" --debug"); + if (prefs.isGradleDebugEnabled()) { + args.add("--debug"); } - if(prefs.isGradleScanEnabled()) { - sb.append(" --scan"); + if (prefs.isGradleScanEnabled()) { + args.add("--scan"); } - if(prefs.isGradleWarningEnabled()) { - sb.append(" --warning-mode all"); + if (prefs.isGradleWarningEnabled()) { + args.add("--warning-mode"); + args.add("all"); } - return sb.toString(); + + return args; } - + public void execTask(GradleTask task) { execTask(task, false); } - + public void execTask(GradleTask task, boolean stopIfRunning) { - if(stopIfRunning && isBuilding()) { + if (stopIfRunning && isBuilding()) { stopAllDaemons(); - } else if(isBuilding()) { + } else if (isBuilding()) { return; } - if(listener != null) { + + buildStart = System.currentTimeMillis(); + if (listener != null) { listener.saveFiles(); currentTask = task; - listener.appendOutput(task, getString(R.string.msg_task_begin, currentTime(), task.getName())); - shell.bgAppend(String.format("cd \"%s\"", module.getAbsolutePath())); - shell.bgAppend(processCommands(task.getCommands())); + BuildParams params = new BuildParams(":" + main.name, task.getTasks(), getArguments()); + writeMessage(Method.PROJECT_BUILD, params); isBuilding = true; - listener.showPreparing(); + listener.prepare(); } } - - public void showDependencies() { - execTask(BaseGradleTasks.SHOW_DEPENDENCIES); - } - - public void recreateServices() { - execTask(BaseGradleTasks.SHOW_DEPENDENCIES, true); - } - + public void assembleDebug(boolean installApk) { execTask(((AssembleDebug) BaseGradleTasks.ASSEMBLE_DEBUG).setInstallApk(installApk)); } - + public void assembleRelease() { execTask(BaseGradleTasks.ASSEMBLE_RELEASE); } - + public void build() { execTask(BaseGradleTasks.BUILD); } - + public void bundle() { execTask(BaseGradleTasks.BUNDLE); } - + public void mergeLibAndProjectDex() { execTask(BaseGradleTasks.MERGE_LIB_AND_PROJECT_DEX); } - + public void mergeExtDex() { execTask(BaseGradleTasks.MERGE_EXT_DEX); } - + public void mergeDex() { execTask(BaseGradleTasks.MERGE_DEX); } - + public void clean() { execTask(BaseGradleTasks.CLEAN); } - + public void cleanAndRebuild() { execTask(BaseGradleTasks.CLEAN_BUILD); } - + public void stopAllDaemons() { - if(isBuilding()) { - forceStopDaemons(); - } else { - shell.bgAppend("gradle --stop"); - currentTask = null; - if(listener != null) { - listener.appendOutput(null, getString(R.string.msg_daemons_stopped)); - } - } + app.newShell(null).bgAppend("gradle --stop"); } - + public void lint() { execTask(BaseGradleTasks.LINT); } - + public void lintDebug() { execTask(BaseGradleTasks.LINT_DEBUG); } - + public void lintRelease() { execTask(BaseGradleTasks.LINT_RELEASE); } - - public void forceStopDaemons() { - if(listener != null) { - final long s = System.currentTimeMillis(); - listener.appendOutput(null, getString(R.string.msg_daemons_force_stopping)); - // Wait for a second! Just to let the user read the message! - while(System.currentTimeMillis() - s <= 1000) {} - } - isBuilding = false; - createShell(); - stopAllDaemons(); - } - - public static boolean isRunning() { + + public boolean isRunning() { return isRunning; } - + public boolean isBuilding() { return isBuilding; } - - public String typeString(int type) { - switch (type) { - case TASK_SHOW_DEPENDENCIES: - return ""; - case TASK_ASSEMBLE_DEBUG: - return getResources().getString(R.string.build_debug); - case TASK_ASSEMBLE_RELEASE: - return getResources().getString(R.string.build_release); - case TASK_BUILD: - return getResources().getString(R.string.build); - case TASK_CLEAN_BUILD: - return getResources().getString(R.string.clean_amp_build); - case TASK_CLEAN: - return getResources().getString(R.string.clean_project); - case TASK_BUNDLE : - return getResources().getString(R.string.create_aab); - case TASK_DEX: - return getResources().getString(R.string.compiling); - default: return ""; - } - } - + + public boolean isConnected() { + return isConnected; + } + + public boolean isProjectInitialized() { + return projectInitialized; + } + + protected void writeMessage(String method, Object params) { + Message msg = new Message(); + msg.id = UNIVERSAL_ID++; + msg.method = method; + msg.param = GSON.toJsonTree(params); + write(msg); + } + public static interface BuildListener { - public void onStartingGradleDaemon(GradleTask task); - public void onRunTask(String taskName, GradleTask task); - public void onBuildSuccessful(String msg, GradleTask task); - public void onBuildFailed(String msg, GradleTask task); - public void onGetDependencies(List dependencies); - public void onGetDependenciesFailed(); - public void saveFiles(); - - public void appendOutput(GradleTask task, CharSequence text); - public void showPreparing(); + void onProjectInitialized(IDEProject project, Module main); + void onConnectionProgressChanged(String status); + void onBuildProgress(GradleTask task, BuildProgressEvent event); + void onBuildSuccessful(GradleTask task, String msg); + void onBuildFailed(GradleTask task, String msg); + void saveFiles(); + + void stdOut(GradleTask task, CharSequence text); + void stdErr(GradleTask task, CharSequence text); + void stdSuccess(GradleTask task, CharSequence text); + void prepare(); } } diff --git a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java index ffff98528..f5cb9e0a8 100644 --- a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java +++ b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java @@ -3,19 +3,25 @@ package com.itsaky.androidide.shell; import com.blankj.utilcode.util.CloseUtils; import com.blankj.utilcode.util.ThrowableUtils; import com.itsaky.androidide.utils.Environment; +import com.itsaky.androidide.utils.Logger; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ShellServer extends Thread { - private final List callbacks = Collections.synchronizedList(new ArrayList()); + private final List callbacks = Collections.synchronizedList(new ArrayList()); private BufferedReader output; private Process process; + + private static final Logger LOG = Logger.instance("ShellServer"); public ShellServer(Callback callback, String command, String dirPath) { addCallback(callback); @@ -30,7 +36,8 @@ public class ShellServer extends Thread { stringBuilder.append("\nexport TMPDIR=" + Environment.path(Environment.mkdirIfNotExits(Environment.TMP_DIR))); stringBuilder.append("\nexport JAVA_HOME=" + Environment.path(Environment.JAVA_HOME)); stringBuilder.append("\nexport ANDROID_HOME=" + Environment.path(Environment.ANDROID_HOME)); - stringBuilder.append("\nexport GRADLE_USER_HOME=$HOME/.gradle"); + stringBuilder.append("\nexport GRADLE_INSTALLATION_DIR=" + Environment.path(Environment.GRADLE_DIR)); + stringBuilder.append("\nexport GRADLE_USER_HOME=" + Environment.path(Environment.GRADLE_USER_HOME)); stringBuilder.append("\nexport PATH=$PATH:$HOME/bin"); stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.JAVA_HOME) + "/bin"); stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.GRADLE_DIR) + "/bin"); @@ -66,6 +73,24 @@ public class ShellServer extends Thread { } } } + + public int getPid() { + int pid = -1; + try { + Field f = process.getClass().getDeclaredField("pid"); + f.setAccessible(true); + pid = f.getInt(process); + f.setAccessible(false); + } catch (Throwable ignored) { + try { + Matcher m = Pattern.compile("pid=(\\d+)").matcher(process.toString()); + pid = m.find() ? Integer.parseInt(m.group(1)) : -1; + } catch (Throwable ignored2) { + pid = -1; + } + } + return pid; + } public ShellServer addCallback(Callback callback) { if (callback != null) { diff --git a/app/src/main/java/com/itsaky/androidide/sockets/SocketConnection.java b/app/src/main/java/com/itsaky/androidide/sockets/SocketConnection.java new file mode 100644 index 000000000..792b38d48 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/sockets/SocketConnection.java @@ -0,0 +1,89 @@ +package com.itsaky.androidide.sockets; + +import com.blankj.utilcode.util.CloseUtils; +import com.blankj.utilcode.util.ThrowableUtils; +import com.google.gson.Gson; +import com.itsaky.androidide.utils.Logger; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; + +public abstract class SocketConnection extends Thread { + + private final String host; + private final int port; + protected Socket mSocket; + protected BufferedOutputStream mWriter; + + public static final String LOCALHOST = "localhost"; + + public SocketConnection(int port) { + this(LOCALHOST, port); + } + + public SocketConnection(String host, int port) { + this.host = host; + this.port = port; + } + + protected abstract void onConnected(); + protected abstract void onDisconnected(); + protected abstract void onFailedToConnect(Throwable exception); + protected abstract void onResponse(String line); + + protected void write(Object msg) { + writeString(GSON.toJson(msg)); + } + + protected void writeString(String msg) { + new Thread(() -> { + try { + final String message = msg + "\n"; + mWriter.write(message.getBytes()); + mWriter.flush(); + } catch (Throwable e) { + LOG.e("Error writing to server: " + ex(e)); + } + }).start(); + } + + @Override + public void run() { + try { + mSocket = tryConnect(); + onConnected(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(mSocket.getInputStream())); + String line; + while((line = reader.readLine()) != null) { + onResponse(line); + } + CloseUtils.closeIOQuietly(mWriter, mSocket); + onDisconnected(); + } catch (Throwable e) { + LOG.e("Failed to connect to socket", ex(e)); + onFailedToConnect(e); + } + } + + private Socket tryConnect() { + Socket socket = null; + while(socket == null || !socket.isConnected() || mWriter == null) { + try { + socket = new Socket(host, port); + OutputStream os = socket.getOutputStream(); + if(os != null) + mWriter = new BufferedOutputStream(os); + } catch (Throwable th){} + } + return socket; + } + + protected String ex(Throwable th) { + return ThrowableUtils.getFullStackTrace(th); + } + + protected static final Logger LOG = Logger.instance("SocketConnection"); + protected static Gson GSON = new Gson(); +} diff --git a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java index 186b28b31..ca1208b52 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java @@ -56,5 +56,8 @@ public class SchemeAndroidIDE extends EditorColorScheme { setColor(DIAGNOSTIC_WARNING, 0xffFFEB3B); setColor(DIAGNOSTIC_INFO, 0xff4CAF50); setColor(DIAGNOSTIC_HINT, 0xffffffff); + + setColor(BUILD_OUT_ERROR, 0xfff44336); + setColor(BUILD_OUT_SUCCESS, 0xff4CAF50); } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java b/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java index 7aa75c174..858546f2e 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java @@ -1,11 +1,13 @@ package com.itsaky.androidide.tasks; import java.io.File; +import java.util.List; public interface GradleTask { public String getName(); public String getCommands(); + public List getTasks(); public int getTaskID(); public boolean shouldSaveFiles(); public boolean canOutput(); diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java index 7c6205022..59f127e66 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java @@ -13,7 +13,6 @@ import com.itsaky.androidide.tasks.gradle.dex.MergeLibAndProjectDex; 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.plugintasks.ShowDependencies; public class BaseGradleTasks { @@ -31,7 +30,5 @@ public class BaseGradleTasks { MERGE_DEX = new MergeDex(), MERGE_EXT_DEX = new MergeExtDex(), - MERGE_LIB_AND_PROJECT_DEX = new MergeLibAndProjectDex(), - - SHOW_DEPENDENCIES = new ShowDependencies(); + MERGE_LIB_AND_PROJECT_DEX = new MergeLibAndProjectDex(); } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeDex.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeDex.java index cf490216b..afcfd9a7d 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeDex.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeDex.java @@ -5,6 +5,8 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import java.io.File; +import java.util.List; +import java.util.Arrays; public class MergeDex extends BaseGradleTask { @@ -18,6 +20,11 @@ public class MergeDex extends BaseGradleTask { return "mergeDexDebug"; } + @Override + public List getTasks() { + return Arrays.asList("mergeDexDebug"); + } + @Override public int getTaskID() { return IDEService.TASK_DEX; diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeExtDex.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeExtDex.java index 1ad34d236..0ff9660ab 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeExtDex.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeExtDex.java @@ -5,6 +5,8 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import java.io.File; +import java.util.Arrays; +import java.util.List; public class MergeExtDex extends BaseGradleTask { @@ -17,6 +19,11 @@ public class MergeExtDex extends BaseGradleTask { public String getCommands() { return "mergeExtDexDebug"; } + + @Override + public List getTasks() { + return Arrays.asList("mergeExtDexDebug"); + } @Override public int getTaskID() { diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeLibAndProjectDex.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeLibAndProjectDex.java index 1bbfce59c..dc35b7a9b 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeLibAndProjectDex.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/dex/MergeLibAndProjectDex.java @@ -5,6 +5,8 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import java.io.File; +import java.util.Arrays; +import java.util.List; public class MergeLibAndProjectDex extends BaseGradleTask { @@ -17,6 +19,11 @@ public class MergeLibAndProjectDex extends BaseGradleTask { public String getCommands() { return "mergeLibDexDebug mergeProjectDexDebug"; } + + @Override + public List getTasks() { + return Arrays.asList("mergeLibDexDebug", "mergeProjectDexDebug"); + } @Override public int getTaskID() { diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java index dc70df754..d59c97104 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java @@ -5,6 +5,8 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import java.io.File; +import java.util.Arrays; +import java.util.List; public class Lint extends BaseGradleTask { @@ -17,6 +19,11 @@ public class Lint extends BaseGradleTask { public String getCommands() { return "lint"; } + + @Override + public List getTasks() { + return Arrays.asList("lint"); + } @Override public int getTaskID() { diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java index 2a720ea46..2e9671c59 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java @@ -5,6 +5,8 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import java.io.File; +import java.util.Arrays; +import java.util.List; public class LintDebug extends BaseGradleTask { @@ -17,6 +19,11 @@ public class LintDebug extends BaseGradleTask { public String getCommands() { return "lintDebug"; } + + @Override + public List getTasks() { + return Arrays.asList("lintDebug"); + } @Override public int getTaskID() { diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java index a49e01436..6ac5386de 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java @@ -5,6 +5,8 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import java.io.File; +import java.util.Arrays; +import java.util.List; public class LintRelease extends BaseGradleTask { @@ -17,6 +19,11 @@ public class LintRelease extends BaseGradleTask { public String getCommands() { return "lintRelease"; } + + @Override + public List getTasks() { + return Arrays.asList("lintRelease"); + } @Override public int getTaskID() { diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/plugintasks/ShowDependencies.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/plugintasks/ShowDependencies.java deleted file mode 100644 index e321ae2c8..000000000 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/plugintasks/ShowDependencies.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.itsaky.androidide.tasks.gradle.plugintasks; - -import com.itsaky.androidide.app.StudioApp; -import com.itsaky.androidide.services.IDEService; -import com.itsaky.androidide.tasks.BaseGradleTask; -import java.io.File; -import com.itsaky.androidide.utils.PreferenceManager; - -public class ShowDependencies extends BaseGradleTask { - - @Override - public String getName() { - return "Start Completion Services"; - } - - @Override - public String getCommands() { - return (StudioApp.getInstance().getPrefManager().getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true) ? "assembleDebug " : "") + "showDependenciesDebug"; - } - - @Override - public int getTaskID() { - return IDEService.TASK_SHOW_DEPENDENCIES; - } - - @Override - public boolean canOutput() { - return true; - } - - @Override - public boolean buildsApk() { - return false; - } - - @Override - public File getApk(String buildFolder, String moduleName) { - return null; - } - - @Override - public Type getType() { - return Type.ANDROIDIDE_PLUGIN; - } -} diff --git a/app/src/main/java/com/itsaky/androidide/utils/Environment.java b/app/src/main/java/com/itsaky/androidide/utils/Environment.java index 3a168d179..7b8d5aa48 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/Environment.java +++ b/app/src/main/java/com/itsaky/androidide/utils/Environment.java @@ -23,13 +23,17 @@ public final class Environment { public static File GRADLE_DIR; public static File GRADLE_PROPS; - public static File GRADLE_PROPS_DIR; + public static File GRADLE_USER_HOME; public static File GRADLE_PLUGIN; public static File BOOTCLASSPATH; public static File CACHES_DIR; public static File AAPT2; + public static File INIT_SCRIPT; public static File LIBHOOKSO; + + public static File GRADLE_API_DIR; + public static File GRADLE_API_LIBS_DIR; public static String ANDROID_SDK_FOLDER_NAME = "android-sdk"; public static String GRADLE_FOLDER_NAME = null; @@ -61,25 +65,24 @@ public final class Environment { HOME = app.getRootDir(); JAVA_HOME = getJavaHome(); ANDROID_HOME = new File(HOME, ANDROID_SDK_FOLDER_NAME); - JLS_HOME = mkdirIfNotExits(new File(HOME, "jls")); + JLS_HOME = mkdirIfNotExits(new File(HOME.getParentFile(), "jls")); TMP_DIR = mkdirIfNotExits(new File(HOME, "tmp")); GRADLE_DIR = new File(HOME, GRADLE_FOLDER_NAME); USRDIR = mkdirIfNotExits(new File(HOME.getParentFile(), "usr")); LIBDIR = mkdirIfNotExits(new File(USRDIR, "lib")); BINDIR = mkdirIfNotExits(new File(USRDIR, "bin")); + GRADLE_API_DIR = mkdirIfNotExits(new File(HOME.getParentFile(), "gradle-api")); + GRADLE_API_LIBS_DIR = new File(GRADLE_API_DIR, "libs"); + + INIT_SCRIPT = new File(mkdirIfNotExits(new File(HOME.getParentFile(), "init")), "androidide.init.gradle"); LIBHOOKSO = new File(LIBDIR, "libhook.so"); CACHES_DIR = new File(HOME, "ide-caches"); BOOTCLASSPATH = new File(""); AAPT2 = new File(files21, "com.android.tools.build/aapt2/4.0.1-6197926"); GRADLE_PLUGIN = new File(files21, "com.itsaky.androidide/gradle"); - GRADLE_PROPS_DIR = new File(HOME, ".gradle"); - GRADLE_PROPS = new File(GRADLE_PROPS_DIR, "gradle.properties"); - } - - public static void updateGradleDir() { - GRADLE_DIR = new File(HOME, GRADLE_FOLDER_NAME); - StudioApp.getInstance().onGradleUpdated(); + GRADLE_USER_HOME = new File(HOME, ".gradle"); + GRADLE_PROPS = new File(GRADLE_USER_HOME, "gradle.properties"); } public static File getGradleDir() { diff --git a/app/src/main/java/com/itsaky/androidide/utils/PreferenceManager.java b/app/src/main/java/com/itsaky/androidide/utils/PreferenceManager.java deleted file mode 100644 index e0a1b4b5b..000000000 --- a/app/src/main/java/com/itsaky/androidide/utils/PreferenceManager.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.itsaky.androidide.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import java.io.File; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; -import com.itsaky.androidide.app.StudioApp; -import java.io.FileReader; - -public class PreferenceManager { - - private SharedPreferences prefs; - private SharedPreferences.Editor editor; - - public static final int LOGSENDER_LATEST_VERSION = 102; - - public static final String KEY_FRAMEWORK_DOWNLOADED = "framework_downloaded"; - public static final String KEY_FRAMEWORK_INSTALLED = "framework_installed"; - public static final String KEY_IS_FIRST_PROJECT_BUILD = "project_isFirstBuild"; - - public static final String KEY_EDITORFLAG_WS_LEADING = "idepref_editor_wsLeading"; - public static final String KEY_EDITORFLAG_WS_TRAILING = "idepref_editor_wsTrailing"; - public static final String KEY_EDITORFLAG_WS_INNER = "idepref_editor_wsInner"; - public static final String KEY_EDITORFLAG_WS_EMPTY_LINE = "idepref_editor_wsEmptyLine"; - public static final String KEY_EDITORFLAG_LINE_BREAK = "idepref_editor_lineBreak"; - public static final String KEY_EDITOR_DRAW_HEX = "idepref_editor_drawHexColors"; - - public static final String KEY_GRADLE_CURRENT_DIR = "idepref_customGradle_currentFolder"; - public static final String KEY_GRADLE_FILENAMES = "idepref_customGradle_folderNames"; - public static final String KEY_GRADLECMD_STACKTRACRE = "idepref_gradleCmd_stacktrace"; - public static final String KEY_GRADLECMD_DEBUG = "idepref_gradleCmd_debug"; - public static final String KEY_GRADLECMD_SCAN = "idepref_gradleCmd_scan"; - public static final String KEY_GRADLECMD_INFO = "idepref_gradleCmd_info"; - public static final String KEY_GRADLECMD_WARNINGMODE = "idepref_gradleCmd_warningMode"; - - public static final String KEY_LOGSENDER_CURRENT_VERSION = "logsender_currentVersion"; - - @SuppressLint("CommitPrefEdits") - public PreferenceManager(Context ctx) { - this.prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(ctx); - this.editor = prefs.edit(); - } - - public PreferenceManager putInt(String key, int val) { - editor.putInt(key, val).apply(); - return this; - } - - public int getInt(String key) { - return prefs.getInt(key, 0); - } - - public int getInt(String key, int def) { - return prefs.getInt(key, def); - } - - public PreferenceManager putFloat(String key, float val) { - editor.putFloat(key, val).apply(); - return this; - } - - public float getFloat(String key) { - return prefs.getFloat(key, 0f); - } - - public float getFloat(String key, float def) { - return prefs.getFloat(key, def); - } - - public PreferenceManager putBoolean(String key, boolean value) { - editor.putBoolean(key, value).apply(); - return this; - } - - public boolean getBoolean(String key) { - return getBoolean(key, false); - } - - public boolean getBoolean(String key, boolean defaultValue) { - return prefs.getBoolean(key, defaultValue); - } - - public PreferenceManager putString(String key, String value) { - editor.putString(key, value).apply(); - return this; - } - - public String getString(String key) { - return prefs.getString(key, null); - } - - public String getString(String key, String defaultValue) { - return prefs.getString(key, defaultValue); - } - - public boolean isFrameworkDownloaded() { - return getBoolean(KEY_FRAMEWORK_DOWNLOADED); - } - - public boolean isFrameworkInstalled() { - return isFrameworkDownloaded() && getBoolean(KEY_FRAMEWORK_INSTALLED); - } - - public boolean isStracktraceEnabled() { - return getBoolean(KEY_GRADLECMD_STACKTRACRE); - } - - public boolean isGradleInfoEnabled() { - return getBoolean(KEY_GRADLECMD_INFO, true); - } - - public boolean isGradleDebugEnabled() { - return getBoolean(KEY_GRADLECMD_DEBUG); - } - - public boolean isGradleScanEnabled() { - return getBoolean(KEY_GRADLECMD_SCAN); - } - - public boolean isGradleWarningEnabled() { - return getBoolean(KEY_GRADLECMD_WARNINGMODE); - } - - public PreferenceManager setGradleStacktraceEnabled(boolean enabled) { - return putBoolean(KEY_GRADLECMD_STACKTRACRE, enabled); - } - - public PreferenceManager setGradleInfoEnabled(boolean enabled) { - return putBoolean(KEY_GRADLECMD_INFO, enabled); - } - - public PreferenceManager setGradleDebugEnabled(boolean enabled) { - return putBoolean(KEY_GRADLECMD_DEBUG, enabled); - } - - public PreferenceManager setGradleScanEnabled(boolean enabled) { - return putBoolean(KEY_GRADLECMD_SCAN, enabled); - } - - public PreferenceManager setGradleWarningEnabled(boolean enabled) { - return putBoolean(KEY_GRADLECMD_WARNINGMODE, enabled); - } - - public PreferenceManager addGradleFolder(String name) { - Set folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet()); - if(folders == null) { - folders = new HashSet(); - } - if(!folders.contains(name)) { - folders.add(name); - } - editor.putStringSet(KEY_GRADLE_FILENAMES, folders).apply(); - return this; - } - - public Set getGradleFolderNames() { - Set folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet()); - if(folders != null && folders.size() > 0) { - return folders; - } else { - return addGradleFolder(Environment.DEFAULT_GRADLE_FOLDER).getGradleFolderNames(); - } - } - - public PreferenceManager setCurentGradleFolderName(String name) { - return addGradleFolder(name).putString(KEY_GRADLE_CURRENT_DIR, name); - } - - public String getCurrentGradleFolderName() { - return getString(KEY_GRADLE_CURRENT_DIR, Environment.DEFAULT_GRADLE_FOLDER); - } -} diff --git a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java index 0ba1a07f9..846c668c4 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java +++ b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java @@ -200,7 +200,7 @@ public class ProjectWriter { } } if(success) - notifySuccess(); + notifySuccess(projectDir); } else { notifyFailed(instance.getString(R.string.failed_create_project_dir)); } @@ -223,10 +223,10 @@ public class ProjectWriter { }); } - private static void notifySuccess() { + private static void notifySuccess(File root) { ThreadUtils.runOnUiThread(() -> { if(callback != null) - callback.onSuccess(); + callback.onSuccess(root); }); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java b/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java new file mode 100644 index 000000000..549983173 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java @@ -0,0 +1,24 @@ +package com.itsaky.androidide.utils; + +import android.graphics.Color; +import android.view.View; +import androidx.core.content.ContextCompat; +import com.google.android.material.transition.MaterialArcMotion; +import com.google.android.material.transition.MaterialContainerTransform; + +public class TransformUtils { + + public static MaterialContainerTransform createContainerTransformFor(View start, View end, View drawingView) { + MaterialContainerTransform transform = new MaterialContainerTransform(); + transform.setStartView(start); + transform.setEndView(end); + transform.addTarget(end); + transform.setDrawingViewId(drawingView.getId()); + transform.setAllContainerColors(ContextCompat.getColor(drawingView.getContext(), com.itsaky.androidide.R.color.primaryDarkColor)); + transform.setElevationShadowEnabled(true); + transform.setPathMotion(new MaterialArcMotion()); + transform.setScrimColor(Color.TRANSPARENT); + return transform; + } + +} diff --git a/app/src/main/java/io/github/rosemoe/editor/text/Content.java b/app/src/main/java/io/github/rosemoe/editor/text/Content.java index aea033856..65cbda2cc 100644 --- a/app/src/main/java/io/github/rosemoe/editor/text/Content.java +++ b/app/src/main/java/io/github/rosemoe/editor/text/Content.java @@ -254,14 +254,15 @@ public class Content implements CharSequence { * * @param text Text to append */ - public void append(CharSequence text) { + public int append(CharSequence text) { if(getLineCount() <= 0) - return; + return 0; final int line = getLineCount() - 1; int col = getColumnCount(line); if(col < 0) col = 0; insert(line, col, text); + return line; } /** diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java index f72f736f8..d34eafe79 100644 --- a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java @@ -778,6 +778,10 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca } invalidate(); } + + public EditorLanguage getEditorLanguage() { + return mLanguage; + } /** * Getter diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java index d43b43667..ccd61e0b8 100644 --- a/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java @@ -61,6 +61,9 @@ public class EditorColorScheme { public static final int LOG_INFO = 32; public static final int LOG_ERROR = 33; public static final int LOG_WARNING = 34; + + public static final int BUILD_OUT_ERROR = 53; + public static final int BUILD_OUT_SUCCESS = 54; //-----------------Highlight colors----------- @@ -126,7 +129,7 @@ public class EditorColorScheme { /** * Max pre-defined color id */ - protected static final int END_COLOR_ID = 52; + protected static final int END_COLOR_ID = 54; /** * Real color saver */ @@ -201,6 +204,8 @@ public class EditorColorScheme { case OPERATOR: color = 0xFF0066D6; break; + case BUILD_OUT_SUCCESS : + case BUILD_OUT_ERROR : case TEXT_NORMAL: color = 0xFF333333; break; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index fbd511705..55a4b1cf7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -17,20 +17,7 @@ - - - + + + - + - + + + + +