Repository: amitshekhariitbhu/Android-Debug-Database Branch: master Commit: bf149df61f03 Files: 108 Total size: 255.2 KB Directory structure: gitextract_t_j5_uvg/ ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── debug-db/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── amitshekhar/ │ │ └── debug/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── amitshekhar/ │ │ │ └── debug/ │ │ │ ├── DebugDBInitProvider.java │ │ │ └── sqlite/ │ │ │ ├── DebugDBFactory.java │ │ │ └── DebugSQLiteDB.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── amitshekhar/ │ └── debug/ │ └── ExampleUnitTest.java ├── debug-db-base/ │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── amitshekhar/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ ├── app.js │ │ │ ├── custom.css │ │ │ ├── dataTables.altEditor.free.js │ │ │ └── index.html │ │ ├── java/ │ │ │ └── com/ │ │ │ └── amitshekhar/ │ │ │ ├── DebugDB.java │ │ │ ├── model/ │ │ │ │ ├── Response.java │ │ │ │ ├── RowDataRequest.java │ │ │ │ ├── TableDataResponse.java │ │ │ │ └── UpdateRowResponse.java │ │ │ ├── server/ │ │ │ │ ├── ClientServer.java │ │ │ │ └── RequestHandler.java │ │ │ ├── sqlite/ │ │ │ │ ├── DBFactory.java │ │ │ │ ├── InMemoryDebugSQLiteDB.java │ │ │ │ └── SQLiteDB.java │ │ │ └── utils/ │ │ │ ├── Constants.java │ │ │ ├── ConverterUtils.java │ │ │ ├── DataType.java │ │ │ ├── DatabaseFileProvider.java │ │ │ ├── DatabaseHelper.java │ │ │ ├── NetworkUtils.java │ │ │ ├── PrefHelper.java │ │ │ ├── TableNameParser.java │ │ │ └── Utils.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── amitshekhar/ │ └── ExampleUnitTest.java ├── debug-db-encrypt/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── amitshekhar/ │ │ └── debug/ │ │ └── encrypt/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── amitshekhar/ │ │ │ └── debug/ │ │ │ └── encrypt/ │ │ │ ├── DebugDBEncryptInitProvider.java │ │ │ └── sqlite/ │ │ │ ├── DebugDBEncryptFactory.java │ │ │ └── DebugEncryptSQLiteDB.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── amitshekhar/ │ └── debug/ │ └── encrypt/ │ └── ExampleUnitTest.java ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── sample-app/ │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── sample/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── sample/ │ │ │ ├── MainActivity.java │ │ │ ├── database/ │ │ │ │ ├── CarDBHelper.java │ │ │ │ ├── ContactDBHelper.java │ │ │ │ ├── ExtTestDBHelper.java │ │ │ │ └── room/ │ │ │ │ ├── AppDatabase.java │ │ │ │ ├── User.java │ │ │ │ ├── UserDBHelper.java │ │ │ │ └── UserDao.java │ │ │ └── utils/ │ │ │ └── Utils.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── com/ │ └── sample/ │ └── ExampleUnitTest.java ├── sample-app-encrypt/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── sample/ │ │ └── encrypt/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── sample/ │ │ │ └── encrypt/ │ │ │ ├── MainActivity.java │ │ │ ├── database/ │ │ │ │ ├── CarDBHelper.java │ │ │ │ ├── ContactDBHelper.java │ │ │ │ ├── ExtTestDBHelper.java │ │ │ │ ├── PersonDBHelper.java │ │ │ │ └── room/ │ │ │ │ ├── AppDatabase.java │ │ │ │ ├── User.java │ │ │ │ ├── UserDBHelper.java │ │ │ │ └── UserDao.java │ │ │ └── utils/ │ │ │ └── Utils.java │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── sample/ │ └── encrypt/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Android Studio generated folders captures/ .externalNativeBuild # IntelliJ project files *.iml .idea/ # Misc .DS_Store ================================================ FILE: CHANGELOG.md ================================================ Change Log ========== Version 1.0.6 *(2019-03-07)* ---------------------------- * Fix: Fix query error * Fix: Fix DebugDb class not found error Version 1.0.5 *(2019-02-18)* ---------------------------- * Reduce size by taking out encrypted database library as a separate module * New: Add support for database delete * Changed compile to implementation * Fix: Minor bug fixes Version 1.0.4 *(2018-06-23)* ---------------------------- * Fix: Fix issue of Room Database Version 1.0.3 *(2018-02-12)* ---------------------------- * New: Add support for debugging inMemory Room Database * Add example for Room Database Version 1.0.2 *(2018-01-08)* ---------------------------- * New: Add SqlCipher support * New: List table name in non case sensitive alphabetical order Version 1.0.1 *(2017-06-23)* ---------------------------- * New: Add insert row feature * New: Add custom database files support * New: Add method for checking isServerRunning * New: Add pragma support * Fix: Minor bug fixes Version 1.0.0 *(2017-02-08)* ---------------------------- * New: Add support for editing database directly * New: Delete rows directly * New: Delete Shared Pref * New: Edit shared preferences directly * New: Add standard code for checking databases files * New: Complete offline support * Refactor library code Version 0.5.0 *(2017-01-21)* ---------------------------- * New: Export DB * New: Method to get DB version * Fix: Fix proguard issue and other minor issues Version 0.4.0 *(2016-11-29)* ---------------------------- * Optimizations * Fix: Fix few minor bugs Version 0.3.0 *(2016-11-23)* ---------------------------- * New: Add support for custom port * Fix: Fix few minor bugs Version 0.2.0 *(2016-11-17)* ---------------------------- * New: Add method for getting address * Fix: Fix few minor bugs Version 0.1.0 *(2016-11-16)* ---------------------------- Initial release. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing 1. Fork it! 2. Checkout the development branch: `git checkout development` 3. Create your feature branch: `git checkout -b my-new-feature` 4. Add your changes to the index: `git add .` 5. Commit your changes: `git commit -m 'Add some feature'` 6. Push to the branch: `git push origin my-new-feature` 7. Submit a pull request against the `development` branch ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Android Debug Database ## Android Debug Database is a powerful library for debugging databases and shared preferences in Android applications ### Android Debug Database allows you to view databases and shared preferences directly in your browser in a very simple way ### What can Android Debug Database do? * See all the databases. * See all the data in the shared preferences used in your application. * Run any sql query on the given database to update and delete your data. * Directly edit the database values. * Directly edit the shared preferences. * Directly add a row in the database. * Directly add a key-value in the shared preferences. * Delete database rows and shared preferences. * Search in your data. * Sort data. * Download database. * Debug Room inMemory database. ## About me Hi, I am Amit Shekhar, Founder @ [Outcome School](https://outcomeschool.com) • IIT 2010-14 • I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos. ### Follow Amit Shekhar - [X/Twitter](https://twitter.com/amitiitbhu) - [LinkedIn](https://www.linkedin.com/in/amit-shekhar-iitbhu) - [GitHub](https://github.com/amitshekhariitbhu) ### Follow Outcome School - [YouTube](https://youtube.com/@OutcomeSchool) - [X/Twitter](https://x.com/outcome_school) - [LinkedIn](https://www.linkedin.com/company/outcomeschool) - [GitHub](http://github.com/OutcomeSchool) ## I teach at Outcome School - [AI and Machine Learning](https://outcomeschool.com/program/ai-and-machine-learning) - [Android](https://outcomeschool.com/program/android) Join Outcome School and get a high-paying tech job: [Outcome School](https://outcomeschool.com) ## [Outcome School Blog](https://outcomeschool.com/blog) - High-quality content to learn Android concepts. ### All these features work without rooting your device -> No need of rooted device ### Using Android Debug Database Library in your application Add this in your `settings.gradle`: ```groovy maven { url 'https://jitpack.io' } ``` If you are using `settings.gradle.kts`, add the following: ```kotlin maven { setUrl("https://jitpack.io") } ``` Add this in your `build.gradle` ```groovy debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.7' ``` If you are using `build.gradle.kts`, add the following: ```kotlin debugImplementation("com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.7") ``` Using the Android Debug Database with encrypted database Add this in your `build.gradle` ```groovy debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db-encrypt:1.0.7' ``` If you are using `build.gradle.kts`, add the following: ```kotlin debugImplementation("com.github.amitshekhariitbhu.Android-Debug-Database:debug-db-encrypt:1.0.7") ``` And to provide the password for the DB, you should add this in the Gradle: DB_PASSWORD_{VARIABLE}, if for example, PERSON is the database name: DB_PASSWORD_PERSON ```groovy debug { resValue("string", "DB_PASSWORD_PERSON", "password") } ``` Use `debugImplementation` so that it will only compile in your debug build and not in your release build. That’s all, just start the application, you will see in the logcat an entry like follows : * D/DebugDB: Open http://XXX.XXX.X.XXX:8080 in your browser * You can also always get the debug address url from your code by calling the method `DebugDB.getAddressLog();` Now open the provided link in your browser. Important: * Your Android phone and laptop should be connected to the same Network (Wifi or LAN). * If you are using it over usb, run `adb forward tcp:8080 tcp:8080` Note : If you want use different port other than 8080. In the app build.gradle file under buildTypes do the following change ```groovy debug { resValue("string", "PORT_NUMBER", "8081") } ``` You will see something like this : ### Seeing values ### Editing values ### Working with emulator * Android Default Emulator: Run the command in the terminal - `adb forward tcp:8080 tcp:8080` and open http://localhost:8080 * Genymotion Emulator: Enable bridge from configure virtual device (option available in genymotion) ### Getting address with toast, in case you missed the address log in logcat As this library is auto-initialize, if you want to get the address log, add the following method and call (we have to do like this to avoid build error in release build as this library will not be included in the release build) using reflection. ```java public static void showDebugDBAddressLogToast(Context context) { if (BuildConfig.DEBUG) { try { Class debugDB = Class.forName("com.amitshekhar.DebugDB"); Method getAddressLog = debugDB.getMethod("getAddressLog"); Object value = getAddressLog.invoke(null); Toast.makeText(context, (String) value, Toast.LENGTH_LONG).show(); } catch (Exception ignore) { } } } ``` ### Adding custom database files As this library is auto-initialize, if you want to debug custom database files, add the following method and call ```java public static void setCustomDatabaseFiles(Context context) { if (BuildConfig.DEBUG) { try { Class debugDB = Class.forName("com.amitshekhar.DebugDB"); Class[] argTypes = new Class[]{HashMap.class}; Method setCustomDatabaseFiles = debugDB.getMethod("setCustomDatabaseFiles", argTypes); HashMap> customDatabaseFiles = new HashMap<>(); // set your custom database files customDatabaseFiles.put(ExtTestDBHelper.DATABASE_NAME, new Pair<>(new File(context.getFilesDir() + "/" + ExtTestDBHelper.DIR_NAME + "/" + ExtTestDBHelper.DATABASE_NAME), "")); setCustomDatabaseFiles.invoke(null, customDatabaseFiles); } catch (Exception ignore) { } } } ``` ### Adding InMemory Room databases As this library is auto-initialize, if you want to debug inMemory Room databases, add the following method and call ```java public static void setInMemoryRoomDatabases(SupportSQLiteDatabase... database) { if (BuildConfig.DEBUG) { try { Class debugDB = Class.forName("com.amitshekhar.DebugDB"); Class[] argTypes = new Class[]{HashMap.class}; HashMap inMemoryDatabases = new HashMap<>(); // set your inMemory databases inMemoryDatabases.put("InMemoryOne.db", database[0]); Method setRoomInMemoryDatabase = debugDB.getMethod("setInMemoryRoomDatabases", argTypes); setRoomInMemoryDatabase.invoke(null, inMemoryDatabases); } catch (Exception ignore) { } } } ``` ### Find this project useful ? :heart: * Support it by clicking the :star: button on the upper right of this page. :v: ### TODO * Simplify emulator issue [Issue Link](https://github.com/amitshekhariitbhu/Android-Debug-Database/issues/6) * And of course many more features and bug fixes. You can connect with me on: - [Twitter](https://twitter.com/amitiitbhu) - [LinkedIn](https://www.linkedin.com/in/amit-shekhar-iitbhu) - [GitHub](https://github.com/amitshekhariitbhu) - [Facebook](https://www.facebook.com/amit.shekhar.iitbhu) [**Read all of our blogs here.**](https://outcomeschool.com/blog) ### License ``` Copyright (C) 2024 Amit Shekhar Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ### Contributing to Android Debug Database All pull requests are welcome, make sure to follow the [contribution guidelines](CONTRIBUTING.md) when you submit pull request. ================================================ FILE: build.gradle ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id 'com.android.application' version '7.3.0' apply false id 'com.android.library' version '7.3.0' apply false } task clean(type: Delete) { delete rootProject.buildDir } ext { compileSdk = 33 targetSdk = 33 minSdk = 14 } ================================================ FILE: debug-db/.gitignore ================================================ /build ================================================ FILE: debug-db/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdk rootProject.ext.compileSdk defaultConfig { minSdk rootProject.ext.minSdk targetSdk rootProject.ext.targetSdk versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { api project(':debug-db-base') testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } ================================================ FILE: debug-db/gradle.properties ================================================ ARTIFACT_ID=debug-db ================================================ FILE: debug-db/proguard-rules.pro ================================================ # 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 ================================================ FILE: debug-db/src/androidTest/java/com/amitshekhar/debug/ExampleInstrumentedTest.java ================================================ package com.amitshekhar.debug; 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("com.amitshekhar.debug.test", appContext.getPackageName()); } } ================================================ FILE: debug-db/src/main/AndroidManifest.xml ================================================ ================================================ FILE: debug-db/src/main/java/com/amitshekhar/debug/DebugDBInitProvider.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.debug; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; import com.amitshekhar.DebugDB; import com.amitshekhar.debug.sqlite.DebugDBFactory; /** * Created by amitshekhar on 16/11/16. */ public class DebugDBInitProvider extends ContentProvider { public DebugDBInitProvider() { } @Override public boolean onCreate() { DebugDB.initialize(getContext(), new DebugDBFactory()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public void attachInfo(Context context, ProviderInfo providerInfo) { if (providerInfo == null) { throw new NullPointerException("DebugDBInitProvider ProviderInfo cannot be null."); } // So if the authorities equal the library internal ones, the developer forgot to set his applicationId if ("com.amitshekhar.debug.DebugDBInitProvider".equals(providerInfo.authority)) { throw new IllegalStateException("Incorrect provider authority in manifest. Most likely due to a " + "missing applicationId variable in application\'s build.gradle."); } super.attachInfo(context, providerInfo); } } ================================================ FILE: debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugDBFactory.java ================================================ package com.amitshekhar.debug.sqlite; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import com.amitshekhar.sqlite.DBFactory; import com.amitshekhar.sqlite.SQLiteDB; public class DebugDBFactory implements DBFactory { @Override public SQLiteDB create(Context context, String path, String password) { return new DebugSQLiteDB(SQLiteDatabase.openOrCreateDatabase(path, null)); } } ================================================ FILE: debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugSQLiteDB.java ================================================ package com.amitshekhar.debug.sqlite; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import com.amitshekhar.sqlite.SQLiteDB; public class DebugSQLiteDB implements SQLiteDB { private final SQLiteDatabase database; public DebugSQLiteDB(SQLiteDatabase database) { this.database = database; } @Override public int delete(String table, String whereClause, String[] whereArgs) { return database.delete(table, whereClause, whereArgs); } @Override public boolean isOpen() { return database.isOpen(); } @Override public void close() { database.close(); } @Override public Cursor rawQuery(String sql, String[] selectionArgs) { return database.rawQuery(sql, selectionArgs); } @Override public void execSQL(String sql) throws SQLException { database.execSQL(sql); } @Override public long insert(String table, String nullColumnHack, ContentValues values) { return database.insert(table, nullColumnHack, values); } @Override public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { return database.update(table, values, whereClause, whereArgs); } @Override public int getVersion() { return database.getVersion(); } } ================================================ FILE: debug-db/src/main/res/values/strings.xml ================================================ debug-db ================================================ FILE: debug-db/src/test/java/com/amitshekhar/debug/ExampleUnitTest.java ================================================ package com.amitshekhar.debug; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() { assertEquals(4, 2 + 2); } } ================================================ FILE: debug-db-base/build.gradle ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ apply plugin: 'com.android.library' android { compileSdk rootProject.ext.compileSdk defaultConfig { minSdk rootProject.ext.minSdk targetSdk rootProject.ext.targetSdk versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue("string", "PORT_NUMBER", "8080") } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation 'com.google.code.gson:gson:2.8.5' implementation "androidx.room:room-runtime:2.5.0" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } ================================================ FILE: debug-db-base/proguard-rules.pro ================================================ # 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 -keepparameternames -keepattributes Exceptions,InnerClasses,Signature,Deprecated,EnclosingMethod # Preserve all annotations. -keepattributes *Annotation* # Preserve all public classes, and their public and protected fields and # methods. -keep public class * { public protected *; } # Preserve all .class method names. -keepclassmembernames class * { java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String, boolean); } # Preserve all native method names and the names of their classes. -keepclasseswithmembernames class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers class * extends java.lang.Enum { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your library doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Your library may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class mypackage.MyClass # -keep public interface mypackage.MyInterface # -keep public class * implements mypackage.MyInterface ================================================ FILE: debug-db-base/src/androidTest/java/com/amitshekhar/ExampleInstrumentedTest.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar; 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.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.amitshekhar.test", appContext.getPackageName()); } } ================================================ FILE: debug-db-base/src/main/AndroidManifest.xml ================================================ ================================================ FILE: debug-db-base/src/main/assets/app.js ================================================ $( document ).ready(function() { getDBList(); $("#query").keypress(function(e){ if(e.which == 13) { queryFunction(); } }); //update currently selected database $( document ).on( "click", "#db-list .list-group-item", function() { $("#db-list .list-group-item").each(function() { $(this).removeClass('selected'); }); $(this).addClass('selected'); }); //update currently table database $( document ).on( "click", "#table-list .list-group-item", function() { $("#table-list .list-group-item").each(function() { $(this).removeClass('selected'); }); $(this).addClass('selected'); }); }); var isDatabaseSelected = true; function getData(tableName) { $.ajax({url: "getAllDataFromTheTable?tableName="+tableName, success: function(result){ result = JSON.parse(result); inflateData(result); }}); } function queryFunction() { var query = $('#query').val(); $.ajax({url: "query?query="+escape(query), success: function(result){ result = JSON.parse(result); inflateData(result); }}); } function downloadDb() { if (isDatabaseSelected) { $.ajax({url: "downloadDb", success: function(){ window.location = 'downloadDb'; }}); } } function deleteDb() { if (isDatabaseSelected) { $.ajax({url: "deleteDb", success: function(result){ result = JSON.parse(result); if(result.isSuccessful){ console.log("Database deleted successfully"); showSuccessInfo("Database Deleted Successfully"); getDBList(); } else { console.log("Database delete failed"); showErrorInfo("Database Delete Failed"); } }}); } } function getDBList() { $.ajax({url: "getDbList", success: function(result){ result = JSON.parse(result); var dbList = result.rows; $('#db-list').empty(); var isSelectionDone = false; for(var count = 0; count < dbList.length; count++){ var dbName = dbList[count][0]; var isEncrypted = dbList[count][1]; var isDownloadable = dbList[count][2]; var dbAttribute = isEncrypted == "true" ? ' ' : ""; if(dbName.indexOf("journal") == -1 && dbName.indexOf("-wal") == -1 && dbName.indexOf("-shm") == -1){ $("#db-list").append("" + dbName + dbAttribute + ""); if(!isSelectionDone){ isSelectionDone = true; $('#db-list').find('a').trigger('click'); } } } }}); } var lastTableName = getHashValue('table'); function openDatabaseAndGetTableList(db, isDownloadable) { if("APP_SHARED_PREFERENCES" == db) { $('#run-query').removeClass('active'); $('#run-query').addClass('disabled'); $('#selected-db-download').removeClass('active'); $('#selected-db-delete').removeClass('active'); $('#selected-db-download').addClass('disabled'); $('#selected-db-delete').addClass('disabled'); isDatabaseSelected = false; $("#selected-db-info").text("SharedPreferences"); } else { $('#run-query').removeClass('disabled'); $('#run-query').addClass('active'); $("#selected-db-info").text("Selected Database : "+db); if("true" == isDownloadable) { $('#selected-db-download').addClass('active'); $('#selected-db-delete').addClass('active'); $('#selected-db-download').removeClass('disabled'); $('#selected-db-delete').removeClass('disabled'); } else { $('#selected-db-download').removeClass('active'); $('#selected-db-delete').removeClass('active'); $('#selected-db-download').addClass('disabled'); $('#selected-db-delete').addClass('disabled'); } isDatabaseSelected = true; } $.ajax({url: "getTableList?database="+db, success: function(result){ result = JSON.parse(result); var tableList = result.rows; var dbVersion = result.dbVersion; if("APP_SHARED_PREFERENCES" != db) { $("#selected-db-info").text("Selected Database : "+db +" Version : "+dbVersion); } $('#table-list').empty() for(var count = 0; count < tableList.length; count++){ var tableName = tableList[count]; $("#table-list").append("" + tableName + ""); } if (lastTableName !== null) { $('a[data-table-name=' + lastTableName + ']').trigger('click'); } }}); } function inflateData(result){ if(result.isSuccessful){ if(!result.isSelectQuery){ showSuccessInfo("Query Executed Successfully"); return; } var columnHeader = result.tableInfos; // set function to return cell data for different usages like set, display, filter, search etc.. for(var i = 0; i < columnHeader.length; i++) { columnHeader[i]['targets'] = i; columnHeader[i]['data'] = function(row, type, val, meta) { var dataType = row[meta.col].dataType; if (type == "sort" && dataType == "boolean") { return row[meta.col].value ? 1 : 0; } return row[meta.col].value; } } var columnData = result.rows; var tableId = "#db-data"; if ($.fn.DataTable.isDataTable(tableId) ) { $(tableId).DataTable().destroy(); } $("#db-data-div").remove(); $("#parent-data-div").append('
'); var availableButtons; if (result.isEditable) { availableButtons = [ { text : 'Add', name : 'add' // don not change name }, { extend: 'selected', // Bind to Selected row text: 'Edit', name: 'edit' // do not change name }, { extend: 'selected', text: 'Delete', name: 'delete' } ]; } else { availableButtons = []; } $(tableId).dataTable({ "data": columnData, "columnDefs": columnHeader, 'bPaginate': true, 'searching': true, 'bFilter': true, 'bInfo': true, "bSort" : true, "scrollX": true, "iDisplayLength": 10, "dom": "Bfrtip", select: 'single', altEditor: true, // Enable altEditor buttons: availableButtons }) //attach row-updated listener $(tableId).on('update-row.dt', function (e, updatedRowData, callback) { var updatedRowDataArray = JSON.parse(updatedRowData); //add value for each column var data = columnHeader; for(var i = 0; i < data.length; i++) { data[i].value = updatedRowDataArray[i].value; data[i].dataType = updatedRowDataArray[i].dataType; } //send update table data request to server updateTableData(data, callback); }); //attach delete-updated listener $(tableId).on('delete-row.dt', function (e, updatedRowData, callback) { var deleteRowDataArray = JSON.parse(updatedRowData); console.log(deleteRowDataArray); //add value for each column var data = columnHeader; for(var i = 0; i < data.length; i++) { data[i].value = deleteRowDataArray[i].value; data[i].dataType = deleteRowDataArray[i].dataType; } //send delete table data request to server deleteTableData(data, callback); }); $(tableId).on('add-row.dt', function (e, updatedRowData, callback) { var deleteRowDataArray = JSON.parse(updatedRowData); console.log(deleteRowDataArray); //add value for each column var data = columnHeader; for(var i = 0; i < data.length; i++) { data[i].value = deleteRowDataArray[i].value; data[i].dataType = deleteRowDataArray[i].dataType; } //send delete table data request to server addTableData(data, callback); }); // hack to fix alignment issue when scrollX is enabled $(".dataTables_scrollHeadInner").css({"width":"100%"}); $(".table ").css({"width":"100%"}); }else{ if(!result.isSelectQuery){ showErrorInfo("Query Execution Failed"); }else { showErrorInfo("Some Error Occurred"); } } } //send update database request to server function updateTableData(updatedData, callback) { //get currently selected element var selectedTableElement = $("#table-list .list-group-item.selected"); var filteredUpdatedData = updatedData.map(function(columnData){ return { title: columnData.title, isPrimary: columnData.isPrimary, value: columnData.value, dataType: columnData.dataType } }); //build request parameters var requestParameters = {}; requestParameters.dbName = selectedTableElement.attr('data-db-name'); requestParameters.tableName = selectedTableElement.attr('data-table-name');; requestParameters.updatedData = encodeURIComponent(JSON.stringify(filteredUpdatedData)); //execute request $.ajax({ url: "updateTableData", type: 'GET', data: requestParameters, success: function(response) { response = JSON.parse(response); if(response.isSuccessful){ console.log("Data updated successfully"); callback(true); showSuccessInfo("Data Updated Successfully"); } else { console.log("Data updated failed"); callback(false); } } }) } function deleteTableData(deleteData, callback) { var selectedTableElement = $("#table-list .list-group-item.selected"); var filteredUpdatedData = deleteData.map(function(columnData){ return { title: columnData.title, isPrimary: columnData.isPrimary, value: columnData.value, dataType: columnData.dataType } }); //build request parameters var requestParameters = {}; requestParameters.dbName = selectedTableElement.attr('data-db-name'); requestParameters.tableName = selectedTableElement.attr('data-table-name');; requestParameters.deleteData = encodeURIComponent(JSON.stringify(filteredUpdatedData)); //execute request $.ajax({ url: "deleteTableData", type: 'GET', data: requestParameters, success: function(response) { response = JSON.parse(response); if(response.isSuccessful){ console.log("Data deleted successfully"); callback(true); showSuccessInfo("Data Deleted Successfully"); } else { console.log("Data delete failed"); callback(false); } } }) } function addTableData(deleteData, callback) { var selectedTableElement = $("#table-list .list-group-item.selected"); var filteredUpdatedData = deleteData.map(function(columnData){ return { title: columnData.title, isPrimary: columnData.isPrimary, value: columnData.value, dataType: columnData.dataType } }); console.log(filteredUpdatedData); //build request parameters var requestParameters = {}; requestParameters.dbName = selectedTableElement.attr('data-db-name'); requestParameters.tableName = selectedTableElement.attr('data-table-name');; requestParameters.addData = encodeURIComponent(JSON.stringify(filteredUpdatedData)); console.log(requestParameters); //execute request $.ajax({ url: "addTableData", type: 'GET', data: requestParameters, success: function(response) { response = JSON.parse(response); if(response.isSuccessful){ console.log("Data Added successfully"); callback(true); getData(requestParameters.tableName); showSuccessInfo("Data Added Successfully"); } else { console.log("Data Adding failed"); callback(false); } } }); } function showSuccessInfo(message){ var snackbarId = "snackbar"; var snackbarElement = $("#"+snackbarId); snackbarElement.addClass("show"); snackbarElement.css({"backgroundColor": "#5cb85c"}); snackbarElement.html(message) setTimeout(function(){ snackbarElement.removeClass("show"); }, 3000); } function showErrorInfo(message){ var snackbarId = "snackbar"; var snackbarElement = $("#"+snackbarId); snackbarElement.addClass("show"); snackbarElement.css({"backgroundColor": "#d9534f"}); snackbarElement.html(message) setTimeout(function(){ snackbarElement.removeClass("show"); }, 3000); } function getHashValue(key) { var matches = location.hash.match(new RegExp(key + '=([^&]*)')); return matches ? matches[1] : null; } ================================================ FILE: debug-db-base/src/main/assets/custom.css ================================================ .padding-fifty { padding-top: 50px; } .padding-twenty { padding-top: 20px; } .display-none { display: none; } .list-group-item { word-break: break-all; } .list-group-item.selected { background: #dff0d8 !important; color: #3c763d !important; font-weight: bold; } #snackbar { visibility: hidden; min-width: 250px; margin-left: -125px; background-color: #5cb85c; color: #fff; text-align: center; border-radius: 2px; padding: 16px; position: fixed; z-index: 1; left: 50%; bottom: 30px; font-size: 17px; } #snackbar.show { visibility: visible; -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; animation: fadein 0.5s, fadeout 0.5s 2.5s; } @-webkit-keyframes fadein { from {bottom: 0; opacity: 0;} to {bottom: 30px; opacity: 1;} } @keyframes fadein { from {bottom: 0; opacity: 0;} to {bottom: 30px; opacity: 1;} } @-webkit-keyframes fadeout { from {bottom: 30px; opacity: 1;} to {bottom: 0; opacity: 0;} } ================================================ FILE: debug-db-base/src/main/assets/dataTables.altEditor.free.js ================================================ /*! Datatables altEditor 1.0 */ /** * @summary altEditor * @description Lightweight editor for DataTables * @version 1.0 * @file dataTables.editor.lite.js * @author kingkode (www.kingkode.com) * @contact www.kingkode.com/contact * @copyright Copyright 2016 Kingkode * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * This source file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. * * For details please refer to: http://www.kingkode.com */ (function(factory) { if (typeof define === 'function' && define.amd) { // AMD define(['jquery', 'datatables.net'], function($) { return factory($, window, document); }); } else if (typeof exports === 'object') { // CommonJS module.exports = function(root, $) { if (!root) { root = window; } if (!$ || !$.fn.dataTable) { $ = require('datatables.net')(root, $).$; } return factory($, root, root.document); }; } else { // Browser factory(jQuery, window, document); } }(function($, window, document, undefined) { 'use strict'; var DataTable = $.fn.dataTable; var _instance = 0; /** * altEditor provides modal editing of records for Datatables * * @class altEditor * @constructor * @param {object} oTD DataTables settings object * @param {object} oConfig Configuration object for altEditor */ var altEditor = function(dt, opts) { if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.8')) { throw ("Warning: altEditor requires DataTables 1.10.8 or greater"); } // User and defaults configuration object this.c = $.extend(true, {}, DataTable.defaults.altEditor, altEditor.defaults, opts ); /** * @namespace Settings object which contains customisable information for altEditor instance */ this.s = { /** @type {DataTable.Api} DataTables' API instance */ dt: new DataTable.Api(dt), /** @type {String} Unique namespace for events attached to the document */ namespace: '.altEditor' + (_instance++) }; /** * @namespace Common and useful DOM elements for the class instance */ this.dom = { /** @type {jQuery} altEditor handle */ modal: $('
'), }; /* Constructor logic */ this._constructor(); } $.extend(altEditor.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Initialise the RowReorder instance * * @private */ _constructor: function() { // console.log('altEditor Enabled') var that = this; var dt = this.s.dt; this._setup(); dt.on('destroy.altEditor', function() { dt.off('.altEditor'); $(dt.table().body()).off(that.s.namespace); $(document.body).off(that.s.namespace); }); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Setup dom and bind button actions * * @private */ _setup: function() { // console.log('Setup'); var that = this; var dt = this.s.dt; // add modal $('body').append('\ '); // add Edit Button if (this.s.dt.button('edit:name')) { this.s.dt.button('edit:name').action(function(e, dt, node, config) { var rows = dt.rows({ selected: true }).count(); that._openEditModal(); }); $(document).on('click', '#editRowBtn', function(e) { e.preventDefault(); e.stopPropagation(); that._editRowData(); }); } // add Delete Button if (this.s.dt.button('delete:name')) { this.s.dt.button('delete:name').action(function(e, dt, node, config) { var rows = dt.rows({ selected: true }).count(); that._openDeleteModal(); }); $(document).on('click', '#deleteRowBtn', function(e) { e.preventDefault(); e.stopPropagation(); that._deleteRow(); }); } // add Add Button if (this.s.dt.button('add:name')) { this.s.dt.button('add:name').action(function(e, dt, node, config) { var rows = dt.rows({ selected: true }).count(); that._openAddModal(); }); $(document).on('click', '#addRowBtn', function(e) { e.preventDefault(); e.stopPropagation(); that._addRowData(); }); } }, /** * Emit an event on the DataTable for listeners * * @param {string} name Event name * @param {array} args Event arguments * @private */ _emitEvent: function(name, args) { this.s.dt.iterator('table', function(ctx, i) { $(ctx.nTable).triggerHandler(name + '.dt', args); }); }, /** * Open Edit Modal for selected row * * @private */ _openEditModal: function() { var that = this; var dt = this.s.dt; var columnDefs = []; for (var i = 0; i < dt.context[0].aoColumns.length; i++) { columnDefs.push({ title: dt.context[0].aoColumns[i].sTitle, dataType: dt.context[0].aoColumns[i].dataType, isPrimary: dt.context[0].aoColumns[i].isPrimary, }) } var adata = dt.rows({ selected: true }); var data = ""; data += "
"; for (var j in columnDefs) { var cellData = adata.data()[0][j]; var inputSectionHTML = "
__INPUT_HTML__
"; var inputHTML = "
"; var option1Checked = ""; var option2Checked = "checked"; if (cellData.dataType == "boolean") { if(JSON.parse(cellData.value)) { option1Checked = "checked"; option2Checked = ""; } inputHTML = "
" } //set input type var inputType = "text"; var inputReadOnlyAttribute = ""; switch (cellData.dataType) { case 'integer': inputType = "number"; break; case 'real': inputType = "number"; break; case 'boolean': inputType = "checkbox"; break; case 'long': inputType = "number"; break; case 'float': inputType = "number"; break; case 'text': inputType = "text"; break; case 'string_set': inputType = "text"; break; } //set input to read-only if it is a primary key if (columnDefs[j].isPrimary) { inputReadOnlyAttribute = "readonly" } //append input html inputSectionHTML = inputSectionHTML.replace(/__INPUT_HTML__/g, inputHTML); inputSectionHTML = inputSectionHTML.replace(/__INPUT_READ_ONLY_ATTRIBUTE__/g, inputReadOnlyAttribute); inputSectionHTML = inputSectionHTML.replace(/__INPUT_TYPE__/g, inputType); inputSectionHTML = inputSectionHTML.replace(/__INPUT_DATA_TYPE__/g, cellData.dataType); inputSectionHTML = inputSectionHTML.replace(/__INPUT_VALUE__/g, cellData.value); inputSectionHTML = inputSectionHTML.replace(/__INPUT_NAME__/g, columnDefs[j].title); inputSectionHTML = inputSectionHTML.replace(/__OPTION_1_CHECKED__/g, option1Checked); inputSectionHTML = inputSectionHTML.replace(/__OPTION_2_CHECKED__/g, option2Checked); data += inputSectionHTML; } data += "
"; $('#altEditor-modal').on('show.bs.modal', function() { $('#altEditor-modal').find('.modal-title').html('Edit Record'); $('#altEditor-modal').find('.modal-body').html('
' + data + '
'); $('#altEditor-modal').find('.modal-footer').html("\ "); }); $('#altEditor-modal').modal('show'); $('#altEditor-modal input[0]').focus(); }, _editRowData: function() { var that = this; var dt = this.s.dt; var data = []; $('form[name="altEditor-form"] input').each(function(i) { var addToList = true; var value = $(this).val(); if($(this).attr('type') == "radio" && $(this).prop('checked') == false) { addToList = false; } value = $(this).attr('type') == "radio" ? $(this).val() == "1" : value; if (addToList){ data.push({ "value": value, "dataType": $(this).attr('data-type') }); } }); var editButtonCurrentText = $("#editRowBtn").text(); $("#editRowBtn").addClass('disabled'); $("#editRowBtn").text("Saving.."); that._emitEvent("update-row", [ JSON.stringify(data), function(isUpdated) { //set error message and other properties based on whether update is successfull or not var alertAdditionClasses = "alert-success"; var alertMessage = "This record has been updated"; var alertHeading = "Success"; if (!isUpdated) { alertAdditionClasses = "alert-danger"; alertMessage = "Error occurred while updating this record"; alertHeading = "Error"; } //create alert element html and append it to modal var messageHTML = '\ \ '; messageHTML = messageHTML.replace(/__ALERT_ADDITION_CLASSES__/g, alertAdditionClasses); messageHTML = messageHTML.replace(/__ALERT_HEADING__/g, alertHeading); messageHTML = messageHTML.replace(/__ALERT_MESSAGE__/g, alertMessage); $('#altEditor-modal .modal-body').append(messageHTML); //update datatable, if update is successfull if (isUpdated) { dt.row({ selected: true }).data(data); //remove existing alert elements $('#altEditor-modal').modal('hide'); } $("#editRowBtn").removeClass('disabled'); $("#editRowBtn").text(editButtonCurrentText); } ]); }, /** * Open Delete Modal for selected row * * @private */ _openDeleteModal: function() { var that = this; var dt = this.s.dt; var columnDefs = []; for (var i = 0; i < dt.context[0].aoColumns.length; i++) { columnDefs.push({ title: dt.context[0].aoColumns[i].sTitle }) } var adata = dt.rows({ selected: true }); var data = ""; data += "
"; for (var i in columnDefs) { var cellData = adata.data()[0][i]; var inputType = "text"; switch (cellData.dataType) { case 'integer': inputType = "number"; break; case 'real': inputType = "number"; break; case 'boolean': inputType = "checkbox"; break; case 'long': inputType = "number"; break; case 'float': inputType = "number"; break; case 'text': inputType = "text"; break; case 'string_set': inputType = "text"; break; } data += "
" + cellData.value + "
"; } data += "
"; $('#altEditor-modal').on('show.bs.modal', function() { $('#altEditor-modal').find('.modal-title').html('Delete Record'); $('#altEditor-modal').find('.modal-body').html('
' + data + '
'); $('#altEditor-modal').find('.modal-footer').html("\ "); }); $('#altEditor-modal').modal('show'); $('#altEditor-modal input[0]').focus(); }, _deleteRow: function() { var that = this; var dt = this.s.dt; var data = []; $('form[name="altEditor-form"] input').each(function(i) { var addToList = true; var value = $(this).val(); value = $(this).val(); console.log("Value : " + value); if (addToList){ data.push({ "value": value, "dataType": $(this).attr('data-type') }); } }); $('#altEditor-modal .modal-body .alert').remove(); var message = ''; $('#altEditor-modal .modal-body').append(message); that._emitEvent("delete-row", [ JSON.stringify(data), function(isDeleted) { if (isDeleted) { dt.row({ selected: true }).remove(); dt.draw(); } //remove existing alert elements $('#altEditor-modal').modal('hide'); } ]); }, /** * Open Add Modal for selected row * * @private */ _openAddModal: function() { var that = this; var dt = this.s.dt; var columnDefs = []; for (var i = 0; i < dt.context[0].aoColumns.length; i++) { columnDefs.push({ title: dt.context[0].aoColumns[i].sTitle, dataType: dt.context[0].aoColumns[i].sType, isPrimary: dt.context[0].aoColumns[i].isPrimary, value : "", }) } var data = ""; data += "
"; for (var j in columnDefs) { var inputSectionHTML = "
__INPUT_HTML__
"; var inputHTML = "
"; var option1Checked = ""; var option2Checked = "checked"; if (columnDefs[j].dataType == "boolean") { if(JSON.parse(columnDefs[j].value)) { option1Checked = "checked"; option2Checked = ""; } inputHTML = "
" } //set input type var inputType = "text"; var inputReadOnlyAttribute = ""; switch (columnDefs[j].dataType) { case 'num': inputType = "number"; break; case 'string': inputType = "text"; break; } //set input to read-only if it is a primary key // if (columnDefs[j].isPrimary) { // inputReadOnlyAttribute = "readonly" // } //append input html inputSectionHTML = inputSectionHTML.replace(/__INPUT_HTML__/g, inputHTML); inputSectionHTML = inputSectionHTML.replace(/__INPUT_READ_ONLY_ATTRIBUTE__/g, inputReadOnlyAttribute); inputSectionHTML = inputSectionHTML.replace(/__INPUT_TYPE__/g, inputType); inputSectionHTML = inputSectionHTML.replace(/__INPUT_DATA_TYPE__/g, columnDefs[j].dataType); inputSectionHTML = inputSectionHTML.replace(/__INPUT_VALUE__/g, columnDefs[j].value); inputSectionHTML = inputSectionHTML.replace(/__INPUT_NAME__/g, columnDefs[j].title); inputSectionHTML = inputSectionHTML.replace(/__OPTION_1_CHECKED__/g, option1Checked); inputSectionHTML = inputSectionHTML.replace(/__OPTION_2_CHECKED__/g, option2Checked); data += inputSectionHTML; } data += "
"; $('#altEditor-modal').on('show.bs.modal', function() { $('#altEditor-modal').find('.modal-title').html('Add Record'); $('#altEditor-modal').find('.modal-body').html('
' + data + '
'); $('#altEditor-modal').find('.modal-footer').html("\ "); }); $('#altEditor-modal').modal('show'); $('#altEditor-modal input[0]').focus(); }, _addRowData: function() { console.log('add row') var that = this; var dt = this.s.dt; var data = []; $('form[name="altEditor-form"] input').each(function(i) { var addToList = true; var value = $(this).val(); if($(this).attr('type') == "radio" && $(this).prop('checked') == false) { addToList = false; } value = $(this).attr('type') == "radio" ? $(this).val() == "1" : value; if (addToList){ data.push({ "value": value, "dataType": $(this).attr('data-type') }); } }); var editButtonCurrentText = $("#editRowBtn").text(); $("#addRowBtn").addClass('disabled'); $("#addRowBtn").text("Saving.."); console.log(JSON.stringify(data)); that._emitEvent("add-row", [ JSON.stringify(data), function(isAdded) { //set error message and other properties based on whether update is successfull or not var alertAdditionClasses = "alert-success"; var alertMessage = "This record has been added"; var alertHeading = "Success"; if (!isAdded) { alertAdditionClasses = "alert-danger"; alertMessage = "Error occurred while adding this record"; alertHeading = "Error"; } //create alert element html and append it to modal var messageHTML = '\ \ '; messageHTML = messageHTML.replace(/__ALERT_ADDITION_CLASSES__/g, alertAdditionClasses); messageHTML = messageHTML.replace(/__ALERT_HEADING__/g, alertHeading); messageHTML = messageHTML.replace(/__ALERT_MESSAGE__/g, alertMessage); $('#altEditor-modal .modal-body').append(messageHTML); //update datatable, if update is successfull if (isAdded) { dt.row().data(data); //remove existing alert elements $('#altEditor-modal').modal('hide'); } $("#addRowBtn").removeClass('disabled'); $("#addRowBtn").text(editButtonCurrentText); } ]); }, _getExecutionLocationFolder: function() { var fileName = "dataTables.altEditor.js"; var scriptList = $("script[src]"); var jsFileObject = $.grep(scriptList, function(el) { if (el.src.indexOf(fileName) !== -1) { return el; } }); var jsFilePath = jsFileObject[0].src; var jsFileDirectory = jsFilePath.substring(0, jsFilePath.lastIndexOf("/") + 1); return jsFileDirectory; } }); /** * altEditor version * * @static * @type String */ altEditor.version = '1.0'; /** * altEditor defaults * * @namespace */ altEditor.defaults = { /** @type {Boolean} Ask user what they want to do, even for a single option */ alwaysAsk: false, /** @type {string|null} What will trigger a focus */ focus: null, // focus, click, hover /** @type {column-selector} Columns to provide auto fill for */ columns: '', // all /** @type {boolean|null} Update the cells after a drag */ update: null, // false is editor given, true otherwise /** @type {DataTable.Editor} Editor instance for automatic submission */ editor: null }; /** * Classes used by altEditor that are configurable * * @namespace */ altEditor.classes = { /** @type {String} Class used by the selection button */ btn: 'btn' }; // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on('preInit.dt.altEditor', function(e, settings, json) { if (e.namespace !== 'dt') { return; } var init = settings.oInit.altEditor; var defaults = DataTable.defaults.altEditor; if (init || defaults) { var opts = $.extend({}, init, defaults); if (init !== false) { new altEditor(settings, opts); } } }); // Alias for access DataTable.altEditor = altEditor; return altEditor; })); ================================================ FILE: debug-db-base/src/main/assets/index.html ================================================ Android Debug Database
Databases
Tables
Data
Data Updated Successfully
================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/DebugDB.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar; import android.content.Context; import android.util.Log; import android.util.Pair; import androidx.sqlite.db.SupportSQLiteDatabase; import com.amitshekhar.server.ClientServer; import com.amitshekhar.sqlite.DBFactory; import com.amitshekhar.utils.NetworkUtils; import java.io.File; import java.util.HashMap; /** * Created by amitshekhar on 15/11/16. */ public class DebugDB { private static final String TAG = DebugDB.class.getSimpleName(); private static final int DEFAULT_PORT = 8080; private static ClientServer clientServer; private static String addressLog = "not available"; private DebugDB() { // This class in not publicly instantiable } public static void initialize(Context context, DBFactory dbFactory) { int portNumber; try { portNumber = Integer.valueOf(context.getString(R.string.PORT_NUMBER)); } catch (NumberFormatException ex) { Log.e(TAG, "PORT_NUMBER should be integer", ex); portNumber = DEFAULT_PORT; Log.i(TAG, "Using Default port : " + DEFAULT_PORT); } clientServer = new ClientServer(context, portNumber, dbFactory); clientServer.start(); addressLog = NetworkUtils.getAddressLog(context, portNumber); Log.d(TAG, addressLog); } public static String getAddressLog() { Log.d(TAG, addressLog); return addressLog; } public static void shutDown() { if (clientServer != null) { clientServer.stop(); clientServer = null; } } public static void setCustomDatabaseFiles(HashMap> customDatabaseFiles) { if (clientServer != null) { clientServer.setCustomDatabaseFiles(customDatabaseFiles); } } public static void setInMemoryRoomDatabases(HashMap databases) { if (clientServer != null) { clientServer.setInMemoryRoomDatabases(databases); } } public static boolean isServerRunning() { return clientServer != null && clientServer.isRunning(); } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/model/Response.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.model; import java.util.ArrayList; import java.util.List; /** * Created by amitshekhar on 15/11/16. */ public class Response { public List rows = new ArrayList<>(); public List columns = new ArrayList<>(); public boolean isSuccessful; public String error; public int dbVersion; public Response() { } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/model/RowDataRequest.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.model; /** * Created by amitshekhar on 04/02/17. */ public class RowDataRequest { public String title; public boolean isPrimary; public String dataType; public String value; } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/model/TableDataResponse.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.model; import java.util.List; /** * Created by amitshekhar on 04/02/17. */ public class TableDataResponse { public List tableInfos; public boolean isSuccessful; public List rows; public String errorMessage; public boolean isEditable; public boolean isSelectQuery; public static class TableInfo { public String title; public boolean isPrimary; } public static class ColumnData { public String dataType; public Object value; } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/model/UpdateRowResponse.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.model; /** * Created by amitshekhar on 04/02/17. */ public class UpdateRowResponse { public boolean isSuccessful; } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/server/ClientServer.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.server; /** * Created by amitshekhar on 15/11/16. */ import android.content.Context; import android.util.Log; import android.util.Pair; import androidx.sqlite.db.SupportSQLiteDatabase; import com.amitshekhar.sqlite.DBFactory; import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.HashMap; public class ClientServer implements Runnable { private static final String TAG = "ClientServer"; private final int mPort; private final RequestHandler mRequestHandler; private boolean mIsRunning; private ServerSocket mServerSocket; public ClientServer(Context context, int port, DBFactory dbFactory) { mRequestHandler = new RequestHandler(context, dbFactory); mPort = port; } public void start() { mIsRunning = true; new Thread(this).start(); } public void stop() { try { mIsRunning = false; if (null != mServerSocket) { mServerSocket.close(); mServerSocket = null; } } catch (Exception e) { Log.e(TAG, "Error closing the server socket.", e); } } @Override public void run() { try { mServerSocket = new ServerSocket(mPort); while (mIsRunning) { Socket socket = mServerSocket.accept(); mRequestHandler.handle(socket); socket.close(); } } catch (SocketException e) { // The server was stopped; ignore. } catch (IOException e) { Log.e(TAG, "Web server error.", e); } catch (Exception ignore) { Log.e(TAG, "Exception.", ignore); } } public void setCustomDatabaseFiles(HashMap> customDatabaseFiles) { mRequestHandler.setCustomDatabaseFiles(customDatabaseFiles); } public void setInMemoryRoomDatabases(HashMap databases) { mRequestHandler.setInMemoryRoomDatabases(databases); } public boolean isRunning() { return mIsRunning; } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/server/RequestHandler.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.server; import android.content.Context; import android.content.res.AssetManager; import android.net.Uri; import android.text.TextUtils; import android.util.Pair; import androidx.sqlite.db.SupportSQLiteDatabase; import com.amitshekhar.model.Response; import com.amitshekhar.model.RowDataRequest; import com.amitshekhar.model.TableDataResponse; import com.amitshekhar.model.UpdateRowResponse; import com.amitshekhar.sqlite.DBFactory; import com.amitshekhar.sqlite.InMemoryDebugSQLiteDB; import com.amitshekhar.sqlite.SQLiteDB; import com.amitshekhar.utils.Constants; import com.amitshekhar.utils.DatabaseFileProvider; import com.amitshekhar.utils.DatabaseHelper; import com.amitshekhar.utils.PrefHelper; import com.amitshekhar.utils.Utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; /** * Created by amitshekhar on 06/02/17. */ public class RequestHandler { private final Context mContext; private final Gson mGson; private final AssetManager mAssets; private final DBFactory mDbFactory; private boolean isDbOpened; private SQLiteDB sqLiteDB; private HashMap> mDatabaseFiles; private HashMap> mCustomDatabaseFiles; private String mSelectedDatabase = null; private HashMap mRoomInMemoryDatabases = new HashMap<>(); public RequestHandler(Context context, DBFactory dbFactory) { mContext = context; mAssets = context.getResources().getAssets(); mGson = new GsonBuilder().serializeNulls().create(); mDbFactory = dbFactory; } public void handle(Socket socket) throws IOException { BufferedReader reader = null; PrintStream output = null; try { String route = null; // Read HTTP headers and parse out the route. reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line; while (!TextUtils.isEmpty(line = reader.readLine())) { if (line.startsWith("GET /")) { int start = line.indexOf('/') + 1; int end = line.indexOf(' ', start); route = line.substring(start, end); break; } } // Output stream that we send the response to output = new PrintStream(socket.getOutputStream()); if (route == null || route.isEmpty()) { route = "index.html"; } byte[] bytes; if (route.startsWith("getDbList")) { final String response = getDBListResponse(); bytes = response.getBytes(); } else if (route.startsWith("getAllDataFromTheTable")) { final String response = getAllDataFromTheTableResponse(route); bytes = response.getBytes(); } else if (route.startsWith("getTableList")) { final String response = getTableListResponse(route); bytes = response.getBytes(); } else if (route.startsWith("addTableData")) { final String response = addTableDataAndGetResponse(route); bytes = response.getBytes(); } else if (route.startsWith("updateTableData")) { final String response = updateTableDataAndGetResponse(route); bytes = response.getBytes(); } else if (route.startsWith("deleteTableData")) { final String response = deleteTableDataAndGetResponse(route); bytes = response.getBytes(); } else if (route.startsWith("query")) { final String response = executeQueryAndGetResponse(route); bytes = response.getBytes(); } else if (route.startsWith("deleteDb")) { final String response = deleteSelectedDatabaseAndGetResponse(); bytes = response.getBytes(); } else if (route.startsWith("downloadDb")) { bytes = Utils.getDatabase(mSelectedDatabase, mDatabaseFiles); } else { bytes = Utils.loadContent(route, mAssets); } if (null == bytes) { writeServerError(output); return; } // Send out the content. output.println("HTTP/1.0 200 OK"); output.println("Content-Type: " + Utils.detectMimeType(route)); if (route.startsWith("downloadDb")) { output.println("Content-Disposition: attachment; filename=" + mSelectedDatabase); } else { output.println("Content-Length: " + bytes.length); } output.println(); output.write(bytes); output.flush(); } finally { try { if (null != output) { output.close(); } if (null != reader) { reader.close(); } } catch (Exception e) { e.printStackTrace(); } } } public void setCustomDatabaseFiles(HashMap> customDatabaseFiles) { mCustomDatabaseFiles = customDatabaseFiles; } public void setInMemoryRoomDatabases(HashMap databases) { mRoomInMemoryDatabases = databases; } private void writeServerError(PrintStream output) { output.println("HTTP/1.0 500 Internal Server Error"); output.flush(); } private void openDatabase(String database) { closeDatabase(); if (mRoomInMemoryDatabases.containsKey(database)) { sqLiteDB = new InMemoryDebugSQLiteDB(mRoomInMemoryDatabases.get(database)); } else { File databaseFile = mDatabaseFiles.get(database).first; String password = mDatabaseFiles.get(database).second; sqLiteDB = mDbFactory.create(mContext, databaseFile.getAbsolutePath(), password); } isDbOpened = true; } private void closeDatabase() { if (sqLiteDB != null && sqLiteDB.isOpen()) { sqLiteDB.close(); } sqLiteDB = null; isDbOpened = false; } private String getDBListResponse() { mDatabaseFiles = DatabaseFileProvider.getDatabaseFiles(mContext); if (mCustomDatabaseFiles != null) { mDatabaseFiles.putAll(mCustomDatabaseFiles); } Response response = new Response(); if (mDatabaseFiles != null) { for (HashMap.Entry> entry : mDatabaseFiles.entrySet()) { String[] dbEntry = {entry.getKey(), !entry.getValue().second.equals("") ? "true" : "false", "true"}; response.rows.add(dbEntry); } } if (mRoomInMemoryDatabases != null) { for (HashMap.Entry entry : mRoomInMemoryDatabases.entrySet()) { String[] dbEntry = {entry.getKey(), "false", "false"}; response.rows.add(dbEntry); } } response.rows.add(new String[]{Constants.APP_SHARED_PREFERENCES, "false", "false"}); response.isSuccessful = true; return mGson.toJson(response); } private String getAllDataFromTheTableResponse(String route) { String tableName = null; if (route.contains("?tableName=")) { tableName = route.substring(route.indexOf("=") + 1, route.length()); } TableDataResponse response; if (isDbOpened) { String sql = "SELECT * FROM " + tableName; response = DatabaseHelper.getTableData(sqLiteDB, sql, tableName); } else { response = PrefHelper.getAllPrefData(mContext, tableName); } return mGson.toJson(response); } private String executeQueryAndGetResponse(String route) { String query = null; String data = null; String first; try { if (route.contains("?query=")) { query = route.substring(route.indexOf("=") + 1, route.length()); } try { query = URLDecoder.decode(query, "UTF-8"); } catch (Exception e) { e.printStackTrace(); } if (query != null) { String[] statements = query.split(";"); for (int i = 0; i < statements.length; i++) { String aQuery = statements[i].trim(); first = aQuery.split(" ")[0].toLowerCase(); if (first.equals("select") || first.equals("pragma")) { TableDataResponse response = DatabaseHelper.getTableData(sqLiteDB, aQuery, null); data = mGson.toJson(response); if (!response.isSuccessful) { break; } } else { TableDataResponse response = DatabaseHelper.exec(sqLiteDB, aQuery); data = mGson.toJson(response); if (!response.isSuccessful) { break; } } } } } catch (Exception e) { e.printStackTrace(); } if (data == null) { Response response = new Response(); response.isSuccessful = false; data = mGson.toJson(response); } return data; } private String getTableListResponse(String route) { String database = null; if (route.contains("?database=")) { database = route.substring(route.indexOf("=") + 1, route.length()); } Response response; if (Constants.APP_SHARED_PREFERENCES.equals(database)) { response = PrefHelper.getAllPrefTableName(mContext); closeDatabase(); mSelectedDatabase = Constants.APP_SHARED_PREFERENCES; } else { openDatabase(database); response = DatabaseHelper.getAllTableName(sqLiteDB); mSelectedDatabase = database; } return mGson.toJson(response); } private String addTableDataAndGetResponse(String route) { UpdateRowResponse response; try { Uri uri = Uri.parse(URLDecoder.decode(route, "UTF-8")); String tableName = uri.getQueryParameter("tableName"); String updatedData = uri.getQueryParameter("addData"); List rowDataRequests = mGson.fromJson(updatedData, new TypeToken>() { }.getType()); if (Constants.APP_SHARED_PREFERENCES.equals(mSelectedDatabase)) { response = PrefHelper.addOrUpdateRow(mContext, tableName, rowDataRequests); } else { response = DatabaseHelper.addRow(sqLiteDB, tableName, rowDataRequests); } return mGson.toJson(response); } catch (Exception e) { e.printStackTrace(); response = new UpdateRowResponse(); response.isSuccessful = false; return mGson.toJson(response); } } private String updateTableDataAndGetResponse(String route) { UpdateRowResponse response; try { Uri uri = Uri.parse(URLDecoder.decode(route, "UTF-8")); String tableName = uri.getQueryParameter("tableName"); String updatedData = uri.getQueryParameter("updatedData"); List rowDataRequests = mGson.fromJson(updatedData, new TypeToken>() { }.getType()); if (Constants.APP_SHARED_PREFERENCES.equals(mSelectedDatabase)) { response = PrefHelper.addOrUpdateRow(mContext, tableName, rowDataRequests); } else { response = DatabaseHelper.updateRow(sqLiteDB, tableName, rowDataRequests); } return mGson.toJson(response); } catch (Exception e) { e.printStackTrace(); response = new UpdateRowResponse(); response.isSuccessful = false; return mGson.toJson(response); } } private String deleteTableDataAndGetResponse(String route) { UpdateRowResponse response; try { Uri uri = Uri.parse(URLDecoder.decode(route, "UTF-8")); String tableName = uri.getQueryParameter("tableName"); String updatedData = uri.getQueryParameter("deleteData"); List rowDataRequests = mGson.fromJson(updatedData, new TypeToken>() { }.getType()); if (Constants.APP_SHARED_PREFERENCES.equals(mSelectedDatabase)) { response = PrefHelper.deleteRow(mContext, tableName, rowDataRequests); } else { response = DatabaseHelper.deleteRow(sqLiteDB, tableName, rowDataRequests); } return mGson.toJson(response); } catch (Exception e) { e.printStackTrace(); response = new UpdateRowResponse(); response.isSuccessful = false; return mGson.toJson(response); } } private String deleteSelectedDatabaseAndGetResponse() { UpdateRowResponse response = new UpdateRowResponse(); if (mSelectedDatabase == null || !mDatabaseFiles.containsKey(mSelectedDatabase)) { response.isSuccessful = false; return mGson.toJson(response); } try { closeDatabase(); File dbFile = mDatabaseFiles.get(mSelectedDatabase).first; response.isSuccessful = dbFile.delete(); if (response.isSuccessful) { mDatabaseFiles.remove(mSelectedDatabase); mCustomDatabaseFiles.remove(mSelectedDatabase); } return mGson.toJson(response); } catch (Exception e) { e.printStackTrace(); response.isSuccessful = false; return mGson.toJson(response); } } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/sqlite/DBFactory.java ================================================ package com.amitshekhar.sqlite; import android.content.Context; public interface DBFactory { SQLiteDB create(Context context, String path, String password); } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/sqlite/InMemoryDebugSQLiteDB.java ================================================ package com.amitshekhar.sqlite; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import androidx.sqlite.db.SupportSQLiteDatabase; /** * Created by anandgaurav on 12/02/18. */ public class InMemoryDebugSQLiteDB implements SQLiteDB { private final SupportSQLiteDatabase database; public InMemoryDebugSQLiteDB(SupportSQLiteDatabase database) { this.database = database; } @Override public int delete(String table, String whereClause, String[] whereArgs) { return database.delete(table, whereClause, whereArgs); } @Override public boolean isOpen() { return database.isOpen(); } @Override public void close() { // no ops } @Override public Cursor rawQuery(String sql, String[] selectionArgs) { return database.query(sql, selectionArgs); } @Override public void execSQL(String sql) throws SQLException { database.execSQL(sql); } @Override public long insert(String table, String nullColumnHack, ContentValues values) { return database.insert(table, 0, values); } @Override public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { return database.update(table, 0, values, whereClause, whereArgs); } @Override public int getVersion() { return database.getVersion(); } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/sqlite/SQLiteDB.java ================================================ package com.amitshekhar.sqlite; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; /** * Created by anandgaurav on 12/02/18. */ public interface SQLiteDB { int delete(String table, String whereClause, String[] whereArgs); boolean isOpen(); void close(); Cursor rawQuery(String sql, String[] selectionArgs); void execSQL(String sql) throws SQLException; long insert(String table, String nullColumnHack, ContentValues values); int update(String table, ContentValues values, String whereClause, String[] whereArgs); int getVersion(); } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/Constants.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; /** * Created by amitshekhar on 16/11/16. */ public final class Constants { private Constants() { // This class in not publicly instantiable } public static final String APP_SHARED_PREFERENCES = "APP_SHARED_PREFERENCES"; public static final String PK = "pk"; public static final String NAME = "name"; public static final String NULL = "null"; } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/ConverterUtils.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; import java.io.UnsupportedEncodingException; /** * Created by amitshekhar on 06/02/17. */ public class ConverterUtils { private static final int MAX_BLOB_LENGTH = 512; private static final String UNKNOWN_BLOB_LABEL = "{blob}"; private ConverterUtils() { // This class in not publicly instantiable } public static String blobToString(byte[] blob) { if (blob.length <= MAX_BLOB_LENGTH) { if (fastIsAscii(blob)) { try { return new String(blob, "US-ASCII"); } catch (UnsupportedEncodingException ignored) { } } } return UNKNOWN_BLOB_LABEL; } public static boolean fastIsAscii(byte[] blob) { for (byte b : blob) { if ((b & ~0x7f) != 0) { return false; } } return true; } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/DataType.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; /** * Created by amitshekhar on 04/02/17. */ public class DataType { private DataType() { // This class in not publicly instantiable } public static final String BOOLEAN = "boolean"; public static final String INTEGER = "integer"; public static final String REAL = "real"; public static final String TEXT = "text"; public static final String LONG = "long"; public static final String FLOAT = "float"; public static final String STRING_SET = "string_set"; } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseFileProvider.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; import android.content.Context; import android.util.Pair; import java.io.File; import java.text.MessageFormat; import java.util.HashMap; /** * Created by amitshekhar on 06/02/17. */ public class DatabaseFileProvider { private final static String DB_PASSWORD_RESOURCE = "DB_PASSWORD_{0}"; private DatabaseFileProvider() { // This class in not publicly instantiable } public static HashMap> getDatabaseFiles(Context context) { HashMap> databaseFiles = new HashMap<>(); try { for (String databaseName : context.databaseList()) { String password = getDbPasswordFromStringResources(context, databaseName); databaseFiles.put(databaseName, new Pair<>(context.getDatabasePath(databaseName), password)); } } catch (Exception e) { e.printStackTrace(); } return databaseFiles; } private static String getDbPasswordFromStringResources(Context context, String name) { String nameWithoutExt = name; if (nameWithoutExt.endsWith(".db")) { nameWithoutExt = nameWithoutExt.substring(0, nameWithoutExt.lastIndexOf('.')); } String resourceName = MessageFormat.format(DB_PASSWORD_RESOURCE, nameWithoutExt.toUpperCase()); String password = ""; int resourceId = context.getResources().getIdentifier(resourceName, "string", context.getPackageName()); if (resourceId != 0) { password = context.getString(resourceId); } return password; } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseHelper.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; import android.content.ContentValues; import android.database.Cursor; import android.text.TextUtils; import com.amitshekhar.model.Response; import com.amitshekhar.model.RowDataRequest; import com.amitshekhar.model.TableDataResponse; import com.amitshekhar.model.UpdateRowResponse; import com.amitshekhar.sqlite.SQLiteDB; import java.util.ArrayList; import java.util.HashSet; import java.util.List; /** * Created by amitshekhar on 06/02/17. */ public class DatabaseHelper { private DatabaseHelper() { // This class in not publicly instantiable } public static Response getAllTableName(SQLiteDB database) { Response response = new Response(); Cursor c = database.rawQuery("SELECT name FROM sqlite_master WHERE type='table' OR type='view' ORDER BY name COLLATE NOCASE", null); if (c.moveToFirst()) { while (!c.isAfterLast()) { response.rows.add(c.getString(0)); c.moveToNext(); } } c.close(); response.isSuccessful = true; try { response.dbVersion = database.getVersion(); } catch (Exception ignore) { } return response; } public static TableDataResponse getTableData(SQLiteDB db, String selectQuery, String tableName) { TableDataResponse tableData = new TableDataResponse(); tableData.isSelectQuery = true; if (tableName == null) { tableName = getTableName(selectQuery); } final String quotedTableName = getQuotedTableName(tableName); if (tableName != null) { final String pragmaQuery = "PRAGMA table_info(" + quotedTableName + ")"; tableData.tableInfos = getTableInfo(db, pragmaQuery); } Cursor cursor = null; boolean isView = false; try { cursor = db.rawQuery("SELECT type FROM sqlite_master WHERE name=?", new String[]{quotedTableName}); if (cursor.moveToFirst()) { isView = "view".equalsIgnoreCase(cursor.getString(0)); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } tableData.isEditable = tableName != null && tableData.tableInfos != null && !isView; if (!TextUtils.isEmpty(tableName)) { selectQuery = selectQuery.replace(tableName, quotedTableName); } try { cursor = db.rawQuery(selectQuery, null); } catch (Exception e) { e.printStackTrace(); tableData.isSuccessful = false; tableData.errorMessage = e.getMessage(); return tableData; } if (cursor != null) { cursor.moveToFirst(); // setting tableInfo when tableName is not known and making // it non-editable also by making isPrimary true for all if (tableData.tableInfos == null) { tableData.tableInfos = new ArrayList<>(); for (int i = 0; i < cursor.getColumnCount(); i++) { TableDataResponse.TableInfo tableInfo = new TableDataResponse.TableInfo(); tableInfo.title = cursor.getColumnName(i); tableInfo.isPrimary = true; tableData.tableInfos.add(tableInfo); } } tableData.isSuccessful = true; tableData.rows = new ArrayList<>(); String[] columnNames = cursor.getColumnNames(); List tableInfoListModified = new ArrayList<>(); for (String columnName : columnNames) { for (TableDataResponse.TableInfo tableInfo : tableData.tableInfos) { if (columnName.equals(tableInfo.title)) { tableInfoListModified.add(tableInfo); break; } } } if (tableData.tableInfos.size() != tableInfoListModified.size()) { tableData.tableInfos = tableInfoListModified; tableData.isEditable = false; } if (cursor.getCount() > 0) { do { List row = new ArrayList<>(); for (int i = 0; i < cursor.getColumnCount(); i++) { TableDataResponse.ColumnData columnData = new TableDataResponse.ColumnData(); switch (cursor.getType(i)) { case Cursor.FIELD_TYPE_BLOB: columnData.dataType = DataType.TEXT; columnData.value = ConverterUtils.blobToString(cursor.getBlob(i)); break; case Cursor.FIELD_TYPE_FLOAT: columnData.dataType = DataType.REAL; columnData.value = cursor.getDouble(i); break; case Cursor.FIELD_TYPE_INTEGER: columnData.dataType = DataType.INTEGER; columnData.value = cursor.getLong(i); break; case Cursor.FIELD_TYPE_STRING: columnData.dataType = DataType.TEXT; columnData.value = cursor.getString(i); break; default: columnData.dataType = DataType.TEXT; columnData.value = cursor.getString(i); } row.add(columnData); } tableData.rows.add(row); } while (cursor.moveToNext()); } cursor.close(); return tableData; } else { tableData.isSuccessful = false; tableData.errorMessage = "Cursor is null"; return tableData; } } private static String getQuotedTableName(String tableName) { return String.format("[%s]", tableName); } private static List getTableInfo(SQLiteDB db, String pragmaQuery) { Cursor cursor; try { cursor = db.rawQuery(pragmaQuery, null); } catch (Exception e) { e.printStackTrace(); return null; } if (cursor != null) { List tableInfoList = new ArrayList<>(); cursor.moveToFirst(); if (cursor.getCount() > 0) { do { TableDataResponse.TableInfo tableInfo = new TableDataResponse.TableInfo(); for (int i = 0; i < cursor.getColumnCount(); i++) { final String columnName = cursor.getColumnName(i); switch (columnName) { case Constants.PK: tableInfo.isPrimary = cursor.getInt(i) == 1; break; case Constants.NAME: tableInfo.title = cursor.getString(i); break; default: } } tableInfoList.add(tableInfo); } while (cursor.moveToNext()); } cursor.close(); return tableInfoList; } return null; } public static UpdateRowResponse addRow(SQLiteDB db, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); if (rowDataRequests == null || tableName == null) { updateRowResponse.isSuccessful = false; return updateRowResponse; } tableName = getQuotedTableName(tableName); ContentValues contentValues = new ContentValues(); for (RowDataRequest rowDataRequest : rowDataRequests) { if (Constants.NULL.equals(rowDataRequest.value)) { rowDataRequest.value = null; } switch (rowDataRequest.dataType) { case DataType.INTEGER: contentValues.put(rowDataRequest.title, Long.valueOf(rowDataRequest.value)); break; case DataType.REAL: contentValues.put(rowDataRequest.title, Double.valueOf(rowDataRequest.value)); break; case DataType.TEXT: contentValues.put(rowDataRequest.title, rowDataRequest.value); break; default: contentValues.put(rowDataRequest.title, rowDataRequest.value); break; } } long result = db.insert(tableName, null, contentValues); updateRowResponse.isSuccessful = result > 0; return updateRowResponse; } public static UpdateRowResponse updateRow(SQLiteDB db, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); if (rowDataRequests == null || tableName == null) { updateRowResponse.isSuccessful = false; return updateRowResponse; } tableName = getQuotedTableName(tableName); ContentValues contentValues = new ContentValues(); String whereClause = null; List whereArgsList = new ArrayList<>(); for (RowDataRequest rowDataRequest : rowDataRequests) { if (Constants.NULL.equals(rowDataRequest.value)) { rowDataRequest.value = null; } if (rowDataRequest.isPrimary) { if (whereClause == null) { whereClause = rowDataRequest.title + "=? "; } else { whereClause = whereClause + "and " + rowDataRequest.title + "=? "; } whereArgsList.add(rowDataRequest.value); } else { switch (rowDataRequest.dataType) { case DataType.INTEGER: contentValues.put(rowDataRequest.title, Long.valueOf(rowDataRequest.value)); break; case DataType.REAL: contentValues.put(rowDataRequest.title, Double.valueOf(rowDataRequest.value)); break; case DataType.TEXT: contentValues.put(rowDataRequest.title, rowDataRequest.value); break; default: } } } String[] whereArgs = new String[whereArgsList.size()]; for (int i = 0; i < whereArgsList.size(); i++) { whereArgs[i] = whereArgsList.get(i); } db.update(tableName, contentValues, whereClause, whereArgs); updateRowResponse.isSuccessful = true; return updateRowResponse; } public static UpdateRowResponse deleteRow(SQLiteDB db, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); if (rowDataRequests == null || tableName == null) { updateRowResponse.isSuccessful = false; return updateRowResponse; } tableName = getQuotedTableName(tableName); String whereClause = null; List whereArgsList = new ArrayList<>(); for (RowDataRequest rowDataRequest : rowDataRequests) { if (Constants.NULL.equals(rowDataRequest.value)) { rowDataRequest.value = null; } if (rowDataRequest.isPrimary) { if (whereClause == null) { whereClause = rowDataRequest.title + "=? "; } else { whereClause = whereClause + "and " + rowDataRequest.title + "=? "; } whereArgsList.add(rowDataRequest.value); } } if (whereArgsList.size() == 0) { updateRowResponse.isSuccessful = true; return updateRowResponse; } String[] whereArgs = new String[whereArgsList.size()]; for (int i = 0; i < whereArgsList.size(); i++) { whereArgs[i] = whereArgsList.get(i); } db.delete(tableName, whereClause, whereArgs); updateRowResponse.isSuccessful = true; return updateRowResponse; } public static TableDataResponse exec(SQLiteDB database, String sql) { TableDataResponse tableDataResponse = new TableDataResponse(); tableDataResponse.isSelectQuery = false; try { String tableName = getTableName(sql); if (!TextUtils.isEmpty(tableName)) { String quotedTableName = getQuotedTableName(tableName); sql = sql.replace(tableName, quotedTableName); } database.execSQL(sql); } catch (Exception e) { e.printStackTrace(); tableDataResponse.isSuccessful = false; tableDataResponse.errorMessage = e.getMessage(); return tableDataResponse; } tableDataResponse.isSuccessful = true; return tableDataResponse; } private static String getTableName(String selectQuery) { // TODO: Handle JOIN Query TableNameParser tableNameParser = new TableNameParser(selectQuery); HashSet tableNames = (HashSet) tableNameParser.tables(); for (String tableName : tableNames) { if (!TextUtils.isEmpty(tableName)) { return tableName; } } return null; } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/NetworkUtils.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; import android.annotation.SuppressLint; import android.content.Context; import android.net.wifi.WifiManager; /** * Created by amitshekhar on 15/11/16. */ public final class NetworkUtils { private NetworkUtils() { // This class in not publicly instantiable } public static String getAddressLog(Context context, int port) { WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); int ipAddress = wifiManager.getConnectionInfo().getIpAddress(); @SuppressLint("DefaultLocale") final String formattedIpAddress = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff), (ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff)); return "Open http://" + formattedIpAddress + ":" + port + " in your browser"; } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/PrefHelper.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; import android.content.Context; import android.content.SharedPreferences; import com.amitshekhar.model.Response; import com.amitshekhar.model.RowDataRequest; import com.amitshekhar.model.TableDataResponse; import com.amitshekhar.model.UpdateRowResponse; import org.json.JSONArray; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Created by amitshekhar on 06/02/17. */ public class PrefHelper { private static final String PREFS_SUFFIX = ".xml"; private PrefHelper() { // This class in not publicly instantiable } public static List getSharedPreferenceTags(Context context) { ArrayList tags = new ArrayList<>(); String rootPath = context.getApplicationInfo().dataDir + "/shared_prefs"; File root = new File(rootPath); if (root.exists()) { for (File file : root.listFiles()) { String fileName = file.getName(); if (fileName.endsWith(PREFS_SUFFIX)) { tags.add(fileName.substring(0, fileName.length() - PREFS_SUFFIX.length())); } } } Collections.sort(tags); return tags; } public static Response getAllPrefTableName(Context context) { Response response = new Response(); List prefTags = getSharedPreferenceTags(context); for (String tag : prefTags) { response.rows.add(tag); } response.isSuccessful = true; return response; } public static TableDataResponse getAllPrefData(Context context, String tag) { TableDataResponse response = new TableDataResponse(); response.isEditable = true; response.isSuccessful = true; response.isSelectQuery = true; TableDataResponse.TableInfo keyInfo = new TableDataResponse.TableInfo(); keyInfo.isPrimary = true; keyInfo.title = "Key"; TableDataResponse.TableInfo valueInfo = new TableDataResponse.TableInfo(); valueInfo.isPrimary = false; valueInfo.title = "Value"; response.tableInfos = new ArrayList<>(); response.tableInfos.add(keyInfo); response.tableInfos.add(valueInfo); response.rows = new ArrayList<>(); SharedPreferences preferences = context.getSharedPreferences(tag, Context.MODE_PRIVATE); Map allEntries = preferences.getAll(); for (Map.Entry entry : allEntries.entrySet()) { List row = new ArrayList<>(); TableDataResponse.ColumnData keyColumnData = new TableDataResponse.ColumnData(); keyColumnData.dataType = DataType.TEXT; keyColumnData.value = entry.getKey(); row.add(keyColumnData); TableDataResponse.ColumnData valueColumnData = new TableDataResponse.ColumnData(); valueColumnData.value = entry.getValue().toString(); if (entry.getValue() != null) { if (entry.getValue() instanceof String) { valueColumnData.dataType = DataType.TEXT; } else if (entry.getValue() instanceof Integer) { valueColumnData.dataType = DataType.INTEGER; } else if (entry.getValue() instanceof Long) { valueColumnData.dataType = DataType.LONG; } else if (entry.getValue() instanceof Float) { valueColumnData.dataType = DataType.FLOAT; } else if (entry.getValue() instanceof Boolean) { valueColumnData.dataType = DataType.BOOLEAN; } else if (entry.getValue() instanceof Set) { valueColumnData.dataType = DataType.STRING_SET; } } else { valueColumnData.dataType = DataType.TEXT; } row.add(valueColumnData); response.rows.add(row); } return response; } public static UpdateRowResponse addOrUpdateRow(Context context, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); if (tableName == null) { return updateRowResponse; } RowDataRequest rowDataKey = rowDataRequests.get(0); RowDataRequest rowDataValue = rowDataRequests.get(1); String key = rowDataKey.value; String value = rowDataValue.value; String dataType = rowDataValue.dataType; if (Constants.NULL.equals(value)) { value = null; } SharedPreferences preferences = context.getSharedPreferences(tableName, Context.MODE_PRIVATE); try { switch (dataType) { case DataType.TEXT: preferences.edit().putString(key, value).apply(); updateRowResponse.isSuccessful = true; break; case DataType.INTEGER: preferences.edit().putInt(key, Integer.valueOf(value)).apply(); updateRowResponse.isSuccessful = true; break; case DataType.LONG: preferences.edit().putLong(key, Long.valueOf(value)).apply(); updateRowResponse.isSuccessful = true; break; case DataType.FLOAT: preferences.edit().putFloat(key, Float.valueOf(value)).apply(); updateRowResponse.isSuccessful = true; break; case DataType.BOOLEAN: preferences.edit().putBoolean(key, Boolean.valueOf(value)).apply(); updateRowResponse.isSuccessful = true; break; case DataType.STRING_SET: JSONArray jsonArray = new JSONArray(value); Set stringSet = new HashSet<>(); for (int i = 0; i < jsonArray.length(); i++) { stringSet.add(jsonArray.getString(i)); } preferences.edit().putStringSet(key, stringSet).apply(); updateRowResponse.isSuccessful = true; break; default: preferences.edit().putString(key, value).apply(); updateRowResponse.isSuccessful = true; } } catch (Exception e) { e.printStackTrace(); } return updateRowResponse; } public static UpdateRowResponse deleteRow(Context context, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); if (tableName == null) { return updateRowResponse; } RowDataRequest rowDataKey = rowDataRequests.get(0); String key = rowDataKey.value; SharedPreferences preferences = context.getSharedPreferences(tableName, Context.MODE_PRIVATE); try { preferences.edit() .remove(key).apply(); updateRowResponse.isSuccessful = true; } catch (Exception ex) { updateRowResponse.isSuccessful = false; } return updateRowResponse; } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/TableNameParser.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Ultra light, Ultra fast parser to extract table name out SQLs, supports oracle dialect SQLs as well. * * @author Nadeem Mohammad *

* Ref : https://github.com/mnadeem/sql-table-name-parser */ public final class TableNameParser { private static final int NO_INDEX = -1; private static final String SPACE = " "; private static final String REGEX_SPACE = "\\s+"; private static final String TOKEN_ORACLE_HINT_START = "/*+"; private static final String TOKEN_ORACLE_HINT_END = "*/"; private static final String TOKEN_SINGLE_LINE_COMMENT = "--"; private static final String TOKEN_SEMI_COLON = ";"; private static final String TOKEN_PARAN_START = "("; private static final String TOKEN_COMMA = ","; private static final String TOKEN_SET = "set"; private static final String TOKEN_OF = "of"; private static final String TOKEN_DUAL = "dual"; private static final String TOKEN_DELETE = "delete"; private static final String TOKEN_CREATE = "create"; private static final String TOKEN_INDEX = "index"; private static final String TOKEN_ASTERICK = "*"; private static final String KEYWORD_JOIN = "join"; private static final String KEYWORD_INTO = "into"; private static final String KEYWORD_TABLE = "table"; private static final String KEYWORD_FROM = "from"; private static final String KEYWORD_USING = "using"; private static final String KEYWORD_UPDATE = "update"; private static final List concerned = Arrays.asList(KEYWORD_TABLE, KEYWORD_INTO, KEYWORD_JOIN, KEYWORD_USING, KEYWORD_UPDATE); private static final List ignored = Arrays.asList(TOKEN_PARAN_START, TOKEN_SET, TOKEN_OF, TOKEN_DUAL); private static String TOKEN_NEWLINE = "\\r\\n|\\r|\\n|\\n\\r"; private Map tables = new HashMap(); /** * Extracts table names out of SQL * * @param sql */ public TableNameParser(final String sql) { String nocomments = removeComments(sql); String normalized = normalized(nocomments); String cleansed = clean(normalized); String[] tokens = cleansed.split(REGEX_SPACE); int index = 0; String firstToken = tokens[index]; if (isOracleSpecialDelete(firstToken, tokens, index)) { handleSpecialOracleSpecialDelete(firstToken, tokens, index); } else if (isCreateIndex(firstToken, tokens, index)) { handleCreateIndex(firstToken, tokens, index); } else { while (moreTokens(tokens, index)) { String currentToken = tokens[index++]; if (isFromToken(currentToken)) { processFromToken(tokens, index); } else if (shouldProcess(currentToken)) { String nextToken = tokens[index++]; considerInclusion(nextToken); if (moreTokens(tokens, index)) { nextToken = tokens[index++]; } } } } } private String removeComments(final String sql) { StringBuilder sb = new StringBuilder(sql); int nextCommentPosition = sb.indexOf(TOKEN_SINGLE_LINE_COMMENT); while (nextCommentPosition > -1) { int end = indexOfRegex(TOKEN_NEWLINE, sb.substring(nextCommentPosition)); if (end == -1) { return sb.substring(0, nextCommentPosition); } else { sb.replace(nextCommentPosition, end + nextCommentPosition, ""); } nextCommentPosition = sb.indexOf(TOKEN_SINGLE_LINE_COMMENT); } return sb.toString(); } private int indexOfRegex(String regex, String string) { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(string); return matcher.find() ? matcher.start() : -1; } private String normalized(final String sql) { String normalized = sql.trim().replaceAll(TOKEN_NEWLINE, SPACE).replaceAll(TOKEN_COMMA, " , ") .replaceAll("\\(", " ( ").replaceAll("\\)", " ) "); if (normalized.endsWith(TOKEN_SEMI_COLON)) { normalized = normalized.substring(0, normalized.length() - 1); } return normalized; } private String clean(final String normalized) { int start = normalized.indexOf(TOKEN_ORACLE_HINT_START); int end = NO_INDEX; if (start != NO_INDEX) { end = normalized.indexOf(TOKEN_ORACLE_HINT_END); if (end != NO_INDEX) { String firstHalf = normalized.substring(0, start); String secondHalf = normalized.substring(end + 2, normalized.length()); return firstHalf.trim() + SPACE + secondHalf.trim(); } } return normalized; } private boolean isOracleSpecialDelete(final String currentToken, final String[] tokens, int index) { index++;// Point to next token if (TOKEN_DELETE.equals(currentToken)) { if (moreTokens(tokens, index)) { String nextToken = tokens[index++]; if (!KEYWORD_FROM.equals(nextToken) && !TOKEN_ASTERICK.equals(nextToken)) { return true; } } } return false; } private void handleSpecialOracleSpecialDelete(final String currentToken, final String[] tokens, int index) { String tableName = tokens[index + 1]; considerInclusion(tableName); } private boolean isCreateIndex(String currentToken, String[] tokens, int index) { index++; // Point to next token if (TOKEN_CREATE.equals(currentToken.toLowerCase()) && hasIthToken(tokens, index, 3)) { String nextToken = tokens[index++]; if (TOKEN_INDEX.equals(nextToken.toLowerCase())) { return true; } } return false; } private void handleCreateIndex(String currentToken, String[] tokens, int index) { String tableName = tokens[index + 4]; considerInclusion(tableName); } private boolean hasIthToken(String[] tokens, int currentIndex, int tokenNumber) { if (moreTokens(tokens, currentIndex) && tokens.length > currentIndex + tokenNumber) { return true; } return false; } private boolean shouldProcess(final String currentToken) { return concerned.contains(currentToken.toLowerCase()); } private boolean isFromToken(final String currentToken) { return KEYWORD_FROM.equals(currentToken.toLowerCase()); } private void processFromToken(final String[] tokens, int index) { String currentToken = tokens[index++]; considerInclusion(currentToken); String nextToken = null; if (moreTokens(tokens, index)) { nextToken = tokens[index++]; } if (shouldProcessMultipleTables(nextToken)) { processNonAliasedMultiTables(tokens, index, nextToken); } else { processAliasedMultiTables(tokens, index, currentToken); } } private void processNonAliasedMultiTables(final String[] tokens, int index, String nextToken) { while (nextToken.equals(TOKEN_COMMA)) { String currentToken = tokens[index++]; considerInclusion(currentToken); if (moreTokens(tokens, index)) { nextToken = tokens[index++]; } else { break; } } } private void processAliasedMultiTables(final String[] tokens, int index, String currentToken) { String nextNextToken = null; if (moreTokens(tokens, index)) { nextNextToken = tokens[index++]; } if (shouldProcessMultipleTables(nextNextToken)) { while (moreTokens(tokens, index) && nextNextToken.equals(TOKEN_COMMA)) { if (moreTokens(tokens, index)) { currentToken = tokens[index++]; } if (moreTokens(tokens, index)) { index++; } if (moreTokens(tokens, index)) { nextNextToken = tokens[index++]; } considerInclusion(currentToken); } } } private boolean shouldProcessMultipleTables(final String nextToken) { return nextToken != null && nextToken.equals(TOKEN_COMMA); } private boolean moreTokens(final String[] tokens, int index) { return index < tokens.length; } private void considerInclusion(final String token) { if (!ignored.contains(token.toLowerCase()) && !this.tables.containsKey(token.toLowerCase())) { this.tables.put(token.toLowerCase(), token); } } /** * @return table names extracted out of sql */ public Collection tables() { return new HashSet(this.tables.values()); } } ================================================ FILE: debug-db-base/src/main/java/com/amitshekhar/utils/Utils.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar.utils; import android.content.res.AssetManager; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; /** * Created by amitshekhar on 06/02/17. */ public class Utils { private static final String TAG = "Utils"; private Utils() { // This class in not publicly instantiable } public static String detectMimeType(String fileName) { if (TextUtils.isEmpty(fileName)) { return null; } else if (fileName.endsWith(".html")) { return "text/html"; } else if (fileName.endsWith(".js")) { return "application/javascript"; } else if (fileName.endsWith(".css")) { return "text/css"; } else { return "application/octet-stream"; } } public static byte[] loadContent(String fileName, AssetManager assetManager) throws IOException { InputStream input = null; try { ByteArrayOutputStream output = new ByteArrayOutputStream(); input = assetManager.open(fileName); byte[] buffer = new byte[1024]; int size; while (-1 != (size = input.read(buffer))) { output.write(buffer, 0, size); } output.flush(); return output.toByteArray(); } catch (FileNotFoundException e) { return null; } finally { try { if (null != input) { input.close(); } } catch (Exception e) { e.printStackTrace(); } } } public static byte[] getDatabase(String selectedDatabase, HashMap> databaseFiles) { if (TextUtils.isEmpty(selectedDatabase) || !databaseFiles.containsKey(selectedDatabase)) { return null; } byte[] byteArray = new byte[0]; try { File file = databaseFiles.get(selectedDatabase).first; byteArray = null; try { InputStream inputStream = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[(int) file.length()]; int bytesRead; while ((bytesRead = inputStream.read(b)) != -1) { bos.write(b, 0, bytesRead); } byteArray = bos.toByteArray(); } catch (IOException e) { Log.e(TAG, "getDatabase: ", e); } } catch (Exception e) { e.printStackTrace(); } return byteArray; } } ================================================ FILE: debug-db-base/src/main/res/values/strings.xml ================================================ Debug-DB ================================================ FILE: debug-db-base/src/test/java/com/amitshekhar/ExampleUnitTest.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.amitshekhar; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: debug-db-encrypt/.gitignore ================================================ /build ================================================ FILE: debug-db-encrypt/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdk rootProject.ext.compileSdk defaultConfig { minSdk rootProject.ext.minSdk targetSdk rootProject.ext.targetSdk versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { api project(':debug-db-base') implementation 'net.zetetic:android-database-sqlcipher:3.5.9' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } ================================================ FILE: debug-db-encrypt/gradle.properties ================================================ ARTIFACT_ID=debug-db-enncrypt ================================================ FILE: debug-db-encrypt/proguard-rules.pro ================================================ # 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 ================================================ FILE: debug-db-encrypt/src/androidTest/java/com/amitshekhar/debug/encrypt/ExampleInstrumentedTest.java ================================================ package com.amitshekhar.debug.encrypt; 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("com.amitshekhar.debug.encrypt.test", appContext.getPackageName()); } } ================================================ FILE: debug-db-encrypt/src/main/AndroidManifest.xml ================================================ ================================================ FILE: debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/DebugDBEncryptInitProvider.java ================================================ package com.amitshekhar.debug.encrypt; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; import com.amitshekhar.DebugDB; import com.amitshekhar.debug.encrypt.sqlite.DebugDBEncryptFactory; public class DebugDBEncryptInitProvider extends ContentProvider { public DebugDBEncryptInitProvider() { } @Override public boolean onCreate() { DebugDB.initialize(getContext(), new DebugDBEncryptFactory()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public void attachInfo(Context context, ProviderInfo providerInfo) { if (providerInfo == null) { throw new NullPointerException("DebugDBEncryptInitProvider ProviderInfo cannot be null."); } // So if the authorities equal the library internal ones, the developer forgot to set his applicationId if ("com.amitshekhar.debug.encrypt.DebugDBEncryptInitProvider".equals(providerInfo.authority)) { throw new IllegalStateException("Incorrect provider authority in manifest. Most likely due to a " + "missing applicationId variable in application\'s build.gradle."); } super.attachInfo(context, providerInfo); } } ================================================ FILE: debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugDBEncryptFactory.java ================================================ package com.amitshekhar.debug.encrypt.sqlite; import android.content.Context; import com.amitshekhar.sqlite.DBFactory; import com.amitshekhar.sqlite.SQLiteDB; import net.sqlcipher.database.SQLiteDatabase; public class DebugDBEncryptFactory implements DBFactory { @Override public SQLiteDB create(Context context, String path, String password) { SQLiteDatabase.loadLibs(context); return new DebugEncryptSQLiteDB(SQLiteDatabase.openOrCreateDatabase(path, password, null)); } } ================================================ FILE: debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugEncryptSQLiteDB.java ================================================ package com.amitshekhar.debug.encrypt.sqlite; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import com.amitshekhar.sqlite.SQLiteDB; import net.sqlcipher.database.SQLiteDatabase; /** * Created by anandgaurav on 12/02/18. */ public class DebugEncryptSQLiteDB implements SQLiteDB { private final SQLiteDatabase database; public DebugEncryptSQLiteDB(SQLiteDatabase database) { this.database = database; } @Override public int delete(String table, String whereClause, String[] whereArgs) { return database.delete(table, whereClause, whereArgs); } @Override public boolean isOpen() { return database.isOpen(); } @Override public void close() { database.close(); } @Override public Cursor rawQuery(String sql, String[] selectionArgs) { return database.rawQuery(sql, selectionArgs); } @Override public void execSQL(String sql) throws SQLException { database.execSQL(sql); } @Override public long insert(String table, String nullColumnHack, ContentValues values) { return database.insert(table, nullColumnHack, values); } @Override public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { return database.update(table, values, whereClause, whereArgs); } @Override public int getVersion() { return database.getVersion(); } } ================================================ FILE: debug-db-encrypt/src/main/res/values/strings.xml ================================================ debug-db-encrypt ================================================ FILE: debug-db-encrypt/src/test/java/com/amitshekhar/debug/encrypt/ExampleUnitTest.java ================================================ package com.amitshekhar.debug.encrypt; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() { assertEquals(4, 2 + 2); } } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Thu Apr 07 10:15:47 CST 2022 distributionBase=GRADLE_USER_HOME distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME ================================================ FILE: gradle.properties ================================================ # # /* # * Copyright (C) 2019 Amit Shekhar # * Copyright (C) 2011 Android Open Source Project # * # * Licensed under the Apache License, Version 2.0 (the "License"); # * you may not use this file except in compliance with the License. # * You may obtain a copy of the License at # * # * http://www.apache.org/licenses/LICENSE-2.0 # * # * Unless required by applicable law or agreed to in writing, software # * distributed under the License is distributed on an "AS IS" BASIS, # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # * See the License for the specific language governing permissions and # * limitations under the License. # */ # # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true android.useAndroidX=true android.enableJetifier=true signing.keyId=xxxxxx signing.password=xxxxxx signing.secretKeyRingFile=xxxxxx.gpg ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: sample-app/build.gradle ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ apply plugin: 'com.android.application' android { compileSdk rootProject.ext.compileSdk defaultConfig { minSdk rootProject.ext.minSdk applicationId "com.sample" targetSdk rootProject.ext.targetSdk versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { debug { resValue("string", "PORT_NUMBER", "8080") } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { debugImplementation project(':debug-db') implementation 'androidx.appcompat:appcompat:1.4.2' implementation "androidx.room:room-runtime:2.5.0" annotationProcessor "androidx.room:room-compiler:2.5.0" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } ================================================ FILE: sample-app/proguard-rules.pro ================================================ # 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 ================================================ FILE: sample-app/src/androidTest/java/com/sample/ExampleInstrumentedTest.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.sample; 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.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.sample", appContext.getPackageName()); } } ================================================ FILE: sample-app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample-app/src/main/java/com/sample/MainActivity.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.sample; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.view.View; import androidx.appcompat.app.AppCompatActivity; import com.sample.database.CarDBHelper; import com.sample.database.ContactDBHelper; import com.sample.database.ExtTestDBHelper; import com.sample.database.room.User; import com.sample.database.room.UserDBHelper; import com.sample.utils.Utils; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class MainActivity extends AppCompatActivity { @SuppressLint("CommitPrefEdits") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Set stringSet = new HashSet<>(); stringSet.add("SetOne"); stringSet.add("SetTwo"); stringSet.add("SetThree"); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); SharedPreferences prefsOne = getSharedPreferences("countPrefOne", Context.MODE_PRIVATE); SharedPreferences prefsTwo = getSharedPreferences("countPrefTwo", Context.MODE_PRIVATE); sharedPreferences.edit().putString("testOne", "one").commit(); sharedPreferences.edit().putInt("testTwo", 2).commit(); sharedPreferences.edit().putLong("testThree", 100000L).commit(); sharedPreferences.edit().putFloat("testFour", 3.01F).commit(); sharedPreferences.edit().putBoolean("testFive", true).commit(); sharedPreferences.edit().putStringSet("testSix", stringSet).commit(); prefsOne.edit().putString("testOneNew", "one").commit(); prefsTwo.edit().putString("testTwoNew", "two").commit(); ContactDBHelper contactDBHelper = new ContactDBHelper(getApplicationContext()); if (contactDBHelper.count() == 0) { for (int i = 0; i < 100; i++) { String name = "name_" + i; String phone = "phone_" + i; String email = "email_" + i; String street = "street_" + i; String place = "place_" + i; contactDBHelper.insertContact(name, phone, email, street, place); } } CarDBHelper carDBHelper = new CarDBHelper(getApplicationContext()); if (carDBHelper.count() == 0) { for (int i = 0; i < 50; i++) { String name = "name_" + i; String color = "RED"; float mileage = i + 10.45f; carDBHelper.insertCar(name, color, mileage); } } ExtTestDBHelper extTestDBHelper = new ExtTestDBHelper(getApplicationContext()); if (extTestDBHelper.count() == 0) { for (int i = 0; i < 20; i++) { String value = "value_" + i; extTestDBHelper.insertTest(value); } } // Room database UserDBHelper userDBHelper = new UserDBHelper(getApplicationContext()); if (userDBHelper.count() == 0) { List userList = new ArrayList<>(); for (int i = 0; i < 20; i++) { User user = new User(); user.id = (long) (i + 1); user.name = "user_" + i; userList.add(user); } userDBHelper.insertUser(userList); } // Room inMemory database if (userDBHelper.countInMemory() == 0) { List userList = new ArrayList<>(); for (int i = 0; i < 20; i++) { User user = new User(); user.id = (long) (i + 1); user.name = "in_memory_user_" + i; userList.add(user); } userDBHelper.insertUserInMemory(userList); } Utils.setCustomDatabaseFiles(getApplicationContext()); Utils.setInMemoryRoomDatabases(userDBHelper.getInMemoryDatabase()); } public void showDebugDbAddress(View view) { Utils.showDebugDBAddressLogToast(getApplicationContext()); } } ================================================ FILE: sample-app/src/main/java/com/sample/database/CarDBHelper.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.sample.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import java.util.ArrayList; /** * Created by amitshekhar on 06/02/17. */ public class CarDBHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "Car.db"; public static final String CARS_TABLE_NAME = "cars"; public static final String CARS_COLUMN_ID = "id"; public static final String CARS_COLUMN_NAME = "name"; public static final String CARS_COLUMN_COLOR = "color"; public static final String CCARS_COLUMN_MILEAGE = "mileage"; public CarDBHelper(Context context) { super(context, DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL( "create table cars " + "(id integer primary key, name text, color text, mileage real)" ); db.execSQL("create table [transaction] (id integer primary key, name text)"); for (int i = 0; i < 10; i++) { db.execSQL("insert into [transaction] (name) values ('hello');"); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL("DROP TABLE IF EXISTS cars"); onCreate(db); } public boolean insertCar(String name, String color, float mileage) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put("name", name); contentValues.put("color", color); contentValues.put("mileage", mileage); db.insert("cars", null, contentValues); return true; } public Cursor getData(int id) { SQLiteDatabase db = this.getReadableDatabase(); Cursor res = db.rawQuery("select * from cars where id=" + id + "", null); return res; } public int numberOfRows() { SQLiteDatabase db = this.getReadableDatabase(); int numRows = (int) DatabaseUtils.queryNumEntries(db, CARS_TABLE_NAME); return numRows; } public boolean updateCar(Integer id, String name, String color, float mileage) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put("name", name); contentValues.put("color", color); contentValues.put("mileage", mileage); db.update("cars", contentValues, "id = ? ", new String[]{Integer.toString(id)}); return true; } public Integer deleteCar(Integer id) { SQLiteDatabase db = this.getWritableDatabase(); return db.delete("cars", "id = ? ", new String[]{Integer.toString(id)}); } public ArrayList getAllCars() { ArrayList arrayList = new ArrayList<>(); //hp = new HashMap(); SQLiteDatabase db = this.getReadableDatabase(); Cursor res = db.rawQuery("select * from cars", null); res.moveToFirst(); while (!res.isAfterLast()) { arrayList.add(res.getString(res.getColumnIndex(CARS_COLUMN_NAME))); res.moveToNext(); } return arrayList; } public int count() { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery("select * from cars", null); if (cursor != null && cursor.getCount() > 0) { cursor.moveToFirst(); return cursor.getInt(0); } else { return 0; } } } ================================================ FILE: sample-app/src/main/java/com/sample/database/ContactDBHelper.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.sample.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import java.util.ArrayList; import java.util.Calendar; /** * Created by amitshekhar on 16/11/16. */ public class ContactDBHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "Contact.db"; public static final String CONTACTS_TABLE_NAME = "contacts"; public static final String CONTACTS_COLUMN_ID = "id"; public static final String CONTACTS_COLUMN_NAME = "name"; public static final String CONTACTS_COLUMN_EMAIL = "email"; public static final String CONTACTS_COLUMN_STREET = "street"; public static final String CONTACTS_COLUMN_CITY = "place"; public static final String CONTACTS_COLUMN_PHONE = "phone"; public static final String CONTACTS_CREATED_AT = "createdAt"; public ContactDBHelper(Context context) { super(context, DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL( "create table contacts " + "(id integer primary key, name text, phone text, email text, street text, place text, createdAt integer)" ); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL("DROP TABLE IF EXISTS contacts"); onCreate(db); } public boolean insertContact(String name, String phone, String email, String street, String place) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put("name", name); contentValues.put("phone", phone); contentValues.put("email", email); contentValues.put("street", street); contentValues.put("place", place); contentValues.put(CONTACTS_CREATED_AT, Calendar.getInstance().getTimeInMillis()); db.insert("contacts", null, contentValues); return true; } public Cursor getData(int id) { SQLiteDatabase db = this.getReadableDatabase(); Cursor res = db.rawQuery("select * from contacts where id=" + id + "", null); return res; } public int numberOfRows() { SQLiteDatabase db = this.getReadableDatabase(); int numRows = (int) DatabaseUtils.queryNumEntries(db, CONTACTS_TABLE_NAME); return numRows; } public boolean updateContact(Integer id, String name, String phone, String email, String street, String place) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put("name", name); contentValues.put("phone", phone); contentValues.put("email", email); contentValues.put("street", street); contentValues.put("place", place); db.update("contacts", contentValues, "id = ? ", new String[]{Integer.toString(id)}); return true; } public Integer deleteContact(Integer id) { SQLiteDatabase db = this.getWritableDatabase(); return db.delete("contacts", "id = ? ", new String[]{Integer.toString(id)}); } public ArrayList getAllCotacts() { ArrayList arrayList = new ArrayList<>(); //hp = new HashMap(); SQLiteDatabase db = this.getReadableDatabase(); Cursor res = db.rawQuery("select * from contacts", null); res.moveToFirst(); while (!res.isAfterLast()) { arrayList.add(res.getString(res.getColumnIndex(CONTACTS_COLUMN_NAME))); res.moveToNext(); } return arrayList; } public int count() { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery("select * from contacts", null); if (cursor != null && cursor.getCount() > 0) { cursor.moveToFirst(); return cursor.getInt(0); } else { return 0; } } } ================================================ FILE: sample-app/src/main/java/com/sample/database/ExtTestDBHelper.java ================================================ package com.sample.database; import android.content.ContentValues; import android.content.Context; import android.content.ContextWrapper; import android.database.Cursor; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import java.io.File; import java.util.Calendar; public class ExtTestDBHelper extends SQLiteOpenHelper { public static final String DIR_NAME = "custom_dir"; public static final String DATABASE_NAME = "ExtTest.db"; public static final String TEST_TABLE_NAME = "test"; public static final String TEST_ID = "id"; public static final String TEST_COLUMN_VALUE = "value"; public static final String TEST_CREATED_AT = "createdAt"; public ExtTestDBHelper(Context context) { super(new CustomDatabasePathContext(context), DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL( String.format( "create table %s (%s integer primary key, %s text, %s integer)", TEST_TABLE_NAME, TEST_ID, TEST_COLUMN_VALUE, TEST_CREATED_AT ) ); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE_NAME); onCreate(db); } public boolean insertTest(String value) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put("value", value); contentValues.put(TEST_CREATED_AT, Calendar.getInstance().getTimeInMillis()); db.insert(TEST_TABLE_NAME, null, contentValues); return true; } public int count() { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery("select COUNT(*) from " + TEST_TABLE_NAME, null); if (cursor != null && cursor.getCount() > 0) { cursor.moveToFirst(); return cursor.getInt(0); } else { return 0; } } private static class CustomDatabasePathContext extends ContextWrapper { public CustomDatabasePathContext(Context base) { super(base); } @Override public File getDatabasePath(String name) { File databaseDir = new File(String.format("%s/%s", getFilesDir(), ExtTestDBHelper.DIR_NAME)); databaseDir.mkdirs(); File databaseFile = new File(String.format("%s/%s/%s", getFilesDir(), ExtTestDBHelper.DIR_NAME, name)); return databaseFile; } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); } } } ================================================ FILE: sample-app/src/main/java/com/sample/database/room/AppDatabase.java ================================================ package com.sample.database.room; import androidx.room.Database; import androidx.room.RoomDatabase; /** * Created by anandgaurav on 12/02/18. */ @Database(entities = {User.class}, version = 1, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); } ================================================ FILE: sample-app/src/main/java/com/sample/database/room/User.java ================================================ package com.sample.database.room; import androidx.room.Entity; import androidx.room.PrimaryKey; /** * Created by anandgaurav on 12/02/18. */ @Entity(tableName = "users") public class User { @PrimaryKey public Long id; public String name; } ================================================ FILE: sample-app/src/main/java/com/sample/database/room/UserDBHelper.java ================================================ package com.sample.database.room; import android.content.Context; import androidx.room.Room; import androidx.sqlite.db.SupportSQLiteDatabase; import java.util.List; /** * Created by anandgaurav on 12/02/18. */ public class UserDBHelper { private final AppDatabase appDatabase; private final AppDatabase inMemoryAppDatabase; public UserDBHelper(Context context) { appDatabase = Room.databaseBuilder(context, AppDatabase.class, "User.db") .allowMainThreadQueries() .build(); inMemoryAppDatabase = Room.inMemoryDatabaseBuilder(context, AppDatabase.class) .allowMainThreadQueries() .build(); } public void insertUser(List userList) { appDatabase.userDao().insertAll(userList); } public void insertUserInMemory(List userList) { inMemoryAppDatabase.userDao().insertAll(userList); } public int count() { return appDatabase.userDao().loadAll().size(); } public int countInMemory() { return inMemoryAppDatabase.userDao().loadAll().size(); } public SupportSQLiteDatabase getInMemoryDatabase() { return inMemoryAppDatabase.getOpenHelper().getWritableDatabase(); } } ================================================ FILE: sample-app/src/main/java/com/sample/database/room/UserDao.java ================================================ package com.sample.database.room; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; import java.util.List; /** * Created by anandgaurav on 12/02/18. */ @Dao public interface UserDao { @Query("SELECT * FROM users") List loadAll(); @Query("SELECT * FROM users WHERE id IN (:userIds)") List loadAllByIds(List userIds); @Insert(onConflict = OnConflictStrategy.REPLACE) void insert(User user); @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List users); @Delete void delete(User user); } ================================================ FILE: sample-app/src/main/java/com/sample/utils/Utils.java ================================================ /* * * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.sample.utils; import android.content.Context; import android.util.Pair; import android.widget.Toast; import androidx.sqlite.db.SupportSQLiteDatabase; import com.sample.BuildConfig; import com.sample.database.ExtTestDBHelper; import java.io.File; import java.lang.reflect.Method; import java.util.HashMap; /** * Created by amitshekhar on 07/02/17. */ public class Utils { private Utils() { // This class is not publicly instantiable } public static void showDebugDBAddressLogToast(Context context) { if (BuildConfig.DEBUG) { try { Class debugDB = Class.forName("com.amitshekhar.DebugDB"); Method getAddressLog = debugDB.getMethod("getAddressLog"); Object value = getAddressLog.invoke(null); Toast.makeText(context, (String) value, Toast.LENGTH_LONG).show(); } catch (Exception ignore) { } } } public static void setCustomDatabaseFiles(Context context) { if (BuildConfig.DEBUG) { try { Class debugDB = Class.forName("com.amitshekhar.DebugDB"); Class[] argTypes = new Class[]{HashMap.class}; Method setCustomDatabaseFiles = debugDB.getMethod("setCustomDatabaseFiles", argTypes); HashMap> customDatabaseFiles = new HashMap<>(); // set your custom database files customDatabaseFiles.put(ExtTestDBHelper.DATABASE_NAME, new Pair<>(new File(context.getFilesDir() + "/" + ExtTestDBHelper.DIR_NAME + "/" + ExtTestDBHelper.DATABASE_NAME), "")); setCustomDatabaseFiles.invoke(null, customDatabaseFiles); } catch (Exception ignore) { } } } public static void setInMemoryRoomDatabases(SupportSQLiteDatabase... database) { if (BuildConfig.DEBUG) { try { Class debugDB = Class.forName("com.amitshekhar.DebugDB"); Class[] argTypes = new Class[]{HashMap.class}; HashMap inMemoryDatabases = new HashMap<>(); // set your inMemory databases inMemoryDatabases.put("InMemoryOne.db", database[0]); Method setRoomInMemoryDatabase = debugDB.getMethod("setInMemoryRoomDatabases", argTypes); setRoomInMemoryDatabase.invoke(null, inMemoryDatabases); } catch (Exception ignore) { } } } } ================================================ FILE: sample-app/src/main/res/layout/activity_main.xml ================================================