{
+
+ private final boolean asRoot;
+
+ RunCommandTask(boolean asRoot) {
+ this.asRoot = asRoot;
+ }
+
+ @Override protected void onPreExecute() {
+ dialog = ProgressDialog.show(TerminalActivity.this, getResources().getString(R.string.menu_terminal), "Please Wait...");
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.setCancelable(true);
+ }
+
+ @Override protected CommandResult doInBackground(String... commands) {
+ if (asRoot) {
+ return Shell.SU.run(commands);
+ } else {
+ return Shell.SH.run(commands);
+ }
+ }
+
+ @Override protected void onPostExecute(CommandResult result) {
+ if (!isFinishing()) {
+ if (dialog.isShowing()){
+ dialog.dismiss();
+ card.setVisibility(View.VISIBLE);
+ outputText.append(resultToHtml(result));
+ commandText.setText(inputText.getText().toString());
+ inputText.setText("");
+ } else {
+ }
+ }
+ }
+
+ private Spanned resultToHtml(CommandResult result) {
+ StringBuilder html = new StringBuilder();
+ // exit status
+ html.append("");
+ html.append("").append("Command: ").append("");
+ html.append("").append(inputText.getText().toString()).append("");
+ html.append("
");
+ html.append("Exit Code: ");
+ if (result.isSuccessful()) {
+ html.append("").append(result.exitCode).append("");
+ } else {
+ html.append("").append(result.exitCode).append("");
+ }
+ html.append("
");
+ // stdout
+ if (result.stdout.size() > 0) {
+ html.append(">STDOUT:
")
+ .append(result.getStdout().replaceAll("\n", "
"))
+ .append("
");
+ }
+ // stderr
+ if (result.stderr.size() > 0) {
+ html.append("STDERR:
")
+ .append(result.getStderr().replaceAll("\n", "
"))
+ .append("
");
+ }
+ html.append("");
+ html.append("").append("\n-----Finished-----").append("");
+ html.append("
");
+ return Html.fromHtml(html.toString());
+ }
+
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (dialog != null){
+ if (dialog.isShowing()){
+ dialog.dismiss();
+ //Shell.SU.closeConsole();
+ } else {
+ finish();
+ }
+ } else {
+ finish();
+ }
+ }
+
+ /*
+
+ public String getShellResult() {
+ try {
+ String cmds = "";
+ String[] arr = cmds.split("\n"); // 用,分割
+ for (String cmd : arr) {
+ cmds += cmd + ";";
+ }
+ String[] cmdA = {"/bin/sh", "-c", cmds};
+ Process process = Runtime.getRuntime().exec(cmdA);
+ LineNumberReader br = new LineNumberReader(new InputStreamReader(
+ process.getInputStream()));
+ StringBuffer sb = new StringBuffer();
+ String line;
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ sb.append(line).append("\n");
+ return sb.toString();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return e.toString();
+ }
+ return null;
+
+ }
+
+ public void exec(){
+ new Thread(() -> {
+ b = getShellResult();
+ han.sendEmptyMessage(0);
+ }).start();
+ }
+
+ Handler han = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ if (msg.what == 0) {
+ outputText.append(b+"\n");
+ }
+ }
+ };
+
+ */
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/VersionsActivity.java b/app/src/main/java/org/koishi/launcher/h2o2pro/VersionsActivity.java
new file mode 100644
index 00000000..d0a94169
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/VersionsActivity.java
@@ -0,0 +1,726 @@
+package org.koishi.launcher.h2o2pro;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.google.android.material.button.MaterialButton;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.google.android.material.snackbar.Snackbar;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.android.material.textfield.TextInputLayout;
+import com.mistake.revision.VanillaActivity;
+
+import org.json.JSONObject;
+import org.koishi.launcher.h2o2pro.adapters.BaseRecycleAdapter;
+//import org.koishi.launcher.h2o2pro.adapters.SeachRecordAdapter;
+import org.koishi.launcher.h2o2pro.tool.GetGameJson;
+import org.koishi.launcher.h2o2pro.tool.data.DbDao;
+import org.koishi.launcher.h2o2pro.tool.file.AppExecute;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+public class VersionsActivity extends AppCompatActivity {
+
+ private Button mbtn_serarch;
+ private Dialog mDialog;
+ private EditText met_search;
+ private FloatingActionButton dir,ver;
+ private LinearLayout page;
+ //private TabItem rb1,rb2;
+ private TabLayout tab;
+ private RecyclerView mRecyclerView,mVerRecyclerView;
+ private TextView mtv_deleteAll;
+ private SearchDirAdapter mAdapter;
+ private VersionRecyclerAdapter mVerAdapter;
+ private String getBoatDir;
+ private String sd1 = "/storage/emulated/0/games/com.koishi.launcher/h2o2/gamedir";
+ private String sd2 = "/sdcard/games/com.koishi.launcher/h2o2/gamedir";
+ private String sd3 = "/mnt/sdcard/games/com.koishi.launcher/h2o2/gamedir";
+
+ private List verList;
+
+ private DbDao mDbDao;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_versions);
+ getWindow().setStatusBarColor(getResources().getColor(R.color.material_card_background));
+ page = findViewById(R.id.dir_layout);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
+ toolbar.setNavigationOnClickListener(v -> {
+ finish();
+ startActivity(new Intent(VersionsActivity.this,HomeActivity.class));
+ });
+ Typeface tf = Typeface.createFromAsset(this.getAssets(),
+ "Sans.ttf");
+ TextView bigTitle= (TextView) toolbar.getChildAt(0);
+ bigTitle.setTypeface(tf);
+ bigTitle.setText(getResources().getString(R.string.menu_ver));
+
+ //rb1 = findViewById(R.id.ver_title_dir);
+ //rb2 = findViewById(R.id.ver_title_ver);
+
+ initViews();
+ initVers();
+
+ //rb1.setOnClickListener(v->{
+ //mRecyclerView.setVisibility(View.VISIBLE);
+ //mVerRecyclerView.setVisibility(View.GONE);
+ //});
+ //rb2.setOnClickListener(v->{
+ // mRecyclerView.setVisibility(View.GONE);
+ //mVerRecyclerView.setVisibility(View.VISIBLE);
+ //});
+ dir = findViewById(R.id.ver_new_dir);
+ ver = findViewById(R.id.ver_new_ver);
+ ver.hide();
+ tab = findViewById(R.id.ver_tab);
+ tab.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+
+ // tab.getPosition() 返回数字,从0开始
+ // tab.getText() 返回字符串类型,从0开始
+ if (tab.getPosition()==0){
+ mRecyclerView.setVisibility(View.VISIBLE);
+ mVerRecyclerView.setVisibility(View.GONE);
+ ver.hide();
+ dir.show();
+ }
+ if (tab.getPosition()==1){
+ mRecyclerView.setVisibility(View.GONE);
+ mVerRecyclerView.setVisibility(View.VISIBLE);
+ dir.hide();
+ ver.show();
+ }
+ }
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+
+ }
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+
+ }
+ });
+
+ }
+
+ private void initViews() {
+ mDbDao =new DbDao(this);
+ //mbtn_serarch = findViewById(R.id.btn_serarch);
+ //met_search = findViewById(R.id.et_search);
+ //mtv_deleteAll = findViewById(R.id.tv_deleteAll);
+ //mtv_deleteAll.setOnClickListener(view -> {
+ //mDbDao.deleteData();
+ //mAdapter.updata(mDbDao.queryData(""));
+ //});
+ mRecyclerView = findViewById(R.id.mRecyclerView);
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+ mAdapter = new SearchDirAdapter(mDbDao.queryData(""), this);
+ mAdapter.setRvItemOnclickListener(position -> {
+ mDbDao.delete(mDbDao.queryData("").get(position));
+ mAdapter.updata(mDbDao.queryData(""));
+ });
+ if (!mDbDao.hasData(sd1)){
+ mDbDao.insertData(sd1);
+ mAdapter.updata(mDbDao.queryData(""));
+ }
+ mRecyclerView.setAdapter(mAdapter);
+ //事件监听
+ /*
+ mbtn_serarch.setOnClickListener(view -> {
+
+ if (met_search.getText().toString().trim().length() != 0){
+ boolean hasData = mDbDao.hasData(met_search.getText().toString().trim());
+ if (!hasData){
+ mDbDao.insertData(met_search.getText().toString().trim());
+ }else {
+ Toast.makeText(VersionsActivity.this, "该内容已在历史记录中", Toast.LENGTH_SHORT).show();
+ }
+
+ //
+ mAdapter.updata(mDbDao.queryData(""));
+
+ }else {
+ Toast.makeText(VersionsActivity.this, "请输入内容", Toast.LENGTH_SHORT).show();
+ }
+
+ });
+ */
+ }
+
+ public void initVers(){
+ File versionlist = new File(GetGameJson.getBoatCfg("game_directory","null") + "/versions");
+ if (versionlist.isDirectory() && versionlist.exists()) {
+ Comparator cp = Collator.getInstance(Locale.CHINA);
+ String[] getVer = versionlist.list();
+ List< String > verList = Arrays.asList(getVer); //此集合无法操作添加元素
+ Collections.sort(verList, cp);
+ mVerRecyclerView = findViewById(R.id.mVerRecyclerView);
+ mVerRecyclerView.setLayoutManager(new LinearLayoutManager(this));//设置布局管理器
+ mVerRecyclerView.setAdapter(mVerAdapter = new VersionRecyclerAdapter(this, verList));
+ } else {
+ mVerRecyclerView = findViewById(R.id.mVerRecyclerView);
+ mVerRecyclerView.setAdapter(null);
+ }
+ }
+
+ public void newDir(View v){
+ showDirDialog();
+ }
+
+ public void newVer(View v){
+ finish();
+ startActivity(new Intent(VersionsActivity.this, VanillaActivity.class));
+ }
+
+ public void showDirDialog() {
+ mDialog = new Dialog(VersionsActivity.this);
+ View dialogView = VersionsActivity.this.getLayoutInflater().inflate(R.layout.custom_dialog_directory, null);
+ mDialog.setContentView(dialogView);
+ //mDialog.getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundColor(Color.TRANSPARENT);
+ MaterialButton cancel = dialogView.findViewById(R.id.custom_dir_cancel);
+ MaterialButton start = dialogView.findViewById(R.id.custom_dir_ok);
+ TextInputLayout lay = dialogView.findViewById(R.id.dialog_dir_lay);
+ lay.setError(getString(R.string.ver_input_hint));
+ start.setEnabled(false);
+ TextInputEditText et = dialogView.findViewById(R.id.dialog_dir_name);
+ et.addTextChangedListener(new TextWatcher(){
+ @Override
+ public void beforeTextChanged(CharSequence p1, int p2, int p3, int p4) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence p1, int p2, int p3, int p4) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable p1) {
+ String value = et.getText().toString();
+ if (value.matches("(/storage/emulated/0|/sdcard|/mnt/sdcard).*")){
+ lay.setErrorEnabled(false);
+ start.setEnabled(true);
+ } else {
+ lay.setError(getString(R.string.ver_input_hint));
+ start.setEnabled(false);
+ }
+ }
+ });
+
+ cancel.setOnClickListener(v-> mDialog.dismiss());
+ start.setOnClickListener(v->{
+ if (et.getText().toString().trim().length() != 0){
+ boolean hasData = mDbDao.hasData(et.getText().toString().trim());
+ if (!hasData){
+ File f = new File(et.getText().toString().trim());
+ if (f.exists()){
+ if (f.isDirectory()){
+ getBoatDir = et.getText().toString();
+ newDir();
+ } else {
+ Snackbar.make(page, getResources().getString(R.string.ver_not_dir), Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ } else {
+ getBoatDir = et.getText().toString();
+ newDir();
+ }
+ }else {
+ Snackbar.make(page, getResources().getString(R.string.ver_already_exists), Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+
+ //
+ mAdapter.updata(mDbDao.queryData(""));
+
+ }else {
+ Snackbar.make(page, "Please input", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+
+ });
+
+ mDialog.setContentView(dialogView);
+ WindowManager windowManager = VersionsActivity.this.getWindowManager();
+ Display display = windowManager.getDefaultDisplay();
+ WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
+ lp.width = (int)(display.getWidth()*0.9); //设置宽度 dialog.getWindow().setAttributes(lp);
+ mDialog.show();
+ }
+
+ public void newDir(){
+ new Thread(() -> {
+ try {
+ AppExecute.output(VersionsActivity.this, "pack.zip", getBoatDir);
+ //Snackbar.make(getView(), getResources().getString(R.string.install_done), Snackbar.LENGTH_LONG)
+ //.setAction("Action", null).show();
+ han.sendEmptyMessage(1);
+ } catch (IOException e) {
+ Snackbar.make(page, getResources().getString(R.string.ver_not_right_dir)+e.toString(), Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ han.sendEmptyMessage(0);
+ }
+ }).start();
+ }
+
+ @SuppressLint("HandlerLeak")
+ Handler han = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ if (msg.what == 0) {
+ mDialog.dismiss();
+ }
+ if (msg.what == 1) {
+ mDialog.dismiss();
+ mDbDao.insertData(getBoatDir);
+ mAdapter.updata(mDbDao.queryData(""));
+ Snackbar.make(page, getResources().getString(R.string.ver_add_done), Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ if (msg.what == 2) {
+ //mVerRecyclerView.setAdapter(null);
+ //initVers();
+ Snackbar.make(page, getResources().getString(R.string.ver_add_done), Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+
+ }
+ };
+
+ //public void refresh(){
+ //finish();
+ //startActivity(new Intent(VersionsActivity.this,VersionsActivity.class));
+ //}
+
+ class SearchDirAdapter extends BaseRecycleAdapter {
+ public SearchDirAdapter(List< String > datas, Context mContext) {
+ super(datas, mContext);
+ }
+
+ @SuppressLint({"ResourceAsColor", "UseCompatLoadingForDrawables"})
+ @Override
+ protected void bindData(BaseViewHolder holder, final int position) {
+
+ TextView textView = (TextView) holder.getView(R.id.tv_record);
+ TextView textView1 = (TextView) holder.getView(R.id.tv_name);
+ LinearLayout lay = (LinearLayout) holder.getView(R.id.ver_item);
+ ImageView check = (ImageView) holder.getView(R.id.ver_check_icon);
+ MaterialButton del = (MaterialButton) holder.getView(R.id.tv_remove_dir);
+ MaterialButton delDir = (MaterialButton) holder.getView(R.id.tv_del_dir);
+ textView.setText(datas.get(position));
+ if (datas.get(position).equals(GetGameJson.getBoatCfg("game_directory", "null"))) {
+ //lay.setSelected(true);
+ //check.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher_check_file_blue_true));
+ lay.setBackground(getResources().getDrawable(R.drawable.recycler_button_pressed));
+ } else {
+ //lay.setSelected(false);
+ //check.setImageDrawable(getResources().getDrawable(R.drawable.cv_shape));
+ lay.setBackground(getResources().getDrawable(R.drawable.recycler_button_normal));
+ }
+ if (datas.get(position).equals(sd1) || datas.get(position).equals(sd2) || datas.get(position).equals(sd3)) {
+ del.setVisibility(View.GONE);
+ delDir.setVisibility(View.GONE);
+ } else {
+ del.setVisibility(View.VISIBLE);
+ delDir.setVisibility(View.VISIBLE);
+ }
+
+ String str1 = textView.getText().toString();
+ str1 = str1.substring(0, str1.lastIndexOf("/"));
+ int idx = str1.lastIndexOf("/");
+ str1 = str1.substring(idx+1,str1.length());
+ textView1.setText(str1);
+
+ File f = new File(textView.getText().toString());
+ if (f.isDirectory() && f.exists()){
+
+ } else {
+ check.setImageDrawable(getResources().getDrawable(R.drawable.xicon_red));
+ delDir.setVisibility(View.VISIBLE);
+ }
+ /*
+ if (f.exists() && f.isDirectory()) {
+ mRvItemOnclickListener.RvItemOnclick(position);
+ mAdapter.updata(mDbDao.queryData(""));
+ } else {
+ if (null != mRvItemOnclickListener) {
+ mRvItemOnclickListener.RvItemOnclick(position);
+ mAdapter.updata(mDbDao.queryData(""));
+ }
+ }
+ */
+ lay.setOnClickListener(v -> {
+ if (f.exists() && f.isDirectory()) {
+ setDir(textView.getText().toString());
+ //finish();
+ //startActivity(new Intent(VersionsActivity.this,VersionsActivity.class));
+ mAdapter.updata(mDbDao.queryData(""));
+ mVerRecyclerView.setAdapter(null);
+ initVers();
+ } else {
+ if (null != mRvItemOnclickListener) {
+ mRvItemOnclickListener.RvItemOnclick(position);
+ mAdapter.updata(mDbDao.queryData(""));
+ Snackbar.make(page, getResources().getString(R.string.ver_null_dir), Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ mVerRecyclerView.setAdapter(null);
+ }
+ }
+
+ });
+ //
+ del.setOnClickListener(view -> {
+ //if (null != mRvItemOnclickListener) {
+ //if (datas.get(position).equals(GetGameJson.getBoatCfg("game_directory", "null"))) {
+ //setDir(sd1);
+ //} else {
+
+ //}
+ // }
+ if (null!=mRvItemOnclickListener){
+ mRvItemOnclickListener.RvItemOnclick(position);
+ }
+ });
+
+ delDir.setOnClickListener(view -> {
+ if (null != mRvItemOnclickListener) {
+ if (datas.get(position).equals(GetGameJson.getBoatCfg("game_directory", "null"))) {
+ setDir(sd1);
+ } else {
+
+ }
+ AlertDialog alertDialog1 = new AlertDialog.Builder(VersionsActivity.this)
+ .setTitle(getResources().getString(R.string.action))//标题
+ .setIcon(R.drawable.ic_boat)//图标
+ .setMessage(R.string.ver_if_del)
+ .setPositiveButton("Yes Yes Yes", new DialogInterface.OnClickListener() {//添加"Yes"按钮
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ File f = new File(datas.get(position));
+ //TODO
+ mRvItemOnclickListener.RvItemOnclick(position);
+ //finish();
+ //startActivity(new Intent(VersionsActivity.this,VersionsActivity.class));
+ mAdapter.updata(mDbDao.queryData(""));
+ new Thread(() -> {
+ //String file2= "/data/data/com.koishi.launcher.h2o2/app_runtime";
+ deleteDirWihtFile(f);
+ /*
+ File file = new File(file2);
+ if(file.isDirectory()){
+ deleteDirectory(file2);
+ }
+ if(file.isFile()){
+ deleteFile(file2);
+ }
+ */
+ }).start();
+
+ }
+ })
+ .setNegativeButton("No No No", new DialogInterface.OnClickListener() {//添加"Yes"按钮
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ //TODO
+ }
+ })
+ .create();
+
+ alertDialog1.show();
+ }
+ });
+ }
+
+ public void deleteDirWihtFile(File dir) {
+ if (dir == null || !dir.exists() || !dir.isDirectory())
+ return;
+ for (File file : dir.listFiles()) {
+ if (file.isFile())
+ file.delete(); // 删除所有文件
+ else if (file.isDirectory())
+ deleteDirWihtFile(file); // 递规的方式删除文件夹
+ }
+ dir.delete();// 删除目录本身
+ }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.dir_item;
+ }
+
+ public void setDir(String dir) {
+ try {
+ FileInputStream in = new FileInputStream("/storage/emulated/0/games/com.koishi.launcher/h2o2/config.txt");
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str = new String(b);
+ JSONObject json = new JSONObject(str);
+ json.remove("game_directory");
+ json.remove("game_assets");
+ json.remove("assets_root");
+ json.remove("currentVersion");
+ json.put("game_directory", dir);
+ json.put("game_assets", dir + "/assets/virtual/legacy");
+ json.put("assets_root", dir + "/assets");
+ json.put("currentVersion", dir + "/versions");
+ FileWriter fr = new FileWriter(new File("/storage/emulated/0/games/com.koishi.launcher/h2o2/config.txt"));
+ fr.write(json.toString());
+ fr.close();
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+ }
+
+ class VersionRecyclerAdapter extends RecyclerView.Adapter{
+ private List datas;
+ private LayoutInflater inflater;
+ public VersionRecyclerAdapter(Context context,List datas){
+ inflater=LayoutInflater.from(context);
+ this.datas=datas;
+ }
+ //创建每一行的View 用RecyclerView.ViewHolder包装
+ @Override
+ public VersionRecyclerAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View itemView=inflater.inflate(R.layout.version_local_item,null);
+ return new MyViewHolder(itemView);
+ }
+ //给每一行View填充数据
+ @Override
+ public void onBindViewHolder(VersionRecyclerAdapter.MyViewHolder holder, @SuppressLint("RecyclerView") int position) {
+ holder.textview.setText(datas.get(position));
+ File f = new File(GetGameJson.getBoatCfg("game_directory","Null")+"/versions/"+datas.get(position));
+ if (f.isDirectory()&&f.exists()){
+ }else{
+ holder.rl.setEnabled(false);
+ holder.ic.setImageDrawable(getResources().getDrawable(R.drawable.xicon_red));
+ }
+ holder.rl.setOnClickListener(v->{
+ holder.dirs = datas.get(position);
+ showExecDialog(holder.dirs);
+ });
+ holder.btn.setOnClickListener(v->{
+ AlertDialog alertDialog1 = new AlertDialog.Builder(VersionsActivity.this)
+ .setTitle(getResources().getString(R.string.action))//标题
+ .setIcon(R.drawable.ic_boat)//图标
+ .setMessage(R.string.ver_if_del)
+ .setPositiveButton("Yes Yes Yes", new DialogInterface.OnClickListener() {//添加"Yes"按钮
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ holder.ic.setImageDrawable(getResources().getDrawable(R.drawable.xicon_red));
+ holder.btn.setVisibility(View.INVISIBLE);
+ holder.textview.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
+ holder.rl.setEnabled(false);
+ File f = new File(GetGameJson.getBoatCfg("game_directory","Null")+"/versions/"+datas.get(position));
+ //TODO
+ new Thread(() -> {
+ //String file2= "/data/data/com.koishi.launcher.h2o2/app_runtime";
+ if (f.isDirectory()){
+ deleteDirWihtFile(f);
+ }else{
+ deleteFile(GetGameJson.getBoatCfg("game_directory","Null")+"/versions/"+datas.get(position));
+ }
+ /*
+ File file = new File(file2);
+ if(file.isDirectory()){
+ deleteDirectory(file2);
+ }
+ if(file.isFile()){
+ deleteFile(file2);
+ }
+ */
+ han.sendEmptyMessage(2);
+ }).start();
+
+ }
+ })
+ .setNegativeButton("No No No", new DialogInterface.OnClickListener() {//添加"Yes"按钮
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ //TODO
+ }
+ })
+ .create();
+
+ alertDialog1.show();
+ });
+ }
+
+ public void showExecDialog(String dir) {
+ mDialog = new Dialog(VersionsActivity.this);
+ View dialogView = VersionsActivity.this.getLayoutInflater().inflate(R.layout.custom_dialog_choose_exec, null);
+ mDialog.setContentView(dialogView);
+ String load = GetGameJson.getAppCfg("allVerLoad","false");
+ String loadDir;
+ if (load.equals("true")){
+ loadDir = GetGameJson.getBoatCfg("game_directory","Null")+"/versions/"+dir;
+ }else{
+ loadDir = GetGameJson.getBoatCfg("game_directory","Null");
+ }
+ LinearLayout lay = dialogView.findViewById(R.id.ver_exec_mod);
+ lay.setOnClickListener(v->{
+ Intent i = new Intent(VersionsActivity.this, ModsActivity.class);
+ Bundle bundle=new Bundle();
+ bundle.putString("mod", loadDir);
+ i.putExtras(bundle);
+ //i.putExtra("dat",c);
+ VersionsActivity.this.startActivity(i);
+ });
+ //mDialog.getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundColor(Color.TRANSPARENT);
+
+ mDialog.setContentView(dialogView);
+ WindowManager windowManager = VersionsActivity.this.getWindowManager();
+ Display display = windowManager.getDefaultDisplay();
+ WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
+ lp.width = (int)(display.getWidth()*0.9); //设置宽度 dialog.getWindow().setAttributes(lp);
+ mDialog.show();
+ }
+ //数据源的数量
+ @Override
+ public int getItemCount() {
+ return datas.size();
+ }
+ class MyViewHolder extends RecyclerView.ViewHolder{
+ private TextView textview;
+ private MaterialButton btn;
+ private ImageView ic;
+ private LinearLayout rl;
+ private String dirs;
+
+ public MyViewHolder(View itemView) {
+ super(itemView);
+ textview = itemView.findViewById(R.id.ver_name);
+ btn = itemView.findViewById(R.id.ver_remove);
+ rl = itemView.findViewById(R.id.ver_item);
+ ic = itemView.findViewById(R.id.ver_icon);
+ }
+ }
+
+ public void deleteDirWihtFile(File dir) {
+ if (dir == null || !dir.exists() || !dir.isDirectory())
+ return;
+ for (File file : dir.listFiles()) {
+ if (file.isFile())
+ file.delete(); // 删除所有文件
+ else if (file.isDirectory())
+ deleteDirWihtFile(file); // 递规的方式删除文件夹
+ }
+ dir.delete();// 删除目录本身
+ }
+
+ public boolean deleteFile(String filePath) {
+ File file = new File(filePath);
+ if (file.isFile() && file.exists()) {
+ return file.delete();
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ finish();
+ startActivity(new Intent(VersionsActivity.this,HomeActivity.class));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ //updateDirList();
+ //initViews();
+ //mRecyclerView.post(() -> updateDirList());
+
+ String currentDir = GetGameJson.getBoatCfg("game_directory","Null");
+ File f = new File(currentDir);
+ //if (mRecyclerView.isComputingLayout()) {
+ //updateDirList();
+ //if (f.exists()&&f.isDirectory()){
+ //if (mDbDao.hasData(dir.trim())){
+ //mDbDao.delete(dir);
+ //updateDirList();
+ //}else{
+//
+ // }
+ //}
+ // }
+ if (f.exists()&&f.isDirectory()){
+ initVers();
+ } else {
+ setDir(sd1);
+ Snackbar.make(page, getResources().getString(R.string.ver_null_dir), Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ mDbDao.delete(currentDir);
+ mAdapter.updata(mDbDao.queryData(""));
+ initVers();
+ }
+ }
+
+ public void setDir(String dir) {
+ try {
+ FileInputStream in = new FileInputStream("/storage/emulated/0/games/com.koishi.launcher/h2o2/config.txt");
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str = new String(b);
+ JSONObject json = new JSONObject(str);
+ json.remove("game_directory");
+ json.remove("game_assets");
+ json.remove("assets_root");
+ json.remove("currentVersion");
+ json.put("game_directory", dir);
+ json.put("game_assets", dir + "/assets/virtual/legacy");
+ json.put("assets_root", dir + "/assets");
+ json.put("currentVersion", dir + "/versions");
+ FileWriter fr = new FileWriter(new File("/storage/emulated/0/games/com.koishi.launcher/h2o2/config.txt"));
+ fr.write(json.toString());
+ fr.close();
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/WelcomeActivity.java b/app/src/main/java/org/koishi/launcher/h2o2pro/WelcomeActivity.java
new file mode 100644
index 00000000..ce9cdb56
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/WelcomeActivity.java
@@ -0,0 +1,141 @@
+package org.koishi.launcher.h2o2pro;
+
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.browser.customtabs.CustomTabsIntent;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.appcompat.widget.Toolbar;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import org.koishi.launcher.h2o2pro.ui.custom.SettingsFragment;
+import org.koishi.launcher.h2o2pro.ui.home.WelcomeFragment;
+
+public class WelcomeActivity extends AppCompatActivity implements View.OnClickListener {
+
+ public Boolean isPermed;
+ public CheckBox skip;
+ public SharedPreferences sp;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_welcome);
+ Toolbar tb = findViewById(R.id.toolbar);
+ Typeface tf = Typeface.createFromAsset(this.getAssets(),
+ "Sans.ttf");
+ TextView bigTitle= (TextView) tb.getChildAt(0);
+ bigTitle.setTypeface(tf);
+ bigTitle.setText(getResources().getString(R.string.app_name));
+ getWindow().setStatusBarColor(getResources().getColor(R.color.material_card_background));
+
+ skip = findViewById(R.id.welcome_skip);
+
+ sp = this.getSharedPreferences("isChecked", 0);
+ boolean result = sp.getBoolean("skip", false); // 这里就是开始取值了 false代表的就是如果没有得到对应数据我们默认显示为false
+ // 把得到的状态设置给CheckBox组件
+ skip.setChecked(result);
+ skip.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (skip.isChecked()) {
+ sp = this.getSharedPreferences("isChecked", 0);
+ SharedPreferences.Editor edit = sp.edit();
+ edit.putBoolean("skip", true);
+ edit.apply();
+ } else {
+ sp = this.getSharedPreferences("isChecked", 0);
+ SharedPreferences.Editor edit = sp.edit();
+ edit.putBoolean("skip", false);
+ edit.apply();
+ }
+ });
+ checkPermission();
+ if (isPermed) {
+
+ }else {
+ skip.setChecked(false);
+ }
+ //if (isTabletDevice(WelcomeActivity.this) == true){
+ //skip.setChecked(false);
+ //finish();
+ //Toast.makeText(WelcomeActivity.this,getResources().getString(R.string.stop_app),Toast.LENGTH_LONG).show();
+ //} else{
+
+ //}
+ if (skip.isChecked()) {
+ startActivity(new Intent(this,SplashActivity.class));
+ this.finish();
+ } else {
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.perm, new WelcomeFragment())
+ .commit();
+ /*
+ if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager())) {
+ //表明已经有这个权限了
+ getPremissionBtn.setEnabled(false);
+ startBtn.setEnabled(true);
+ getPremissionBtn.setText(getResources().getString(R.string.welcome_perm_true));
+ getPremissionBtn.setTextColor(getResources().getColor(R.color.green_light));
+ } else {
+ getPremissionBtn.setEnabled(true);
+ startBtn.setEnabled(false);
+ getPremissionBtn.setText(getResources().getString(R.string.welcome_perm));
+ getPremissionBtn.setTextColor(getResources().getColor(R.color.red_light));
+
+ }
+ */
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ @Override
+ public void onClick(View v) {
+
+ }
+
+ public Boolean checkPermission() {
+ isPermed = true;
+ if (android.os.Build.VERSION.SDK_INT >= 23) {
+ if (this.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ isPermed = false;
+ }
+ if (this.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) !=PackageManager.PERMISSION_GRANTED) {
+ isPermed = false;
+ }
+ }
+ return isPermed;
+ }
+
+ /*
+ private boolean isTabletDevice(Context context) {
+ if ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >=
+ Configuration.SCREENLAYOUT_SIZE_LARGE){
+ return true;
+ } else {
+ return false;
+ }
+ }
+ */
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/adapters/BaseRecycleAdapter.java b/app/src/main/java/org/koishi/launcher/h2o2pro/adapters/BaseRecycleAdapter.java
new file mode 100644
index 00000000..2e63e616
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/adapters/BaseRecycleAdapter.java
@@ -0,0 +1,286 @@
+package org.koishi.launcher.h2o2pro.adapters;
+
+import android.content.Context;
+
+import android.icu.text.Transliterator;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by yi.huangxing on 17/12/13.类描述:
+ */
+
+public abstract class BaseRecycleAdapter extends RecyclerView.Adapter{
+
+ protected List datas;
+ protected Context mContext;
+
+ public BaseRecycleAdapter(List datas,Context mContext) {
+ this.datas = datas;
+ this.mContext =mContext;
+ }
+
+ // 头部控件
+ private View mHeaderView;
+
+ // 底部控件
+ private View mFooterView;
+
+
+ // item 的三种类型
+ public static final int ITEM_TYPE_NORMAL = 0X1111; // 正常的item类型
+ public static final int ITEM_TYPE_HEADER = 0X1112; // header
+ public static final int ITEM_TYPE_FOOTER = 0X1113; // footer
+
+
+ private boolean isHasHeader = false;
+
+ private boolean isHasFooter = false;
+
+ @Override
+ public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+
+ if(viewType==ITEM_TYPE_FOOTER){
+ // 如果是底部类型,返回底部视图
+ return new BaseViewHolder(mFooterView);
+ }
+
+ if(viewType==ITEM_TYPE_HEADER){
+ return new BaseViewHolder(mHeaderView);
+ }
+ View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(),parent,false);
+ return new BaseViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(BaseRecycleAdapter.BaseViewHolder holder, final int position) {
+
+ if(isHasHeader&&isHasFooter){
+ // 有头布局和底部时,向前便宜一个,且最后一个不能绑定数据
+ if(position==0 ||position==datas.size()+1){
+ return;
+ }
+ bindData(holder,position-1);
+ }
+
+ if(position!=0&&isHasHeader&&!isHasFooter){
+ // 有顶部,但没有底部
+ bindData(holder,position-1);
+ }
+
+ if(!isHasHeader&&isHasFooter){
+ // 没有顶部,但有底部
+ if(position==datas.size()){
+ return;
+ }
+ bindData(holder,position);
+ }
+
+ if(!isHasHeader&&!isHasFooter){
+ // 没有顶部,没有底部
+ bindData(holder,position);
+ }
+
+ }
+
+ /**
+ * 添加头部视图
+ * @param header
+ */
+ public void setHeaderView(View header){
+ this.mHeaderView = header;
+ isHasHeader = true;
+ notifyDataSetChanged();
+ }
+
+ /**
+ * 添加底部视图
+ * @param footer
+ */
+ public void setFooterView(View footer){
+ this.mFooterView = footer;
+ isHasFooter = true;
+ notifyDataSetChanged();
+ }
+
+
+
+ @Override
+ public int getItemViewType(int position) {
+
+ // 根据索引获取当前View的类型,以达到复用的目的
+
+ // 根据位置的索引,获取当前position的类型
+ if(isHasHeader&&position==0){
+ return ITEM_TYPE_HEADER;
+ }
+ if(isHasHeader&&isHasFooter&&position==datas.size()+1){
+ // 有头部和底部时,最后底部的应该等于size+!
+ return ITEM_TYPE_FOOTER;
+ }else if(!isHasHeader&&isHasFooter&&position==datas.size()){
+ // 没有头部,有底部,底部索引为size
+ return ITEM_TYPE_FOOTER;
+ }
+ return ITEM_TYPE_NORMAL;
+ }
+
+ /**
+ * 刷新数据
+ * @param datas
+ */
+ public void refresh(List datas){
+ this.datas.clear();
+ this.datas.addAll(datas);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * 刷新数据
+ * @param data
+ */
+ public void updata(List data){
+ this.datas=data;
+ notifyDataSetChanged();
+ }
+
+ /**
+ * 添加数据
+ * @param datas
+ */
+ public void addData(List datas){
+ this.datas.addAll(datas);
+ notifyDataSetChanged();
+ }
+ /**
+ * 移除数据
+ *
+ * @param position
+ */
+ public void remove(int position) {
+ if (position >= 0 && position < datas.size()) {
+ datas.remove(position);
+ notifyDataSetChanged();
+ }
+ }
+
+
+ /**
+ * 绑定数据
+ * @param holder 具体的viewHolder
+ * @param position 对应的索引
+ */
+ protected abstract void bindData(BaseViewHolder holder, int position);
+
+
+
+ @Override
+ public int getItemCount() {
+ int size = datas.size();
+ if(isHasFooter)
+ size ++;
+ if(isHasHeader)
+ size++;
+ return size;
+ }
+
+
+
+
+ /**
+ * 封装ViewHolder ,子类可以直接使用
+ */
+ public class BaseViewHolder extends RecyclerView.ViewHolder{
+
+
+ private Map mViewMap;
+
+ public BaseViewHolder(View itemView) {
+ super(itemView);
+ mViewMap = new HashMap<>();
+ }
+
+ /**
+ * 获取设置的view
+ * @param id
+ * @return
+ */
+ public View getView(int id) {
+ View view = mViewMap.get(id);
+ if (view == null) {
+ view = itemView.findViewById(id);
+ mViewMap.put(id, view);
+ }
+ return view;
+ }
+ }
+
+ /**
+ * 获取子item
+ * @return
+ */
+ public abstract int getLayoutId();
+
+
+
+ /**
+ * 设置文本属性
+ * @param view
+ * @param text
+ */
+ public void setItemText(View view,String text){
+ if(view instanceof TextView){
+ ((TextView) view).setText(text);
+ }
+ }
+
+
+ public RvItemOnclickListener getRvItemOnclickListener() {
+ return mRvItemOnclickListener;
+ }
+
+ public void setRvItemOnclickListener(RvItemOnclickListener rvItemOnclickListener) {
+ mRvItemOnclickListener = rvItemOnclickListener;
+ }
+
+ protected RvItemOnclickListener mRvItemOnclickListener;
+
+
+ public interface RvItemOnclickListener{
+
+ void RvItemOnclick(int position);
+ }
+
+
+
+ /**
+ * 这个方法很重要的
+ * @param holder
+ */
+//
+// @Override
+// public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+// super.onAttachedToRecyclerView(recyclerView);
+//
+// RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
+// if(manager instanceof GridLayoutManager) {
+// final GridLayoutManager gridManager = ((GridLayoutManager) manager);
+// gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+// @Override
+// public int getSpanSize(int position) {
+// return getItemViewType(position) == TYPE_HEADER
+// ? gridManager.getSpanCount() : 1;
+// }
+// });
+// }
+// }
+
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/AssetsUtils.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/AssetsUtils.java
new file mode 100644
index 00000000..dc4fee27
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/AssetsUtils.java
@@ -0,0 +1,107 @@
+package org.koishi.launcher.h2o2pro.tool;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+/**
+ * Created by shenhua on 1/17/2017.
+ * Email shenhuanet@126.com
+ */
+public class AssetsUtils {
+
+ private static AssetsUtils instance;
+ private static final int SUCCESS = 1;
+ private static final int FAILED = 0;
+ private Context context;
+ private FileOperateCallback callback;
+ private volatile boolean isSuccess;
+ private String errorStr;
+
+ public static AssetsUtils getInstance(Context context) {
+ if (instance == null)
+ instance = new AssetsUtils(context);
+ return instance;
+ }
+
+ private AssetsUtils (Context context) {
+ this.context = context;
+ }
+
+ private Handler handler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ if (callback != null) {
+ if (msg.what == SUCCESS) {
+ callback.onSuccess();
+ }
+ if (msg.what == FAILED) {
+ callback.onFailed(msg.obj.toString());
+ }
+ }
+ }
+ };
+
+ public AssetsUtils copyAssetsToSD(final String srcPath, final String sdPath) {
+ new Thread(() -> {
+ copyAssetsToDst(context, srcPath, sdPath);
+ if (isSuccess)
+ handler.obtainMessage(SUCCESS).sendToTarget();
+ else
+ handler.obtainMessage(FAILED, errorStr).sendToTarget();
+ }).start();
+ return this;
+ }
+
+ public void setFileOperateCallback(FileOperateCallback callback) {
+ this.callback = callback;
+ }
+
+ private void copyAssetsToDst(Context context, String srcPath, String dstPath) {
+ try {
+ String fileNames[] = context.getAssets().list(srcPath);
+ if (fileNames.length > 0) {
+ File file = new File(Environment.getExternalStorageDirectory(), dstPath);
+ if (!file.exists()) file.mkdirs();
+ for (String fileName : fileNames) {
+ if (!srcPath.equals("")) { // assets 文件夹下的目录
+ copyAssetsToDst(context, srcPath + File.separator + fileName, dstPath + File.separator + fileName);
+ } else { // assets 文件夹
+ copyAssetsToDst(context, fileName, dstPath + File.separator + fileName);
+ }
+ }
+ } else {
+ File outFile = new File(Environment.getExternalStorageDirectory(), dstPath);
+ InputStream is = context.getAssets().open(srcPath);
+ FileOutputStream fos = new FileOutputStream(outFile);
+ byte[] buffer = new byte[1024];
+ int byteCount;
+ while ((byteCount = is.read(buffer)) != -1) {
+ fos.write(buffer, 0, byteCount);
+ }
+ fos.flush();
+ is.close();
+ fos.close();
+ }
+ isSuccess = true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ errorStr = e.getMessage();
+ isSuccess = false;
+ }
+ }
+
+ public interface FileOperateCallback {
+ void onSuccess();
+
+ void onFailed(String error);
+ }
+
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/GetGameJson.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/GetGameJson.java
new file mode 100644
index 00000000..e53e717f
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/GetGameJson.java
@@ -0,0 +1,115 @@
+package org.koishi.launcher.h2o2pro.tool;
+
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+
+public class GetGameJson {
+
+ public static String boatCfg = "/storage/emulated/0/games/com.koishi.launcher/h2o2/config.txt";
+ public static String h2oCfg = "/storage/emulated/0/games/com.koishi.launcher/h2o2/h2ocfg.json";
+
+ public static void setBoatJson(String name, String value) {
+ try {
+ FileInputStream in = new FileInputStream(boatCfg);
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str = new String(b);
+ JSONObject json = new JSONObject(str);
+ json.remove(name);
+ json.put(name, value);
+ FileWriter fr = new FileWriter(new File(boatCfg));
+ fr.write(json.toString());
+ fr.close();
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+
+ public static void setAppJson(String name, String value) {
+ try {
+ FileInputStream in = new FileInputStream(h2oCfg);
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str = new String(b);
+ JSONObject json = new JSONObject(str);
+ json.remove(name);
+ json.put(name, value);
+ FileWriter fr = new FileWriter(new File(h2oCfg));
+ fr.write(json.toString());
+ fr.close();
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+
+ public static void setExtraJson(String name, String value, String dir) {
+ try {
+ FileInputStream in = new FileInputStream(dir);
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str = new String(b);
+ JSONObject json = new JSONObject(str);
+ json.remove(name);
+ json.put(name, value);
+ FileWriter fr = new FileWriter(new File(dir));
+ fr.write(json.toString());
+ fr.close();
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+
+ //---------------------获取json的值---------------------
+
+ //h2ocfg
+ public static String getBoatCfg(String name, String defaultValue) {
+ try {
+ FileInputStream in=new FileInputStream(boatCfg);
+ byte[] b=new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str=new String(b);
+ JSONObject json=new JSONObject(str);
+ return json.getString(name);
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ return defaultValue;
+ }
+
+ public static String getAppCfg(String name, String defaultValue) {
+ try {
+ FileInputStream in=new FileInputStream(h2oCfg);
+ byte[] b=new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str=new String(b);
+ JSONObject json=new JSONObject(str);
+ return json.getString(name);
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ return defaultValue;
+ }
+
+ public static String getExtraCfg(String value, String defaultValue, String dir) {
+ try {
+ FileInputStream in=new FileInputStream(dir);
+ byte[] b=new byte[in.available()];
+ in.read(b);
+ in.close();
+ String str=new String(b);
+ JSONObject json=new JSONObject(str);
+ return json.getString(value);
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ return defaultValue;
+ }
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/StatusBarUtil.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/StatusBarUtil.java
new file mode 100644
index 00000000..0b194b33
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/StatusBarUtil.java
@@ -0,0 +1,57 @@
+package org.koishi.launcher.h2o2pro.tool;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.WindowManager;
+
+public class StatusBarUtil {
+ public static boolean hasNavigationBarShow(Activity activity) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return false;
+ }
+ WindowManager wm = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ //获取整个屏幕的高度
+ display.getRealMetrics(outMetrics);
+ int heightPixels = outMetrics.heightPixels;
+ int widthPixels = outMetrics.widthPixels;
+ //获取内容展示部分的高度
+ outMetrics = new DisplayMetrics();
+ display.getMetrics(outMetrics);
+ int heightPixelsContent = outMetrics.heightPixels;
+ int widthPixelsContent = outMetrics.widthPixels;
+ int h = heightPixels - heightPixelsContent;
+ int w = widthPixels - widthPixelsContent;
+ return w > 0 || h > 0; //竖屏和横屏两种情况
+ }
+
+ /**
+ * 获取导航栏高度
+ *
+ * @param context
+ * @return
+ */
+ public static int getNavigationBarHeight(Context context) {
+ return getSystemComponentDimen(context, "navigation_bar_height");
+ }
+
+ public static int getSystemComponentDimen(Context context, String dimenName) {
+ // 反射手机运行的类:android.R.dimen.status_bar_height.
+ int statusHeight = -1;
+ try {
+ Class> clazz = Class.forName("com.android.internal.R$dimen");
+ Object object = clazz.newInstance();
+ String heightStr = clazz.getField(dimenName).get(object).toString();
+ int height = Integer.parseInt(heightStr);
+ //dp->px
+ statusHeight = context.getResources().getDimensionPixelSize(height);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return statusHeight;
+ }
+}
diff --git a/app/src/main/java/com/koishi/launcher/h2o2/tools/Consant.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/Consant.java
similarity index 80%
rename from app/src/main/java/com/koishi/launcher/h2o2/tools/Consant.java
rename to app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/Consant.java
index ca31e235..415aab33 100644
--- a/app/src/main/java/com/koishi/launcher/h2o2/tools/Consant.java
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/Consant.java
@@ -1,4 +1,4 @@
-package com.koishi.launcher.h2o2.tools;
+package org.koishi.launcher.h2o2pro.tool.data;
/**
* 作者:Leon
diff --git a/app/src/main/java/com/koishi/launcher/h2o2/tools/DBHelper.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DBHelper.java
similarity index 79%
rename from app/src/main/java/com/koishi/launcher/h2o2/tools/DBHelper.java
rename to app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DBHelper.java
index 432de2da..78d5c82e 100644
--- a/app/src/main/java/com/koishi/launcher/h2o2/tools/DBHelper.java
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DBHelper.java
@@ -1,12 +1,13 @@
-package com.koishi.launcher.h2o2.tools;
+package org.koishi.launcher.h2o2pro.tool.data;
+import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import com.koishi.launcher.h2o2.tools.DBUser.User;
+import org.koishi.launcher.h2o2pro.tool.data.DBUser.User;
/**
* 操作数据库辅助类
@@ -17,7 +18,7 @@ public class DBHelper {
public static final int DB_VERSION = 1;
public static final String DB_NAME = "example.db";
public static final String USER_TABLE_NAME = "user_table";
- public static final String[] USER_COLS = { User.USERNAME, User.PASSWORD,
+ public static final String[] USER_COLS = { User.USERNAME, User.PASSWORD, User.API,
User.ISSAVED };
private SQLiteDatabase db;
@@ -46,7 +47,7 @@ private void establishDb() {
* 表名
* @return 插入记录的id -1表示插入不成功
*/
- public long insertOrUpdate(String userName, String password, int isSaved) {
+ public long insertOrUpdate(String userName, String password, String api, int isSaved) {
boolean isUpdate = false;
String[] usernames = queryAllUserName();
for (int i = 0; i < usernames.length; i++) {
@@ -57,12 +58,13 @@ public long insertOrUpdate(String userName, String password, int isSaved) {
}
long id = -1;
if (isUpdate) {
- id = update(userName, password, isSaved);
+ id = update(userName, password, api, isSaved);
} else {
if (db != null) {
ContentValues values = new ContentValues();
values.put(User.USERNAME, userName);
values.put(User.PASSWORD, password);
+ values.put(User.API, api);
values.put(User.ISSAVED, isSaved);
id = db.insert(USER_TABLE_NAME, null, values);
}
@@ -94,10 +96,11 @@ public long delete(String userName) {
* 表名
* @return 更新过后记录的id -1表示更新不成功
*/
- public long update(String username, String password, int isSaved) {
+ public long update(String username, String password, String api, int isSaved) {
ContentValues values = new ContentValues();
values.put(User.USERNAME, username);
values.put(User.PASSWORD, password);
+ values.put(User.API, api);
values.put(User.ISSAVED, isSaved);
long id = db.update(USER_TABLE_NAME, values, User.USERNAME + " = '"
+ username + "'", null);
@@ -106,7 +109,7 @@ public long update(String username, String password, int isSaved) {
/**
* 根据用户名查询密码
- *
+ *
* @param username
* 用户名
* @param tableName
@@ -126,12 +129,36 @@ public String queryPasswordByName(String username) {
return password;
}
+ /**
+ * 根据用户名查询密码
+ *
+ * @param username
+ * 用户名
+ * @param tableName
+ * 表名
+ * @return Hashmap 查询的记录
+ */
+
+ public String queryApiByName(String username) {
+ String sql = "select * from " + USER_TABLE_NAME + " where "
+ + User.USERNAME + " = '" + username + "'";
+ Cursor cursor5 = db.rawQuery(sql, null);
+ String api = "";
+ if (cursor5.getCount() > 0) {
+ cursor5.moveToFirst();
+ api = cursor5.getString(cursor5.getColumnIndex(User.API));
+ }
+
+ return api;
+ }
+
/**
* 记住密码选项框是否被选中
*
* @param username
* @return
*/
+ @SuppressLint("Range")
public int queryIsSavedByName(String username) {
String sql = "select * from " + USER_TABLE_NAME + " where "
+ User.USERNAME + " = '" + username + "'";
@@ -151,6 +178,7 @@ public int queryIsSavedByName(String username) {
* 表名
* @return 所有记录的list集合
*/
+ @SuppressLint("Range")
public String[] queryAllUserName() {
if (db != null) {
Cursor cursor = db.query(USER_TABLE_NAME, null, null, null, null,
@@ -194,8 +222,8 @@ public DBOpenHelper(Context context) {
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table " + USER_TABLE_NAME + " (" + User._ID
- + " integer primary key," + User.USERNAME + " text, "
- + User.PASSWORD + " text, " + User.ISSAVED + " INTEGER)");
+ + " integer primary key, " + User.USERNAME + " text, "
+ + User.PASSWORD + " text, " + User.API + " text, " + User.ISSAVED + " INTEGER)");
}
@Override
diff --git a/app/src/main/java/com/koishi/launcher/h2o2/tools/DBUser.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DBUser.java
similarity index 77%
rename from app/src/main/java/com/koishi/launcher/h2o2/tools/DBUser.java
rename to app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DBUser.java
index 4c6aa783..ecfe3814 100644
--- a/app/src/main/java/com/koishi/launcher/h2o2/tools/DBUser.java
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DBUser.java
@@ -1,4 +1,4 @@
-package com.koishi.launcher.h2o2.tools;
+package org.koishi.launcher.h2o2pro.tool.data;
import android.provider.BaseColumns;
@@ -11,6 +11,7 @@ public final class DBUser {
public static final class User implements BaseColumns {
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
+ public static final String API = "api";
public static final String ISSAVED = "issaved";
}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DbDao.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DbDao.java
new file mode 100644
index 00000000..ccae7426
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/DbDao.java
@@ -0,0 +1,99 @@
+package org.koishi.launcher.h2o2pro.tool.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by yi.huangxing on 17/12/13.类描述: 数据库的增删改查
+ */
+
+public class DbDao {
+
+ private Context context;
+ private RecordSQLiteOpenHelper helper;
+ private SQLiteDatabase db;
+
+ public DbDao(Context context) {
+ this.context = context;
+ init();
+ }
+
+ private void init() {
+
+ //实例化数据库SQLiteOpenHelper子类对象
+ helper = new RecordSQLiteOpenHelper(context);
+ // 第一次进入时查询所有的历史记录
+ queryData("");
+ }
+
+ public List queryData(String tempName) {
+ List data = new ArrayList<>();
+ //模糊搜索
+ Cursor cursor = helper.getReadableDatabase().rawQuery(
+ "select id as _id,name from records where name like '%" + tempName + "%' order by id desc ", null);
+
+ while (cursor.moveToNext()) {
+ //注意这里的name跟建表的name统一
+ String name = cursor.getString(cursor.getColumnIndex("name"));
+ data.add(name);
+ }
+ cursor.close();
+ return data;
+
+ }
+
+ /**
+ * 检查数据库中是否已经有该条记录
+ *
+ * @param tempName
+ * @return
+ */
+ public boolean hasData(String tempName) {
+ //从Record这个表里找到name=tempName的id
+ Cursor cursor = helper.getReadableDatabase().rawQuery(
+ "select id as _id,name from records where name =?", new String[]{tempName});
+ //判断是否有下一个
+ return cursor.moveToNext();
+ }
+
+ /**
+ * 插入数据
+ *
+ * @param tempName
+ */
+ public void insertData(String tempName) {
+ db = helper.getWritableDatabase();
+ db.execSQL("insert into records(name) values('" + tempName + "')");
+ db.close();
+ }
+
+ /**
+ * 插入数据
+ *
+ * @param name
+ * @return
+ */
+
+ public int delete(String name) {
+ // 获取数据
+ SQLiteDatabase db = helper.getWritableDatabase();
+ // 执行SQL
+ int delete = db.delete("records", " name=?", new String[]{name});
+ // 关闭数据库连接
+ db.close();
+ return delete;
+ }
+
+ /**
+ * 清空数据
+ */
+ public void deleteData() {
+ db = helper.getWritableDatabase();
+ db.execSQL("delete from records");
+ db.close();
+ }
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/RecordSQLiteOpenHelper.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/RecordSQLiteOpenHelper.java
new file mode 100644
index 00000000..b382ecb5
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/data/RecordSQLiteOpenHelper.java
@@ -0,0 +1,31 @@
+package org.koishi.launcher.h2o2pro.tool.data;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Created by yi.huangxing on 17/12/13.类描述:
+ */
+
+public class RecordSQLiteOpenHelper extends SQLiteOpenHelper{
+ private static String name = "record.db";
+ private static Integer version = 1;
+
+
+ public RecordSQLiteOpenHelper(Context context) {
+ super(context, name, null, version);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ //打开数据库,建立了一个叫records的表,里面只有一列name来存储历史记录:
+ db.execSQL("create table records(id integer primary key autoincrement,name varchar(200))");
+
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
+
+ }
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/file/AppExecute.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/file/AppExecute.java
new file mode 100644
index 00000000..a9ca6e4e
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/file/AppExecute.java
@@ -0,0 +1,56 @@
+package org.koishi.launcher.h2o2pro.tool.file;
+
+import android.content.Context;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class AppExecute {
+ public static void output(Context context, String assetName, String outputDirectory) throws IOException {
+ File file = new File(outputDirectory);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+
+ InputStream inputStream = null;
+ inputStream = context.getAssets().open(assetName);
+ ZipInputStream zipInputStream = new ZipInputStream(inputStream);
+ ZipEntry zipEntry = zipInputStream.getNextEntry();
+ byte[] buffer = new byte[1024 * 1024];
+ int count = 0;
+ while (zipEntry != null) {
+ //如果是一个目录
+ if (zipEntry.isDirectory()) {
+ //String name = zipEntry.getName();
+ //name = name.substring(0, name.length() - 1);
+ file = new File(outputDirectory + File.separator + zipEntry.getName());
+ file.mkdir();
+ } else {
+ //如果是文件
+ file = new File(outputDirectory + File.separator
+ + zipEntry.getName());
+ //创建该文件
+ file.createNewFile();
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ while ((count = zipInputStream.read(buffer)) > 0) {
+ fileOutputStream.write(buffer, 0, count);
+ }
+
+ fileOutputStream.close();
+ }
+ //定位到下一个文件入口
+ zipEntry = zipInputStream.getNextEntry();
+
+ }
+ zipInputStream.close();
+
+ }
+
+
+
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/Json.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/Json.java
new file mode 100644
index 00000000..4b1ebf20
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/Json.java
@@ -0,0 +1,166 @@
+package org.koishi.launcher.h2o2pro.tool.json;
+
+import org.koishi.launcher.h2o2pro.tool.json.abstracts.JsonHandler;
+import org.koishi.launcher.h2o2pro.tool.json.abstracts.JsonValue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * The type Json.
+ */
+public class Json {
+
+ private final JsonGenerator jsonGenerator = new JsonGenerator();
+ private final JsonParser jsonParser = new JsonParser();
+
+ /**
+ * Parse {@link String} to {@link JsonValue}.
+ *
+ * @param json the json used to parse
+ * @return the json value is the raw return type
+ */
+ public JsonValue fromJsonString(String json) {
+ return jsonParser.parse(json);
+ }
+
+ /**
+ * Parse {@link String} to {@link JsonValue} and cast it to {@link Class}
+ *
+ * @param the type parameter determines the type of {@link JsonValue}
+ * @param json the input stream is used to parse
+ * @param clazz the clazz is used to cast {@link T}
+ * @return the value {@link T}
+ */
+ public T fromJsonString(String json, Class clazz) {
+ return jsonParser.parse(json, clazz);
+ }
+
+
+ /**
+ * Parse {@link File} to {@link JsonValue} and cast it to {@link Class}
+ *
+ * @param the type parameter determines the type of {@link JsonValue}
+ * @param file the file is used to parse
+ * @param clazz the clazz is used to cast {@link T}
+ * @return the value {@link T}
+ * @throws FileNotFoundException the file not found exception
+ */
+ public T fromFile(File file, Class clazz) throws FileNotFoundException {
+ return clazz.cast(jsonParser.parse(file));
+ }
+
+ /**
+ * Parse {@link File} to {@link JsonValue}.
+ *
+ * @param file the file used to parse
+ * @return the json value raw return type
+ * @throws FileNotFoundException the file not found exception
+ */
+ public JsonValue fromFile(File file) throws FileNotFoundException {
+ return jsonParser.parse(new FileInputStream(file));
+ }
+
+
+ /**
+ * Parse {@link InputStream} to {@link JsonValue} and cast it to {@link Class}
+ *
+ * @param the type parameter determines the type of {@link JsonValue}
+ * @param inputStream the input stream is used to parse
+ * @param clazz the clazz is used to cast {@link T}
+ * @return the value {@link T}
+ */
+ public T parse(InputStream inputStream, Class clazz) {
+ return jsonParser.parse(inputStream, clazz);
+ }
+
+ /**
+ * Parse json value with {@link InputStream}
+ *
+ * @param inputStream the input stream is used to parse
+ * @return the json value is the raw return type
+ */
+ public JsonValue parse(InputStream inputStream) {
+ return jsonParser.parse(inputStream);
+ }
+
+ /**
+ * To json value.
+ * Parse object of type {@link T} to {@link JsonValue}
+ *
+ * @param the type parameter used to determine value
+ * @param value the value used to parse
+ * @return the json value returns raw type of {@link JsonValue}
+ */
+ public JsonValue toJson(T value) {
+ return jsonGenerator.toJson(value);
+ }
+
+
+ /**
+ * From json to list.
+ * Convert {@link JsonValue} to {@link T} and cast with {@link Class}
+ *
+ * @param the type parameter determines object type
+ * @param the type parameter is the class that is used t ocast
+ * @param jsonValue the json value is used to convert
+ * @param listClazz the list clazz is used to cast {@link T}
+ * @param clazz the clazz is used to determine object in {@link List}
+ * @return T is a list with cast {@link Class}
+ */
+ public , K> T fromJson(JsonValue jsonValue, Class listClazz, Class clazz) {
+ return jsonGenerator.fromJson(jsonValue, listClazz, clazz);
+ }
+
+ /**
+ * From json with cast.
+ * Convert {@link JsonValue} to {@link T}
+ *
+ * @param the type parameter determines type of return object
+ * @param jsonValue the json value is used to convert to {@link T}
+ * @param clazz the clazz is used to cast {@link T}
+ * @return {@link T} cast with {@link Class}
+ */
+ public T fromJsonCast(JsonValue jsonValue, Class clazz) {
+ return jsonGenerator.fromJsonCast(jsonValue, clazz);
+ }
+
+ /**
+ * From json.
+ * Convert {@link JsonValue} to raw object
+ *
+ * @param the type parameter determines type of return object
+ * @param jsonValue the json value is used to convert to {@link Object}
+ * @param clazz the clazz is used create object
+ * @return the object
+ */
+ public Object fromJson(JsonValue jsonValue, Class clazz) {
+ return jsonGenerator.fromJson(jsonValue, clazz);
+ }
+
+
+ /**
+ * Register handler.
+ * Register a {@link JsonHandler >} by {@link Class>}
+ *
+ * @param clazz the clazz used to put {@link Class>} and {@link JsonHandler>} in {@link #jsonGenerator}
+ * @param jsonHandler the json handler is used to insert
+ */
+ public void registerHandler(Class> clazz, JsonHandler> jsonHandler) {
+ jsonGenerator.registerHandler(clazz, jsonHandler);
+ }
+
+ /**
+ * Unregister handler.
+ * Unregister a {@link JsonHandler} by {@link Class>}
+ *
+ * @param clazz the clazz used to remove {@link JsonHandler} from {@link #jsonGenerator}
+ */
+ public void unregisterHandler(Class> clazz) {
+ jsonGenerator.unregisterHandler(clazz);
+ }
+
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/JsonGenerator.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/JsonGenerator.java
new file mode 100644
index 00000000..146bfb83
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/JsonGenerator.java
@@ -0,0 +1,299 @@
+package org.koishi.launcher.h2o2pro.tool.json;
+
+import org.koishi.launcher.h2o2pro.tool.json.abstracts.JsonValue;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonArray;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonBoolean;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonNull;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonNumber;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonObject;
+import org.koishi.launcher.h2o2pro.tool.json.abstracts.JsonHandler;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonString;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The class Json generator is used to parse json to a class or object to json.
+ */
+public class JsonGenerator {
+
+ /**
+ * The Handlers is {@link Map} with key {@link Class>} and {@link JsonHandler>}
+ */
+ protected final HashMap, JsonHandler>> handlers = new HashMap<>();
+
+ /**
+ * To json value.
+ * Parse object of type {@link T} to {@link JsonValue}
+ *
+ * @param the type parameter used to determine value
+ * @param value the value used to parse
+ * @return the json value returns raw type of {@link JsonValue}
+ */
+ public JsonValue toJson(T value) {
+ JsonValue jsonValue;
+ if (value == null) {
+ jsonValue = new JsonNull();
+ return jsonValue;
+ }
+
+ JsonHandler jsonHandler = getHandler(value.getClass());
+
+ if (jsonHandler != null) {
+ return jsonHandler.deserialize(value);
+ }
+
+ Class> clazz = value.getClass();
+
+ if (String.class.isAssignableFrom(clazz)) {
+ jsonValue = new JsonString();
+ ((JsonString) jsonValue).setValue((String) value);
+ return jsonValue;
+ } else if (Enum.class.isAssignableFrom(clazz)) {
+ jsonValue = new JsonString();
+ ((JsonString) jsonValue).setValue(((Enum) value).name());
+ return jsonValue;
+ } else if (Number.class.isAssignableFrom(clazz)) {
+ jsonValue = new JsonNumber();
+ ((JsonNumber) jsonValue).setValue(value.toString());
+ return jsonValue;
+ } else if (Boolean.class.isAssignableFrom(clazz)) {
+ jsonValue = new JsonBoolean();
+ ((JsonBoolean) jsonValue).setValue((Boolean) value);
+ return jsonValue;
+ } else if (List.class.isAssignableFrom(clazz)) {
+ JsonArray jsonArray = new JsonArray();
+ List> list = (List>) value;
+ for (Object listValue : list) {
+ jsonArray.add(toJson(listValue));
+ }
+ return jsonArray;
+ } else if (Map.class.isAssignableFrom(clazz)) {
+ JsonObject jsonObject = new JsonObject();
+ Map, ?> map = (Map, ?>) value;
+
+ for (Map.Entry, ?> entry : map.entrySet()) {
+ Object key = entry.getKey();
+ Object mapValue = entry.getValue();
+ if (key == null) {
+ jsonObject.add("null", toJson(mapValue));
+ continue;
+ }
+
+ Class> keyClazz = key.getClass();
+ if (String.class.isAssignableFrom(keyClazz)
+ || Number.class.isAssignableFrom(keyClazz)
+ || Boolean.class.isAssignableFrom(keyClazz)) {
+ jsonObject.add(String.valueOf(key), toJson(mapValue));
+ } else if (Enum.class.isAssignableFrom(keyClazz)) {
+ jsonObject.add(((Enum) key).name(), toJson(mapValue));
+ }
+
+ }
+ return jsonObject;
+ } else {
+ JsonObject jsonObject = new JsonObject();
+
+ if (hasSuperclass(clazz)) {
+ for (Field declaredField : clazz.getSuperclass().getDeclaredFields()) {
+ declaredField.setAccessible(true);
+ try {
+ JsonValue transformedValue = toJson(declaredField.get(value));
+ transformedValue.setKey(declaredField.getName());
+
+ jsonObject.add(transformedValue);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ for (Field declaredField : clazz.getDeclaredFields()) {
+ declaredField.setAccessible(true);
+ try {
+ JsonValue transformedValue = toJson(declaredField.get(value));
+ transformedValue.setKey(declaredField.getName());
+
+ jsonObject.add(transformedValue);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ jsonValue = jsonObject;
+ }
+
+
+ return jsonValue;
+ }
+
+ /**
+ * From json to list.
+ * Convert {@link JsonValue} to {@link T} and cast with {@link Class}
+ *
+ * @param the type parameter determines object type
+ * @param the type parameter is the class that is used t ocast
+ * @param jsonValue the json value is used to convert
+ * @param listClazz the list clazz is used to cast {@link T}
+ * @param clazz the clazz is used to determine object in {@link List}
+ * @return T is a list with cast {@link Class}
+ */
+ public , K> T fromJson(JsonValue jsonValue, Class listClazz, Class clazz) {
+ return listClazz.cast(fromJson(null, jsonValue, clazz));
+ }
+
+ /**
+ * From json with cast.
+ * Convert {@link JsonValue} to {@link T}
+ *
+ * @param the type parameter determines type of return object
+ * @param jsonValue the json value is used to convert to {@link T}
+ * @param clazz the clazz is used to cast {@link T}
+ * @return {@link T} cast with {@link Class}
+ */
+ public T fromJsonCast(JsonValue jsonValue, Class clazz) {
+ return clazz.cast(fromJson(null, jsonValue, clazz));
+ }
+
+ /**
+ * From json.
+ * Convert {@link JsonValue} to raw object
+ *
+ * @param the type parameter determines type of return object
+ * @param jsonValue the json value is used to convert to {@link Object}
+ * @param clazz the clazz is used create object
+ * @return the object
+ */
+ public Object fromJson(JsonValue jsonValue, Class clazz) {
+ return fromJson(null, jsonValue, clazz);
+ }
+
+ /**
+ * From json.
+ * Convert {@link JsonValue} to raw object
+ *
+ * @param the type parameter determines type of return object
+ * @param key the key is used to get a value out of {@link JsonObject}
+ * @param jsonValue the json value is used to convert to {@link Object}
+ * @param clazz the clazz is used create object
+ * @return the object
+ */
+ public Object fromJson(String key, JsonValue jsonValue, Class clazz) {
+ if (jsonValue instanceof JsonString) {
+ return clazz.cast(((JsonString) jsonValue).getValue());
+ } else if (jsonValue instanceof JsonNumber) {
+ return ((JsonNumber) jsonValue).getNumber(clazz);
+ } else if (jsonValue instanceof JsonBoolean) {
+ return clazz.cast(((JsonBoolean) jsonValue).isValue());
+ } else if (jsonValue instanceof JsonNull) {
+ return null;
+ } else if (jsonValue instanceof JsonObject) {
+ if (key != null) {
+ return fromJson(((JsonObject) jsonValue).get(key), clazz);
+ } else {
+ try {
+ T object = clazz.newInstance();
+
+ JsonHandler jsonHandler = getHandler(clazz);
+
+ if (jsonHandler != null) {
+ return jsonHandler.serialize(jsonValue);
+ }
+
+ Class> objectClazz = object.getClass();
+
+ if (hasSuperclass(objectClazz)) {
+ for (Field declaredField : objectClazz.getSuperclass().getDeclaredFields()) {
+ declaredField.setAccessible(true);
+ declaredField.set(object, fromJson(declaredField.getName(), jsonValue, declaredField.getType()));
+ }
+ }
+
+ for (Field declaredField : objectClazz.getDeclaredFields()) {
+ declaredField.setAccessible(true);
+ declaredField.set(object, fromJson(declaredField.getName(), jsonValue, declaredField.getType()));
+ }
+
+ return object;
+ } catch (InstantiationException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ } else if (jsonValue instanceof JsonArray) {
+ List list = new ArrayList();
+ ((JsonArray) jsonValue).loop((integer, listValue) -> {
+ list.add(fromJson(listValue, clazz));
+ });
+ return list;
+ }
+
+
+ return null;
+ }
+
+ /**
+ * Has superclass boolean.
+ * Checks if given class has a superclass
+ *
+ * @param the type parameter determines type of class used in class
+ * @param clazz the clazz is used to check
+ * @return the boolean true or false
+ */
+ public boolean hasSuperclass(Class clazz) {
+ return clazz.getSuperclass() != null;
+ }
+
+ /**
+ * Register handler.
+ * Register a {@link JsonHandler>} by {@link Class>}
+ *
+ * @param clazz the clazz used to put {@link Class>} and {@link JsonHandler>} in {@link #handlers}
+ * @param jsonHandler the json handler is used to insert
+ */
+ public void registerHandler(Class> clazz, JsonHandler> jsonHandler) {
+ if (!isRegistered(clazz)) {
+ handlers.put(clazz, jsonHandler);
+ }
+ }
+
+ /**
+ * Unregister handler.
+ * Unregister a {@link JsonHandler} by {@link Class>}
+ *
+ * @param clazz the clazz used to remove {@link JsonHandler} from {@link #handlers}
+ */
+ public void unregisterHandler(Class> clazz) {
+ if (isRegistered(clazz)) {
+ handlers.remove(clazz);
+ }
+ }
+
+ /**
+ * Gets handler from {@link #handlers}.
+ *
+ * @param clazz the clazz used to get {@link JsonHandler>} from {@link #handlers}
+ * @return the handler null or {@link JsonHandler>}
+ */
+ public JsonHandler> getHandler(Class> clazz) {
+ if (!isRegistered(clazz)) return null;
+
+ return handlers.get(clazz);
+ }
+
+ /**
+ * Is registered boolean.
+ * Check if {@link JsonHandler>} is registered in {@link #handlers}
+ *
+ * @param clazz the clazz used to get {@link JsonHandler>}
+ * @return the boolean true or false
+ */
+ public boolean isRegistered(Class> clazz) {
+ return handlers.containsKey(clazz);
+ }
+
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/JsonParser.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/JsonParser.java
new file mode 100644
index 00000000..314873b6
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/JsonParser.java
@@ -0,0 +1,536 @@
+package org.koishi.launcher.h2o2pro.tool.json;
+
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonArray;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonBoolean;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonNull;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonNumber;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonObject;
+import org.koishi.launcher.h2o2pro.tool.json.abstracts.JsonValue;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonString;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The class Json parser is used to parse a {@link String} or a {@link File} to {@link JsonValue}
+ */
+public class JsonParser {
+
+ private char[] jsonBuffer;
+ private int jsonBufferPosition = 0;
+
+ /**
+ * Parse {@link InputStream} to {@link JsonValue} and cast it to {@link Class}
+ *
+ * @param the type parameter determines the type of {@link JsonValue}
+ * @param inputStream the input stream is used to parse
+ * @param clazz the clazz is used to cast {@link T}
+ * @return the value {@link T}
+ */
+ public T parse(InputStream inputStream, Class clazz) {
+ return clazz.cast(parse(inputStream));
+ }
+
+ /**
+ * Parse json value with {@link InputStream}
+ *
+ * @param inputStream the input stream is used to parse
+ * @return the json value is the raw return type
+ */
+ public JsonValue parse(InputStream inputStream) {
+ try {
+ prepareParser(inputStream);
+ } catch (IOException exception) {
+ exception.printStackTrace();
+ }
+
+ skipWhiteSpace();
+ switch (peekChar()) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ case 'E':
+ case 'e':
+ case '+':
+ case '-': {
+ return parseJsonNumber();
+ }
+ case 'f':
+ case 't': {
+ return parseJsonBoolean();
+ }
+ case 'n': {
+ return parseJsonNull();
+ }
+ case '"': {
+ return parseJsonString();
+ }
+ case '{': {
+ JsonObject jsonObject = parseJsonObject();
+ jsonBuffer = null;
+ jsonBufferPosition = 0;
+ return jsonObject;
+ }
+ case '[': {
+ JsonArray jsonArray = parseJsonArray();
+ jsonBuffer = null;
+ jsonBufferPosition = 0;
+ return jsonArray;
+ }
+ default: {
+ throw new RuntimeException("Could not parse file to Json!");
+ }
+ }
+ }
+
+
+ /**
+ * Parse {@link String} to {@link JsonValue} and cast it to {@link Class}
+ *
+ * @param the type parameter determines the type of {@link JsonValue}
+ * @param json the input stream is used to parse
+ * @param clazz the clazz is used to cast {@link T}
+ * @return the value {@link T}
+ */
+ public T parse(String json, Class clazz) {
+ return clazz.cast(parse(json));
+ }
+
+ /**
+ * Parse {@link String} to {@link JsonValue}.
+ *
+ * @param json the json used to parse
+ * @return the json value is the raw return type
+ */
+ public JsonValue parse(String json) {
+ return parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
+ }
+
+
+ /**
+ * Parse {@link File} to {@link JsonValue} and cast it to {@link Class}
+ *
+ * @param the type parameter determines the type of {@link JsonValue}
+ * @param file the file is used to parse
+ * @param clazz the clazz is used to cast {@link T}
+ * @return the value {@link T}
+ * @throws FileNotFoundException the file not found exception
+ */
+ public T parse(File file, Class clazz) throws FileNotFoundException {
+ return clazz.cast(parse(file));
+ }
+
+ /**
+ * Parse {@link File} to {@link JsonValue}.
+ *
+ * @param file the file used to parse
+ * @return the json value raw return type
+ * @throws FileNotFoundException the file not found exception
+ */
+ public JsonValue parse(File file) throws FileNotFoundException {
+ return parse(new FileInputStream(file));
+ }
+
+
+ /**
+ * Prepare parser to parse given input to {@link JsonValue}.
+ *
+ * @param inputStream the input stream is used to parse
+ * @throws IOException the io exception
+ */
+ public void prepareParser(InputStream inputStream) throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ stringBuilder.append(line).append("\n");
+ }
+
+ jsonBuffer = stringBuilder.toString().toCharArray();
+ bufferedReader.close();
+ }
+
+ /**
+ * Parse char from {@link #jsonBuffer} and increase {@link #jsonBufferPosition}
+ *
+ * @return the char
+ */
+ public char parseChar() {
+ try {
+ return jsonBuffer[jsonBufferPosition++];
+ } catch (IndexOutOfBoundsException exception) {
+ return (char) -1;
+ }
+ }
+
+ /**
+ * Peek char with {@link #parseChar()} and revoke {@link #jsonBufferPosition}
+ *
+ * @return the char
+ */
+ public char peekChar() {
+ char currentChar = parseChar();
+ jsonBufferPosition--;
+ return currentChar;
+ }
+
+ /**
+ * Skip white space.
+ */
+ public void skipWhiteSpace() {
+ while (isWhiteSpace()) {
+ parseChar();
+ }
+ }
+
+
+ /**
+ * Parse json object json object.
+ * Iterates through chars and construct json object.
+ *
+ * @return the json object
+ */
+ public JsonObject parseJsonObject() {
+ JsonObject jsonObject = new JsonObject();
+
+ skipWhiteSpace();
+ // Skip '{'
+ parseChar();
+
+ char currentChar = peekChar();
+ while (currentChar != '}') {
+ skipWhiteSpace();
+
+ // Check the next char and handle it!
+ switch (peekChar()) {
+ case '"': {
+
+ String key = parseJsonString().getValue();
+
+ skipWhiteSpace();
+ // Skip ':'
+ currentChar = parseChar();
+ skipWhiteSpace();
+
+ switch (peekChar()) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ case 'E':
+ case 'e':
+ case '+':
+ case '-': {
+ JsonNumber jsonNumber = parseJsonNumber();
+ jsonNumber.setKey(key);
+ jsonObject.add(jsonNumber);
+ break;
+ }
+ case 'f':
+ case 't': {
+ JsonBoolean jsonBoolean = parseJsonBoolean();
+ jsonBoolean.setKey(key);
+ jsonObject.add(jsonBoolean);
+ break;
+ }
+ case 'n': {
+ JsonNull jsonNull = parseJsonNull();
+ jsonNull.setKey(key);
+ jsonObject.add(jsonNull);
+ break;
+ }
+ case '"': {
+ JsonString jsonString = parseJsonString();
+ jsonString.setKey(key);
+ jsonObject.add(jsonString);
+ break;
+ }
+ case '{': {
+ JsonObject object = parseJsonObject();
+ object.setKey(key);
+ jsonObject.add(object);
+ break;
+ }
+ case '[': {
+ JsonArray jsonArray = parseJsonArray();
+ jsonArray.setKey(key);
+ jsonObject.add(jsonArray);
+ break;
+ }
+ }
+
+ break;
+ }
+ case '}':
+ case ',': {
+ currentChar = parseChar();
+ break;
+ }
+ default: {
+ throw new RuntimeException(String.format("Could not parse JsonObject stuck at Index: %s as Char: %s", jsonBufferPosition, jsonBuffer[jsonBufferPosition]));
+ }
+
+ }
+ }
+ return jsonObject;
+ }
+
+ /**
+ * Parse json array json array.
+ * Iterates through chars and construct json array.
+ *
+ * @return the json array
+ */
+ public JsonArray parseJsonArray() {
+ JsonArray jsonArray = new JsonArray();
+
+ skipWhiteSpace();
+ // Skip '['
+ parseChar();
+
+ char currentChar = peekChar();
+
+
+ while (currentChar != ']') {
+ skipWhiteSpace();
+
+ // Check the next char and handle it!
+ switch (peekChar()) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ case 'E':
+ case 'e':
+ case '+':
+ case '-': {
+ jsonArray.add(parseJsonNumber());
+ break;
+ }
+ case '"': {
+ jsonArray.add(parseJsonString());
+ break;
+ }
+ case 'f':
+ case 't': {
+ jsonArray.add(parseJsonBoolean());
+ break;
+ }
+ case 'n': {
+ jsonArray.add(parseJsonNull());
+ break;
+ }
+ case '{': {
+ jsonArray.add(parseJsonObject());
+ break;
+ }
+ case '[': {
+ jsonArray.add(parseJsonArray());
+ break;
+ }
+ case ']':
+ case ',': {
+ currentChar = parseChar();
+ break;
+ }
+ default: {
+ throw new RuntimeException(String.format("Could not parse JsonArray stuck at Index: %s as Char: %s", jsonBufferPosition, jsonBuffer[jsonBufferPosition]));
+ }
+ }
+ }
+
+ return jsonArray;
+ }
+
+ /**
+ * Parse json string.
+ * Iterates through chars and construct json string.
+ *
+ * @return the json string
+ */
+ public JsonString parseJsonString() {
+ JsonString jsonString = new JsonString();
+
+ StringBuilder stringBuilder = new StringBuilder();
+
+ skipWhiteSpace();
+
+
+ // Take the first '"'
+ parseChar();
+
+ char currentChar = parseChar();
+
+ while (currentChar != '"') {
+ // Check if in a string is an escape char
+ if (currentChar == '\\') {
+ stringBuilder.append(currentChar);
+ currentChar = parseChar();
+ }
+
+ stringBuilder.append(currentChar);
+ currentChar = parseChar();
+ }
+
+ skipWhiteSpace();
+
+ jsonString.setValue(stringBuilder.toString());
+
+ return jsonString;
+ }
+
+ /**
+ * Parse json null.
+ * Iterates through chars and check if json null is given.
+ *
+ * @return the json null
+ */
+ public JsonNull parseJsonNull() {
+ char currentChar = parseChar();
+ if (currentChar == 'n' && isNextChar('u', 0) && isNextChar('l', 1) && isNextChar('l', 2)) {
+ for (int i = 0; i < 3; i++) {
+ jsonBufferPosition++;
+ }
+ return new JsonNull();
+ }
+ return null;
+ }
+
+ /**
+ * Parse json boolean.
+ * Iterates through chars and check if json boolean is given.
+ *
+ * @return the json boolean
+ */
+ public JsonBoolean parseJsonBoolean() {
+ char currentChar = parseChar();
+ JsonBoolean jsonBoolean = null;
+ if (currentChar == 't' && isNextChar('r', 0) && isNextChar('u', 1) && isNextChar('e', 2)) {
+ for (int i = 0; i < 3; i++) {
+ jsonBufferPosition++;
+ }
+ jsonBoolean = new JsonBoolean();
+ jsonBoolean.setValue(true);
+ } else if (currentChar == 'f' && isNextChar('a', 0) && isNextChar('l', 1) && isNextChar('s', 2) && isNextChar('e', 3)) {
+ for (int i = 0; i < 4; i++) {
+ jsonBufferPosition++;
+ }
+ jsonBoolean = new JsonBoolean();
+ jsonBoolean.setValue(false);
+ }
+ return jsonBoolean;
+ }
+
+ /**
+ * Parse json number.
+ * Iterates through chars and check if json number is given.
+ *
+ * @return the json number
+ */
+ public JsonNumber parseJsonNumber() {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ char currentChar = parseChar();
+
+ while (isNumber(currentChar)) {
+ stringBuilder.append(currentChar);
+
+ currentChar = parseChar();
+ }
+
+ jsonBufferPosition--;
+
+ JsonNumber jsonNumber = new JsonNumber();
+ jsonNumber.setValue(stringBuilder.toString());
+
+ return jsonNumber;
+ }
+
+ /**
+ * Is white space boolean.
+ * Check if current char in {@link #jsonBuffer} is a whitespace
+ *
+ * @return the boolean true or false
+ */
+ public boolean isWhiteSpace() {
+ char currentChar = jsonBuffer[jsonBufferPosition];
+ return currentChar == ' ' || currentChar == '\t' || currentChar == '\r' || currentChar == '\n';
+ }
+
+ /**
+ * Is next char boolean.
+ * Check if next char is given char by position
+ *
+ * @param currentChar the current char is the char that is going to be checked
+ * @param position the position used to get char at position from {@link #jsonBuffer}
+ * @return the boolean true or false
+ */
+ public boolean isNextChar(char currentChar, int position) {
+ for (int i = 0; i < position; i++) {
+ parseChar();
+ }
+ char nextChar = peekChar();
+ for (int i = 0; i < position; i++) {
+ jsonBufferPosition--;
+ }
+ return nextChar == currentChar;
+ }
+
+ /**
+ * Is number boolean.
+ * Check if current char is a number
+ * @param currentChar the current char is used to check
+ * @return the boolean true or false
+ */
+ public boolean isNumber(char currentChar) {
+ char[] numberElements = new char[]{
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '.',
+ 'E',
+ 'e',
+ '+',
+ '-'
+ };
+
+ for (char listChar : numberElements) {
+ if (listChar == currentChar) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+}
diff --git a/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/abstracts/JsonHandler.java b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/abstracts/JsonHandler.java
new file mode 100644
index 00000000..08bdeebf
--- /dev/null
+++ b/app/src/main/java/org/koishi/launcher/h2o2pro/tool/json/abstracts/JsonHandler.java
@@ -0,0 +1,29 @@
+package org.koishi.launcher.h2o2pro.tool.json.abstracts;
+
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonArray;
+import org.koishi.launcher.h2o2pro.tool.json.model.JsonObject;
+
+/**
+ * The abstract class {@link JsonHandler} with generic type {@link T} is used to serialize and deserialize an object
+ *
+ * @param the type parameter type of object which is going to be serialized or deserialized.
+ */
+public abstract class JsonHandler