package com.javalive09.codebag;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import com.javalive09.codebag.logger.Log;
import com.javalive09.codebag.logger.LogFragment;
import com.javalive09.codebag.logger.LogWrapper;
import com.javalive09.codebag.logger.MessageOnlyLogFilter;
import com.javalive09.codebag.node.FileHolder;
import com.javalive09.codebag.node.NodeItem;
import com.unnamed.b.atv.model.TreeNode;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import dalvik.system.DexFile;
/**
* Created by peter on 16/9/21.
*/
public class EntryTreeActivity extends AppCompatActivity {
public static final String GIT_HUB_HOME = "https://raw.githubusercontent.com/";
public static final String NODE_NAME = "node";
TreeFragment treeFragment = new TreeFragment();
static List<Activity> list = new ArrayList<>();
LogFragment logFragment;
boolean showAllFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initActionBar();
initStatusBar();
list.add(this);
if (showEntryTree()) {
initTreeFragment();
initLogFragment();
}
}
protected void exit() {
for (Activity act : list) {
act.finish();
}
}
protected boolean showEntryTree() {
return true;
}
protected boolean showAllFile() {
return false;
}
/**
* @return log max limit , M unit
*/
public int getMaxLog() {
return 20;
}
private void initTreeFragment() {
showAllFile = showAllFile();
getSupportFragmentManager().beginTransaction().replace(R.id.content, treeFragment).commit();
}
public void startDetailFragment(NodeItem item) {
PlayFragment detailFragment = new PlayFragment();
Bundle bundle = new Bundle();
bundle.putParcelable(NODE_NAME, item);
detailFragment.setArguments(bundle);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_right_in, R.anim.slide_left_out, R.anim.slide_left_in, R.anim.slide_right_out);
ft.addToBackStack(null);
ft.hide(treeFragment).add(R.id.content, detailFragment).commit();
}
@Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
} else {
super.onBackPressed();
}
}
private void invokeMethod(NodeItem node) {
try {
String methodName = node.text;
String className = node.className;
Class<?> cls = Class.forName(className);
Constructor<?> con = cls.getConstructor();
Object obj = con.newInstance();
if (obj != null) {
Field mActivity = cls.getSuperclass().getDeclaredField("activity");
mActivity.setAccessible(true);
mActivity.set(obj, EntryTreeActivity.this);
Field fragment = cls.getSuperclass().getDeclaredField("treeFragment");
fragment.setAccessible(true);
fragment.set(obj, treeFragment);
Method method = cls.getDeclaredMethod(methodName);
method.invoke(obj);
}
} catch (Exception e) {
startDetailFragment(node);
}
}
private void initLogFragment() {
// Wraps Android's native log framework.
LogWrapper logWrapper = new LogWrapper();
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
Log.setLogNode(logWrapper);
// Filter strips out everything except the message text.
MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter();
logWrapper.setNext(msgFilter);
logFragment = (LogFragment) getSupportFragmentManager()
.findFragmentById(R.id.log_fragment);
msgFilter.setNext(logFragment.getLogView());
logFragment.getLogView().setTextAppearance(this, R.style.Log);
boolean show = getLogFragmentStatus();
logFragment.show(show);
}
private void saveLogFragmentShow(boolean show) {
SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
sp.edit().putBoolean("show", show).apply();
}
private boolean getLogFragmentStatus() {
SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
boolean show = sp.getBoolean("show", true);
return show;
}
public TreeNode getCodeNode() {
String pkgName = getPackageName();
TreeNode rootNode = TreeNode.root();
String apkDir = null;
try {
apkDir = getPackageManager().getApplicationInfo(pkgName, 0).sourceDir;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
DexFile dexFile;
try {
if (!TextUtils.isEmpty(apkDir)) {
dexFile = new DexFile(apkDir);
int startLen = pkgName.length() + 1;
Enumeration<String> apkClassNames = dexFile.entries();
while (apkClassNames.hasMoreElements()) {
String className = apkClassNames.nextElement();
if (className.startsWith(pkgName)
&& !className.contains("$")
&& !className.endsWith(".R")
&& !className.contains("BuildConfig")) {
String fileName = className.substring(startLen);
String[] strings = fileName.split("\\.");
if (showAllFile) {
loadCodeBagNode(className, strings, 0, rootNode);
} else {
if (isEntry(className)) {
loadCodeBagNode(className, strings, 0, rootNode);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return rootNode;
}
private boolean isEntry(String className) {
Class<?> cls = null;
try {
cls = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
String superClassName = superclass.getSimpleName();
if (Entry.class.getSimpleName().equals(superClassName)) {
return true;
}
}
return false;
}
/**
* @param className class全路径名
* @param strings class全路径名除去根路径,剩下的字符以“.”为划分记号,划分成的数组
* @param index 游标在strings数组中的位置
* @param currentNode 当前节点(作为父节点)
*/
private void loadCodeBagNode(String className, String[] strings, int index, TreeNode currentNode) {
if (index > strings.length - 1) {
return;
}
String subNodeName = strings[index];
if (index == strings.length - 1) {//数组的最后一个元素为class
getSubNode(subNodeName, NodeItem.CLASS, className, currentNode);
} else {//数组中其他元素为目录
TreeNode subNode = getSubNode(subNodeName, NodeItem.DIR, className, currentNode);
index++;
loadCodeBagNode(className, strings, index, subNode);
}
}
/**
* @param subNodeName 子节点名字(游标所在数组的元素名字)--- 是区分各个子节点的关键字
* @param type 子节点类型(目录/类)
* @param className 子节点全名(目录一般为null,类才有)
* @param currentNode 父节点
* @return Node
*/
private TreeNode getSubNode(String subNodeName, int type, String className, TreeNode currentNode) {
for (TreeNode n : currentNode.getChildren()) {//父节点有子节点列表,则遍历一下
NodeItem value = (NodeItem) n.getValue();
if (value.text.equals(subNodeName)) {
return n;
}
}
return createSubNode(subNodeName, type, className, currentNode);
}
private TreeNode createSubNode(String nodeName, int icon, String className, TreeNode currentNode) {
TreeNode node = createTreeNode(icon, nodeName, className);
//method
if (icon == NodeItem.CLASS) {
addMethodNodes(node);
}
currentNode.addChild(node);
return node;
}
private void addMethodNodes(TreeNode parentNode) {
NodeItem value = (NodeItem) parentNode.getValue();
String className = value.className;
if (!TextUtils.isEmpty(className) && className.contains(getPackageName())) {
try {
Class<?> cls = Class.forName(className);
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
String superClassName = superclass.getSimpleName();
if (superClassName.equals(Entry.class.getSimpleName())) {
Method[] methods = cls.getDeclaredMethods();
for (Method m : methods) {
if (Modifier.PUBLIC == m.getModifiers()
&& m.getParameterTypes().length == 0) {
String methodName = m.getName();
TreeNode node = createTreeNode(NodeItem.METHOD, methodName, className);
parentNode.addChild(node);
}
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
private TreeNode createTreeNode(int icon, String nodeName, String className) {
NodeItem item = new NodeItem(icon, nodeName, className);
TreeNode node = new TreeNode(item).setViewHolder(new FileHolder(getApplicationContext()));
node.setClickListener(listener);
node.setLongClickListener(longClickListener);
return node;
}
private TreeNode.TreeNodeClickListener listener = new TreeNode.TreeNodeClickListener() {
@Override
public void onClick(TreeNode node, Object value) {
NodeItem item = (NodeItem) value;
if (item.icon == NodeItem.METHOD) {
invokeMethod(item);
}
}
};
private TreeNode.TreeNodeLongClickListener longClickListener = new TreeNode.TreeNodeLongClickListener() {
@Override
public boolean onLongClick(TreeNode node, Object value) {
return true;
}
};
@Override
protected void onDestroy() {
super.onDestroy();
list.remove(this);
}
private void initStatusBar() {
int ver = android.os.Build.VERSION.SDK_INT;
if (ver >= 21) {
new StatusBarApiInvoke(this).invoke();
}
}
private String getRootUrl() {
int ownerStrId = getResources().getIdentifier("git_owner", "string", getPackageName());
if (ownerStrId != 0) {
int repoStrId = getResources().getIdentifier("git_repo", "string", getPackageName());
if (repoStrId != 0) {
int dirStrId = getResources().getIdentifier("git_dir", "string", getPackageName());
if (dirStrId != 0) {
String owner = getString(ownerStrId);
String repo = getString(repoStrId);
String rootDir = getString(dirStrId);
return GIT_HUB_HOME + owner + "/" + repo + "/master/" + rootDir + "/src/main/java/";
} else {
Toast.makeText(EntryTreeActivity.this, R.string.no_dir, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(EntryTreeActivity.this, R.string.no_repo, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(EntryTreeActivity.this, R.string.no_owner, Toast.LENGTH_LONG).show();
}
return "";
}
private static class StatusBarApiInvoke {
Activity mAct;
StatusBarApiInvoke(Activity act) {
mAct = act;
}
@TargetApi(21)
void invoke() {
Window window = mAct.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(mAct.getResources().getColor(R.color.statusbar));
}
}
private void initActionBar() {
setOverflowShowingAlways();
ActionBar bar = getSupportActionBar();
if (bar != null) {
bar.setDisplayHomeAsUpEnabled(true);
}
}
private void setOverflowShowingAlways() {
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class
.getDeclaredField("sHasPermanentMenuKey");
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
onBackPressed();
} else if (id == R.id.action_showlog) {
showLogView(true);
} else if (id == R.id.action_clearlog) {
clearLog();
} else if (id == R.id.action_exit) {
exit();
} else if (id == R.id.action_hidelog) {
showLogView(false);
} else if (id == R.id.action_autoscroll_log) {
logFragment.setAutoScroll(true);
} else if (id == R.id.action_stop_autoscroll_log) {
logFragment.setAutoScroll(false);
} else if (id == R.id.action_sharelog) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, logFragment.getLogView().getText());
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getString(R.string.action_sharelog)));
}
return super.onOptionsItemSelected(item);
}
protected void showLogView(boolean show) {
if (logFragment != null) {
saveLogFragmentShow(show);
logFragment.show(show);
}
}
protected void clearLog() {
if (logFragment != null) {
logFragment.clear();
}
}
public AlertDialog showAlertDialog(String title, String content) {
AlertDialog dialog = new AlertDialog.Builder(EntryTreeActivity.this,
R.style.AppCompatAlertDialogStyle).create();
dialog.setCanceledOnTouchOutside(true);
View dialogView = View.inflate(EntryTreeActivity.this, R.layout.alertdialog_view, null);
dialog.setView(dialogView);
TextView titleView = (TextView) dialogView.findViewById(R.id.title);
TextView contentView = (TextView) dialogView.findViewById(R.id.content);
titleView.setText(title);
contentView.setText(content);
dialog.show();
return dialog;
}
}