Fixed ILayoutInflater not inflating any views

This commit is contained in:
Akash Yadav
2021-12-06 17:36:58 +05:30
parent 466ac55e3a
commit 3b96d5750f
33 changed files with 135 additions and 847 deletions
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -17,8 +15,6 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide;
import android.content.Context;
@@ -68,6 +64,8 @@ public class DesignerActivity extends StudioActivity {
} catch (Throwable th) {
mBinding.realContainer.removeAllViews();
mBinding.realContainer.addView(createErrorText(th));
LOG.error (getString(R.string.err_cannot_inflate_layout));
}
}
@@ -88,92 +86,4 @@ public class DesignerActivity extends StudioActivity {
error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white));
return error;
}
private String printRClassHierarchy () throws IllegalArgumentException, IllegalAccessException {
return printClassHierarchy(android.R.class, 0);
}
private String printClassHierarchy (Class<?> clazz, int indent) throws IllegalArgumentException, IllegalAccessException {
final StringBuilder sb = new StringBuilder();
sb.append("class " + clazz.getName() + " {");
newLineIndent(sb, ++indent);
sb.append("fields : [");
newLineIndent(sb, ++indent);
printFields (sb, clazz, indent);
newLineIndent(sb, --indent);
sb.append("]");
newLineIndent(sb, indent);
sb.append("classes : [");
newLineIndent(sb, ++indent);
printClasses (sb, clazz, indent);
newLineIndent(sb, --indent);
sb.append("]");
return sb.toString();
}
private void newLineIndent(StringBuilder sb, int indent) {
sb.append("\n");
sb.append(repeat(" ", indent * 4));
}
private void printFields(StringBuilder sb, Class<?> clazz, int indent) throws IllegalArgumentException, IllegalAccessException {
boolean first = true;
for (Field f : clazz.getDeclaredFields()) {
if (!first) {
newLineIndent(sb, indent);
}
if(!f.isAccessible()) {
f.setAccessible(true);
}
sb.append(f.getName());
sb.append(" = ");
if (f.getType() == int[].class) {
printIntArr(sb, (int[]) f.get(null));
} else if (f.getType() == int.class) {
sb.append(f.get(null));
}
first = false;
}
}
private void printClasses(StringBuilder sb, Class<?> clazz, int indent) throws IllegalArgumentException, IllegalAccessException {
boolean first = true;
for (Class<?> c : clazz.getClasses()) {
if (!first) {
newLineIndent(sb, indent);
}
sb.append("class " + c.getSimpleName() + " [");
newLineIndent(sb, ++indent);
printFields(sb, c, indent);
newLineIndent(sb, --indent);
sb.append("]");
first = false;
}
}
private String repeat (String to, int till) {
String result = "";
for (int i=1;i<=till;i++) {
result += to;
}
return result;
}
private void printIntArr (StringBuilder sb, int[] arr) {
boolean first = true;
sb.append("[");
for (int i : arr) {
if (!first) {
sb.append(", ");
}
sb.append(i);
first = false;
}
sb.append("]");
}
}
@@ -95,7 +95,7 @@ public class DownloadActivity extends StudioActivity {
.build()
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
} catch (Throwable th){
LOG.error("Unable to start file picker", th);
LOG.error(getString (R.string.err_cannot_start_file_picker), th);
}
} else if(id == binding.install.getId()) {
if(choosenDir == null) {
@@ -134,10 +134,10 @@ public class DownloadActivity extends StudioActivity {
new Thread (reader).start();
} catch (DownloadActivity.InstallationException e) {
LOG.error("Installation error", e);
LOG.error(getString(R.string.err_installation), e);
onInstallationFailed(e.exitCode);
} catch (IOException e) {
LOG.error("Installation error", e);
LOG.error(getString(R.string.err_installation), e);
onInstallationFailed(5); // Exit code 5 : I/O Error
}
}
@@ -270,7 +270,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
saveAll(false /* No notification */);
saved = true;
} catch (Throwable th) {
LOG.error(":onPause(): Failed to save files", th);
LOG.error(getString(R.string.err_cannot_save_files), th);
saved = false;
}
@@ -1081,7 +1081,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
intent.putExtra(DesignerActivity.KEY_LAYOUT_PATH, mCurrentFile.getAbsolutePath());
startActivity(intent);
} catch (Throwable th) {
LOG.error("Unable to preview layout", th);
LOG.error(getString(R.string.err_cannot_preview_layout), th);
getApp().toast(R.string.msg_cannot_preview_layout, Toaster.Type.ERROR);
}
}
@@ -92,7 +92,7 @@ public class StudioApp extends BaseApplication {
mXmlCompletionService = new XMLCompletionService (mAttrInfo, mWidgetInfo);
} catch (Throwable th) {
LOG.error ("Unable to start XML completion service", th);
LOG.error (getString (com.itsaky.androidide.R.string.err_init_sdkinfo), th);
}
}, "SDK Information Loader").start();
}
@@ -33,6 +33,7 @@ import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import com.itsaky.androidide.app.StudioApp;
public class JavaAutoComplete implements AutoCompleteProvider {
@@ -66,10 +67,10 @@ public class JavaAutoComplete implements AutoCompleteProvider {
}
} catch (Throwable th) {
LOG.error("CompletionError", th);
LOG.error(StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_completion), th);
}
} else {
LOG.error("Cannot compute completions for Java file. No LanguageServer implementation found.");
LOG.error(StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_no_server_implementation));
}
return new ArrayList<CompletionItem>();
}
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -17,8 +15,6 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.lsp;
import androidx.annotation.RequiresApi;
@@ -80,4 +76,9 @@ public class AsynchronousSocketLauncher extends LSPClientLauncher {
CloseUtils.closeIOQuietly(serverSocket);
}
@Override
public String getLanguageCode() {
return "";
}
}
@@ -28,6 +28,7 @@ import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import java.util.Locale;
import com.itsaky.androidide.app.StudioApp;
/**
* Launches a client for a Language server and handles connection.
@@ -83,7 +84,7 @@ public abstract class LSPClientLauncher extends Thread {
try {
launch();
} catch (Throwable th) {
LOG.error("Error starting language server", th);
LOG.error(StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_cannot_start_server, getLanguageCode()), th);
}
}
@@ -111,6 +112,11 @@ public abstract class LSPClientLauncher extends Thread {
*/
public abstract void shutdown();
/**
* Get the language code for the server
*/
public abstract String getLanguageCode();
/**
* A listener to listen for language server start event.
*/
@@ -195,7 +201,7 @@ public abstract class LSPClientLauncher extends Thread {
this.out.write(data.data);
this.out.flush();
} catch (Throwable e) {
LOG.error ("Failed to write to LSP4J socket", e);
LOG.error (StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_cannot_write_socket), e);
}
}
}
@@ -25,6 +25,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Future;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import com.itsaky.androidide.app.StudioApp;
public class StandardStreamsLauncher extends LSPClientLauncher {
@@ -56,8 +57,7 @@ public class StandardStreamsLauncher extends LSPClientLauncher {
listening.get();
} catch (Throwable th) {
LOG.error (
"---> StandardStreamsLauncher <---",
"An error occured while waiting for language server process to stop",
StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_server_disconnected),
th
);
}
@@ -79,4 +79,9 @@ public class StandardStreamsLauncher extends LSPClientLauncher {
CloseUtils.closeIO(in, out);
}
@Override
public String getLanguageCode() {
return this.langCode;
}
}
@@ -78,7 +78,7 @@ public class JLSHandler implements LSPHandler {
mLauncher.start();
} catch (Throwable th) {
LOG.error("Unable to start Java Language server", th);
LOG.error(StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_cannot_start_server, LSPProvider.LANGUAGE_JAVA), th);
}
}
@@ -88,7 +88,7 @@ public class JLSHandler implements LSPHandler {
final IDELanguageServer server = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_JAVA);
if (server == null) {
LOG.error ("Cannot initialize Java Language Server. Server instance is null!");
LOG.error (StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_no_server_implementation));
return Optional.empty();
}
@@ -119,7 +119,7 @@ public class JLSHandler implements LSPHandler {
}
return Optional.of(result);
} catch (Throwable e) {
LOG.error ("An error occurred while initializing language server.", e);
LOG.error (StudioApp.getInstance().getString(com.itsaky.androidide.R.string.err_init_server), e);
return Optional.empty();
}
}
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -17,8 +15,6 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.services.builder;
import com.itsaky.androidide.project.IDEModule;
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -17,7 +15,6 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.services.builder;
import androidx.annotation.StringRes;
@@ -151,12 +148,12 @@ public class IDEService {
text = line.replace(app.getRootDir().getAbsolutePath(), "HOME");
}
if (line.contains(app.getRootDir().getParentFile().getAbsolutePath())) {
text = line.replace(app.getRootDir().getParentFile().getAbsolutePath(), "ROOT_DIR");
text = line.replace(app.getRootDir().getParentFile().getAbsolutePath(), "ROOT");
}
listener.appendOutput(currentTask, text);
if(line.contains(RUN_TASK)) {
if(line.startsWith(RUN_TASK)) {
listener.onRunTask(currentTask, line.trim());
} else if(line.startsWith(STARTING_DAEMON)) {
listener.onStartingGradleDaemon(currentTask);
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -17,8 +15,6 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.services.builder;
import com.blankj.utilcode.util.ThreadUtils;
+15
View File
@@ -275,4 +275,19 @@
<string name="msg_editor_font_size">Please choose default font size of editor :</string>
<string name="msg_clear_cache">This will delete caches directory created by Gradle. If you delete it, the next build may take a bit longer than normal as Gradle will download required files again.\nIMPORTANT: This will also delete AAPT2. You will need to reinstall it otherwise you\'ll end up with a build failure.\n\nAre you sure you want to delete caches?</string>
<!-- Error Messages used while Logging -->
<string name="err_installation">Installation error</string>
<string name="err_completion">Completion error</string>
<string name="err_no_server_implementation">No LanguageServer implementation found.</string>
<string name="err_init_server">Error initializing language server</string>
<string name="err_init_sdkinfo">Unable to initialize SDK Info</string>
<string name="err_cannot_start_file_picker">Unable to start file picker</string>
<string name="err_cannot_parse_apk_metadata">Failed to parse APK metadata</string>
<string name="err_cannot_save_files">Unable to save files</string>
<string name="err_cannot_preview_layout">Unable to preview layout</string>
<string name="err_cannot_inflate_layout">Unable to inflate layout</string>
<string name="err_cannot_start_server">Unable to start Language Server for: %s</string>
<string name="err_cannot_write_socket">Failed to write to Socket stream</string>
<string name="err_server_disconnected">Language server process unexpectedly exited.</string>
</resources>
+1
View File
@@ -32,5 +32,6 @@ dependencies {
implementation 'com.blankj:utilcodex:1.30.6'
implementation 'org.jsoup:jsoup:1.14.2'
implementation project (":common")
api project (':sdk-info')
}
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -27,8 +25,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import com.itsaky.androidide.ui.resources.ResourceTable;
import com.itsaky.androidide.ui.resources.ResourceTableFactory;
import com.itsaky.androidide.ui.util.Preconditions;
import com.itsaky.androidide.ui.view.IAttribute;
import com.itsaky.androidide.ui.view.IAttributeAdapter;
@@ -65,7 +61,6 @@ class XMLLayoutInflater extends BaseLayoutInflater {
private final Set<File> resDirs;
private final AttrInfo attrInfo;
private final WidgetInfo widgetInfo;
private final ResourceTable frameworkResourceTable;
private final IResourceFinder resFinder;
private ContextProvider contextProvider;
@@ -94,9 +89,8 @@ class XMLLayoutInflater extends BaseLayoutInflater {
this.widgetInfo = config.widgetInfo;
this.resFinder = config.resFinder;
this.contextProvider = config.contextProvider;
this.frameworkResourceTable = ResourceTableFactory.newFrameworkResourceTable();
Preconditions.assertAllNotNull("LayoutInflater parameters cannot be null", resDirs, attrInfo, widgetInfo, resFinder, frameworkResourceTable);
Preconditions.assertAllNotNull("LayoutInflater parameters cannot be null", resDirs, attrInfo, widgetInfo, resFinder);
}
@Override
@@ -1,122 +0,0 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.ui.resources;
import androidx.annotation.NonNull;
import java.util.regex.Pattern;
public class AttributeResource {
public static final String ANDROID_NS = "http://schemas.android.com/apk/res/android";
public static final String ANDROID_RES_NS_PREFIX = "http://schemas.android.com/apk/res/";
public static final String RES_AUTO_NS_URI = "http://schemas.android.com/apk/res-auto";
public static final String NULL_VALUE = "@null";
public static final String EMPTY_VALUE = "@empty";
public static final Pattern IS_RESOURCE_REFERENCE = Pattern.compile("^\\s*@");
public final @NonNull ResName resName;
public final @NonNull String value;
public final @NonNull String trimmedValue;
public final @NonNull String contextPackageName;
private final Integer referenceResId;
public AttributeResource(@NonNull ResName resName, @NonNull String value, @NonNull String contextPackageName) {
this(resName, value, contextPackageName, null);
}
public AttributeResource(@NonNull ResName resName, @NonNull String value, @NonNull String contextPackageName, Integer referenceResId) {
this.referenceResId = referenceResId;
if (!resName.type.equals("attr")) throw new IllegalStateException("\"" + resName.getFullyQualifiedName() + "\" unexpected");
this.resName = resName;
this.value = value;
this.trimmedValue = value.trim();
this.contextPackageName = contextPackageName;
}
public boolean isResourceReference() {
return isResourceReference(trimmedValue);
}
public @NonNull ResName getResourceReference() {
if (!isResourceReference()) throw new RuntimeException("not a resource reference: " + this);
return ResName.qualifyResName(deref(trimmedValue).replace("+", ""), contextPackageName, "style");
}
public boolean isStyleReference() {
return isStyleReference(trimmedValue);
}
public ResName getStyleReference() {
if (!isStyleReference()) throw new RuntimeException("not a style reference: " + this);
return ResName.qualifyResName(value.substring(1), contextPackageName, "attr");
}
public boolean isNull() {
return NULL_VALUE.equals(trimmedValue);
}
public boolean isEmpty() {
return EMPTY_VALUE.equals(trimmedValue);
}
@Override
public String toString() {
return "Attribute{" +
"name='" + resName + '\'' +
", value='" + value + '\'' +
", contextPackageName='" + contextPackageName + '\'' +
'}';
}
public static boolean isResourceReference(String value) {
return IS_RESOURCE_REFERENCE.matcher(value).find() && !isNull(value);
}
public static @NonNull ResName getResourceReference(String value, String defPackage, String defType) {
if (!isResourceReference(value)) throw new IllegalArgumentException("not a resource reference: " + value);
return ResName.qualifyResName(deref(value).replace("+", ""), defPackage, defType);
}
private static @NonNull String deref(@NonNull String value) {
return value.substring(value.indexOf('@') + 1);
}
public static boolean isStyleReference(String value) {
return value.startsWith("?");
}
public static ResName getStyleReference(String value, String defPackage, String defType) {
if (!isStyleReference(value)) throw new IllegalArgumentException("not a style reference: " + value);
return ResName.qualifyResName(value.substring(1), defPackage, defType);
}
public static boolean isNull(String value) {
return NULL_VALUE.equals(value);
}
public static boolean isEmpty(String value) {
return EMPTY_VALUE.equals(value);
}
public Integer getReferenceResId() {
return referenceResId;
}
}
@@ -1,189 +0,0 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.ui.resources;
import androidx.annotation.NonNull;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ResName {
public static final String ID_TYPE = "id";
private static final Pattern FQN_PATTERN = Pattern.compile("^([^:]*):([^/]+)/(.+)$");
private static final int NAMESPACE = 1;
private static final int TYPE = 2;
private static final int NAME = 3;
public final @NonNull String packageName;
public final @NonNull String type;
public final @NonNull String name;
public final int hashCode;
public ResName(@NonNull String packageName, @NonNull String type, @NonNull String name) {
this.packageName = packageName;
this.type = type.trim();
this.name = name.trim();
hashCode = computeHashCode();
}
public ResName(@NonNull String fullyQualifiedName) {
Matcher matcher = FQN_PATTERN.matcher(fullyQualifiedName.trim());
if (!matcher.find()) {
throw new IllegalStateException("\"" + fullyQualifiedName + "\" is not fully qualified");
}
packageName = matcher.group(NAMESPACE);
type = matcher.group(TYPE).trim();
name = matcher.group(NAME).trim();
hashCode = computeHashCode();
if (packageName.equals("xmlns")) throw new IllegalStateException("\"" + fullyQualifiedName + "\" unexpected");
}
/**
* @return null if the resource could not be qualified.
*/
public static String qualifyResourceName(@NonNull String possiblyQualifiedResourceName, String defaultPackageName, String defaultType) {
ResName resName = qualifyResName(possiblyQualifiedResourceName, defaultPackageName, defaultType);
return resName != null ? resName.getFullyQualifiedName() : null;
}
public static ResName qualifyResName(@NonNull String possiblyQualifiedResourceName, ResName defaults) {
return qualifyResName(possiblyQualifiedResourceName, defaults.packageName, defaults.type);
}
public static ResName qualifyResName(@NonNull String possiblyQualifiedResourceName, String defaultPackageName, String defaultType) {
int indexOfColon = possiblyQualifiedResourceName.indexOf(':');
int indexOfSlash = possiblyQualifiedResourceName.indexOf('/');
String type = null;
String packageName = null;
String name = possiblyQualifiedResourceName;
if (indexOfColon > indexOfSlash) {
if (indexOfSlash > 0) {
type = possiblyQualifiedResourceName.substring(0, indexOfSlash);
}
packageName = possiblyQualifiedResourceName.substring(indexOfSlash + 1, indexOfColon);
name = possiblyQualifiedResourceName.substring(indexOfColon + 1);
} else if (indexOfSlash > indexOfColon) {
if (indexOfColon > 0) {
packageName = possiblyQualifiedResourceName.substring(0, indexOfColon);
}
type = possiblyQualifiedResourceName.substring(indexOfColon + 1, indexOfSlash);
name = possiblyQualifiedResourceName.substring(indexOfSlash + 1);
}
if ((type == null && defaultType == null) || (packageName == null && defaultPackageName == null)) {
return null;
}
if (packageName == null) {
packageName = defaultPackageName;
} else if ("*android".equals(packageName)) {
packageName = "android";
}
return new ResName(packageName, type == null ? defaultType : type, name);
}
public static String qualifyResName(String possiblyQualifiedResourceName, String contextPackageName) {
if (possiblyQualifiedResourceName == null) {
return null;
}
if (AttributeResource.isNull(possiblyQualifiedResourceName)) {
return null;
}
// Was not able to fully qualify the resource name
String fullyQualifiedResourceName = qualifyResourceName(possiblyQualifiedResourceName, contextPackageName, null);
if (fullyQualifiedResourceName == null) {
return null;
}
return fullyQualifiedResourceName.replaceAll("[@+]", "");
}
public static ResName qualifyFromFilePath(@NonNull final String packageName, @NonNull final String filePath) {
final File file = new File(filePath);
final String type = file.getParentFile().getName().split("-", 0)[0];
final String name = baseNameFor(file.getName());
return new ResName(packageName, type, name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResName resName = (ResName) o;
if (hashCode() != resName.hashCode()) return false;
if (!packageName.equals(resName.packageName)) return false;
if (!type.equals(resName.type)) return false;
if (!name.equals(resName.name)) return false;
return true;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public String toString() {
return "ResName{" + getFullyQualifiedName() + "}";
}
public String getFullyQualifiedName() {
return packageName + ":" + type + "/" + name;
}
public String getNamespaceUri() {
return "http://schemas.android.com/apk/res/" + packageName;
}
public ResName withPackageName(String packageName) {
if (packageName.equals(this.packageName)) return this;
return new ResName(packageName, type, name);
}
public void mustBe(String expectedType) {
if (!type.equals(expectedType)) {
throw new RuntimeException("expected " + getFullyQualifiedName() + " to be a " + expectedType + ", is a " + type);
}
}
private int computeHashCode() {
int result = packageName.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + name.hashCode();
return result;
}
static String baseNameFor(String name) {
int dotIndex = name.indexOf(".");
return dotIndex >= 0 ? name.substring(0, dotIndex) : name;
}
}
@@ -1,80 +0,0 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.ui.resources;
import java.util.HashMap;
import java.util.Map;
public class ResourceIdGenerator {
private final Map<String, TypeTracker> typeInfo = new HashMap<>();
private int packageIdentifier;
private static class TypeTracker {
private int typeIdentifier;
private int currentMaxEntry;
TypeTracker(int typeIdentifier) {
this.typeIdentifier = typeIdentifier;
}
void record(int entryIdentifier) {
currentMaxEntry = Math.max(currentMaxEntry, entryIdentifier);
}
public int getFreeIdentifier() {
return ++currentMaxEntry;
}
public int getTypeIdentifier() {
return typeIdentifier;
}
}
ResourceIdGenerator(int packageIdentifier) {
this.packageIdentifier = packageIdentifier;
}
public void record(int resId, String type, String name) {
TypeTracker typeTracker = typeInfo.get(type);
if (typeTracker == null) {
typeTracker = new TypeTracker(ResourceIds.getTypeIdentifier(resId));
typeInfo.put(type, typeTracker);
}
typeTracker.record(ResourceIds.getEntryIdentifier(resId));
}
public int generate(String type, String name) {
TypeTracker typeTracker = typeInfo.get(type);
if (typeTracker == null) {
typeTracker = new TypeTracker(getNextFreeTypeIdentifier());
typeInfo.put(type, typeTracker);
}
return ResourceIds.makeIdentifer(packageIdentifier, typeTracker.getTypeIdentifier(), typeTracker.getFreeIdentifier());
}
private int getNextFreeTypeIdentifier() {
int result = 0;
for (TypeTracker typeTracker : typeInfo.values()) {
result = Math.max(result, typeTracker.getTypeIdentifier());
}
return ++result;
}
}
@@ -1,42 +0,0 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.ui.resources;
public class ResourceIds {
public static boolean isFrameworkResource(int resId) {
return ((resId >>> 24) == 0x1);
}
public static int getPackageIdentifier(int resId) {
return (resId >>> 24);
}
public static int getTypeIdentifier(int resId) {
return (resId & 0x00FF0000) >>> 16;
}
public static int getEntryIdentifier(int resId) {
return resId & 0x0000FFFF;
}
public static int makeIdentifer(int packageIdentifier, int typeIdentifier, int entryIdenifier) {
return packageIdentifier << 24 | typeIdentifier << 16 | entryIdenifier;
}
}
@@ -1,70 +0,0 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.ui.resources;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class ResourceTable {
private int packageIdentifier = 0;
private final String packageName;
private final BiMap<Integer, ResName> resourceTable = HashBiMap.create();
private final ResourceIdGenerator androidResourceIdGenerator = new ResourceIdGenerator(0x01);
public ResourceTable(String packageName) {
this.packageName = packageName;
}
public Integer getResourceId(ResName resName) {
Integer id = resourceTable.inverse().get(resName);
if (id == null && resName != null && resName.name.contains(".")) {
id = resourceTable.inverse().get(new ResName(resName.packageName, resName.type, underscorize(resName.name)));
}
return id != null ? id : 0;
}
public int getPackageIdentifier() {
return packageIdentifier;
}
void addResource(int resId, String type, String name) {
if (ResourceIds.isFrameworkResource(resId)) {
androidResourceIdGenerator.record(resId, type, name);
}
ResName resName = new ResName(packageName, type, name);
int resIdPackageIdentifier = ResourceIds.getPackageIdentifier(resId);
if (getPackageIdentifier() == 0) {
this.packageIdentifier = resIdPackageIdentifier;
} else if (getPackageIdentifier() != resIdPackageIdentifier) {
throw new IllegalArgumentException("Incompatible package for " + packageName + ":" + type + "/" + name + " with resId " + resIdPackageIdentifier + " to ResourceIndex with packageIdentifier " + getPackageIdentifier());
}
ResName existingEntry = resourceTable.put(resId, resName);
if (existingEntry != null && !existingEntry.equals(resName)) {
throw new IllegalArgumentException("ResId " + Integer.toHexString(resId) + " mapped to both " + resName + " and " + existingEntry);
}
}
private static String underscorize(String s) {
return s == null ? null : s.replace('.', '_');
}
}
@@ -1,123 +0,0 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
package com.itsaky.androidide.ui.resources;
import androidx.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ResourceTableFactory {
@Nullable
public static ResourceTable newFrameworkResourceTable () {
final ResourceTable table = new ResourceTable ("android");
final Class<?> androidR = android.R.class;
try {
processResources(table, androidR);
} catch (IllegalArgumentException|IllegalAccessException e) {
return null;
}
return table;
}
private static void processResources(ResourceTable table, Class<?> rClass) throws IllegalArgumentException, IllegalAccessException {
final Map<String, List<Integer>> styleableMap = new HashMap<>();
for (Class<?> innerClass : rClass.getClasses()) {
if ("styleable".equals(innerClass.getSimpleName())) {
for (final Field attr : innerClass.getDeclaredFields()) {
final Class<?> attrClassType = attr.getType();
if (attrClassType == int[].class) { // <declare-styleable> declaration
addStyleableGroup (styleableMap, attr);
} else if (attrClassType == int.class) { // Member of <declare-styleable>
addStyleableMember (table, styleableMap, attr);
}
}
} else {
for (Field field : innerClass.getDeclaredFields()) {
if (field.getType().equals(Integer.TYPE) && Modifier.isStatic(field.getModifiers())) {
int id = field.getInt(null);
String resourceName = field.getName();
if (id != 0) {
table.addResource(id, innerClass.getSimpleName(), resourceName);
}
}
}
}
}
}
private static void addStyleableGroup(final Map<String, List<Integer>> styleableMap, final Field attr) throws IllegalArgumentException, IllegalAccessException {
final String name = attr.getName();
List<Integer> members = styleableMap.get(name);
if (members == null) {
members = new ArrayList<>();
}
final int[] mm = (int[]) attr.get(null);
if (mm == null || mm.length <= 0) {
return;
}
for (int i=0;i<mm.length;i++) {
members.add(mm[i]);
}
styleableMap.put(name, members);
}
private static void addStyleableMember(final ResourceTable table, final Map<String, List<Integer>> styleableMap, final Field attr) throws IllegalArgumentException, IllegalAccessException {
final String name = attr.getName();
final int separator = findGroupAndAttrNameSeparator (styleableMap, name); // If this is a <declare-styleable> member, then the name will definitely contain am underscore
final String groupName = name.substring(0, separator);
final String attrName = name.substring(separator + 1);
final List<Integer> members = styleableMap.get(groupName);
if (members == null) {
throw new IllegalStateException ("No styleable declared for " + groupName + "\nstyleableMap = " + styleableMap); // No entry for this name
}
final int index = (int) attr.get(null);
if (index < 0 || index >= members.size()) {
throw new IndexOutOfBoundsException ("Index " + index + " is out of bounds (size=" + members.size() + "). name=" + name + ", grpName=" + groupName + ", attrName=" + attrName);
}
table.addResource(members.get(index), "attr", attrName);
}
private static int findGroupAndAttrNameSeparator(Map<String, List<Integer>> styleableMap, String name) {
int index = name.length() - 1;
while (index > 0) {
int i = name.lastIndexOf("_", index);
String group = name.substring(0, i);
List<Integer> mm = styleableMap.get(group);
if (mm != null) {
return i;
} else {
index = i - 1;
}
}
return -1;
}
}
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -1,7 +1,5 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* 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
@@ -36,6 +34,7 @@ import androidx.core.view.GravityCompat;
import com.itsaky.androidide.ui.inflater.IResourceFinder;
import com.itsaky.androidide.ui.view.IAttribute;
import com.itsaky.androidide.ui.view.IAttributeAdapter;
import com.itsaky.androidide.utils.Logger;
import java.io.File;
import java.lang.reflect.Field;
import java.util.regex.Pattern;
@@ -47,34 +46,40 @@ public class BaseViewAttrAdapter implements IAttributeAdapter {
private final Pattern HEX_COLOR = Pattern.compile("#[a-fA-F0-9]{6,8}");
protected static final Logger LOG = Logger.instance ("BaseViewAttrAdapter");
public static final int ENUM_NOT_FOUND = -4116;
@Override
public boolean isApplicableTo(View view) {
return true;
return true; // Can be applied to any view
}
@Override
public boolean apply(IAttribute attribute, View view, IResourceFinder resFinder) {
final String namespace = attribute.getNamespace();
final String name = attribute.getNamespace();
final String name = attribute.getAttributeName();
final String value = attribute.getValue();
final ViewGroup.LayoutParams params = view.getLayoutParams();
final Resources res = view.getResources();
final DisplayMetrics dm = res.getDisplayMetrics();
final Context ctx = view.getContext();
if (canHandleNamespace(namespace)) {
if (!canHandleNamespace(namespace)) {
LOG.error("Cannot handle namespace: " + namespace);
return false;
}
LOG.debug ("Adding attribute", attribute);
boolean handled = true;
switch (name) {
case "layout_height" :
params.height = parseDimension (value, -2, dm, resFinder);
LOG.error("Added layout_height", params.height);
break;
case "layout_width" :
params.width = parseDimension (value, -2, dm, resFinder);
LOG.error("Added layout_width", params.width);
break;
case "alpha" :
view.setAlpha(parseFloat(value));
@@ -221,33 +226,12 @@ public class BaseViewAttrAdapter implements IAttributeAdapter {
break;
}
// In case these attributes are related to margins
// We need to apply them by casting the params to ViewGroup.MarginLayoutParams
if (!handled && params instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) params;
switch (name) {
case "layout_margin" :
final int margin = parseDimension(value, 0, dm, resFinder);
marginParams.setMargins(margin, margin, margin, margin);
break;
case "layout_marginLeft" :
marginParams.leftMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginTop" :
marginParams.topMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginRight" :
marginParams.rightMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginBottom" :
marginParams.bottomMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginStart" :
marginParams.setMarginStart(parseDimension(value, 0, dm, resFinder));
break;
case "layout_marginEnd" :
marginParams.setMarginEnd(parseDimension(value, 0, dm, resFinder));
break;
// ----- Handle attributes related to parent ------
if (!handled) {
// Layout Margins
if (params instanceof ViewGroup.MarginLayoutParams) {
handleMarginParams((ViewGroup.MarginLayoutParams) params, name, value, dm, resFinder);
}
}
@@ -255,6 +239,33 @@ public class BaseViewAttrAdapter implements IAttributeAdapter {
return handled;
}
private void handleMarginParams(ViewGroup.MarginLayoutParams params, String name, String value, DisplayMetrics dm, IResourceFinder resFinder) {
switch (name) {
case "layout_margin" :
final int margin = parseDimension(value, 0, dm, resFinder);
params.setMargins(margin, margin, margin, margin);
break;
case "layout_marginLeft" :
params.leftMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginTop" :
params.topMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginRight" :
params.rightMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginBottom" :
params.bottomMargin = parseDimension(value, 0, dm, resFinder);
break;
case "layout_marginStart" :
params.setMarginStart(parseDimension(value, 0, dm, resFinder));
break;
case "layout_marginEnd" :
params.setMarginEnd(parseDimension(value, 0, dm, resFinder));
break;
}
}
protected boolean canHandleNamespace(String namespace) {
// TODO Compare namespace URI instead of name
@@ -428,7 +439,7 @@ public class BaseViewAttrAdapter implements IAttributeAdapter {
case "drawable" :
return ContextCompat.getDrawable(ctx, id);
default :
return newTransparentDrawable();
return newTransparentDrawable();
}
}
@@ -462,7 +473,9 @@ public class BaseViewAttrAdapter implements IAttributeAdapter {
if (HEX_COLOR.matcher(color).matches()) {
try {
return Color.parseColor(color);
} catch (Throwable th) {}
} catch (Throwable th) {
// Ignored
}
} else if (color.startsWith("@color/")) {
return parseColor(resFinder.findColor(color.substring("@color/".length())), resFinder, ctx);
} else if (color.startsWith("@android:color/")) {
@@ -43,7 +43,7 @@ public class BaseViewGroupAttrAdapter extends BaseViewAttrAdapter {
}
final ViewGroup group = (ViewGroup) view;
final String namespace = attribute.getNamespace();
final String name = attribute.getNamespace();
final String name = attribute.getAttributeName();
final String value = attribute.getValue();
if (!canHandleNamespace(namespace)) {
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -43,7 +41,7 @@ public class LinearLayoutAttrAdapter extends BaseViewGroupAttrAdapter {
final LinearLayout linear = (LinearLayout) view;
final String namespace = attribute.getNamespace();
final String name = attribute.getNamespace();
final String name = attribute.getAttributeName();
final String value = attribute.getValue();
if (!canHandleNamespace(namespace)) {
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -50,7 +48,7 @@ public class TextViewAttrAdapter extends BaseViewAttrAdapter {
final TextView text = (TextView) view;
final String namespace = attribute.getNamespace();
final String name = attribute.getNamespace();
final String name = attribute.getAttributeName();
final String value = attribute.getValue();
final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
final Resources res = view.getResources();
@@ -59,7 +57,7 @@ public class TextViewAttrAdapter extends BaseViewAttrAdapter {
final Drawable[] drawables = text.getCompoundDrawables();
final Drawable[] drawablesRelative = text.getCompoundDrawablesRelative();
if (canHandleNamespace(namespace)) {
if (!canHandleNamespace(namespace)) {
return false;
}
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -29,10 +27,11 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Objects;
public abstract class BaseView implements IView {
protected final List<IAttribute> attributes = new ArrayList<>();
protected final Set<IAttribute> attributes = new HashSet<>();
protected final Set<IAttributeAdapter> attrAdapters = new HashSet<>();
protected final String qualifiedName;
@@ -83,14 +82,13 @@ public abstract class BaseView implements IView {
this.attributes.add(attr);
for (IAttributeAdapter adapter : attrAdapters) {
if (adapter.isApplicableTo(asView())) {
if (adapter.apply(attr, asView(), resFinder)) {
if (adapter.isApplicableTo(asView())
&& adapter.apply(attr, asView(), resFinder)) {
break;
}
}
}
}
@Override
public void removeAttribute(IAttribute attr) {
this.attributes.remove(attr);
@@ -121,16 +119,18 @@ public abstract class BaseView implements IView {
return false;
}
if (obj instanceof IView) {
IView that = (IView) obj;
return this.asView().equals(that.asView());
}
return false;
return this.hashCode() == obj.hashCode();
}
@Override
public int hashCode() {
return asView().hashCode();
return Objects.hash(
attributes,
attrAdapters,
qualifiedName,
view,
parent,
isPlaceholder
);
}
}
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -24,6 +22,7 @@ import com.itsaky.androidide.ui.view.IAttribute;
import com.itsaky.androidide.ui.view.IFixedValue;
import java.util.HashSet;
import java.util.Set;
import java.util.Objects;
public class UiAttribute implements IAttribute {
@@ -76,18 +75,14 @@ public class UiAttribute implements IAttribute {
@Override
public boolean equals(Object obj) {
if (obj instanceof UiAttribute) {
UiAttribute that = (UiAttribute) obj;
return this.getNamespace().equals(that.getNamespace())
&& this.getAttributeName().equals(that.getAttributeName())
&& this.getValue().equals(that.getValue())
&& this.getFixedValues().equals(that.getFixedValues());
if (obj == null) {
return false;
}
return false;
return this.hashCode() == obj.hashCode();
}
@Override
public int hashCode() {
return toString().hashCode();
return Objects.hash(namespace, name, value, fixedValues);
}
}
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -1,8 +1,6 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or