Provide Android framework resources as suggestions in UI Designer

This commit is contained in:
Akash Yadav
2022-03-13 13:20:25 +05:30
parent 8f59e6a0a3
commit 54980ce763
24 changed files with 1028 additions and 893 deletions
+1
View File
@@ -50,6 +50,7 @@
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_attr_editor_sheet_item.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_attr_value_editor.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_attr_value_editor_enum.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_boolean_attr_editor.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_code_editor.xml" value="0.20185185185185187" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_completion_item.xml" value="0.23854166666666668" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_crash_report.xml" value="0.25" />
Binary file not shown.
@@ -20,4 +20,6 @@ package com.itsaky.androidide.fragments.attr;
/**
* @author Akash Yadav
*/
public class BooleanEditor extends BaseValueEditorFragment {}
public class BooleanEditor extends BaseValueEditorFragment {
}
@@ -34,6 +34,7 @@ import com.itsaky.androidide.adapters.AttrValueCompletionAdapter;
import com.itsaky.androidide.databinding.LayoutDimensionAttrEditorBinding;
import com.itsaky.androidide.utils.Logger;
import com.itsaky.androidide.utils.TextWatcherAdapter;
import com.itsaky.inflater.values.FrameworkValues;
import com.itsaky.inflater.values.ValuesTableFactory;
import java.util.ArrayList;
@@ -45,124 +46,129 @@ import java.util.stream.Collectors;
* @author Akash Yadav
*/
public class DimensionEditor extends BaseValueEditorFragment {
private static final Logger LOG = Logger.instance("DimensionEditor");
private static final Logger LOG = Logger.instance ("DimensionEditor");
public TextWatcherAdapter dimensionInputWatcher;
public TextWatcherAdapter dimensionResInputWatcher;
private LayoutDimensionAttrEditorBinding binding;
private String[] dimensionUnits;
@Nullable
@Override
public View onCreateView(
public View onCreateView (
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
this.binding = LayoutDimensionAttrEditorBinding.inflate(inflater, container, false);
return this.binding.getRoot();
this.binding = LayoutDimensionAttrEditorBinding.inflate (inflater, container, false);
return this.binding.getRoot ();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
final var dimensionInput =
Objects.requireNonNull(this.binding.dimensionInput.getEditText());
Objects.requireNonNull (this.binding.dimensionInput.getEditText ());
if (dimensionInputWatcher != null) {
dimensionInput.removeTextChangedListener(dimensionInputWatcher);
dimensionInput.removeTextChangedListener (dimensionInputWatcher);
}
dimensionInputWatcher =
new TextWatcherAdapter() {
new TextWatcherAdapter () {
@Override
public void afterTextChanged(@NonNull Editable s) {
final var text = s.toString().trim();
public void afterTextChanged (@NonNull Editable s) {
final var text = s.toString ().trim ();
final var unit =
getDimensionUnits()[binding.unitSelector.getSelectedItemPosition()];
getDimensionUnits ()[binding.unitSelector.getSelectedItemPosition ()];
// This will call CommonParseUtils#parseDimension
notifyValueChanged(text.concat(unit));
notifyValueChanged (text.concat (unit));
}
};
dimensionInput.addTextChangedListener(dimensionInputWatcher);
dimensionInput.addTextChangedListener (dimensionInputWatcher);
final var dimensionResInput =
(MaterialAutoCompleteTextView)
Objects.requireNonNull(binding.dimensionResInput.getEditText());
Objects.requireNonNull (binding.dimensionResInput.getEditText ());
if (dimensionResInputWatcher != null) {
dimensionResInput.removeTextChangedListener(dimensionResInputWatcher);
dimensionResInput.removeTextChangedListener (dimensionResInputWatcher);
}
dimensionResInputWatcher =
new TextWatcherAdapter() {
new TextWatcherAdapter () {
@Override
public void afterTextChanged(@NonNull Editable s) {
final var text = s.toString().trim();
if (TextUtils.isEmpty(text)) {
public void afterTextChanged (@NonNull Editable s) {
final var text = s.toString ().trim ();
if (TextUtils.isEmpty (text)) {
return;
}
notifyValueChanged(text);
notifyValueChanged (text);
}
};
dimensionResInput.addTextChangedListener(dimensionResInputWatcher);
dimensionResInput.addTextChangedListener (dimensionResInputWatcher);
final var future =
CompletableFuture.supplyAsync(
CompletableFuture.supplyAsync (
() -> {
final var list = new ArrayList<String>();
final var tables = ValuesTableFactory.getAllTables();
for (var entry : tables.entrySet()) {
final var dimens = entry.getValue().getTable("dimen");
final var list = new ArrayList<String> ();
final var tables = ValuesTableFactory.getAllTables ();
for (var entry : tables.entrySet ()) {
final var dimens = entry.getValue ().getTable ("dimen");
if (dimens != null) {
list.addAll(
dimens.keySet().stream()
.map("@dimen/"::concat)
.collect(Collectors.toSet()));
list.addAll (
dimens.keySet ().stream ()
.map ("@dimen/"::concat)
.collect (Collectors.toSet ()));
}
}
list.addAll (FrameworkValues.listDimens ()
.stream ()
.map ("@android:dimen/"::concat)
.collect (Collectors.toList ())
);
return list;
});
future.whenComplete(
future.whenComplete (
(list, throwable) -> {
if (list == null || list.isEmpty()) {
LOG.error("No completion items found");
if (list == null || list.isEmpty ()) {
LOG.error ("No completion items found");
return;
}
LOG.debug("Found", list.size(), "dimension resources for completion");
ThreadUtils.runOnUiThread(
LOG.debug ("Found", list.size (), "dimension resources for completion");
ThreadUtils.runOnUiThread (
() -> {
final var adapter =
new AttrValueCompletionAdapter(requireContext(), list);
dimensionResInput.setThreshold(1);
dimensionResInput.setAdapter(adapter);
adapter.notifyDataSetChanged();
new AttrValueCompletionAdapter (requireContext (), list);
dimensionResInput.setThreshold (1);
dimensionResInput.setAdapter (adapter);
adapter.notifyDataSetChanged ();
});
});
}
@NonNull
private String[] getDimensionUnits() {
private String[] getDimensionUnits () {
if (dimensionUnits == null) {
dimensionUnits =
requireContext().getResources().getStringArray(R.array.dimension_units);
requireContext ().getResources ().getStringArray (R.array.dimension_units);
}
return dimensionUnits;
}
@Override
protected void notifyValueChanged(@NonNull String newValue) {
protected void notifyValueChanged (@NonNull String newValue) {
try {
super.notifyValueChanged(newValue);
super.notifyValueChanged (newValue);
} catch (Throwable e) {
LOG.error("Unable to update dimension value to '" + newValue + "'", e);
LOG.error ("Unable to update dimension value to '" + newValue + "'", e);
}
}
}
@@ -26,13 +26,11 @@ import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.itsaky.androidide.utils.Logger;
/**
* @author Akash Yadav
*/
/** @author Akash Yadav */
public class EnumEditor extends FixedValueEditor {
private static final Logger LOG = Logger.instance("EnumEditor");
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -1,4 +1,4 @@
/************************************************************************************
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
*/
package com.itsaky.androidide.handlers;
import android.view.View;
@@ -36,223 +36,229 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class BuildServiceHandler extends IDEHandler implements BuildListener {
private IDEService service;
private boolean shouldInstallApk = false;
public BuildServiceHandler(Provider provider) {
super(provider);
public BuildServiceHandler (Provider provider) {
super (provider);
}
public IDEService getService() {
public IDEService getService () {
return service;
}
@Override
public void start() {
if (this.activity() == null) {
throwNPE();
}
AndroidProject project = provider.provideAndroidProject();
if (project == null) {
throwNPE();
}
if (project.getProjectPath() == null) {
throwNPE();
}
this.service = new IDEService(new File(project.getProjectPath()));
this.service.setListener(this);
public void start () {
Objects.requireNonNull (this.activity ());
AndroidProject project = provider.provideAndroidProject ();
Objects.requireNonNull (project);
Objects.requireNonNull (project.getProjectPath ());
this.service = new IDEService (new File (project.getProjectPath ()));
this.service.setListener (this);
}
@Override
public void stop() {
this.service.stopAllDaemons();
this.service.exit();
public void stop () {
this.service.stopAllDaemons ();
this.service.exit ();
}
@Override
public void onBuildModified() {
public void onBuildModified () {
// Does nothing currently
}
@Override
public void onProjectLoaded(IDEProject project, Optional<IDEModule> appModule) {
project.iconPath = provider.provideAndroidProject().getIconPath();
activity().setIDEProject(project);
if (appModule.isPresent()) {
IDEModule app = appModule.get();
activity().setStatus(activity().getString(R.string.msg_starting_completion));
public void onProjectLoaded (IDEProject project, Optional<IDEModule> appModule) {
project.iconPath = provider.provideAndroidProject ().getIconPath ();
activity ().setIDEProject (project);
if (appModule.isPresent ()) {
IDEModule app = appModule.get ();
activity ().setStatus (activity ().getString (R.string.msg_starting_completion));
final List<String> paths =
app.dependencies.stream()
.filter(this::isClasspathValid)
.collect(Collectors.toList());
provider.provideAndroidProject().setClassPaths(paths);
app.dependencies.stream ()
.filter (this::isClasspathValid)
.collect (Collectors.toList ());
provider.provideAndroidProject ().setClassPaths (paths);
File androidJar =
new File(
new File (
Environment.ANDROID_HOME,
String.format("platforms/%s/android.jar", app.compileSdkVersion));
if (androidJar.exists()) {
Environment.setBootClasspath(androidJar);
provider.provideAndroidProject().addClasspath(androidJar.getAbsolutePath());
activity().updateServices();
} else activity().setStatus("android.jar not found!");
} else activity().setStatus("Cannot get :app module...");
}
@Override
public void onStartingGradleDaemon(GradleTask task) {
activity().setStatus(activity().getString(R.string.msg_starting_daemon));
}
@Override
public void onRunTask(GradleTask task, String name) {
if (task != null && task.canOutput()) {
activity().setStatus(service.typeString(task.getTaskID()) + " " + name);
String.format ("platforms/%s/android.jar", app.compileSdkVersion));
if (androidJar.exists ()) {
Environment.setBootClasspath (androidJar);
provider.provideAndroidProject ().addClasspath (androidJar.getAbsolutePath ());
activity ().updateServices ();
} else {
activity ().setStatus ("android.jar not found!");
}
} else {
activity ().setStatus ("Cannot get :app module...");
}
}
@Override
public void onBuildSuccessful(GradleTask task, String msg) {
appendOutput(task, msg);
if (task == null || activity() == null) return;
if (task.canOutput()) {
activity().setStatus(msg);
public void onStartingGradleDaemon (GradleTask task) {
activity ().setStatus (activity ().getString (R.string.msg_starting_daemon));
}
@Override
public void onRunTask (GradleTask task, String name) {
if (task != null && task.canOutput ()) {
activity ().setStatus (service.typeString (task.getTaskID ()) + " " + name);
}
}
@Override
public void onBuildSuccessful (GradleTask task, String msg) {
appendOutput (task, msg);
if (task == null || activity () == null) {
return;
}
if (task.canOutput ()) {
activity ().setStatus (msg);
}
// If this task generates APK, ask user if he/she wants to install them
if (shouldInstallApk && task instanceof ApkGeneratingTask) {
final IDEProject project = activity().provideIDEProject();
final IDEProject project = activity ().provideIDEProject ();
final Optional<IDEModule> app =
project.getModuleByPath(":app"); // TODO Handle multiple application modules
if (app.isPresent()) {
final String path = app.get().projectDir;
project.getModuleByPath (":app"); // TODO Handle multiple application modules
if (app.isPresent ()) {
final String path = app.get ().projectDir;
if (path != null) {
final File buildDir = new File(path, "build");
installApks(((ApkGeneratingTask) task).getApks(buildDir));
final File buildDir = new File (path, "build");
installApks (((ApkGeneratingTask) task).getApks (buildDir));
}
}
}
activity()
.getApp()
.getPrefManager()
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity().invalidateOptionsMenu();
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
activity ()
.getApp ()
.getPrefManager ()
.putBoolean (PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity ().invalidateOptionsMenu ();
activity ().getBinding ().buildProgressIndicator.setVisibility (View.GONE);
}
@Override
public void onBuildFailed(GradleTask task, String msg) {
appendOutput(task, msg);
if (task == null) return;
if (task.canOutput()) {
activity().setStatus(msg);
}
activity()
.getApp()
.getPrefManager()
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity().invalidateOptionsMenu();
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
}
@Override
public void saveFiles() {
activity().saveAll(false, false);
}
@Override
public void appendOutput(GradleTask task, CharSequence text) {
if (text == null) return;
if (task != null && !task.canOutput()) {
public void onBuildFailed (GradleTask task, String msg) {
appendOutput (task, msg);
if (task == null) {
return;
}
activity().appendBuildOut(text.toString());
if (task.canOutput ()) {
activity ().setStatus (msg);
}
activity ()
.getApp ()
.getPrefManager ()
.putBoolean (PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity ().invalidateOptionsMenu ();
activity ().getBinding ().buildProgressIndicator.setVisibility (View.GONE);
}
@Override
public void prepareBuild() {
public void saveFiles () {
activity ().saveAll (false, false);
}
@Override
public void appendOutput (GradleTask task, CharSequence text) {
if (text == null) {
return;
}
if (task != null && !task.canOutput ()) {
return;
}
activity ().appendBuildOut (text.toString ());
}
@Override
public void prepareBuild () {
boolean isFirstBuild =
activity()
.getApp()
.getPrefManager()
.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true);
activity()
.setStatus(
activity()
.getString(
activity ()
.getApp ()
.getPrefManager ()
.getBoolean (PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true);
activity ()
.setStatus (
activity ()
.getString (
isFirstBuild
? R.string.preparing_first
: R.string.preparing));
if (isFirstBuild) {
activity().showFirstBuildNotice();
activity ().showFirstBuildNotice ();
}
activity().getBinding().buildProgressIndicator.setVisibility(View.VISIBLE);
activity ().getBinding ().buildProgressIndicator.setVisibility (View.VISIBLE);
}
public void setShouldInstallApk(boolean install) {
public void setShouldInstallApk (boolean install) {
this.shouldInstallApk = install;
}
public void assembleDebug(boolean shouldInstallApk) {
setShouldInstallApk(shouldInstallApk);
getService().assembleDebug(shouldInstallApk);
public void assembleDebug (boolean shouldInstallApk) {
setShouldInstallApk (shouldInstallApk);
getService ().assembleDebug (shouldInstallApk);
}
private boolean isClasspathValid(String path) {
if (path == null || path.trim().length() <= 0 || path.trim().equals("/")) return false;
File file = new File(path);
return file.isFile()
&& file.getName().endsWith(".jar")
/** Release R.jar shouldn't be included in classpath */
&& !file.getAbsolutePath().endsWith("/release/R.jar");
private boolean isClasspathValid (String path) {
if (path == null || path.trim ().length () <= 0 || path.trim ().equals ("/")) {
return false;
}
File file = new File (path);
return file.isFile ()
&& file.getName ().endsWith (".jar")
// Release R.jar shouldn't be included in classpath
&& !file.getAbsolutePath ().endsWith ("/release/R.jar");
}
private void installApks(final Set<File> apks) {
if (activity() == null || apks == null || apks.isEmpty()) {
private void installApks (final Set<File> apks) {
if (activity () == null || apks == null || apks.isEmpty ()) {
return;
}
if (apks.size() == 1) {
activity().install(apks.iterator().next());
if (apks.size () == 1) {
activity ().install (apks.iterator ().next ());
} else {
final List<File> files = new ArrayList<File>(apks);
final List<File> files = new ArrayList<> (apks);
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(activity().getString(R.string.title_install_apks));
builder.setItems(
getNames(files),
DialogUtils.newMaterialDialogBuilder (activity ());
builder.setTitle (activity ().getString (R.string.title_install_apks));
builder.setItems (
getNames (files),
(d, w) -> {
d.dismiss();
activity().install(files.get(w));
d.dismiss ();
activity ().install (files.get (w));
});
builder.show();
builder.show ();
}
}
private CharSequence[] getNames(final Collection<File> apks) {
final String[] names = new String[apks.size()];
private CharSequence[] getNames (final Collection<File> apks) {
final String[] names = new String[apks.size ()];
int i = 0;
for (File apk : apks) {
names[i] = apk.getName();
names[i] = apk.getName ();
++i;
}
return names;
}
}
@@ -41,404 +41,403 @@ import java.util.regex.Pattern;
public class FileOptionsHandler extends IDEHandler
implements OptionsListFragment.OnOptionsClickListener {
public static final String RES_PATH_REGEX = "/.*/src/.*/res";
public static final String LAYOUT_RES_PATH_REGEX = "/.*/src/.*/res/layout";
public static final String MENU_RES_PATH_REGEX = "/.*/src/.*/res/menu";
public static final String DRAWABLE_RES_PATH_REGEX = "/.*/src/.*/res/drawable";
public static final String JAVA_PATH_REGEX = "/.*/src/.*/java";
public FileOptionsHandler(Provider provider) {
super(provider);
public FileOptionsHandler (Provider provider) {
super (provider);
}
@Override
public void start() {
if (activity() == null) {
throwNPE();
}
public void start () {
Objects.requireNonNull (this.activity ());
}
@Override
public void stop() {}
public void stop () {
}
@Override
public void onOptionsClick(SheetOption option) {
public void onOptionsClick (SheetOption option) {
if (option.extra instanceof File) {
final File f = (File) option.extra;
switch (option.id) {
case 0:
ClipboardUtils.copyText("[AndroidIDE] Copied File Path", f.getAbsolutePath());
activity().getApp().toast(R.string.copied, Toaster.Type.SUCCESS);
ClipboardUtils.copyText ("[AndroidIDE] Copied File Path", f.getAbsolutePath ());
activity ().getApp ().toast (R.string.copied, Toaster.Type.SUCCESS);
break;
case 1:
renameFile(f);
renameFile (f);
break;
case 2:
delete(f);
delete (f);
break;
case 3:
createNewFile(f);
createNewFile (f);
break;
case 4:
createNewFolder(f);
createNewFolder (f);
break;
}
}
}
private void createNewFile(File f) {
createNewFile(f, false);
private void createNewFile (File f) {
createNewFile (f, false);
}
private void createNewFile(final File f, boolean forceUnknownType) {
private void createNewFile (final File f, boolean forceUnknownType) {
if (forceUnknownType) {
createNewEmptyFile(f);
createNewEmptyFile (f);
} else {
final boolean isJava =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ JAVA_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ JAVA_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
final boolean isRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
final boolean isLayoutRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ LAYOUT_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ LAYOUT_RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
final boolean isMenuRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ MENU_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ MENU_RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
final boolean isDrawableRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ DRAWABLE_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ DRAWABLE_RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
if (isJava) {
createJavaClass(f);
} else if (isLayoutRes && f.getName().equals("layout")) {
createLayoutRes(f);
} else if (isMenuRes && f.getName().equals("menu")) {
createMenuRes(f);
} else if (isDrawableRes && f.getName().equals("drawable")) {
createDrawableRes(f);
} else if (isRes && f.getName().equals("res")) {
createNewResource(f);
createJavaClass (f);
} else if (isLayoutRes && f.getName ().equals ("layout")) {
createLayoutRes (f);
} else if (isMenuRes && f.getName ().equals ("menu")) {
createMenuRes (f);
} else if (isDrawableRes && f.getName ().equals ("drawable")) {
createDrawableRes (f);
} else if (isRes && f.getName ().equals ("res")) {
createNewResource (f);
} else {
createNewEmptyFile(f);
createNewEmptyFile (f);
}
}
}
private void createJavaClass(final File f) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
private void createJavaClass (final File f) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
final LayoutCreateFileJavaBinding binding =
LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater());
builder.setView(binding.getRoot());
builder.setTitle(R.string.new_java_class);
builder.setPositiveButton(
LayoutCreateFileJavaBinding.inflate (activity ().getLayoutInflater ());
builder.setView (binding.getRoot ());
builder.setTitle (R.string.new_java_class);
builder.setPositiveButton (
R.string.text_create,
(p1, p2) -> {
p1.dismiss();
p1.dismiss ();
final String name =
Objects.requireNonNull(binding.name.getEditText())
.getText()
.toString()
.trim();
final String pkgName = ProjectWriter.getPackageName(f);
if (pkgName == null || pkgName.trim().length() <= 0) {
activity()
.getApp()
.toast(R.string.msg_get_package_failed, Toaster.Type.ERROR);
Objects.requireNonNull (binding.name.getEditText ())
.getText ()
.toString ()
.trim ();
final String pkgName = ProjectWriter.getPackageName (f);
if (pkgName == null || pkgName.trim ().length () <= 0) {
activity ()
.getApp ()
.toast (R.string.msg_get_package_failed, Toaster.Type.ERROR);
} else {
final int id = binding.typeGroup.getCheckedButtonId();
final var javaName = name.endsWith(".java") ? name : name.concat(".java");
final int id = binding.typeGroup.getCheckedButtonId ();
final var javaName = name.endsWith (".java") ? name : name.concat (".java");
final var className =
!name.contains(".")
!name.contains (".")
? name
: name.substring(0, name.lastIndexOf("."));
if (id == binding.typeClass.getId()) {
createFile(
f, javaName, ProjectWriter.createJavaClass(pkgName, className));
} else if (id == binding.typeInterface.getId()) {
createFile(
: name.substring (0, name.lastIndexOf ("."));
if (id == binding.typeClass.getId ()) {
createFile (
f, javaName, ProjectWriter.createJavaClass (pkgName, className));
} else if (id == binding.typeInterface.getId ()) {
createFile (
f,
javaName,
ProjectWriter.createJavaInterface(pkgName, className));
} else if (id == binding.typeEnum.getId()) {
createFile(
f, javaName, ProjectWriter.createJavaEnum(pkgName, className));
} else if (id == binding.typeActivity.getId()) {
createFile(
f, javaName, ProjectWriter.createActivity(pkgName, className));
ProjectWriter.createJavaInterface (pkgName, className));
} else if (id == binding.typeEnum.getId ()) {
createFile (
f, javaName, ProjectWriter.createJavaEnum (pkgName, className));
} else if (id == binding.typeActivity.getId ()) {
createFile (
f, javaName, ProjectWriter.createActivity (pkgName, className));
} else {
createFile(f, name, "");
createFile (f, name, "");
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.setCancelable(false);
builder.create().show();
builder.setNegativeButton (android.R.string.cancel, null);
builder.setCancelable (false);
builder.create ().show ();
}
private void createLayoutRes(File f) {
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml");
private void createLayoutRes (File f) {
createNewFileWithContent (
Environment.mkdirIfNotExits (f), ProjectWriter.createLayout (), ".xml");
}
private void createMenuRes(File f) {
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml");
private void createMenuRes (File f) {
createNewFileWithContent (
Environment.mkdirIfNotExits (f), ProjectWriter.createMenu (), ".xml");
}
private void createDrawableRes(File f) {
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml");
private void createDrawableRes (File f) {
createNewFileWithContent (
Environment.mkdirIfNotExits (f), ProjectWriter.createDrawable (), ".xml");
}
private void createNewResource(File f) {
private void createNewResource (File f) {
final String[] labels = {
activity().getString(R.string.restype_drawable),
activity().getString(R.string.restype_layout),
activity().getString(R.string.restype_menu),
activity().getString(R.string.restype_other)
activity ().getString (R.string.restype_drawable),
activity ().getString (R.string.restype_layout),
activity ().getString (R.string.restype_menu),
activity ().getString (R.string.restype_other)
};
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(R.string.new_xml_resource);
builder.setItems(
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
builder.setTitle (R.string.new_xml_resource);
builder.setItems (
labels,
(dialogInterface, position) -> {
if (position == 0) {
createDrawableRes(new File(f, "drawable"));
createDrawableRes (new File (f, "drawable"));
} else if (position == 1) {
createLayoutRes(new File(f, "layout"));
createLayoutRes (new File (f, "layout"));
} else if (position == 2) {
createMenuRes(new File(f, "menu"));
createMenuRes (new File (f, "menu"));
} else if (position == 3) {
createNewFile(f, true);
createNewFile (f, true);
}
});
builder.create().show();
builder.create ().show ();
}
private void createNewEmptyFile(File f) {
createNewFileWithContent(f, "");
private void createNewEmptyFile (File f) {
createNewFileWithContent (f, "");
}
private void createNewFileWithContent(File f, String content) {
createNewFileWithContent(f, content, null);
private void createNewFileWithContent (File f, String content) {
createNewFileWithContent (f, content, null);
}
private void createNewFileWithContent(File folder, String content, String extension) {
private void createNewFileWithContent (File folder, String content, String extension) {
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.file_name);
builder.setTitle(R.string.new_file);
builder.setMessage(
activity()
.getString(R.string.msg_can_contain_slashes)
.concat("\n\n")
.concat(
activity()
.getString(
LayoutDialogTextInputBinding.inflate (activity ().getLayoutInflater ());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
Objects.requireNonNull (binding.name.getEditText ()).setHint (R.string.file_name);
builder.setTitle (R.string.new_file);
builder.setMessage (
activity ()
.getString (R.string.msg_can_contain_slashes)
.concat ("\n\n")
.concat (
activity ()
.getString (
R.string.msg_newfile_dest,
folder.getAbsolutePath())));
builder.setView(binding.getRoot());
builder.setCancelable(false);
builder.setPositiveButton(
folder.getAbsolutePath ())));
builder.setView (binding.getRoot ());
builder.setCancelable (false);
builder.setPositiveButton (
R.string.text_create,
(p1, p2) -> {
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
if (extension != null && extension.trim().length() > 0) {
name = name.endsWith(extension) ? name : name.concat(extension);
p1.dismiss ();
String name = binding.name.getEditText ().getText ().toString ().trim ();
if (extension != null && extension.trim ().length () > 0) {
name = name.endsWith (extension) ? name : name.concat (extension);
}
createFile(folder, name, content);
createFile (folder, name, content);
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();
builder.setNegativeButton (android.R.string.cancel, null);
builder.create ().show ();
}
private void createFile(File f, String name, String content) {
if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
final File file = new File(f, name);
if (file.exists()) {
activity().getApp().toast(R.string.msg_file_exists, Toaster.Type.ERROR);
private void createFile (File f, String name, String content) {
if (name.length () > 0 && name.length () <= 40 && !name.startsWith ("/")) {
final File file = new File (f, name);
if (file.exists ()) {
activity ().getApp ().toast (R.string.msg_file_exists, Toaster.Type.ERROR);
} else {
if (FileIOUtils.writeFileFromString(file, content)) {
if (FileIOUtils.writeFileFromString (file, content)) {
// TODO Notify language servers about file created event
activity().getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS);
if (activity().getLastHoldTreeNode() != null) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(activity()));
activity().getLastHoldTreeNode().addChild(node);
activity()
.getFileTreeFragment()
.expandNode(activity().getLastHoldTreeNode());
activity ().getApp ().toast (R.string.msg_file_created, Toaster.Type.SUCCESS);
if (activity ().getLastHoldTreeNode () != null) {
TreeNode node = new TreeNode (file);
node.setViewHolder (new FileTreeViewHolder (activity ()));
activity ().getLastHoldTreeNode ().addChild (node);
activity ()
.getFileTreeFragment ()
.expandNode (activity ().getLastHoldTreeNode ());
} else {
activity().getFileTreeFragment().listProjectFiles();
activity ().getFileTreeFragment ().listProjectFiles ();
}
} else {
activity()
.getApp()
.toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR);
activity ()
.getApp ()
.toast (R.string.msg_file_creation_failed, Toaster.Type.ERROR);
}
}
} else {
activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
activity ().getApp ().toast (R.string.msg_invalid_name, Toaster.Type.ERROR);
}
}
private void createNewFolder(File f) {
private void createNewFolder (File f) {
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.folder_name);
builder.setTitle(R.string.new_folder);
builder.setMessage(R.string.msg_can_contain_slashes);
builder.setView(binding.getRoot());
builder.setCancelable(false);
builder.setPositiveButton(
LayoutDialogTextInputBinding.inflate (activity ().getLayoutInflater ());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
Objects.requireNonNull (binding.name.getEditText ()).setHint (R.string.folder_name);
builder.setTitle (R.string.new_folder);
builder.setMessage (R.string.msg_can_contain_slashes);
builder.setView (binding.getRoot ());
builder.setCancelable (false);
builder.setPositiveButton (
R.string.text_create,
(p1, p2) -> {
p1.dismiss();
final String name = binding.name.getEditText().getText().toString().trim();
if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
final File file = new File(f, name);
if (file.exists()) {
activity()
.getApp()
.toast(R.string.msg_folder_exists, Toaster.Type.ERROR);
p1.dismiss ();
final String name = binding.name.getEditText ().getText ().toString ().trim ();
if (name.length () > 0 && name.length () <= 40 && !name.startsWith ("/")) {
final File file = new File (f, name);
if (file.exists ()) {
activity ()
.getApp ()
.toast (R.string.msg_folder_exists, Toaster.Type.ERROR);
} else {
if (file.mkdirs()) {
activity()
.getApp()
.toast(R.string.msg_folder_created, Toaster.Type.SUCCESS);
if (activity().getLastHoldTreeNode() != null) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(activity()));
activity().getLastHoldTreeNode().addChild(node);
activity()
.getFileTreeFragment()
.expandNode(activity().getLastHoldTreeNode());
if (file.mkdirs ()) {
activity ()
.getApp ()
.toast (R.string.msg_folder_created, Toaster.Type.SUCCESS);
if (activity ().getLastHoldTreeNode () != null) {
TreeNode node = new TreeNode (file);
node.setViewHolder (new FileTreeViewHolder (activity ()));
activity ().getLastHoldTreeNode ().addChild (node);
activity ()
.getFileTreeFragment ()
.expandNode (activity ().getLastHoldTreeNode ());
} else {
activity().getFileTreeFragment().listProjectFiles();
activity ().getFileTreeFragment ().listProjectFiles ();
}
} else {
activity()
.getApp()
.toast(
activity ()
.getApp ()
.toast (
R.string.msg_folder_creation_failed,
Toaster.Type.ERROR);
}
}
} else {
activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
activity ().getApp ().toast (R.string.msg_invalid_name, Toaster.Type.ERROR);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();
builder.setNegativeButton (android.R.string.cancel, null);
builder.create ().show ();
}
private void delete(final File f) {
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setNegativeButton(R.string.no, null)
.setPositiveButton(
private void delete (final File f) {
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
builder.setNegativeButton (R.string.no, null)
.setPositiveButton (
R.string.yes,
(p1, p2) -> {
p1.dismiss();
final boolean deleted = FileUtils.delete(f);
activity()
.getApp()
.toast(
p1.dismiss ();
final boolean deleted = FileUtils.delete (f);
activity ()
.getApp ()
.toast (
deleted ? R.string.deleted : R.string.delete_failed,
deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if (deleted) {
// TODO Notify language servers about file delete event
if (activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
activity().getFileTreeFragment().expandNode(parent);
if (activity ().getLastHoldTreeNode () != null) {
TreeNode parent = activity ().getLastHoldTreeNode ().getParent ();
parent.deleteChild (activity ().getLastHoldTreeNode ());
activity ().getFileTreeFragment ().expandNode (parent);
} else {
activity().getFileTreeFragment().listProjectFiles();
activity ().getFileTreeFragment ().listProjectFiles ();
}
final var frag = activity().getEditorForFile(f);
final var frag = activity ().getEditorForFile (f);
if (frag != null) {
activity()
.closeFile(
activity()
.findIndexOfEditorByFile(
frag.getFile()));
activity ()
.closeFile (
activity ()
.findIndexOfEditorByFile (
frag.getFile ()));
}
}
})
.setTitle(R.string.title_confirm_delete)
.setMessage(
activity()
.getString(
.setTitle (R.string.title_confirm_delete)
.setMessage (
activity ()
.getString (
R.string.msg_confirm_delete,
String.format("%s [%s]", f.getName(), f.getAbsolutePath())))
.setCancelable(false)
.create()
.show();
String.format ("%s [%s]", f.getName (), f.getAbsolutePath ())))
.setCancelable (false)
.create ()
.show ();
}
private void renameFile(File f) {
private void renameFile (File f) {
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity()));
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull(binding.name.getEditText())
.setHint(activity().getString(R.string.new_name));
binding.name.getEditText().setText(f.getName());
builder.setTitle(R.string.rename_file);
builder.setMessage(R.string.msg_rename_file);
builder.setView(binding.getRoot());
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(
LayoutDialogTextInputBinding.inflate (LayoutInflater.from (activity ()));
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
Objects.requireNonNull (binding.name.getEditText ())
.setHint (activity ().getString (R.string.new_name));
binding.name.getEditText ().setText (f.getName ());
builder.setTitle (R.string.rename_file);
builder.setMessage (R.string.msg_rename_file);
builder.setView (binding.getRoot ());
builder.setNegativeButton (android.R.string.cancel, null);
builder.setPositiveButton (
R.string.rename_file,
(p1, p2) -> {
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
p1.dismiss ();
String name = binding.name.getEditText ().getText ().toString ().trim ();
boolean renamed =
name.length() > 0 && name.length() <= 40 && FileUtils.rename(f, name);
activity()
.getApp()
.toast(
name.length () > 0 && name.length () <= 40 && FileUtils.rename (f, name);
activity ()
.getApp ()
.toast (
renamed ? R.string.renamed : R.string.rename_failed,
renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if (renamed) {
// TODO Notify language servers about file rename event
if (activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
TreeNode node = new TreeNode(new File(f.getParentFile(), name));
node.setViewHolder(new FileTreeViewHolder(activity()));
parent.addChild(node);
activity().getFileTreeFragment().expandNode(parent);
if (activity ().getLastHoldTreeNode () != null) {
TreeNode parent = activity ().getLastHoldTreeNode ().getParent ();
parent.deleteChild (activity ().getLastHoldTreeNode ());
TreeNode node = new TreeNode (new File (f.getParentFile (), name));
node.setViewHolder (new FileTreeViewHolder (activity ()));
parent.addChild (node);
activity ().getFileTreeFragment ().expandNode (parent);
} else {
activity().getFileTreeFragment().listProjectFiles();
activity ().getFileTreeFragment ().listProjectFiles ();
}
}
});
builder.create().show();
builder.create ().show ();
}
}
@@ -36,10 +36,6 @@ public abstract class IDEHandler {
this.provider = provider;
}
protected void throwNPE() {
throw new NullPointerException();
}
protected EditorActivity activity() {
return provider.provideEditorActivity();
}
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of AndroidIDE.
~
~ AndroidIDE is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ AndroidIDE is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:padding="16dp">
<RadioGroup
android:id="@+id/choice_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<RadioButton
android:id="@+id/bool_true"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:padding="8dp"
android:text="true"
tools:ignore="HardcodedText" />
<RadioButton
android:id="@+id/bool_false"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:padding="8dp"
android:text="false"
tools:ignore="HardcodedText" />
</RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -14,8 +14,7 @@
~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
+2
View File
@@ -35,4 +35,6 @@ dependencies {
implementation androidx.animated_vectors
api androidx.multidex
testImplementation tests.junit4
}
@@ -35,16 +35,16 @@ import java.util.Properties;
@SuppressLint("SdCardPath")
public final class Environment {
public static final Map<String, String> IDE_PROPS = new HashMap<>();
public static final Map<String, String> ENV_VARS = new HashMap<>();
public static final Map<String, String> IDE_PROPS = new HashMap<> ();
public static final Map<String, String> ENV_VARS = new HashMap<> ();
public static final String PROJECTS_FOLDER = "AndroidIDEProjects";
private static final String DEFAULT_JAVA_HOME =
"/data/data/com.itsaky.androidide/files/framework/jdk";
private static final String DEFAULT_ANDROID_HOME =
"/data/data/com.itsaky.androidide/files/framework/android-sdk";
private static final Logger LOG = Logger.instance("Environment");
private static final List<String> blacklist = new ArrayList<>();
private static final Logger LOG = Logger.instance ("Environment");
private static final List<String> blacklist = new ArrayList<> ();
public static File ROOT;
public static File SYSROOT;
public static File HOME;
@@ -63,7 +63,7 @@ public final class Environment {
* contains only the classes included in android.jar
*/
public static File COMPILER_MODULE;
public static File INIT_SCRIPT;
public static File PROJECT_DATA_FILE;
public static File GRADLE_PROPS;
@@ -73,196 +73,193 @@ public final class Environment {
public static File SHELL;
public static File LOGIN_SHELL;
public static File BOOTCLASSPATH;
public static void init() {
final BaseApplication app = BaseApplication.getBaseInstance();
ROOT = app.getIDEDataDir();
SYSROOT = mkdirIfNotExits(new File(app.getIDEDataDir(), "sysroot"));
HOME = mkdirIfNotExits(app.getRootDir());
ANDROIDIDE_HOME = mkdirIfNotExits(new File(HOME, ".androidide"));
TMP_DIR = mkdirIfNotExits(new File(SYSROOT, "tmp"));
BIN_DIR = mkdirIfNotExits(new File(SYSROOT, "bin"));
LIB_DIR = mkdirIfNotExits(new File(SYSROOT, "lib"));
PROJECTS_DIR = mkdirIfNotExits(new File(FileUtil.getExternalStorageDir(), PROJECTS_FOLDER));
COMPILER_MODULE = mkdirIfNotExits(new File(ANDROIDIDE_HOME, "compiler-module"));
IDE_PROPS_FILE = new File(SYSROOT, "etc/ide-environment.properties");
LIB_HOOK = new File(LIB_DIR, "libhook.so");
LIB_HOOK2 = new File(LIB_DIR, "libhook2.so");
PROJECT_DATA_FILE = new File(TMP_DIR, "ide_project");
public static void init () {
final BaseApplication app = BaseApplication.getBaseInstance ();
ROOT = app.getIDEDataDir ();
SYSROOT = mkdirIfNotExits (new File (app.getIDEDataDir (), "sysroot"));
HOME = mkdirIfNotExits (app.getRootDir ());
ANDROIDIDE_HOME = mkdirIfNotExits (new File (HOME, ".androidide"));
TMP_DIR = mkdirIfNotExits (new File (SYSROOT, "tmp"));
BIN_DIR = mkdirIfNotExits (new File (SYSROOT, "bin"));
LIB_DIR = mkdirIfNotExits (new File (SYSROOT, "lib"));
PROJECTS_DIR = mkdirIfNotExits (new File (FileUtil.getExternalStorageDir (), PROJECTS_FOLDER));
COMPILER_MODULE = mkdirIfNotExits (new File (ANDROIDIDE_HOME, "compiler-module"));
IDE_PROPS_FILE = new File (SYSROOT, "etc/ide-environment.properties");
LIB_HOOK = new File (LIB_DIR, "libhook.so");
LIB_HOOK2 = new File (LIB_DIR, "libhook2.so");
PROJECT_DATA_FILE = new File (TMP_DIR, "ide_project");
INIT_SCRIPT =
new File(
mkdirIfNotExits(new File(app.getIDEDataDir(), "init")),
new File (
mkdirIfNotExits (new File (app.getIDEDataDir (), "init")),
"data/common/androidide.init.gradle");
BOOTCLASSPATH = new File("");
GRADLE_USER_HOME = new File(HOME, ".gradle");
GRADLE_PROPS = new File(GRADLE_USER_HOME, "gradle.properties");
IDE_PROPS.putAll(readProperties());
ANDROID_HOME = new File(readProp("ANDROID_HOME", DEFAULT_ANDROID_HOME));
JAVA_HOME = new File(readProp("JAVA_HOME", DEFAULT_JAVA_HOME));
JAVA = new File(JAVA_HOME, "bin/java");
BUSYBOX = new File(BIN_DIR, "busybox");
SHELL = new File(BIN_DIR, "bash");
LOGIN_SHELL = new File(BIN_DIR, "login");
setExecutable(JAVA);
setExecutable(BUSYBOX);
setExecutable(SHELL);
System.setProperty("user.home", HOME.getAbsolutePath());
System.setProperty("java.home", JAVA_HOME.getAbsolutePath());
BOOTCLASSPATH = new File ("");
GRADLE_USER_HOME = new File (HOME, ".gradle");
GRADLE_PROPS = new File (GRADLE_USER_HOME, "gradle.properties");
IDE_PROPS.putAll (readProperties ());
ANDROID_HOME = new File (readProp ("ANDROID_HOME", DEFAULT_ANDROID_HOME));
JAVA_HOME = new File (readProp ("JAVA_HOME", DEFAULT_JAVA_HOME));
JAVA = new File (JAVA_HOME, "bin/java");
BUSYBOX = new File (BIN_DIR, "busybox");
SHELL = new File (BIN_DIR, "bash");
LOGIN_SHELL = new File (BIN_DIR, "login");
setExecutable (JAVA);
setExecutable (BUSYBOX);
setExecutable (SHELL);
System.setProperty ("user.home", HOME.getAbsolutePath ());
System.setProperty ("java.home", JAVA_HOME.getAbsolutePath ());
}
public static void setExecutable(@NonNull final File file) {
if (!file.setExecutable(true)) {
LOG.error("Unable to set executable permissions to file", file);
public static void setExecutable (@NonNull final File file) {
if (!file.setExecutable (true)) {
LOG.error ("Unable to set executable permissions to file", file);
}
}
@NonNull
private static Map<String, String> readProperties() {
final Map<String, String> props = new HashMap<>();
if (IDE_PROPS_FILE == null || !IDE_PROPS_FILE.exists()) {
private static Map<String, String> readProperties () {
final Map<String, String> props = new HashMap<> ();
if (IDE_PROPS_FILE == null || !IDE_PROPS_FILE.exists ()) {
return props;
}
try {
Properties p = new Properties();
p.load(new StringReader(FileIOUtils.readFile2String(IDE_PROPS_FILE)));
for (@SuppressWarnings("rawtypes") Map.Entry entry : p.entrySet()) {
props.put(entry.getKey() + "", entry.getValue() + "");
Properties p = new Properties ();
p.load (new StringReader (FileIOUtils.readFile2String (IDE_PROPS_FILE)));
for (@SuppressWarnings("rawtypes") Map.Entry entry : p.entrySet ()) {
props.put (entry.getKey () + "", entry.getValue () + "");
}
} catch (Throwable th) {
LOG.error("Unable to read properties file", th);
LOG.error ("Unable to read properties file", th);
}
return props;
}
public static void setBootClasspath(@NonNull File file) {
BOOTCLASSPATH = new File(file.getAbsolutePath());
public static void setBootClasspath (@NonNull File file) {
BOOTCLASSPATH = new File (file.getAbsolutePath ());
}
public static Map<String, String> getEnvironment() {
if (!ENV_VARS.isEmpty()) {
public static Map<String, String> getEnvironment () {
if (!ENV_VARS.isEmpty ()) {
return ENV_VARS;
}
ENV_VARS.put("HOME", HOME.getAbsolutePath());
ENV_VARS.put("ANDROID_HOME", ANDROID_HOME.getAbsolutePath());
ENV_VARS.put("JAVA_HOME", JAVA_HOME.getAbsolutePath());
ENV_VARS.put("GRADLE_USER_HOME", GRADLE_USER_HOME.getAbsolutePath());
ENV_VARS.put("TMPDIR", TMP_DIR.getAbsolutePath());
ENV_VARS.put("PROJECTS", PROJECTS_DIR.getAbsolutePath());
ENV_VARS.put("LANG", "en_US.UTF-8");
ENV_VARS.put("LC_ALL", "en_US.UTF-8");
ENV_VARS.put("SYSROOT", SYSROOT.getAbsolutePath());
ENV_VARS.put("BUSYBOX", BUSYBOX.getAbsolutePath());
ENV_VARS.put("SHELL", SHELL.getAbsolutePath());
ENV_VARS.put("CONFIG_SHELL", SHELL.getAbsolutePath());
ENV_VARS.put("TERM", "screen");
ENV_VARS.put ("HOME", HOME.getAbsolutePath ());
ENV_VARS.put ("ANDROID_HOME", ANDROID_HOME.getAbsolutePath ());
ENV_VARS.put ("JAVA_HOME", JAVA_HOME.getAbsolutePath ());
ENV_VARS.put ("GRADLE_USER_HOME", GRADLE_USER_HOME.getAbsolutePath ());
ENV_VARS.put ("TMPDIR", TMP_DIR.getAbsolutePath ());
ENV_VARS.put ("PROJECTS", PROJECTS_DIR.getAbsolutePath ());
ENV_VARS.put ("LANG", "en_US.UTF-8");
ENV_VARS.put ("LC_ALL", "en_US.UTF-8");
ENV_VARS.put ("SYSROOT", SYSROOT.getAbsolutePath ());
ENV_VARS.put ("BUSYBOX", BUSYBOX.getAbsolutePath ());
ENV_VARS.put ("SHELL", SHELL.getAbsolutePath ());
ENV_VARS.put ("CONFIG_SHELL", SHELL.getAbsolutePath ());
ENV_VARS.put ("TERM", "screen");
// If LD_LIBRARY_PATH is set, append $SYSROOT/lib to it,
// else set it to $SYSROOT/lib
String ld = System.getenv("LD_LIBRARY_PATH");
if (ld == null || ld.trim().length() <= 0) {
String ld = System.getenv ("LD_LIBRARY_PATH");
if (ld == null || ld.trim ().length () <= 0) {
ld = "";
} else {
ld += File.pathSeparator;
}
ld += LIB_DIR.getAbsolutePath();
ENV_VARS.put("LD_LIBRARY_PATH", ld);
ld += LIB_DIR.getAbsolutePath ();
ENV_VARS.put ("LD_LIBRARY_PATH", ld);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& BaseApplication.isAarch64()
&& LIB_HOOK.exists()) {
&& BaseApplication.isAarch64 ()
&& LIB_HOOK.exists ()) {
// Required for JDK 11
ENV_VARS.put("LD_PRELOAD", LIB_HOOK.getAbsolutePath());
ENV_VARS.put ("LD_PRELOAD", LIB_HOOK.getAbsolutePath ());
}
addToEnvIfPresent(ENV_VARS, "ANDROID_ART_ROOT");
addToEnvIfPresent(ENV_VARS, "DEX2OATBOOTCLASSPATH");
addToEnvIfPresent(ENV_VARS, "ANDROID_I18N_ROOT");
addToEnvIfPresent(ENV_VARS, "ANDROID_RUNTIME_ROOT");
addToEnvIfPresent(ENV_VARS, "ANDROID_TZDATA_ROOT");
addToEnvIfPresent(ENV_VARS, "ANDROID_DATA");
addToEnvIfPresent(ENV_VARS, "ANDROID_ROOT");
String path = createPath();
ENV_VARS.put("PATH", path);
for (String key : IDE_PROPS.keySet()) {
if (!blacklistedVariables().contains(key.trim())) {
ENV_VARS.put(key, readProp(key, ""));
addToEnvIfPresent (ENV_VARS, "ANDROID_ART_ROOT");
addToEnvIfPresent (ENV_VARS, "DEX2OATBOOTCLASSPATH");
addToEnvIfPresent (ENV_VARS, "ANDROID_I18N_ROOT");
addToEnvIfPresent (ENV_VARS, "ANDROID_RUNTIME_ROOT");
addToEnvIfPresent (ENV_VARS, "ANDROID_TZDATA_ROOT");
addToEnvIfPresent (ENV_VARS, "ANDROID_DATA");
addToEnvIfPresent (ENV_VARS, "ANDROID_ROOT");
String path = createPath ();
ENV_VARS.put ("PATH", path);
for (String key : IDE_PROPS.keySet ()) {
if (!blacklistedVariables ().contains (key.trim ())) {
ENV_VARS.put (key, readProp (key, ""));
}
}
return ENV_VARS;
}
@NonNull
private static String createPath() {
private static String createPath () {
String path = "";
path += String.format("%s/bin", JAVA_HOME.getAbsolutePath());
path += String.format(":%s/cmdline-tools/latest/bin", ANDROID_HOME.getAbsolutePath());
path += String.format(":%s/cmake/bin", ANDROID_HOME.getAbsolutePath());
path += String.format(":%s/bin", SYSROOT.getAbsolutePath());
path += String.format(":%s", System.getenv("PATH"));
path += String.format ("%s/bin", JAVA_HOME.getAbsolutePath ());
path += String.format (":%s/cmdline-tools/latest/bin", ANDROID_HOME.getAbsolutePath ());
path += String.format (":%s/cmake/bin", ANDROID_HOME.getAbsolutePath ());
path += String.format (":%s/bin", SYSROOT.getAbsolutePath ());
path += String.format (":%s", System.getenv ("PATH"));
return path;
}
public static void addToEnvIfPresent(Map<String, String> environment, String name) {
String value = System.getenv(name);
public static void addToEnvIfPresent (Map<String, String> environment, String name) {
String value = System.getenv (name);
if (value != null) {
environment.put(name, value);
environment.put (name, value);
}
}
public static String readPropOrNull(String key) {
return readProp(key, null);
}
public static String readProp(String key, String defaultValue) {
String value = IDE_PROPS.getOrDefault(key, defaultValue);
public static String readProp (String key, String defaultValue) {
String value = IDE_PROPS.getOrDefault (key, defaultValue);
if (value == null) {
return defaultValue;
}
if (value.contains("$HOME")) {
value = value.replace("$HOME", HOME.getAbsolutePath());
if (value.contains ("$HOME")) {
value = value.replace ("$HOME", HOME.getAbsolutePath ());
}
if (value.contains("$SYSROOT")) {
value = value.replace("$SYSROOT", SYSROOT.getAbsolutePath());
if (value.contains ("$SYSROOT")) {
value = value.replace ("$SYSROOT", SYSROOT.getAbsolutePath ());
}
if (value.contains("$PATH")) {
value = value.replace("$PATH", createPath());
if (value.contains ("$PATH")) {
value = value.replace ("$PATH", createPath ());
}
return value;
}
public static File mkdirIfNotExits(File in) {
if (in != null && !in.exists()) {
FileUtils.createOrExistsDir(in);
public static File mkdirIfNotExits (File in) {
if (in != null && !in.exists ()) {
FileUtils.createOrExistsDir (in);
}
return in;
}
public static boolean isCompilerModuleInstalled() {
final var modules = new File(COMPILER_MODULE, "lib/modules");
final var release = new File(COMPILER_MODULE, "release");
return modules.exists() && modules.isFile() && release.exists() && release.isFile();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isCompilerModuleInstalled () {
final var modules = new File (COMPILER_MODULE, "lib/modules");
final var release = new File (COMPILER_MODULE, "release");
return modules.exists () && modules.isFile () && release.exists () && release.isFile ();
}
private static List<String> blacklistedVariables() {
if (blacklist.isEmpty()) {
blacklist.add("HOME");
blacklist.add("SYSROOT");
blacklist.add("JLS_HOME");
private static List<String> blacklistedVariables () {
if (blacklist.isEmpty ()) {
blacklist.add ("HOME");
blacklist.add ("SYSROOT");
blacklist.add ("JLS_HOME");
}
return blacklist;
}
@@ -120,12 +120,12 @@ public class Logger {
}
for (Object msg : messages) {
sb.append("\n");
sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR);
sb.append(
msg instanceof Throwable
? ThrowableUtils.getFullStackTrace(((Throwable) msg))
: msg);
sb.append("\n");
sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR);
}
return sb.toString();
+4
View File
@@ -31,4 +31,8 @@ ext {
gson: 'com.google.code.gson:gson:2.8.8',
guava: 'com.google.guava:guava:30.1.1-android'
]
tests = [
junit4: 'junit:junit:4.13'
]
}
+2
View File
@@ -35,4 +35,6 @@ dependencies {
implementation project (":common")
api project (':sdk-info')
testImplementation tests.junit4
}
@@ -0,0 +1,68 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.inflater.values;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Provides framework resource values.
*
* @author Akash Yadav
*/
public class FrameworkValues {
@NonNull
public static List<String> listDimens () {
return listFields (android.R.dimen.class);
}
@NonNull
public static List<String> listStrings () {
return listFields (android.R.string.class);
}
@NonNull
public static List<String> listColors () {
return listFields (android.R.color.class);
}
@NonNull
private static List<String> listFields (final Class<?> klass) {
Objects.requireNonNull (klass);
final var list = new ArrayList<String> ();
final var fields = klass.getDeclaredFields ();
for (var field : fields) {
final var mods = field.getModifiers ();
if (!isPublic (mods) || !isStatic (mods)) {
continue;
}
list.add (field.getName ());
}
return list;
}
}
@@ -48,8 +48,8 @@ import java.util.Objects;
* @author Akash Yadav
*/
public class ValuesTable {
private static final Logger LOG = Logger.instance("ValuesTable");
private static final Logger LOG = Logger.instance ("ValuesTable");
// TODO Parse styles and styleables
private final Map<String, IResource> arrays;
private final Map<String, IResource> strings;
@@ -57,185 +57,185 @@ public class ValuesTable {
private final Map<String, IResource> integers;
private final Map<String, IResource> dimens;
private final Map<String, IResource> booleans;
private final Map<String, Map<String, IResource>> resourceMap = new HashMap<>();
private ValuesTable() {
this.arrays = new HashMap<>();
this.strings = new HashMap<>();
this.colors = new HashMap<>();
this.integers = new HashMap<>();
this.dimens = new HashMap<>();
this.booleans = new HashMap<>();
this.resourceMap.put("array", this.arrays);
this.resourceMap.put("string", this.strings);
this.resourceMap.put("color", this.colors);
this.resourceMap.put("integer", this.integers);
this.resourceMap.put("dimen", this.dimens);
this.resourceMap.put("bool", this.booleans);
private final Map<String, Map<String, IResource>> resourceMap = new HashMap<> ();
protected ValuesTable () {
this.arrays = new HashMap<> ();
this.strings = new HashMap<> ();
this.colors = new HashMap<> ();
this.integers = new HashMap<> ();
this.dimens = new HashMap<> ();
this.booleans = new HashMap<> ();
this.resourceMap.put ("array", this.arrays);
this.resourceMap.put ("string", this.strings);
this.resourceMap.put ("color", this.colors);
this.resourceMap.put ("integer", this.integers);
this.resourceMap.put ("dimen", this.dimens);
this.resourceMap.put ("bool", this.booleans);
}
/**
* Create a new table by reading the files from the given values directory.
*
* @param valuesDirectory The "res/values" directory.
* @return The values table or {@code null} if the directory is invalid or there are no files in
* this directory.
* this directory.
* @throws XmlPullParserException Thrown by {@link XmlPullParserFactory}.
* @throws IOException Thrown by {@link XmlPullParser}.
* @throws IOException Thrown by {@link XmlPullParser}.
*/
@Nullable
public static ValuesTable forDirectory(File valuesDirectory) throws Exception {
public static ValuesTable forDirectory (File valuesDirectory) throws Exception {
if (valuesDirectory == null) {
LOG.error("Cannot create values table for directory: ", null);
LOG.error ("Cannot create values table for directory: ", null);
return null;
}
if (!valuesDirectory.exists()) {
LOG.error(
if (!valuesDirectory.exists ()) {
LOG.error (
"Cannot create values table for directory as it does not exists. Directory:",
valuesDirectory);
return null;
}
final var files = valuesDirectory.listFiles(ValuesTable::isValid);
final var files = valuesDirectory.listFiles (ValuesTable::isValid);
if (files == null) {
LOG.error("No files found in the given values directory");
LOG.error ("No files found in the given values directory");
return null;
}
final var start = System.currentTimeMillis();
final var values = new ValuesTable();
final var start = System.currentTimeMillis ();
final var values = new ValuesTable ();
for (var file : files) {
final var factory = XmlPullParserFactory.newInstance();
final var parser = factory.newPullParser();
parser.setInput(new FileInputStream(file), null);
var event = parser.getEventType();
final var factory = XmlPullParserFactory.newInstance ();
final var parser = factory.newPullParser ();
parser.setInput (new FileInputStream (file), null);
var event = parser.getEventType ();
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final var tag = parser.getName();
if ("resources".equals(tag)) {
event = parser.next();
final var tag = parser.getName ();
if ("resources".equals (tag)) {
event = parser.next ();
continue;
}
readTag(tag, parser, values);
readTag (tag, parser, values);
}
event = parser.next();
event = parser.next ();
}
}
final var duration = System.currentTimeMillis() - start;
LOG.info(
String.format(
Locale.getDefault(),
final var duration = System.currentTimeMillis () - start;
LOG.info (
String.format (
Locale.getDefault (),
"Took %d ms to read %d files in directory: %s",
duration,
files.length,
valuesDirectory));
return values;
}
private static void readTag(String tag, XmlPullParser parser, ValuesTable values)
private static void readTag (String tag, XmlPullParser parser, ValuesTable values)
throws Exception {
if ("style".equals(tag) || "declare-styleable".equals(tag)) {
if ("style".equals (tag) || "declare-styleable".equals (tag)) {
// These tags are not supported yet
// Skip parsing until we reach next tag with same depth as of this tag
skip(parser);
skip (parser);
return;
}
// TODO Should we check if these tags are specified within <resources>?
final var name = getName(parser);
final var name = getName (parser);
if (name == null) {
LOG.error(String.format("A <%s> resource was found with no 'name' attribute", tag));
LOG.error (String.format ("A <%s> resource was found with no 'name' attribute", tag));
return;
}
if ("item".equals(tag)) {
tag = parser.getAttributeValue(null, "type");
if ("item".equals (tag)) {
tag = parser.getAttributeValue (null, "type");
if (tag == null) {
LOG.error("<item> resource value found but no 'type' was specified.");
LOG.error ("<item> resource value found but no 'type' was specified.");
return;
}
}
// check for array before reading value
// because array resources do not have text values
if ("array".equals(tag) || tag.endsWith("-array")) {
readArray(parser, values, name);
if ("array".equals (tag) || tag.endsWith ("-array")) {
readArray (parser, values, name);
return;
}
// Value must be read after all other required values have been read
final var value = parser.nextText();
final var value = parser.nextText ();
IResource resourceValue;
switch (tag) {
case "string":
resourceValue = new StringResource (name, value);
values.strings.put(name, resourceValue);
values.strings.put (name, resourceValue);
break;
case "color":
resourceValue = new ColorResource (name, value);
values.colors.put(name, resourceValue);
values.colors.put (name, resourceValue);
break;
case "bool":
resourceValue = new BooleanResource (name, value);
values.booleans.put(name, resourceValue);
values.booleans.put (name, resourceValue);
break;
case "dimen":
resourceValue = new DimensionResource (name, value);
values.dimens.put(name, resourceValue);
values.dimens.put (name, resourceValue);
break;
case "integer":
resourceValue = new IntegerResource (name, value);
values.integers.put(name, resourceValue);
values.integers.put (name, resourceValue);
break;
default:
LOG.warn("Unknown or unsupported resource value tag:", tag);
LOG.warn ("Unknown or unsupported resource value tag:", tag);
break;
}
}
private static void readArray(@NonNull XmlPullParser parser, ValuesTable values, String name)
private static void readArray (@NonNull XmlPullParser parser, ValuesTable values, String name)
throws XmlPullParserException, IOException {
final var minDepth = parser.getDepth();
final var items = new ArrayList<String>();
var event = parser.getEventType();
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
&& parser.getDepth() >= minDepth) {
final var minDepth = parser.getDepth ();
final var items = new ArrayList<String> ();
var event = parser.getEventType ();
while ((event = parser.next ()) != XmlPullParser.END_DOCUMENT
&& parser.getDepth () >= minDepth) {
if (event == XmlPullParser.START_TAG) {
final var child = parser.getName();
if ("item".equals(child)) {
final var value = parser.nextText();
items.add(value);
final var child = parser.getName ();
if ("item".equals (child)) {
final var value = parser.nextText ();
items.add (value);
}
}
}
final var arr = new String[items.size()];
final var arr = new String[items.size ()];
for (int i = 0; i < arr.length; i++) {
arr[i] = items.get(i);
arr[i] = items.get (i);
}
values.arrays.put(name, new ArrayResource(name, arr));
values.arrays.put (name, new ArrayResource (name, arr));
}
private static void skip(@NonNull XmlPullParser parser)
private static void skip (@NonNull XmlPullParser parser)
throws XmlPullParserException, IOException {
if (parser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
if (parser.getEventType () != XmlPullParser.START_TAG) {
throw new IllegalStateException ();
}
int depth = 1;
while (depth != 0) {
switch (parser.next()) {
switch (parser.next ()) {
case XmlPullParser.END_TAG:
depth--;
break;
@@ -245,21 +245,21 @@ public class ValuesTable {
}
}
}
private static String getName(@NonNull XmlPullParser parser) {
return parser.getAttributeValue(null, "name");
private static String getName (@NonNull XmlPullParser parser) {
return parser.getAttributeValue (null, "name");
}
private static boolean isValid(File file) {
return file != null && file.isFile() && FileUtils.isUtf8(file);
private static boolean isValid (File file) {
return file != null && file.isFile () && FileUtils.isUtf8 (file);
}
@Nullable
public Map<String, IResource> getTable(final String name) {
Objects.requireNonNull(name);
return this.resourceMap.get(name);
public Map<String, IResource> getTable (final String name) {
Objects.requireNonNull (name);
return this.resourceMap.get (name);
}
/**
* Finds a resource value of any type.
*
@@ -267,133 +267,133 @@ public class ValuesTable {
* @return The resource value or {@code null} if there is no resource with the given name.
*/
@SuppressWarnings("unused")
public IResource findResource(final String name) {
public IResource findResource (final String name) {
IResource value;
value = findString(name);
value = findString (name);
if (value != null) {
return value;
}
value = findColor(name);
value = findColor (name);
if (value != null) {
return value;
}
value = findInteger(name);
value = findInteger (name);
if (value != null) {
return value;
}
value = findDimension(name);
value = findDimension (name);
if (value != null) {
return value;
}
value = findArray(name);
value = findArray (name);
if (value != null) {
return value;
}
value = findBoolean(name);
value = findBoolean (name);
//noinspection RedundantIfStatement
if (value != null) {
return value;
}
return null;
}
public ArrayResource findArray(final String name) {
return (ArrayResource) arrays.getOrDefault(name, null);
public ArrayResource findArray (final String name) {
return (ArrayResource) arrays.getOrDefault (name, null);
}
/**
* Find a string value with the given name.
*
* @param name The name of the string resource.
* @return The string resource or {@code null} if there is no resource with the given name.
*/
public StringResource findString(final String name) {
return (StringResource) strings.getOrDefault(name, null);
public StringResource findString (final String name) {
return (StringResource) strings.getOrDefault (name, null);
}
/**
* Find a color value with the given name.
*
* @param name The name of the color resource.
* @return The color resource or {@code null} if there is no resource with the given name.
*/
public ColorResource findColor(final String name) {
return (ColorResource) colors.getOrDefault(name, null);
public ColorResource findColor (final String name) {
return (ColorResource) colors.getOrDefault (name, null);
}
/**
* Find a integer value with the given name.
*
* @param name The name of the integer resource.
* @return The integer resource or {@code null} if there is no resource with the given name.
*/
public IntegerResource findInteger(final String name) {
return (IntegerResource) integers.getOrDefault(name, null);
public IntegerResource findInteger (final String name) {
return (IntegerResource) integers.getOrDefault (name, null);
}
/**
* Find a dimension value with the given name.
*
* @param name The name of the dimension resource.
* @return The dimension resource or {@code null} if there is no resource with the given name.
*/
public DimensionResource findDimension(final String name) {
return (DimensionResource) dimens.getOrDefault(name, null);
public DimensionResource findDimension (final String name) {
return (DimensionResource) dimens.getOrDefault (name, null);
}
/**
* Find a boolean value with the given name.
*
* @param name The name of the boolean resource.
* @return The boolean resource or {@code null} if there is no resource with the given name.
*/
public BooleanResource findBoolean(final String name) {
return (BooleanResource) booleans.getOrDefault(name, null);
public BooleanResource findBoolean (final String name) {
return (BooleanResource) booleans.getOrDefault (name, null);
}
/**
* Reads the contents of the given file and updates this table.
*
* @param file The file to sync with.
*/
public void syncWithFile(final File file) throws Exception {
final var start = System.currentTimeMillis();
final var factory = XmlPullParserFactory.newInstance();
final var parser = factory.newPullParser();
parser.setInput(new FileInputStream(file), null);
var event = parser.getEventType();
public void syncWithFile (final File file) throws Exception {
final var start = System.currentTimeMillis ();
final var factory = XmlPullParserFactory.newInstance ();
final var parser = factory.newPullParser ();
parser.setInput (new FileInputStream (file), null);
var event = parser.getEventType ();
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final var tag = parser.getName();
if ("resources".equals(tag)) {
event = parser.next();
final var tag = parser.getName ();
if ("resources".equals (tag)) {
event = parser.next ();
continue;
}
ValuesTable.readTag(tag, parser, this);
ValuesTable.readTag (tag, parser, this);
}
event = parser.next();
event = parser.next ();
}
LOG.debug(
LOG.debug (
"Syncing with file",
file.getName(),
file.getName (),
"took",
(System.currentTimeMillis() - start),
(System.currentTimeMillis () - start),
"ms");
}
@NonNull
@Override
public String toString() {
public String toString () {
return "ValuesTable{"
+ "strings="
+ strings
@@ -31,109 +31,110 @@ import java.util.regex.Pattern;
* @author Akash Yadav
*/
public class ValuesTableFactory {
private static final Map<File, ValuesTable> valueTables = new HashMap<>();
private static final Logger LOG = Logger.instance("ValuesTableFactory");
public static ValuesTable getTable(final File resDir) {
return valueTables.getOrDefault(resDir, null);
private static final Map<File, ValuesTable> valueTables = new HashMap<> ();
private static final Logger LOG = Logger.instance ("ValuesTableFactory");
public static ValuesTable getTable (final File resDir) {
return valueTables.getOrDefault (resDir, null);
}
public static Map<File, ValuesTable> getAllTables() {
public static Map<File, ValuesTable> getAllTables () {
return valueTables;
}
/**
* Setup this ValuesTableFactory with the given "res" directories.
*
* @param resDirs The resource directories.
*/
public static void setupWithResDirectories(File... resDirs) {
public static void setupWithResDirectories (File... resDirs) {
if (resDirs == null || resDirs.length <= 0) {
LOG.error("Cannot create value tables. No directories were specified.");
LOG.error ("Cannot create value tables. No directories were specified.");
return;
}
valueTables.clear();
final var start = System.currentTimeMillis();
valueTables.clear ();
final var start = System.currentTimeMillis ();
for (var res : resDirs) {
if (res == null || !res.exists()) {
if (res == null || !res.exists ()) {
continue;
}
final var table = createTable(res);
final var table = createTable (res);
if (table == null) {
continue;
}
valueTables.put(res, table);
valueTables.put (res, table);
}
LOG.debug(
String.format(
Locale.getDefault(),
LOG.debug (
String.format (
Locale.getDefault (),
"Created value tables for %d resource directories in %d ms",
resDirs.length,
(System.currentTimeMillis() - start)));
(System.currentTimeMillis () - start)));
}
/**
* Notify that the given file's contents were changed and the corresponding table must be
* updated with new values.
*
* @param file The file that was changed.
*/
public static void syncWithFile(final File file) {
public static void syncWithFile (final File file) {
if (file == null) {
LOG.error("Cannot update value tables. Given file is null.");
LOG.error ("Cannot update value tables. Given file is null.");
return;
}
final var pattern = Pattern.compile(".*/src/.*/res/values/(\\w|_)+\\.xml");
if (!pattern.matcher(file.getAbsolutePath()).matches()) {
final var pattern = Pattern.compile (".*/src/.*/res/values/(\\w|_)+\\.xml");
if (!pattern.matcher (file.getAbsolutePath ()).matches ()) {
// This file is not a values resource file.
return;
}
final var resDir =
Objects.requireNonNull(file.getParentFile()) /* values dir */
.getParentFile() /* res dir*/;
var table = valueTables.getOrDefault(resDir, null);
Objects.requireNonNull (file.getParentFile ()) /* values dir */
.getParentFile () /* res dir*/;
var table = valueTables.getOrDefault (resDir, null);
if (table == null) {
table = createTable(resDir);
table = createTable (resDir);
if (table != null) {
valueTables.put(resDir, table);
valueTables.put (resDir, table);
}
return;
}
try {
table.syncWithFile(file);
table.syncWithFile (file);
} catch (Throwable e) {
LOG.error("Failed to sync values table", e);
LOG.error ("Failed to sync values table", e);
}
}
private static ValuesTable createTable(File res) {
private static ValuesTable createTable (File res) {
try {
final var values = new File(res, "values");
if (!values.exists()) {
final var values = new File (res, "values");
if (!values.exists ()) {
return null;
}
var table = ValuesTable.forDirectory(values);
var table = ValuesTable.forDirectory (values);
if (table == null) {
throw new ParseException();
throw new ParseException ();
}
return table;
} catch (Throwable e) {
LOG.error("Failed to create values table", e);
LOG.error ("Failed to create values table", e);
return null;
}
}
static class ParseException extends RuntimeException {}
static class ParseException extends RuntimeException {
}
}
@@ -28,31 +28,31 @@ import java.util.Objects;
* @author Akash Yadav
*/
abstract class AbstractResource implements IResource {
private final String name;
private final String value;
public AbstractResource (@NonNull String name, @NonNull String value) {
public AbstractResource(@NonNull String name, @NonNull String value) {
this.name = name;
this.value = value;
}
@NonNull
@Override
public String getName () {
public String getName() {
return name;
}
@NonNull
@Override
public String getValue () {
public String getValue() {
return value;
}
@NonNull
@Override
public String toString () {
return getClass ().getSimpleName ()
public String toString() {
return getClass().getSimpleName()
+ "{"
+ "name='"
+ name
@@ -62,24 +62,24 @@ abstract class AbstractResource implements IResource {
+ '\''
+ '}';
}
@Override
public boolean equals (Object o) {
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AbstractResource)) {
return false;
}
AbstractResource that = (AbstractResource) o;
return Objects.equals (getName (), that.getName ())
&& Objects.equals (getValue (), that.getValue ());
return Objects.equals(getName(), that.getName())
&& Objects.equals(getValue(), that.getValue());
}
@Override
public int hashCode () {
return Objects.hash (getName (), getValue ());
public int hashCode() {
return Objects.hash(getName(), getValue());
}
}
@@ -19,12 +19,10 @@ package com.itsaky.inflater.values.models;
import androidx.annotation.NonNull;
/**
* @author Akash Yadav
*/
/** @author Akash Yadav */
public class BooleanResource extends AbstractResource {
public BooleanResource (@NonNull String name, @NonNull String value) {
public BooleanResource(@NonNull String name, @NonNull String value) {
super(name, value);
}
@@ -27,7 +27,7 @@ import androidx.annotation.NonNull;
*/
public class ColorResource extends AbstractResource {
public ColorResource (@NonNull String name, @NonNull String value) {
public ColorResource(@NonNull String name, @NonNull String value) {
super(name, value);
}
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
*/
public class DimensionResource extends AbstractResource {
public DimensionResource (@NonNull String name, @NonNull String value) {
public DimensionResource(@NonNull String name, @NonNull String value) {
super(name, value);
}
}
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
*/
public class IntegerResource extends AbstractResource {
public IntegerResource (@NonNull String name, @NonNull String value) {
public IntegerResource(@NonNull String name, @NonNull String value) {
super(name, value);
}
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
*/
public class StringResource extends AbstractResource {
public StringResource (@NonNull String name, @NonNull String value) {
public StringResource(@NonNull String name, @NonNull String value) {
super(name, value);
}
}