diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b75303 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..c2bae49 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..f43d428 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/.idea/json2java.xml b/.idea/json2java.xml new file mode 100644 index 0000000..db29bca --- /dev/null +++ b/.idea/json2java.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..703e5d4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..24bbc9d --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,44 @@ +import java.text.SimpleDateFormat + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "ma.mhy.sqliteeditorroot" + minSdkVersion 15 + targetSdkVersion 28 + versionCode getMyVersionCode() + versionName getMyVersionName() + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.github.huangyanbin:SmartTable:2.2.0' +// implementation 'androidx.annotation:annotation:1.0.0' +} + +static def getMyVersionCode() { + return Integer.parseInt(new SimpleDateFormat("yyMMdd").format(new Date())) +} + +static def getMyVersionName() { + return "1.0.6." + "git describe --always".execute().getText().trim() +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/ma/mhy/sqliteeditorroot/ExampleInstrumentedTest.java b/app/src/androidTest/java/ma/mhy/sqliteeditorroot/ExampleInstrumentedTest.java new file mode 100644 index 0000000..739e0ae --- /dev/null +++ b/app/src/androidTest/java/ma/mhy/sqliteeditorroot/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package ma.mhy.sqliteeditorroot; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("ma.mhy.sqliteeditorroot", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7f8d3ae --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/App.java b/app/src/main/java/ma/mhy/sqliteeditorroot/App.java new file mode 100644 index 0000000..33d0a66 --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/App.java @@ -0,0 +1,14 @@ +package ma.mhy.sqliteeditorroot; + +import android.app.Application; + + +import ma.mhy.sqliteeditorroot.util.Crashlytics; + +public class App extends Application { + @Override + public void onCreate() { + super.onCreate(); + Thread.setDefaultUncaughtExceptionHandler(new Crashlytics(this)); + } +} diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/C.java b/app/src/main/java/ma/mhy/sqliteeditorroot/C.java new file mode 100644 index 0000000..36c344b --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/C.java @@ -0,0 +1,6 @@ +package ma.mhy.sqliteeditorroot; + +public class C { + public static final String EXTRA_DATABASE_PATH = ".extra.EXTRA_DATABASE_PATH"; + public static final String EXTRA_TABLE_NAME = ".extra.EXTRA_TABLE_NAME"; +} diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/bean/Cell.java b/app/src/main/java/ma/mhy/sqliteeditorroot/bean/Cell.java new file mode 100644 index 0000000..2a8657c --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/bean/Cell.java @@ -0,0 +1,15 @@ +package ma.mhy.sqliteeditorroot.bean; + +public class Cell { + public int col; + public int row; + public String oldVal; + public String newVal; + + public Cell(int col, int row, String oldVal, String newVal) { + this.col = col; + this.row = row; + this.oldVal = oldVal; + this.newVal = newVal; + } +} diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/ui/HelloActivity.java b/app/src/main/java/ma/mhy/sqliteeditorroot/ui/HelloActivity.java new file mode 100644 index 0000000..48915c5 --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/ui/HelloActivity.java @@ -0,0 +1,194 @@ +package ma.mhy.sqliteeditorroot.ui; + + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.ComponentName; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; + +import ma.mhy.sqliteeditorroot.R; +import ma.mhy.sqliteeditorroot.util.RequstRoot; + +//import androidx.annotation.Nullable; + + +public class HelloActivity extends AppCompatActivity { + ProgressDialog progress_dialog; +String tag="mhy"; +//String apkRoot = "chmod 777 " + getPackageCodePath();//getPackageCodePath()来获得当前应用程序对应的 apk 文件的路径 + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + //当前应用的代码执行目录 + // upgradeRootPermission(getPackageCodePath()); + // boolean b = RootCommand(apkRoot);//给这个路径777权限 + // Log.v(tag,"获取Root权限:"+b); + //检测设备是否root 并获取root权限 + get_root(); + + findViewById(R.id.action_ok).setOnClickListener(v -> + new AlertDialog.Builder(HelloActivity.this) + .setCancelable(false) + .setMessage(R.string.app_notice) + .setPositiveButton(R.string.action_know, (dialog, which) -> finish()) + .create() + .show()); + } + + // 判断是否具有ROOT权限 + public static boolean is_root() { + + boolean res = false; + + try { + if ((!new File("/system/bin/su").exists()) && + (!new File("/system/xbin/su").exists())) { + res = false; + } else { + res = true; + } + ; + } catch (Exception e) { + + } + return res; + } + + // 获取ROOT权限.获取Android的ROOT权限其实很简单,只要在Runtime下执行命令"su"就可以了。 + public void get_root() { + + if (is_root()) { + Toast.makeText(this, "请授予ROOT权限!", Toast.LENGTH_LONG).show(); + + //请求root权限 + String apkRoot="chmod 777 "+getPackageCodePath(); + RequstRoot.RootCommand(apkRoot);// + + } else { + try { + progress_dialog = ProgressDialog.show(this, + "ROOT", "正在获取ROOT权限...", true, false); + Runtime.getRuntime().exec("su"); + } catch (Exception e) { + Toast.makeText(this, "获取ROOT权限时出错!", Toast.LENGTH_LONG).show(); + } + } + } + /** + * 应用程序运行命令获取 Root权限,设备必须已破解(获得ROOT权限) + * + * @return 应用程序是/否获取Root权限 + */ + public static boolean upgradeRootPermission(String pkgCodePath) { + Process process = null; + DataOutputStream os = null; + try { + String cmd="chmod 777 " + pkgCodePath; + process = Runtime.getRuntime().exec("su"); //切换到root帐号 + os = new DataOutputStream(process.getOutputStream()); + os.writeBytes(cmd + "\n"); + os.writeBytes("exit\n"); + os.flush(); + process.waitFor(); + } catch (Exception e) { + return false; + } finally { + try { + if (os != null) { + os.close(); + } + process.destroy(); + } catch (Exception e) { + } + } + return true; + } + /** + * 应用程序运行命令获取 Root权限,设备必须已破解(获得ROOT权限) + * + * @param command 命令:String apkRoot="chmod 777 "+getPackageCodePath(); RootCommand(apkRoot); + * @return 应用程序是/否获取Root权限 + */ +// public boolean RootCommand(String command) { +// Process process = null; +// DataOutputStream os = null; +// DataInputStream is = null; +// try { +// process = Runtime.getRuntime().exec("su"); +// os = new DataOutputStream(process.getOutputStream()); +// os.writeBytes(command + "\n"); +// os.writeBytes("exit\n"); +// os.flush(); +// int aa = process.waitFor(); +// Log.w(tag,"waitFor():"+aa); +// is = new DataInputStream(process.getInputStream()); +// byte[] buffer = new byte[is.available()]; +// Log.d(tag,"大小"+buffer.length); +// is.read(buffer); +// String out = new String(buffer); +// Log.e(tag,"返回:"+out); +// } catch (Exception e) { +// e.printStackTrace(); +// Log.e(tag,tag + "205:\n" + e); +// return false; +// } finally { +// try { +// if (os != null) { +// os.close(); +// } +// if (is != null) { +// is.close(); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// Log.e(tag,tag + "217:\n" + e); +// } +// process.destroy(); +// } +// Log.d(tag,tag + "222 SUCCESS"); +// return true; +// } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.hello, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + startActivity(new Intent(Intent.ACTION_VIEW) + .setData(Uri.parse("https://github.com/Yaerin/SQLiteEditor")) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + return true; + } + + @Override + protected void onStop() { + disableSelf(); + super.onStop(); + } + + private void disableSelf() { + getPackageManager().setComponentEnabledSetting( + new ComponentName(this, this.getClass()), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0); + } +} diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/ui/MainActivity.java b/app/src/main/java/ma/mhy/sqliteeditorroot/ui/MainActivity.java new file mode 100644 index 0000000..50d7d06 --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/ui/MainActivity.java @@ -0,0 +1,208 @@ +package ma.mhy.sqliteeditorroot.ui; + +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; + +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.Toast; + +import ma.mhy.sqliteeditorroot.C; + +import java.io.File; +import java.io.IOException; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +//import androidx.annotation.NonNull; +//import androidx.annotation.Nullable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import ma.mhy.sqliteeditorroot.R; + +public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener { + + private String mPath; + private SQLiteDatabase mDatabase; + private List mTableNames; + private String mTableName; + + private ListView mTables; + private ArrayAdapter mAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + mTables = findViewById(R.id.table); + + Uri uri = getIntent().getData(); + if (uri == null || uri.getScheme() == null || uri.getPath() == null) { + Toast.makeText(this, getIntent().toString(), Toast.LENGTH_LONG).show(); + return; + } + //权限申请 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mPath = getFileForUri(uri).getPath(); + } else { + mPath = uri.getPath(); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1000); + } else { + main(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_exec: { + View view = View.inflate(MainActivity.this, R.layout.dialog_edit, null); + EditText editText = view.findViewById(R.id.edit_text); + editText.setHint(R.string.hint_sql); + new AlertDialog.Builder(this) + .setCancelable(false) + .setTitle(R.string.action_exec_sql) + .setView(view) + .setNegativeButton(R.string.action_cancel, (dialog, which) -> dialog.dismiss()) + .setPositiveButton(R.string.action_ok, (dialog, which) -> { + try { + mDatabase.execSQL(editText.getText().toString()); + Toast.makeText(this, R.string.message_success, Toast.LENGTH_SHORT).show(); + refresh(); + } catch (Exception e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + } + }) + .create() + .show(); + break; + } + } + return true; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + main(); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_delete: { + new AlertDialog.Builder(this) + .setTitle(R.string.title_confirm) + .setMessage(R.string.message_irreversible) + .setPositiveButton(R.string.action_ok, (dialog, which) -> { + mDatabase.execSQL("DROP TABLE \"" + mTableName + "\""); + refresh(); + }) + .create() + .show(); + break; + } + } + return true; + } + + @Override + protected void onDestroy() { + mDatabase.close(); + super.onDestroy(); + } + + private void main() { + try { + openDatabase(); + mTableNames = getTableNames(); + initView(); + } catch (Exception e) { + Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show(); + } + } + + private void refresh() { + mTableNames.clear(); + mTableNames.addAll(getTableNames()); + mAdapter.notifyDataSetChanged(); + } + + private void initView() { + mTables.setAdapter(mAdapter = new ArrayAdapter<>( + this, R.layout.item_table, R.id.table_name, mTableNames)); + mTables.setOnItemClickListener((parent, view, position, id) -> + startActivity(new Intent(MainActivity.this, TableActivity.class) + .putExtra(C.EXTRA_DATABASE_PATH, mPath) + .putExtra(C.EXTRA_TABLE_NAME, mTableNames.get(position)))); + mTables.setOnItemLongClickListener((parent, view, position, id) -> { + mTableName = mTableNames.get(position); + PopupMenu popup = new PopupMenu(MainActivity.this, view); + popup.getMenuInflater().inflate(R.menu.main_popup, popup.getMenu()); + popup.setOnMenuItemClickListener(this); + popup.show(); + return true; + }); + } + + private void openDatabase() { + String[] arr = mPath.split("/"); + setTitle("`" + arr[arr.length - 1].replaceAll("\\.db$", "") + "`"); + mDatabase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.OPEN_READWRITE, sqLiteDatabase -> { + }); + } + + private List getTableNames() { + List list = new ArrayList<>(); + Cursor cursor = mDatabase.rawQuery("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name", null); + while (cursor.moveToNext()) { + list.add(cursor.getString(0)); + } + cursor.close(); + Collections.sort(list, Collator.getInstance(Locale.getDefault())); + return list; + } + + private File getFileForUri(Uri uri) { + String path = uri.getEncodedPath(); + + assert path != null; + final int splitIndex = path.indexOf('/', 1); + path = Uri.decode(path.substring(splitIndex + 1)); + File file = new File("/storage", path); + try { + file = file.getCanonicalFile(); + } catch (IOException e) { + throw new IllegalArgumentException("Failed to resolve canonical path for " + file); + } + + return file; + } + +} diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/ui/TableActivity.java b/app/src/main/java/ma/mhy/sqliteeditorroot/ui/TableActivity.java new file mode 100644 index 0000000..ff71fa1 --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/ui/TableActivity.java @@ -0,0 +1,343 @@ +package ma.mhy.sqliteeditorroot.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.Toast; + +import com.bin.david.form.core.SmartTable; +import com.bin.david.form.data.CellInfo; +import com.bin.david.form.data.column.Column; +import com.bin.david.form.data.format.bg.ICellBackgroundFormat; +import com.bin.david.form.data.format.draw.TextDrawFormat; +import com.bin.david.form.data.table.ArrayTableData; +import com.bin.david.form.data.table.TableData; +import ma.mhy.sqliteeditorroot.C; +import ma.mhy.sqliteeditorroot.R; +import ma.mhy.sqliteeditorroot.bean.Cell; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +//import androidx.annotation.Nullable; + +public class TableActivity extends AppCompatActivity { + + private SQLiteDatabase mDatabase; + private String mTableName; + private String[] mColumnNames; + private List mUndoList = new ArrayList<>(); + private List mRedoList = new ArrayList<>(); + private List mUndoVList = new ArrayList<>(); + private List mRedoVList = new ArrayList<>(); + + private ProgressBar mProgressBar; + private SmartTable mTable; + private ArrayTableData mTableData; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_table); + mProgressBar = findViewById(R.id.progress); + mTable = findViewById(R.id.table); + mTable.setZoom(true, 2.0f, 0.5f); + mTable.getConfig() + .setMinTableWidth(mTable.getWidth()) + .setShowTableTitle(false) + .setShowXSequence(false) + .setShowYSequence(false) + .setFixedTitle(true) + .setContentCellBackgroundFormat(new ICellBackgroundFormat() { + @Override + public void drawBackground(Canvas canvas, Rect rect, + CellInfo cellInfo, Paint paint) { + if (cellInfo.row % 2 == 0) { + paint.setColor(0xFFFAFAFA); + canvas.drawRect(rect, paint); + } + for (Cell cell : mUndoVList) { + if (cell.col == cellInfo.col && cell.row == cellInfo.row) { + paint.setColor(0xFFFFE4B5); + canvas.drawRect(rect, paint); + } + } + } + + @Override + public int getTextColor(CellInfo cellInfo) { + return 0; + } + }); + + String path = getIntent().getStringExtra(C.EXTRA_DATABASE_PATH); + mDatabase = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE); + mTableName = getIntent().getStringExtra(C.EXTRA_TABLE_NAME); + + String[] arr = path.split("/"); + setTitle("`" + arr[arr.length - 1].replaceAll("\\.db$", "") + "`." + mTableName); + + init(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.table, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + boolean enabledUndo = mUndoList.size() > 0; + boolean enabledRedo = mRedoList.size() > 0; + boolean enabledSave = mUndoList.size() > 0; + + MenuItem undo = menu.findItem(R.id.action_undo); + MenuItem redo = menu.findItem(R.id.action_redo); + MenuItem save = menu.findItem(R.id.action_save); + + undo.setEnabled(enabledUndo); + undo.setIcon(enabledUndo ? R.drawable.ic_undo_white_24dp : R.drawable.ic_undo_gray_24dp); + redo.setEnabled(enabledRedo); + redo.setIcon(enabledRedo ? R.drawable.ic_redo_white_24dp : R.drawable.ic_redo_gray_24dp); + save.setEnabled(enabledSave); + save.setIcon(enabledSave ? R.drawable.ic_save_white_24dp : R.drawable.ic_save_gray_24dp); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_undo: { + if (mUndoList.size() > 0 && mUndoVList.size() > 0) { + String s = mUndoList.get(mUndoList.size() - 1); + mRedoList.add(s); + mUndoList.remove(s); + Cell c = mUndoVList.get(mUndoVList.size() - 1); + mRedoVList.add(c); + mUndoVList.remove(c); + mTableData.getData()[c.col][c.row] = c.oldVal; + mTable.invalidate(); + } + invalidateOptionsMenu(); + break; + } + + case R.id.action_redo: { + if (mRedoList.size() > 0) { + String s = mRedoList.get(mRedoList.size() - 1); + mUndoList.add(s); + mRedoList.remove(s); + Cell c = mRedoVList.get(mRedoVList.size() - 1); + mUndoVList.add(c); + mRedoVList.remove(c); + mTableData.getData()[c.col][c.row] = c.newVal; + mTable.invalidate(); + } + invalidateOptionsMenu(); + break; + } + + case R.id.action_save: { + try { + execSQLs(); + } catch (Exception e) { + Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show(); + } + break; + } + } + return true; + } + + @Override + protected void onDestroy() { + mDatabase.close(); + super.onDestroy(); + } + + private void execSQLs() { + for (String sql : mUndoList) { + mDatabase.execSQL(sql); + } + mUndoList.clear(); + mRedoList.clear(); + mUndoVList.clear(); + mRedoVList.clear(); + mTable.invalidate(); + invalidateOptionsMenu(); + } + + private String[] getColumnNames() { + Cursor c = mDatabase.rawQuery( + "PRAGMA table_info(\"" + mTableName + "\")", null); + List list = new ArrayList<>(); + while (c.moveToNext()) { + list.add(c.getString(c.getColumnIndex("name"))); + } + c.close(); + return list.toArray(new String[0]); + } + + private String[][] buildArray() { + Cursor cursor = mDatabase.rawQuery( + "SELECT * FROM \"" + mTableName + "\"", null); + Cursor c = mDatabase.rawQuery( + "PRAGMA table_info(\"" + mTableName + "\")", null); + + if (cursor.getCount() == 0) { + cursor.close(); + c.close(); + return null; + } + + String[][] data = new String[cursor.getColumnCount()][cursor.getCount()]; + for (int i = 0; i < cursor.getColumnCount(); i++) { + c.moveToPosition(i); + for (int j = 0; j < cursor.getCount(); j++) { + cursor.moveToPosition(j); + if (c.getString(c.getColumnIndex("type")).equals("BLOB")) { + data[i][j] = "[BLOB]"; + } else { + data[i][j] = cursor.getString(cursor.getColumnIndex(mColumnNames[i])); + } + } + } + + cursor.close(); + c.close(); + return data; + } + + @Nullable + private String getPrimaryKey(String sql) { + String primaryKey = null; + int start = sql.indexOf("(") + 1; + int end = sql.length() - new StringBuilder(sql).reverse().toString().indexOf(")") - 1; + sql = sql.substring(start, end); + String[] arr = sql.replaceAll("\\s+", " ").split(","); + for (String s : arr) { + s = s.trim(); + if (Pattern.compile(".*PRIMARY\\s+KEY.*", + Pattern.CASE_INSENSITIVE).matcher(s).matches()) { + primaryKey = s.split("\\s+")[0].replaceAll("\"", ""); + } + } + return primaryKey; + } + + private String buildSQL(int col, int row, String name, String newValue) { + Cursor cursor = mDatabase.rawQuery( + "SELECT sql FROM sqlite_master WHERE name=\"" + mTableName + "\"", null); + cursor.moveToPosition(0); + String sql = cursor.getString(cursor.getColumnIndex("sql")); + cursor.close(); + + String primaryKey = getPrimaryKey(sql); + + if (primaryKey == null) { + Toast.makeText(this, R.string.err_primary_key, Toast.LENGTH_LONG).show(); + return null; + } + + Cursor c = mDatabase.rawQuery( + String.format("SELECT \"%s\",\"%s\" FROM \"%s\"", primaryKey, name, mTableName), null); + c.moveToPosition(row); + String key = c.getString(c.getColumnIndex(primaryKey)); + String value = c.getString(c.getColumnIndex(name)); + c.close(); + if (value.equals(newValue)) { + return null; + } else { + mTableData.getData()[col][row] = newValue; + mTable.invalidate(); + mUndoVList.add(new Cell(col, row, value, newValue)); + if (!mRedoVList.isEmpty()) { + mRedoVList.clear(); + } + return "UPDATE " + mTableName + + " SET " + name + "=\"" + newValue + "\"" + + " WHERE " + primaryKey + "=\"" + key + "\""; + } + } + + private void init() { + mProgressBar.setVisibility(View.VISIBLE); + mColumnNames = getColumnNames(); + new Thread(() -> { + final String[][][] array = {buildArray()}; + runOnUiThread(() -> { + if (array[0] == null) { + array[0] = new String[mColumnNames.length][0]; + } + mTableData = + ArrayTableData.create(null, mColumnNames, array[0], new TextDrawFormat<>()); + mTableData.setOnItemClickListener(new OnItemClickListener()); + mTable.setTableData(mTableData); + mProgressBar.setVisibility(View.GONE); + }); + }).start(); + } + + private class OnItemClickListener implements TableData.OnItemClickListener { + + @Override + public void onClick(Column column, String value, Object o, int col, int row) { + Cursor cursor = mDatabase.rawQuery( + "PRAGMA table_info(\"" + mTableName + "\")", null); + cursor.moveToPosition(col); + String name = cursor.getString(cursor.getColumnIndex("name")); + String type = cursor.getString(cursor.getColumnIndex("type")); + boolean notNull = cursor.getInt(cursor.getColumnIndex("notnull")) == 1; + cursor.close(); + + if (type.equals("BLOB")) { + Toast.makeText(TableActivity.this, R.string.err_blob_data, Toast.LENGTH_SHORT).show(); + return; + } + + View view = View.inflate(TableActivity.this, R.layout.dialog_edit, null); + EditText editText = view.findViewById(R.id.edit_text); + editText.setText(value); + editText.requestFocus(); + editText.selectAll(); + new AlertDialog.Builder(TableActivity.this) + .setTitle(getString(R.string.title_edit, + name, type + (notNull ? " NOT NULL" : ""))) + .setView(view) + .setNegativeButton(R.string.action_cancel, (dialog, which) -> dialog.dismiss()) + .setPositiveButton(R.string.action_ok, (dialog, which) -> { + String newValue = editText.getText().toString(); + if (TextUtils.isEmpty(newValue) && notNull) { + Toast.makeText(TableActivity.this, + "NOT NULL", Toast.LENGTH_SHORT).show(); + } else if (!newValue.equals(value)) { + String sql = buildSQL(col, row, name, newValue); + if (sql != null) { + mUndoList.add(sql); + if (!mRedoList.isEmpty()) { + mRedoList.clear(); + } + invalidateOptionsMenu(); + } + } + }) + .create() + .show(); + } + } +} diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/util/Crashlytics.java b/app/src/main/java/ma/mhy/sqliteeditorroot/util/Crashlytics.java new file mode 100644 index 0000000..3e055e3 --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/util/Crashlytics.java @@ -0,0 +1,104 @@ +package ma.mhy.sqliteeditorroot.util; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Process; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class Crashlytics implements Thread.UncaughtExceptionHandler { + + private Context mContext; + + public Crashlytics(Context context) { + this.mContext = context; + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + e.printStackTrace(); + String crashTime = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()) + .format(new Date()); + String env = + "########RuntimeEnviormentInormation#######\n" + + "crashTime = " + crashTime + "\n" + + "model = " + Build.MODEL + "\n" + + "android = " + Build.VERSION.RELEASE + "(" + Build.VERSION.SDK_INT + ")\n" + + "brand = " + Build.BRAND + "\n" + + "manufacturer = " + Build.MANUFACTURER + "\n" + + "board = " + Build.BOARD + "\n" + + "hardware = " + Build.HARDWARE + "\n" + + "device = " + Build.DEVICE + "\n" + + "version = " + getVersionName() + "(" + getVersionCode() + ")\n" + + "supportAbis = " + getSupportAbis() + "\n" + + "display = " + Build.DISPLAY + "\n"; + Writer writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + e.printStackTrace(printWriter); + Throwable cause = e.getCause(); + while (cause != null) { + cause.printStackTrace(printWriter); + cause = cause.getCause(); + } + printWriter.close(); + String stack = "############ForceCloseCrashLog############\n" + writer.toString(); + String message = env + stack; + try { + String name = "error_log_" + crashTime + ".log"; + FileOutputStream fos = + new FileOutputStream(new File(mContext.getExternalFilesDir("logs"), name)); + fos.write(message.getBytes()); + fos.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + + Process.killProcess(Process.myPid()); + System.exit(1); + } + + private String getVersionName() { + String versionName = "unknown"; + try { + versionName = mContext.getPackageManager() + .getPackageInfo(mContext.getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return versionName; + } + + private int getVersionCode() { + int versionCode = -1; + try { + versionCode = mContext.getPackageManager() + .getPackageInfo(mContext.getPackageName(), 0).versionCode; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return versionCode; + } + + private String getSupportAbis() { + String[] abis = Build.SUPPORTED_ABIS; + StringBuilder abi = new StringBuilder(); + for (int i = 0; i < abis.length; i++) { + if (i == 0) { + abi.append(abis[i]); + } else { + abi.append(" & ").append(abis[i]); + } + } + return abi.toString(); + } +} diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/util/RequstRoot.java b/app/src/main/java/ma/mhy/sqliteeditorroot/util/RequstRoot.java new file mode 100644 index 0000000..31a0740 --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/util/RequstRoot.java @@ -0,0 +1,77 @@ +package ma.mhy.sqliteeditorroot.util; + + +import java.io.DataOutputStream; + + +import android.app.Activity; + +import android.util.Log; + + +public class RequstRoot extends Activity { + + + /** + * + * 应用程序运行命令获取 Root权限,设备必须已破解(获得ROOT权限) + * + * + * @param command 命令:String apkRoot="chmod 777 "+getPackageCodePath(); RootCommand(apkRoot); + * + * @return 应用程序是/否获取Root权限 + * + */ + + public static boolean RootCommand(String command) { + + Process process = null; + + DataOutputStream os = null; + + try { + + process = Runtime.getRuntime().exec("su"); + + os = new DataOutputStream(process.getOutputStream()); + + os.writeBytes(command + "\n"); + + os.writeBytes("exit\n"); + + os.flush(); + + process.waitFor(); + + } catch (Exception e) { + + Log.d("*** mhy ***", "ROOT REE" + e.getMessage()); + + return false; + + } finally { + + try { + + if (os != null) { + + os.close(); + + } + + process.destroy(); + + } catch (Exception e) { + + } + + } + + Log.d("*** mhy ***", "Root SUC "); + + return true; + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ma/mhy/sqliteeditorroot/util/RootGo.java b/app/src/main/java/ma/mhy/sqliteeditorroot/util/RootGo.java new file mode 100644 index 0000000..41909ca --- /dev/null +++ b/app/src/main/java/ma/mhy/sqliteeditorroot/util/RootGo.java @@ -0,0 +1,54 @@ +package ma.mhy.sqliteeditorroot.util; + +import android.app.ProgressDialog; +import android.content.Context; +import android.widget.Toast; + +import java.io.File; + +public class RootGo { + private ProgressDialog progress_dialog; + + private Context mCtx ; + /*** + * 其中is_root()判断是否已经具有了ROOT权限。 + * 只要/system/bin/su、/system/xbin/su这两个文件中有一个存在, + * 就表明已经具有ROOT权限,如果两个都不存在,则不具有ROOT权限。 + * @return + */ + // 判断是否具有ROOT权限 + public static boolean is_root() { + + boolean res = false; + + try { + if ((!new File("/system/bin/su").exists()) && + (!new File("/system/xbin/su").exists())) { + res = false; + } else { + res = true; + } + ; + } catch (Exception e) { + + } + return res; + } + + // 获取ROOT权限.获取Android的ROOT权限其实很简单,只要在Runtime下执行命令"su"就可以了。 + public void get_root() { + + if (is_root()) { + Toast.makeText(mCtx, "已经具有ROOT权限!", Toast.LENGTH_LONG).show(); + } else { + try { + progress_dialog = ProgressDialog.show(mCtx, + "ROOT", "正在获取ROOT权限...", true, false); + Runtime.getRuntime().exec("su"); + } catch (Exception e) { + Toast.makeText(mCtx, "获取ROOT权限时出错!", Toast.LENGTH_LONG).show(); + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_console_line_white_24dp.xml b/app/src/main/res/drawable/ic_console_line_white_24dp.xml new file mode 100644 index 0000000..d47874e --- /dev/null +++ b/app/src/main/res/drawable/ic_console_line_white_24dp.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_github_circle_white_24dp.xml b/app/src/main/res/drawable/ic_github_circle_white_24dp.xml new file mode 100644 index 0000000..27d2cf3 --- /dev/null +++ b/app/src/main/res/drawable/ic_github_circle_white_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_redo_gray_24dp.xml b/app/src/main/res/drawable/ic_redo_gray_24dp.xml new file mode 100644 index 0000000..bd53d3a --- /dev/null +++ b/app/src/main/res/drawable/ic_redo_gray_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_redo_white_24dp.xml b/app/src/main/res/drawable/ic_redo_white_24dp.xml new file mode 100644 index 0000000..1c4e6c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_redo_white_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_save_gray_24dp.xml b/app/src/main/res/drawable/ic_save_gray_24dp.xml new file mode 100644 index 0000000..f00dda8 --- /dev/null +++ b/app/src/main/res/drawable/ic_save_gray_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_save_white_24dp.xml b/app/src/main/res/drawable/ic_save_white_24dp.xml new file mode 100644 index 0000000..b32b114 --- /dev/null +++ b/app/src/main/res/drawable/ic_save_white_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_undo_gray_24dp.xml b/app/src/main/res/drawable/ic_undo_gray_24dp.xml new file mode 100644 index 0000000..a204ec7 --- /dev/null +++ b/app/src/main/res/drawable/ic_undo_gray_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_undo_white_24dp.xml b/app/src/main/res/drawable/ic_undo_white_24dp.xml new file mode 100644 index 0000000..401b08b --- /dev/null +++ b/app/src/main/res/drawable/ic_undo_white_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/popup_background.xml b/app/src/main/res/drawable/popup_background.xml new file mode 100644 index 0000000..e2588c6 --- /dev/null +++ b/app/src/main/res/drawable/popup_background.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..5ba0e29 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,19 @@ + + + + + +