/*
* CatSaver
* Copyright (C) 2015 HiHex Ltd.
*
* This program 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.
*
* This program 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
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package hihex.cs;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.text.TextUtils;
import com.google.common.collect.TreeMultimap;
import com.google.common.io.Closeables;
import com.x5.template.Chunk;
import com.x5.template.Theme;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
public final class ChunkRenderer {
private final Theme mTheme;
private final PackageManager mPackageManager;
private final Chunk mLogPrefixChunk;
private final Chunk mLogSuffixChunk;
private final Chunk mLogEntryChunk;
private final Chunk mLogAnrEntryPrefixChunk;
private final Chunk mLogAnrEntrySuffixChunk;
public ChunkRenderer(final Context context) {
mTheme = ChunkTheme.create(context);
mPackageManager = context.getPackageManager();
mLogPrefixChunk = mTheme.makeChunk("log#prefix");
mLogSuffixChunk = mTheme.makeChunk("log#suffix");
mLogEntryChunk = mTheme.makeChunk("log#entry");
mLogAnrEntryPrefixChunk = mTheme.makeChunk("log#anr_entry_prefix");
mLogAnrEntrySuffixChunk = mTheme.makeChunk("log#anr_entry_suffix");
}
public void writeHeader(final Writer writer, final int pid, final String processName, final Date timestamp)
throws IOException {
final int colon = processName.indexOf(':');
final String packageName = (colon >= 0) ? processName.substring(0, colon) : processName;
try {
final PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA);
mLogPrefixChunk.put("version", pkgInfo.versionName);
mLogPrefixChunk.put("version_code", String.valueOf(pkgInfo.versionCode));
} catch (final PackageManager.NameNotFoundException e) {
// No package information. Ignore.
}
final HashMap<String, String> buildSettings = new HashMap<>(8);
buildSettings.put("product", Build.PRODUCT);
buildSettings.put("device", Build.DEVICE);
buildSettings.put("manufacturer", Build.MANUFACTURER);
buildSettings.put("board", Build.BOARD);
buildSettings.put("brand", Build.BRAND);
buildSettings.put("model", Build.MODEL);
buildSettings.put("release", Build.VERSION.RELEASE);
//noinspection deprecation
buildSettings.put("abi", Build.CPU_ABI);
buildSettings.put("api_level", String.valueOf(Build.VERSION.SDK_INT));
mLogPrefixChunk.set("date", timestamp.toString());
mLogPrefixChunk.set("process", processName);
mLogPrefixChunk.set("pid", pid);
mLogPrefixChunk.set("build", buildSettings);
mLogPrefixChunk.set("addresses", TextUtils.join(" / ", IpAddresses.getAllIpAddresses()));
mLogPrefixChunk.render(writer);
}
public void writeFooter(final Writer writer) throws IOException {
mLogSuffixChunk.render(writer);
}
public void writeLogEntry(final Writer writer, final LogEntry entry) throws IOException {
mLogEntryChunk.set("log_level", entry.logLevelChar());
mLogEntryChunk.set("date", String.format(Locale.ROOT, "%1$tH:%1$tM:%1$tS.%1$tL", entry.timestamp()));
mLogEntryChunk.set("tag", entry.tag());
mLogEntryChunk.set("message", entry.message());
mLogEntryChunk.set("pid", entry.pid());
mLogEntryChunk.set("tid", entry.tid());
mLogEntryChunk.set("process_name", entry.getProcessName());
mLogEntryChunk.set("thread_name", entry.getThreadName());
mLogEntryChunk.render(writer);
}
public void writeAnrTraces(final Writer writer, final int pid) throws IOException {
mLogAnrEntryPrefixChunk.render(writer);
BufferedReader tracesFile = null;
try {
tracesFile = new BufferedReader(new FileReader("/data/anr/traces.txt"));
final String beginAnr = "----- pid " + pid + " at ";
final String endAnr = "----- end " + pid;
boolean shouldLog = false;
while (true) {
final String line = tracesFile.readLine();
if (line == null) {
break;
}
if (!shouldLog && line.startsWith(beginAnr)) {
shouldLog = true;
}
if (shouldLog) {
writer.write(line);
writer.write('\n');
}
if (shouldLog && line.startsWith(endAnr)) {
shouldLog = false;
}
}
} finally {
Closeables.closeQuietly(tracesFile);
mLogAnrEntrySuffixChunk.render(writer);
}
}
public String renderIndex(final LogFiles logFiles, final PidDatabase database) {
final List<HashMap<String, String>> processes = database.runningProcesses();
final Chunk chunk = mTheme.makeChunk("index");
final TreeMultimap<Long, File> files = logFiles.list();
final ArrayList<HashMap<String, String>> encodedFiles = new ArrayList<>(files.size());
long totalSize = 0;
for (final File file : files.values()) {
final HashMap<String, String> content = new HashMap<>(5);
final long fileSize = file.length();
final String name = file.getName();
final int pid = database.findPid(name);
content.put("file_name", name);
content.put("last_modified", String.valueOf(file.lastModified()));
content.put("file_size", String.valueOf(fileSize));
if (pid != -1) {
content.put("pid", String.valueOf(pid));
content.put("process_name", database.getProcessName(pid));
}
encodedFiles.add(content);
totalSize += fileSize;
}
chunk.set("files", encodedFiles);
chunk.set("total_size", String.valueOf(totalSize));
chunk.set("processes", processes);
return chunk.toString();
}
public String renderSettings(final Preferences preferences) {
final Chunk chunk = mTheme.makeChunk("settings");
chunk.put("filter", preferences.getFilter().pattern());
chunk.put("filesize", String.valueOf(preferences.getPurgeFilesize()));
chunk.put("date", String.valueOf(preferences.getPurgeDuration()));
chunk.put("show_indicator", String.valueOf(preferences.shouldShowIndicator()));
chunk.put("split_size", String.valueOf(preferences.getSplitSize()));
chunk.put("run_on_boot", String.valueOf(preferences.shouldRunOnBoot()));
return chunk.toString();
}
public String renderFilterSettings(final Preferences preferences) {
final Chunk chunk = mTheme.makeChunk("filters");
chunk.put("filter", preferences.getFilter().pattern());
chunk.put("log_filter", preferences.getRawLogFilter());
if (preferences.hasDefaultLogFilter()) {
chunk.put("log_filter_use_default", "true");
}
return chunk.toString();
}
public String renderSettingsError(final String source, final String title, final Throwable e) {
final Chunk chunk = mTheme.makeChunk("settings_error");
chunk.put("title", title);
chunk.put("source", source);
if (e != null) {
chunk.put("message", e.getLocalizedMessage());
}
return chunk.toString();
}
public String renderLive() {
final Chunk chunk = mTheme.makeChunk("live");
return chunk.toString();
}
}