Enhanced working of Gradle Tooling API

- Updated Tooling API
- Removed usage of AndroidIDE's Gradle Plugin
- Removed usage of shared libraries
- Recompiled JDK statically
- Added androidide-cleaner to remove unsupported flags in ELF files
This commit is contained in:
Akash Yadav
2021-08-24 12:15:46 +05:30
parent 1144f6c600
commit a8d79291e3
58 changed files with 1727 additions and 1331 deletions
Binary file not shown.
Binary file not shown.
-4
View File
@@ -49,10 +49,6 @@
android:name=".AboutActivity"
android:configChanges="orientation|screenSize|uiMode">
</activity>
<service
android:name="com.itsaky.androidide.services.IDEService"
android:exported="false">
</service>
<service
android:name=".services.MessagingService"
android:exported="false">
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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);
@@ -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<OperationHierarchyNode> operationTree = new ArrayList<>();
private final Map<File, List<Diagnostic>> diagnostics = new HashMap<>();
private final Stack<Message> 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<OperationDescriptor> parents = new ArrayList<>();
while(parent != null) {
parents.add(0, parent);
parent = parent.parent;
}
List<OperationHierarchyNode> list = new ArrayList<OperationHierarchyNode>(operationTree);
OperationHierarchyNode direct = null;
final int last = parents.size() - 1;
for(int i=0;i<parents.size();i++) {
OperationDescriptor desc = parents.get(i);
if(i == last) {
if(direct != null)
direct.children.add(node);
else list.add(node);
} else {
desc = parents.get(i);
direct = findNodeByLabelInList(desc.name, list);
if(direct != null)
list = new ArrayList<OperationHierarchyNode>(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<OperationHierarchyNode> list) {
if(list == null || label == null) return null;
for(int i=0;i<list.size();i++) {
OperationHierarchyNode node = list.get(i);
if(node != null && node.label.equals(label))
return node;
}
return null;
}
private OperationHierarchyNode createNode(BuildProgressEvent event) {
OperationHierarchyNode node = new OperationHierarchyNode();
node.label = event.descriptor.name;
node.isFinished = isFinished(event);
node.isStarted = isStarted(event);
// A BuildResult is available in the descriptor only if the event has finished executing
node.isSuccessful = node.isFinished && event.descriptor.result != null && (event.descriptor.result.isSuccess || event.descriptor.result.isSkipped);
return node;
}
private boolean isFinished(BuildProgressEvent event) {
return event.type == ProgressType.FINISH
|| event.type == ProgressType.PROJECT_CONFUGURATION_FINISH
|| event.type == ProgressType.TASK_FINISH
|| event.type == ProgressType.TEST_FINISH
|| event.type == ProgressType.TRANSFORM_FINISH
|| event.type == ProgressType.WORK_ITEM_FINISH;
}
private boolean isStarted(BuildProgressEvent event) {
return event.type == ProgressType.START
|| event.type == ProgressType.PROJECT_CONFUGURATION_START
|| event.type == ProgressType.TASK_START
|| event.type == ProgressType.TEST_START
|| event.type == ProgressType.TRANSFORM_START
|| event.type == ProgressType.WORK_ITEM_START;
}
@Override
public void onBuildSuccessful(String msg, GradleTask task) {
public void onBuildSuccessful(GradleTask task, String msg) {
invalidateOptionsMenu();
if(task == null) return;
if(task.canOutput())
setStatus(msg);
@@ -652,12 +767,13 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
recreateCompletionServices = false;
invalidateOptionsMenu();
LOG.i("Operation tree: " + new GsonBuilder().setPrettyPrinting().create().toJson(operationTree));
}
@Override
public void onBuildFailed(String msg, GradleTask task) {
public void onBuildFailed(GradleTask task, String msg) {
invalidateOptionsMenu();
if(task == null) return;
if(task.canOutput()) {
setStatus(msg);
@@ -666,30 +782,15 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
showBuildResult();
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
recreateCompletionServices = false;
invalidateOptionsMenu();
}
@Override
public void onGetDependencies(List<String> 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<String>(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");
}
@@ -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<ProjectTemplate> mTemplates = new ArrayList<ProjectTemplate>();
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<String> 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<AndroidProject> {
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<Void> c;
public OnEndTransitionListener(Callable<Void> 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) {
}
}
}
@@ -45,7 +45,7 @@ public class FileTreeViewHolder extends TreeNode.BaseNodeViewHolder<File> {
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);
@@ -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);
@@ -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<File> 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);
}
}
}
@@ -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;
@@ -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;
@@ -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<ProjectTemplate> mTemplates = new ArrayList<ProjectTemplate>();
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<Void> c;
public OnEndTransitionListener(Callable<Void> 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();
}
}
@@ -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 {
@@ -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 {
@@ -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;
@@ -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);
}
@@ -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<Integer> errorMap = new ArrayList<>();
private final List<Integer> 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++;
}
}
}
}
@@ -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 {
@@ -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 = "<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<String> folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet<String>());
if(folders == null) {
folders = new HashSet<String>();
}
if(!folders.contains(name)) {
folders.add(name);
}
editor.putStringSet(KEY_GRADLE_FILENAMES, folders).apply();
return this;
}
public Set<String> getGradleFolderNames() {
Set<String> folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet<String>());
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);
}
}
@@ -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<File> 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<Project> {"
+ "\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");
}
@@ -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<File, Integer> map = new HashMap<>();
private static final HashMap<File, Integer> 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);
@@ -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;
@@ -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";
}
@@ -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<String> 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<OperationDescriptor> 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<PluginIdentifierDescription> 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<File> 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<Module> 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<Artifact> dependenciesMapAsList(HashMap<String, String> dependencies) {
ArrayList<Artifact> result = new ArrayList<>();
for(Map.Entry<String, String> 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<String> getArguments() {
final PreferenceManager prefs = app.getPrefManager();
final List<String> 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<String> 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();
}
}
@@ -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<Callback> callbacks = Collections.synchronizedList(new ArrayList());
private final List<Callback> callbacks = Collections.synchronizedList(new ArrayList<Callback>());
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) {
@@ -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();
}
@@ -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);
}
}
@@ -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<String> getTasks();
public int getTaskID();
public boolean shouldSaveFiles();
public boolean canOutput();
@@ -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();
}
@@ -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<String> getTasks() {
return Arrays.asList("mergeDexDebug");
}
@Override
public int getTaskID() {
return IDEService.TASK_DEX;
@@ -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<String> getTasks() {
return Arrays.asList("mergeExtDexDebug");
}
@Override
public int getTaskID() {
@@ -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<String> getTasks() {
return Arrays.asList("mergeLibDexDebug", "mergeProjectDexDebug");
}
@Override
public int getTaskID() {
@@ -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<String> getTasks() {
return Arrays.asList("lint");
}
@Override
public int getTaskID() {
@@ -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<String> getTasks() {
return Arrays.asList("lintDebug");
}
@Override
public int getTaskID() {
@@ -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<String> getTasks() {
return Arrays.asList("lintRelease");
}
@Override
public int getTaskID() {
@@ -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;
}
}
@@ -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() {
@@ -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<String> folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet<String>());
if(folders == null) {
folders = new HashSet<String>();
}
if(!folders.contains(name)) {
folders.add(name);
}
editor.putStringSet(KEY_GRADLE_FILENAMES, folders).apply();
return this;
}
public Set<String> getGradleFolderNames() {
Set<String> folders = prefs.getStringSet(KEY_GRADLE_FILENAMES, new HashSet<String>());
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);
}
}
@@ -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);
});
}
@@ -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;
}
}
@@ -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;
}
/**
@@ -778,6 +778,10 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
}
invalidate();
}
public EditorLanguage getEditorLanguage() {
return mLanguage;
}
/**
* Getter
@@ -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;
+136 -31
View File
@@ -17,20 +17,7 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
app:srcCompat="@drawable/ic_settings"
android:id="@+id/settings"
android:scaleType="center"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_height="48dp"
android:layout_width="48dp"
android:layout_margin="8dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"/>
<TextView
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -39,31 +26,149 @@
android:gravity="center"
android:textColor="@color/primaryTextColor"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/activitymainTextView1"
android:id="@+id/toolbar_header"
android:textSize="24sp"
android:layout_centerVertical="true"
android:layout_centerInParent="true"
android:clickable="false"
android:focusable="false"/>
</RelativeLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/real_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<com.itsaky.androidide.views.VerticalViewPager
android:id="@+id/main_viewPager"
android:layout_height="match_parent"
android:layout_width="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
<ImageView
android:src="@mipmap/ic_launcher"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.25"
android:id="@+id/main_image"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/main_moveDown"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="16dp"
app:fabSize="normal"
app:srcCompat="@drawable/ic_chevron_down"
app:tint="@color/primaryTextColor"
app:backgroundTint="@color/secondaryColor"
android:layout_gravity="bottom|end"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/get_started"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold"
android:textColor="@color/primaryTextColor"
android:layout_margin="4dp"
app:layout_constraintTop_toBottomOf="@id/main_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/getStarted"/>
<TextView
android:id="@+id/greetingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start_your_new_awesome_project"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/primaryTextColorLight"
android:layout_marginBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/getStarted"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/create_project"
android:layout_height="56dp"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:textStyle="bold"
android:textSize="14sp"
android:text="@string/msg_create_new_project"
android:singleLine="true"
android:ellipsize="end"
android:textColor="@color/primaryTextColor"
android:elevation="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/greetingText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:rippleColor="@color/rippleHighlightColor"
app:icon="@drawable/ic_add"
app:iconPadding="8dp"
app:iconTint="@color/primaryTextColor"
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"/>
<Button
android:id="@+id/open_project"
android:layout_height="56dp"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:textStyle="bold"
android:textSize="14sp"
android:text="@string/msg_open_existing_project"
android:singleLine="true"
android:ellipsize="end"
android:textColor="@color/primaryTextColor"
android:elevation="0dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/create_project"
app:layout_constraintStart_toStartOf="@id/create_project"
app:rippleColor="@color/rippleHighlightColor"
app:icon="@drawable/ic_folder"
app:iconPadding="8dp"
app:iconTint="@color/primaryTextColor"
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"/>
<Button
android:id="@+id/goto_preferences"
android:layout_height="56dp"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:textStyle="bold"
android:textSize="14sp"
android:text="@string/msg_preferences"
android:singleLine="true"
android:ellipsize="end"
android:textColor="@color/primaryTextColor"
android:elevation="0dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/open_project"
app:layout_constraintStart_toStartOf="@id/create_project"
app:rippleColor="@color/rippleHighlightColor"
app:icon="@drawable/ic_settings"
app:iconPadding="8dp"
app:iconTint="@color/primaryTextColor"
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"/>
<com.google.android.material.card.MaterialCardView
android:id="@+id/createNewCard"
android:layout_height="0dp"
android:layout_width="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:cardBackgroundColor="@color/primaryLightColor"
app:cardElevation="8dp"
style="@style/CreateProjectCardView">
<include
android:id="@+id/createLayout"
layout="@layout/layout_create_project"/>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
-90
View File
@@ -1,90 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:src="@mipmap/ic_launcher"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/main_image"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/get_started"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold"
android:textColor="@color/primaryTextColor"
android:layout_margin="4dp"
app:layout_constraintTop_toBottomOf="@id/main_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/getStarted"/>
<TextView
android:id="@+id/greetingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start_your_new_awesome_project"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/primaryTextColorLight"
android:layout_marginBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/getStarted"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createNew"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="16dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/greetingText"
app:fabSize="normal"
app:srcCompat="@drawable/ic_add"
app:tint="@color/primaryTextColor"
app:backgroundTint="@color/secondaryColor"/>
<com.google.android.material.card.MaterialCardView
android:id="@+id/createNewCard"
android:layout_height="0dp"
android:layout_width="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:cardBackgroundColor="@color/primaryLightColor"
app:cardElevation="8dp"
style="@style/CreateProjectCardView">
<include
android:id="@+id/createLayout"
layout="@layout/layout_create_project"/>
</com.google.android.material.card.MaterialCardView>
<TextView
android:gravity="center"
android:layout_marginBottom="16dp"
android:layout_width="match_parent"
android:text="@string/swipe_up_to_see_your_projects"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/primaryTextColorLight"
android:drawablePadding="16dp"
android:layout_height="wrap_content"
app:drawableTopCompat="@drawable/ic_chevron_up"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.9"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -2,6 +2,15 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group
android:title="@string/group_project"
android:id="@+id/editornav_group_project">
<item
android:id="@+id/editornav_close_project"
android:icon="@drawable/ic_close"
android:title="@string/title_close_project"/>
</group>
<group
android:title="@string/discuss_amp_help"
android:id="@+id/editornav_group_discuss">
+12
View File
@@ -1,4 +1,12 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="group_project">Project</string>
<string name="title_close_project">Close this project</string>
<string name="msg_opened_project_does_not_exist">Last opened project doesn\'t exist!</string>
<string name="msg_create_new_project">Create new project</string>
<string name="msg_open_existing_project">Open existing project</string>
<string name="msg_preferences">Preferences</string>
<string name="msg_opening_project">Opening project&#8230;</string>
<string name="msg_invalid_project">Picked directory is not an Android project</string>
<string name="menu_navigate_definition">Go to definition</string>
<string name="menu_navigate_references">Find all references</string>
<string name="menu_navigate">Code</string>
@@ -17,9 +25,13 @@
<string name="msg_empty_search_query">Please enter text</string>
<string name="msg_select_search_modules">Select modules to search</string>
<string name="msg_searching_project">Finding in project, please wait&#8230;</string>
<string name="msg_final_build_result_prefix">========== FINAL BUILD RESULT ==========</string>
<string name="msg_final_build_result">BUILD %s for task %s in %d seconds</string>
<string name="view_search_results">Search results</string>
<string name="msg_no_definition">Definition not found</string>
<string name="msg_no_references">No references found</string>
<string name="title_confirm_project_close">Close project</string>
<string name="msg_confirm_project_close">Are you sure you want to close this project? All started Gradle daemons will be stopped after this project is closed.</string>
<string name="license_jdk">AndroidIDE uses a modified build of the JDK which is licensed under the GNU General Public License v2.0. The source code and the license of the JDK can be found in the JDK itself or at : </string>
<string name="title_developers">Developers</string>
<string name="msg_developers">\u2022 Akash Yadav\n\u2022 Marvin Stelter</string>
+2 -2
View File
@@ -84,11 +84,11 @@
<item name="fontFamily">@font/josefin_sans</item>
</style>
<style name="AppTheme.FilePicker" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/primaryColor</item>
<item name="colorPrimary">@color/primaryDarkColor</item>
<item name="colorPrimaryDark">@color/primaryDarkColor</item>
<item name="colorAccent">@color/secondaryColor</item>
<item name="android:statusBarColor">@color/secondaryColor</item>
<item name="android:statusBarColor">@color/primaryDarkColor</item>
<item name="android:windowBackground">@color/content_background</item>
<item name="android:navigationBarColor">@color/primaryDarkColor</item>