blob: 6f467c24071a7cd76dd513f307e1d87c1642fbfb [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.rendering;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
import static com.android.SdkConstants.DOT_PNG;
import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.WIDGET_PKG_PREFIX;
import static com.intellij.lang.annotation.HighlightSeverity.ERROR;
import static com.intellij.lang.annotation.HighlightSeverity.WARNING;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.tools.idea.flags.StudioFlags;
import com.android.tools.idea.gradle.project.BuildSettings;
import com.android.tools.idea.gradle.util.BuildMode;
import com.android.tools.idea.lint.UpgradeConstraintLayoutFix;
import com.android.tools.idea.model.AndroidModel;
import com.android.utils.HtmlBuilder;
import com.android.utils.XmlUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.concurrent.GuardedBy;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.xmlpull.v1.XmlPullParserException;
/**
* A {@link LayoutLog} which records the problems it encounters and offers them as a
* single summary at the end
*/
public class RenderLogger extends LayoutLog implements IRenderLogger {
public static final String TAG_MISSING_DIMENSION = "missing.dimension";
public static final String TAG_MISSING_FRAGMENT = "missing.fragment";
public static final String TAG_STILL_BUILDING = "project.building";
static final Logger LOG = Logger.getInstance("#com.android.tools.idea.rendering.RenderLogger");
/**
* Whether render errors should be sent to the IDE log. We generally don't want this, since if for
* example a custom view generates an error, it will go to the IDE log, which will interpret it as an
* IntelliJ error, and will blink the bottom right exception icon and offer to submit an exception
* etc. All these errors should be routed through the render error panel instead. However, since the
* render error panel does massage and collate the exceptions etc quite a bit, this flag is here
* in case we need to ask bug submitters to generate full, raw exceptions.
*/
@SuppressWarnings("UseOfArchaicSystemPropertyAccessors")
private static final boolean LOG_ALL = Boolean.getBoolean("adt.renderLog") ||
(ApplicationManager.getApplication() != null &&
ApplicationManager.getApplication().isUnitTestMode());
/**
* Maximum number of RenderProblems that the logger will save. After hitting this limit, no more errors will be added and an additional
* "too many problems" RenderProblem will be added.
*/
@VisibleForTesting
static final int RENDER_PROBLEMS_LIMIT =
Integer.getInteger("com.android.tools.idea.rendering.RENDER_PROBLEMS_LIMIT", 100);
/**
* Maximum size allowed for StackOverflowError exceptions reported by Layoutlib. The exception stacktrace will be rewritten to
* have STACK_OVERFLOW_TRACE_LIMIT/2 elements from the top and STACK_OVERFLOW_TRACE_LIMIT/2 from the bottom.
*/
@VisibleForTesting
static final int STACK_OVERFLOW_TRACE_LIMIT =
Integer.getInteger("com.android.tools.idea.rendering.STACK_OVERFLOW_TRACE_LIMIT", 100);
private static Set<String> ourIgnoredFidelityWarnings;
private static boolean ourIgnoreAllFidelityWarnings;
private static boolean ourIgnoreFragments;
private final Module myModule;
private final String myName;
private Set<String> myFidelityWarningStrings;
private boolean myHaveExceptions;
private Multiset<String> myTags;
@GuardedBy("myMessages")
private final List<RenderProblem> myMessages = new ArrayList<>();
@GuardedBy("myMessages")
private int myMessagesOverflowCounter;
private List<RenderProblem> myFidelityWarnings;
private Set<String> myMissingClasses;
private Map<String, Throwable> myBrokenClasses;
private Map<String, Throwable> myClassesWithIncorrectFormat;
private String myResourceClass;
private boolean myMissingResourceClass;
private boolean myHasLoadedClasses;
private HtmlLinkManager myLinkManager;
private boolean myMissingSize;
private List<String> myMissingFragments;
private Object myCredential;
/**
* Construct a logger for the given named layout. Don't call this method directly; obtain via {@link RenderService}.
*/
public RenderLogger(@Nullable String name, @Nullable Module module, @Nullable Object credential) {
myName = name;
myModule = module;
myCredential = credential;
}
/**
* Construct a logger for the given named layout. Don't call this method directly; obtain via {@link RenderService}.
*/
public RenderLogger(@Nullable String name, @Nullable Module module) {
this(name, module, null);
}
/**
* Clears all the fidelity warning ignores.
* @see #ignoreAllFidelityWarnings()
* @see #ignoreFidelityWarning(Object)
*/
@VisibleForTesting
static void resetFidelityErrorsFilters() {
ourIgnoreAllFidelityWarnings = false;
if (ourIgnoredFidelityWarnings != null) {
ourIgnoredFidelityWarnings.clear();
}
}
/**
* Ignore the given render fidelity warning for the current session
*
* @param clientData the client data stashed on the render problem
*/
public static void ignoreFidelityWarning(@NotNull Object clientData) {
if (ourIgnoredFidelityWarnings == null) {
ourIgnoredFidelityWarnings = new HashSet<>();
}
ourIgnoredFidelityWarnings.add((String)clientData);
}
public static void ignoreAllFidelityWarnings() {
ourIgnoreAllFidelityWarnings = true;
}
public static void ignoreFragments() {
ourIgnoreFragments = true;
}
@NotNull
private static String describe(@Nullable String message, @Nullable Throwable throwable) {
if (StringUtil.isEmptyOrSpaces(message)) {
return throwable != null && throwable.getMessage() != null ? throwable.getMessage() : "";
}
return message;
}
static boolean isIssue164378(@Nullable Throwable throwable) {
if (throwable instanceof NoSuchFieldError) {
StackTraceElement[] stackTrace = throwable.getStackTrace();
if (stackTrace.length >= 1 && stackTrace[0].getClassName().startsWith("android.support")) {
return true;
}
}
return false;
}
@Nullable
public Module getModule() {
return myModule;
}
// ---- extends LayoutLog ----
@Nullable
public Project getProject() {
if (myModule != null) {
return myModule.getProject();
}
return null;
}
private void logMessageToIdeaLog(@NotNull String message, @Nullable Throwable t) {
String logMessage;
if (t == null) {
logMessage = message;
}
else {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
writer.println(t.getMessage());
t.printStackTrace(writer);
logMessage = message + "\n" + stringWriter.toString();
}
boolean token = RenderSecurityManager.enterSafeRegion(myCredential);
try {
LOG.debug(logMessage);
}
finally {
RenderSecurityManager.exitSafeRegion(token);
}
}
private void logMessageToIdeaLog(@NotNull String message) {
logMessageToIdeaLog(message, null);
}
@Override
public void addMessage(@NotNull RenderProblem message) {
synchronized (myMessages) {
if (myMessages.size() < RENDER_PROBLEMS_LIMIT) {
myMessages.add(message);
}
else {
myMessagesOverflowCounter++;
}
}
logMessageToIdeaLog(XmlUtils.fromXmlAttributeValue(message.getHtml()));
}
@NotNull
public List<RenderProblem> getMessages() {
ImmutableList.Builder<RenderProblem> builder = ImmutableList.builder();
int overflowCounter;
synchronized (myMessages) {
builder.addAll(myMessages);
overflowCounter = myMessagesOverflowCounter;
}
if (overflowCounter > 0) {
builder.add(RenderProblem.createPlain(WARNING, String.format(Locale.US,
"Too many errors (%d more errors not displayed)", overflowCounter)));
}
return builder.build();
}
/**
* Are there any logged errors or warnings during the render?
*
* @return true if there were problems during the render
*/
public boolean hasProblems() {
return hasErrors() || myFidelityWarnings != null;
}
/**
* Are there any logged errors during the render? (warnings are ignored)
*
* @return true if there were errors during the render
*/
public boolean hasErrors() {
boolean hasMessage;
synchronized (myMessages) {
hasMessage = !myMessages.isEmpty();
}
return myHaveExceptions || hasMessage ||
myClassesWithIncorrectFormat != null || myBrokenClasses != null || myMissingClasses != null ||
myMissingSize || myMissingFragments != null;
}
/**
* Returns the fidelity warnings
*/
@NotNull
public List<RenderProblem> getFidelityWarnings() {
return myFidelityWarnings != null ? myFidelityWarnings : Collections.emptyList();
}
@Override
public void error(@Nullable String tag, @Nullable String message, @Nullable Object viewCookie, @Nullable Object data) {
String description = describe(message, null);
// Workaround: older layout libraries don't provide a tag for this error
if (tag == null && message != null &&
(message.startsWith("Failed to find style ") || message.startsWith("Unable to resolve parent style name: "))) {
tag = LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR;
}
addTag(tag);
if (LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR.equals(tag) && myModule != null
&& BuildSettings.getInstance(myModule.getProject()).getBuildMode() == BuildMode.SOURCE_GEN) {
AndroidFacet facet = AndroidFacet.getInstance(myModule);
if (facet != null && AndroidModel.isRequired(facet)) {
description = "Still building project; theme resources from libraries may be missing. Layout should refresh when the " +
"build is complete.\n\n" + description;
tag = TAG_STILL_BUILDING;
addTag(tag);
}
}
addMessage(RenderProblem.createPlain(ERROR, description).tag(tag));
}
/**
* If the given {@link StackOverflowError} has a stacktrace longer then {@link RenderLogger#STACK_OVERFLOW_TRACE_LIMIT}, the stacktrace
* will be summarized by keeping the beginning and end of it and adding an "omitted:omitted" line in the middle.
* The resulting stacktrace will be at most {@link RenderLogger#STACK_OVERFLOW_TRACE_LIMIT} + 1 elements long.
*/
@NotNull
private static StackOverflowError summarizeStackOverFlowException(@NotNull StackOverflowError stackOverflowError) {
StackTraceElement[] stackTraceElements = stackOverflowError.getStackTrace();
int traceSize = stackTraceElements.length;
if (traceSize < STACK_OVERFLOW_TRACE_LIMIT) {
return stackOverflowError;
}
// The stack trace is too large, summarize
int elementsToCopy = STACK_OVERFLOW_TRACE_LIMIT / 2;
// We save STACK_OVERFLOW_TRACE_LIMIT/2 elements from the beginning and the same from the end of the old stacktrace. In the middle
// we add a fake element saying "omitted:omitted"
StackTraceElement[] newStackTraceElements = new StackTraceElement[elementsToCopy * 2 + 1];
newStackTraceElements[elementsToCopy] = new StackTraceElement("omitted", "omitted", "omitted", -1);
System.arraycopy(stackTraceElements, 0, newStackTraceElements, 0, elementsToCopy);
System
.arraycopy(stackTraceElements, stackTraceElements.length - elementsToCopy, newStackTraceElements, elementsToCopy + 1, elementsToCopy);
stackOverflowError.setStackTrace(newStackTraceElements);
return stackOverflowError;
}
@Override
public void error(@Nullable String tag,
@Nullable String message,
@Nullable Throwable throwable,
@Nullable Object viewCookie,
@Nullable Object data) {
String description = describe(message, throwable);
if (throwable != null) {
if (throwable instanceof ClassNotFoundException) {
// The LayoutlibCallback is given a chance to resolve classes,
// and when it fails, it will record it in its own list which
// is displayed in a special way (with action hyperlinks etc).
// Therefore, include these messages in the visible render log,
// especially since the user message from a ClassNotFoundException
// is really not helpful (it just lists the class name without
// even mentioning that it is a class-not-found exception.)
return;
}
if (isIssue164378(throwable)) {
return;
}
if ("Unable to find the layout for Action Bar.".equals(description)) {
description += "\nConsider updating to a more recent version of appcompat, or switch the rendering library in the IDE " +
"down to API 21";
}
if (description.isEmpty()) {
description = "Exception raised during rendering";
}
else if (description.equals(throwable.getLocalizedMessage()) || description.equals(throwable.getMessage())) {
description = "Exception raised during rendering: " + description;
}
else if (message != null && message.equals("onMeasure error") &&
throwable.toString()
.startsWith("java.lang.NoSuchMethodError: android.support.constraint.solver.widgets.Guideline.setRelative")) {
RenderProblem.Html problem = RenderProblem.create(WARNING);
String issue = "214853";
problem.tag(issue);
problem.throwable(throwable);
HtmlBuilder builder = problem.getHtmlBuilder();
builder.add("You appear to be using constraint layout version alpha3 or earlier; you must use version alpha4 or later " +
"with this version of the layout editor (because the API for guidelines changed incompatibly as of alpha4.)");
builder.add(" (");
builder.addLink("Update Library", getLinkManager().createRunnableLink(() -> UpgradeConstraintLayoutFix.apply(myModule)));
builder.add(", ");
ShowExceptionFix detailsFix = new ShowExceptionFix(getProject(), throwable);
builder.addLink("Show Exception", getLinkManager().createRunnableLink(detailsFix));
builder.add(")");
addMessage(problem);
return;
}
else if (message != null && message.startsWith("Failed to configure parser for ") && message.endsWith(DOT_PNG)) {
// See if it looks like a mismatched bitmap/color; if so, make a more intuitive error message
StackTraceElement[] frames = throwable.getStackTrace();
for (StackTraceElement frame : frames) {
if (frame.getMethodName().equals("createFromXml") && frame.getClassName().equals("android.content.res.ColorStateList")) {
String path = message.substring("Failed to configure parser for ".length());
RenderProblem.Html problem = RenderProblem.create(WARNING);
problem.tag("bitmapAsColor");
// deliberately not setting the throwable on the problem: exception is misleading
HtmlBuilder builder = problem.getHtmlBuilder();
builder.add("Resource error: Attempted to load a bitmap as a color state list.").newline();
builder.add("Verify that your style/theme attributes are correct, and make sure layouts are using the right attributes.");
builder.newline().newline();
path = FileUtil.toSystemIndependentName(path);
String basePath = getProject() != null && getProject().getBasePath() != null ?
FileUtil.toSystemIndependentName(getProject().getBasePath()) : null;
if (basePath != null && path.startsWith(basePath)) {
path = path.substring(basePath.length());
path = StringUtil.trimStart(path, File.separator);
}
path = FileUtil.toSystemDependentName(path);
builder.add("The relevant image is ").add(path);
Set<String> widgets = Sets.newHashSet();
for (StackTraceElement f : frames) {
if (f.getMethodName().equals(CONSTRUCTOR_NAME)) {
String className = f.getClassName();
if (className.startsWith(WIDGET_PKG_PREFIX)) {
widgets.add(className.substring(className.lastIndexOf('.') + 1));
}
}
}
if (!widgets.isEmpty()) {
List<String> sorted = Lists.newArrayList(widgets);
Collections.sort(sorted);
builder.newline().newline().add("Widgets possibly involved: ").add(Joiner.on(", ").join(sorted));
}
addMessage(problem);
return;
}
else if (frame.getClassName().startsWith("com.android.tools.")) {
break;
}
}
}
else if (message != null && message.startsWith("Failed to parse file ")
&& throwable instanceof XmlPullParserException) {
XmlPullParserException e = (XmlPullParserException)throwable;
String msg = e.getMessage();
if (msg.startsWith("Binary XML file ")) {
int index = msg.indexOf(':');
if (index != -1 && index < msg.length() - 1) {
msg = msg.substring(index + 1).trim();
}
}
int lineNumber = e.getLineNumber();
int column = e.getColumnNumber();
// Strip out useless input sources pointing back to the internal reader
// e.g. "in java.io.InputStreamReader@4d957e26"
String reader = " in java.io.InputStreamReader@";
int index = msg.indexOf(reader);
if (index != -1) {
int end = msg.indexOf(')', index + 1);
if (end != -1) {
msg = msg.substring(0, index) + msg.substring(end);
}
}
String path = message.substring("Failed to parse file ".length());
RenderProblem.Html problem = RenderProblem.create(WARNING);
problem.tag("xmlParse");
// Don't include exceptions for XML parser errors: that's just displaying irrelevant
// information about how we ended up parsing the file
//problem.throwable(throwable);
HtmlBuilder builder = problem.getHtmlBuilder();
if (lineNumber != -1) {
builder.add("Line ").add(Integer.toString(lineNumber)).add(": ");
}
builder.add(msg);
if (lineNumber != -1) {
builder.add(" (");
File file = new File(path);
String url = HtmlLinkManager.createFilePositionUrl(file, lineNumber, column);
if (url != null) {
builder.addLink("Show", url);
builder.add(")");
}
}
addMessage(problem);
return;
}
else if (throwable instanceof StackOverflowError) {
// StackOverflowError exceptions can have very large stack traces, limit the size when needed
throwable = summarizeStackOverFlowException((StackOverflowError)throwable);
}
myHaveExceptions = true;
}
addTag(tag);
if (getProject() == null) {
addMessage(RenderProblem.createPlain(ERROR, description).tag(tag).throwable(throwable));
}
else {
addMessage(RenderProblem.createPlain(ERROR, description, getProject(), getLinkManager(), throwable).tag(tag));
}
}
// ---- Tags ----
@Override
public void warning(@Nullable String tag, @NotNull String message, @Nullable Object viewCookie, @Nullable Object data) {
String description = describe(message, null);
if (TAG_INFO.equals(tag)) {
Logger.getInstance(getClass()).info(description);
return;
}
if (TAG_RESOURCES_FORMAT.equals(tag)) {
// TODO: Accumulate multiple hits of this form and synthesize into one
if (description.equals("You must supply a layout_width attribute.") //$NON-NLS-1$
|| description.equals("You must supply a layout_height attribute.")) {//$NON-NLS-1$
// Don't log these messages individually; you get one for each missing width and each missing height,
// but there is no correlation to the specific view which is using the given TypedArray,
// so instead just record that fact that *some* views were missing a dimension, and the
// error summary will mention this, and add an action which lists the eligible views
myMissingSize = true;
addTag(TAG_MISSING_DIMENSION);
return;
}
if (description.endsWith(" is not a valid value")) {
// TODO: Consider performing the attribute search up front, rather than on link-click,
// such that we don't add a link where we can't find the attribute in the current layout
// (e.g. it is coming somewhere from an <include> context, etc
Pattern pattern = Pattern.compile("\"(.*)\" in attribute \"(.*)\" is not a valid value");
Matcher matcher = pattern.matcher(description);
if (matcher.matches()) {
addTag(tag);
RenderProblem.Html problem = RenderProblem.create(WARNING);
problem.tag(tag);
String attribute = matcher.group(2);
String value = matcher.group(1);
problem.setClientData(new String[]{attribute, value});
String url = getLinkManager().createEditAttributeUrl(attribute, value);
problem.getHtmlBuilder().add(description).add(" (").addLink("Edit", url).add(")");
addMessage(problem);
return;
}
}
if (description.endsWith(" is not a valid format.")) {
Pattern pattern = Pattern.compile("\"(.*)\" in attribute \"(.*)\" is not a valid format.");
Matcher matcher = pattern.matcher(description);
if (matcher.matches()) {
addTag(tag);
RenderProblem.Html problem = RenderProblem.create(WARNING);
problem.tag(tag);
String attribute = matcher.group(2);
String value = matcher.group(1);
problem.setClientData(new String[]{attribute, value});
String url = getLinkManager().createEditAttributeUrl(attribute, value);
problem.getHtmlBuilder().add(description).add(" (").addLink("Edit", url).add(")");
problem.setClientData(url);
addMessage(problem);
return;
}
}
}
else if (TAG_MISSING_FRAGMENT.equals(tag)) {
if (!ourIgnoreFragments) {
if (myMissingFragments == null) {
myMissingFragments = Lists.newArrayList();
}
String name = data instanceof String ? (String)data : null;
myMissingFragments.add(name);
}
return;
}
addTag(tag);
addMessage(RenderProblem.createPlain(WARNING, description).tag(tag));
}
@Override
public void fidelityWarning(@Nullable String tag,
@Nullable String message,
@Nullable Throwable throwable,
@Nullable Object viewCookie,
@Nullable Object data) {
if (ourIgnoreAllFidelityWarnings || ourIgnoredFidelityWarnings != null && ourIgnoredFidelityWarnings.contains(message)) {
return;
}
String description = describe(message, throwable);
if (myFidelityWarningStrings != null && myFidelityWarningStrings.contains(description)) {
// Exclude duplicates
return;
}
if (throwable != null) {
myHaveExceptions = true;
}
RenderProblem error = RenderProblem.createDeferred(ERROR, tag, description, throwable);
error.setClientData(description);
if (myFidelityWarnings == null) {
myFidelityWarnings = new ArrayList<>();
myFidelityWarningStrings = Sets.newHashSet();
}
myFidelityWarnings.add(error);
assert myFidelityWarningStrings != null;
myFidelityWarningStrings.add(description);
addTag(tag);
}
private void addTag(@Nullable String tag) {
if (tag == null) {
return;
}
if (myTags == null) {
myTags = HashMultiset.create();
}
myTags.add(tag);
}
// ---- Class loading and instantiation problems ----
//
// These are recorded in the logger such that they can later be
// aggregated by the error panel. It is also written into the logger
// rather than stashed on the ViewLoader, since the ViewLoader is reused
// across multiple rendering operations.
/**
* Returns true if the given tag prefix has been seen
*
* @param prefix the tag prefix to look for
* @return true iff any tags with the given prefix was seen during the render
*/
public boolean seenTagPrefix(@NotNull String prefix) {
if (myTags == null) {
return false;
}
return myTags.stream().anyMatch(s -> s.startsWith(prefix));
}
@Override
@NotNull
public HtmlLinkManager getLinkManager() {
if (myLinkManager == null) {
myLinkManager = new HtmlLinkManager();
}
return myLinkManager;
}
@Override
public void setHasLoadedClasses() {
myHasLoadedClasses = true;
}
public boolean isMissingSize() {
return myMissingSize;
}
public boolean hasLoadedClasses() {
return myHasLoadedClasses;
}
public boolean isMissingResourceClass() {
return myMissingResourceClass;
}
@Override
public void setMissingResourceClass() {
myMissingResourceClass = true;
}
@Nullable
public String getResourceClass() {
return myResourceClass;
}
@Override
public void setResourceClass(@NotNull String resourceClass) {
myResourceClass = resourceClass;
}
/**
* Returns all the logged classes with incorrect format during rendering. If any of the {@link Throwable}s stack traces is
* longer than 100 elements, it will be summarized by only keeping the first 50 and last 50 elements and one element in the
* middle to indicate the exception stack trace has been summarized.
*/
@NotNull
public Map<String, Throwable> getClassesWithIncorrectFormat() {
return myClassesWithIncorrectFormat != null ? myClassesWithIncorrectFormat : Collections.emptyMap();
}
/**
* Returns all the logged broken classes during rendering. If any of the {@link Throwable}s stack traces is
* longer than 100 elements, it will be summarized by only keeping the first 50 and last 50 elements and one element in the
* middle to indicate the exception stack trace has been summarized.
*/
@NotNull
public Map<String, Throwable> getBrokenClasses() {
return myBrokenClasses != null ? myBrokenClasses : Collections.emptyMap();
}
@NotNull
public Set<String> getMissingClasses() {
return myMissingClasses != null ? myMissingClasses : Collections.emptySet();
}
@Override
public void addMissingClass(@NotNull String className) {
if (!className.equals(VIEW_FRAGMENT)) {
if (myMissingClasses == null) {
myMissingClasses = new TreeSet<>();
}
myMissingClasses.add(className);
logMessageToIdeaLog("Class not found " + className);
}
}
@Override
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public void addIncorrectFormatClass(@NotNull String className, @NotNull Throwable exception) {
if (myClassesWithIncorrectFormat == null) {
myClassesWithIncorrectFormat = new HashMap<>();
}
myClassesWithIncorrectFormat.put(className, exception);
logMessageToIdeaLog("Class with incorrect format " + className, exception);
}
@Override
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public void addBrokenClass(@NotNull String className, @NotNull Throwable exception) {
while (exception.getCause() != null && exception.getCause() != exception) {
exception = exception.getCause();
}
if (exception instanceof StackOverflowError) {
// StackOverflowError exceptions can have very large stack traces, limit the size when needed
exception = summarizeStackOverFlowException((StackOverflowError)exception);
}
if (myBrokenClasses == null) {
myBrokenClasses = new HashMap<>();
}
myBrokenClasses.put(className, exception);
logMessageToIdeaLog("Broken class " + className, exception);
}
@Nullable
public List<String> getMissingFragments() {
return myMissingFragments;
}
/**
* Android framework log priority levels.
* They are defined in system/core/liblog/include/android/log.h in the Android Framework code.
*/
private static final int ANDROID_LOG_UNKNOWN = 0;
private static final int ANDROID_LOG_DEFAULT = 1;
private static final int ANDROID_LOG_VERBOSE = 2;
private static final int ANDROID_LOG_DEBUG = 3;
private static final int ANDROID_LOG_INFO = 4;
private static final int ANDROID_LOG_WARN = 5;
private static final int ANDROID_LOG_ERROR = 6;
private static final int ANDROID_LOG_FATAL = 7;
private static final int ANDROID_LOG_SILENT = 8;
@Override
public void logAndroidFramework(int priority, String tag, String message) {
if (StudioFlags.NELE_LOG_ANDROID_FRAMEWORK.get()) {
boolean token = RenderSecurityManager.enterSafeRegion(myCredential);
try {
String fullMessage = tag + ": " + message;
switch (priority) {
case ANDROID_LOG_VERBOSE:
case ANDROID_LOG_DEBUG:
LOG.debug(fullMessage);
break;
case ANDROID_LOG_INFO:
LOG.info(fullMessage);
break;
case ANDROID_LOG_WARN:
case ANDROID_LOG_ERROR:
LOG.warn(fullMessage);
break;
case ANDROID_LOG_FATAL:
LOG.error(fullMessage);
break;
}
}
finally {
RenderSecurityManager.exitSafeRegion(token);
}
}
}
}