+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..37a7509
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,201 @@
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8e6184d
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# PinLog Library
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..2cb53c6
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 31
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ minSdkVersion 19
+ targetSdkVersion 31
+ versionCode 1
+ versionName "0.0.1"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..82b6d39
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/ApplicationLogsContract.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/ApplicationLogsContract.kt
new file mode 100644
index 0000000..197bf31
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/ApplicationLogsContract.kt
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog
+
+import android.database.sqlite.SQLiteDatabase
+import android.provider.BaseColumns
+import com.adityaamolbavadekar.pinlog.ApplicationLogsContract.ApplicationLogEntry.DATABASE_CREATE
+import com.adityaamolbavadekar.pinlog.ApplicationLogsContract.ApplicationLogEntry.TABLE_NAME
+
+internal object ApplicationLogsContract {
+
+ object ApplicationLogEntry : BaseColumns {
+ const val DATABASE_VERSION: Int = 1
+
+ // const val DATABASE_NAME: String = "com.adityaamolbavadekar.pinlog.database.logs.db"
+ const val TABLE_NAME: String = "pin_logger_logs"
+ const val COLUMN_NAME_ID: String = "_id"
+ const val COLUMN_NAME_LOG: String = "logs"
+ const val COLUMN_NAME_LOG_LEVEL: String = "log_level"
+
+ const val DATABASE_CREATE = ("CREATE TABLE IF NOT EXISTS "
+ + TABLE_NAME
+ + " ("
+ + COLUMN_NAME_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + COLUMN_NAME_LOG + " TEXT, "
+ + COLUMN_NAME_LOG_LEVEL + " INTEGER"
+ + ");")
+ }
+
+
+ fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
+ if (db == null) {
+ return
+ }
+ try {
+ db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
+ onCreate(db)
+ PinLog.logInfo(
+ "PinLogTable onUpgrade called. Executing drop_table query to delete old logs."
+ )
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ PinLog.logError(
+ "PinLogTable: Exception occurred while executing Database in onUpgrade: $e", e
+ )
+ }
+ }
+
+ fun onCreate(db: SQLiteDatabase?) {
+ if (db == null) return
+
+ try {
+ db.execSQL(DATABASE_CREATE)
+ PinLog.logInfo(
+ "PinLogTable: Successfully created PinLogs Database"
+ )
+
+ } catch (e: Exception) {
+ e.printStackTrace()
+ PinLog.logError(
+ "PinLogTable: Exception occurred while executing Database in onCreate: $e", e
+ )
+ }
+ }
+
+}
+
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/DefaultApplicationLoggingStyle.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/DefaultApplicationLoggingStyle.kt
new file mode 100644
index 0000000..3d3e9b6
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/DefaultApplicationLoggingStyle.kt
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog
+
+import java.util.*
+
+/**
+ *
+ * ```
+ * Example :
+ * "Vr[1.0] 05-17 20:04:11.603 18494-18629/? D/PinLog: Hello World from PinLog"
+ *
+ * ```
+ *
+ * */
+class DefaultApplicationLoggingStyle : LoggingStyle() {
+
+ override fun getFormattedLogData(
+ TAG: String,
+ m: String,
+ e: Throwable?,
+ dateLong: Long,
+ level: PinLog.LogLevel,
+ VERSION_NAME: String,
+ VERSION_CODE: String,
+ PACKAGE_NAME: String
+ ): String {
+ var string =
+ "Vr/[${VERSION_NAME}] " + "${Date(dateLong)}" + "/ " + "${level.SHORT_NAME}/" + "$TAG : " + m
+ if (e != null) {
+ string += "\n" + PinLog.getStackTraceString(e)
+ }
+ return string
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/LogData.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/LogData.kt
new file mode 100644
index 0000000..309a53c
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/LogData.kt
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog
+
+data class LogData(
+ var TAG: String,
+ var m: String?,
+ var e: Throwable?,
+ var level: PinLog.LogLevel,
+ val dateLong : Long = System.currentTimeMillis()
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/LoggingStyle.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/LoggingStyle.kt
new file mode 100644
index 0000000..cbff008
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/LoggingStyle.kt
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog
+
+import android.content.Context
+
+
+/**
+ * Created by Aditya Bavadekar on 18 May 2022
+ *
+ *
+ * Set this property in [PinLog.setLogFormatting]. Override the [LoggingStyle.getFormattedLogData] method to implement custom logging string.
+ *
+ * *Default implementation is [DefaultApplicationLoggingStyle]*
+ *
+ * */
+abstract class LoggingStyle {
+
+ private var context:Context? =null
+
+ constructor()
+
+ /**
+ * Called before storing log to Database.
+ *
+ * *Default implementation is [DefaultApplicationLoggingStyle.getFormattedLogData]*
+ * */
+ abstract fun getFormattedLogData(
+ TAG: String,
+ m: String,
+ e: Throwable?,
+ dateLong: Long,
+ level: PinLog.LogLevel,
+ VERSION_NAME: String,
+ VERSION_CODE: String,
+ PACKAGE_NAME: String
+ ): String
+
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/PinLog.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/PinLog.kt
new file mode 100644
index 0000000..cd1b4f2
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/PinLog.kt
@@ -0,0 +1,1028 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog
+
+import android.app.Application
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.annotation.NonNull
+import com.adityaamolbavadekar.pinlog.database.ApplicationLogDatabaseHelper
+import com.adityaamolbavadekar.pinlog.database.ApplicationLogModel
+import com.adityaamolbavadekar.pinlog.extensions.submitLog
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
+import java.util.*
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import kotlin.math.pow
+import kotlin.system.exitProcess
+
+
+/**
+ * Created by Aditya Bavadekar on 18 May 2022
+ *
+ * **About** :
+ *
+ * PinLog is an easy-to-use android Logging Library.
+ * PinLog supports storing logs for later retrieval, saving logs in a file, saving logs in a zip file and more.
+ * It is made by Aditya Bavadekar and written in Kotlin Language.
+ * The library is Open-Source with its LICENCE bieng Apache 2.0.
+ *
+ * **Usage**:
+ * - [PinLog] should be initialised in the [Application] Class :
+ * ```
+ * PinLog.initialise(this)
+ * PinLog.setDevLogging(true)
+ * PinLog.setBuildConfigClass(BuildConfig::class.java)
+ * ```
+ * *OR*
+ * ```
+ * //For Debuggable Builds
+ * PinLog.initialiseDebug(this@App)
+ *
+ * //For Release Builds
+ * PinLog.initialiseRelease(this@App)
+ * ```
+ * - *Logging*
+ * Methods Available :
+ * 1. [PinLog.logE]
+ * 2. [PinLog.logW]
+ * 3. [PinLog.logI]
+ * 4. [PinLog.logD]
+ *
+ * You can also use these methods directly
+ * in classes which extend ContextWrapper (You dont have to add TAG property) like this :
+ * ```
+ *
+ * class MainActivity : AppCompatActivity() {
+ * override fun onCreate(savedInstanceState: Bundle?) {
+ * super.onCreate(savedInstanceState)
+ * setContentView(R.layout.activity_main)
+ *
+ * //like here no TAG is required
+ * logI("onCreate")
+ *
+ * }
+ * }
+ * ```
+ *
+ *
+ * - You can get Logs stored in database by [PinLog.getAllPinLogs] method.
+ * *Warning : This may return null or empty list if you've set [PinLog.setDevLogging] to `false`*
+ *
+ * @author Aditya Bavadekar
+ * @since 19 May 2022
+ *
+ * */
+object PinLog {
+
+ private var isDevLoggingEnabled = false
+ private var buildConfigClass: Class<*>? = null
+ private var shouldStoreLogs: Boolean = true
+ private var isInitialised: Boolean = false
+ private var app: Application? = null
+ private var applicationLogDataSource: ApplicationLogDatabaseHelper? = null
+ private var pinLoggerService: ExecutorService? = null
+ private var loggingStyle: LoggingStyle? = null
+ private var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null
+ private var exceptionHandlerEnabled: Boolean = false
+ private var sendToEmail: String? = null
+ private var listeners: MutableList = mutableListOf()
+ private var callerPackageName: String = ""
+ private var callerVersionName: String = ""
+ private var callerVersionCode: String = ""
+ private var callerAppName: String = ""
+
+ private fun instance(@NonNull application: Application): Boolean {
+ synchronized(PinLog::class.java) {
+ return if (app == null) {
+ app = application
+ isInitialised = true
+ onInitialisation()
+ true
+ } else {
+ notInitialised()
+ false
+ }
+ }
+ }
+
+ private fun onInitialisation() {
+ logInfo("Dev Logging is enabled")
+ getAppInfo()
+ logInfo("PinLog was initialised successfully for $callerPackageName")
+ if (applicationLogDataSource == null) {
+ applicationLogDataSource = ApplicationLogDatabaseHelper(getContext())
+ }
+ collectBuildConfigData()
+ setupExceptionHandler()
+ }
+
+ private fun setupExceptionHandler() {
+ if (exceptionHandlerEnabled) {
+ if (getContext().mainLooper.thread == Thread.currentThread()) {
+ defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+ Thread.setDefaultUncaughtExceptionHandler(PinLogUncaughtExceptionHandler())
+ }
+ }
+ }
+
+ private fun getContext(): Context {
+ return app!!.applicationContext
+ }
+
+ private fun getAppInfo() {
+ val packageInfo = getContext().packageManager.getPackageInfo(getContext().packageName, 0)
+ callerVersionName = packageInfo.versionName
+ callerVersionCode = "${packageInfo.versionCode}"
+ callerPackageName = packageInfo.packageName
+ callerAppName =
+ (packageInfo.applicationInfo.loadLabel(getContext().packageManager) ?: "") as String
+
+ }
+
+ internal fun logError(m: String, e: Exception?=null) {
+ logE(CLASS_TAG, m, e)
+ }
+
+ internal fun logWarning(m: String) {
+ logW(CLASS_TAG, m)
+ }
+
+ internal fun logInfo(m: String) {
+ logI(CLASS_TAG, m)
+ }
+
+ private fun notInitialised() {
+ logWarning("Could not initialise PinLog as it was previously initialised for $callerPackageName")
+ }
+
+ private fun isAppInitialised(): Boolean {
+ return when {
+ app == null -> {
+ logWarning("Context is null. PinLog is not initialised a method was called before initialising PinLog")
+ false
+ }
+ applicationLogDataSource == null -> {
+ logWarning("Database is not created yet!")
+ false
+ }
+ else -> true
+ }
+ }
+
+
+ /*Public Methods*/
+
+ /**
+ * Provided [LogLevel] it logs to the specific function.
+ *
+ * */
+ @JvmStatic
+ fun log(TAG: String, m: String, logLevel: LogLevel = LogLevel.INFO) {
+ when (logLevel) {
+ LogLevel.INFO -> logI(TAG, m)
+ LogLevel.WARN -> logW(TAG, m)
+ LogLevel.ERROR -> logE(TAG, m)
+ LogLevel.DEBUG -> logD(TAG, m)
+ }
+ }
+
+ /**
+ * Provided [LogLevel] it logs to the specific function.
+ *
+ * */
+ @JvmStatic
+ fun log(TAG: String, m: String, e: Throwable?, logLevel: LogLevel = LogLevel.INFO) {
+ when (logLevel) {
+ LogLevel.INFO -> logI(TAG, m, e)
+ LogLevel.WARN -> logW(TAG, m, e)
+ LogLevel.ERROR -> logE(TAG, m, e)
+ LogLevel.DEBUG -> logD(TAG, m, e)
+ }
+ }
+
+ /**
+ * @return [String] Creates a log and returns it in form of a string.
+ * */
+ @JvmStatic
+ fun getLogAsString(
+ TAG: String,
+ m: String,
+ e: Throwable?,
+ logLevel: LogLevel = LogLevel.INFO
+ ): String? {
+ if (!isAppInitialised()) {
+ return null
+ }
+ return loggingStyle!!.getFormattedLogData(
+ TAG, m, e, System.currentTimeMillis(), logLevel,
+ callerVersionName, callerVersionCode, callerPackageName
+ )
+ }
+
+ /*ERROR*/
+
+ /**
+ * **Logs an Error Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logE(TAG: String, m: String) {
+ if (isDevLoggingEnabled) Log.e(TAG, m)
+ storeLog(LogData(TAG, m, null, LogLevel.ERROR))
+ }
+
+ /**
+ * **Logs an Error Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logE(TAG: String, m: String?, e: Throwable?) {
+ if (isDevLoggingEnabled) Log.e(TAG, m, e)
+ storeLog(LogData(TAG, m, e, LogLevel.ERROR))
+ }
+
+ /*WARN*/
+
+ /**
+ * **Logs a Warning Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logW(TAG: String, m: String?, e: Throwable?) {
+ if (isDevLoggingEnabled) Log.w(TAG, m, e)
+ storeLog(LogData(TAG, m, e, LogLevel.WARN))
+ }
+
+ /**
+ * **Logs a Warning Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logW(TAG: String, m: String) {
+ if (isDevLoggingEnabled) Log.w(TAG, m)
+ storeLog(LogData(TAG, m, null, LogLevel.WARN))
+ }
+
+ /*INFO*/
+
+ /**
+ * **Logs an Info Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logI(TAG: String, m: String?, e: Throwable?) {
+ if (isDevLoggingEnabled) Log.i(TAG, m, e)
+
+ storeLog(LogData(TAG, m, e, LogLevel.INFO))
+ }
+
+ /**
+ * **Logs an Info Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logI(TAG: String, m: String) {
+ if (isDevLoggingEnabled) Log.i(TAG, m)
+ storeLog(LogData(TAG, m, null, LogLevel.INFO))
+ }
+
+ /*DEBUG*/
+
+ /**
+ * **Logs a Debug Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logD(TAG: String, m: String?, e: Throwable?) {
+ if (isDevLoggingEnabled) Log.d(TAG, m, e)
+ storeLog(LogData(TAG, m, e, LogLevel.DEBUG))
+ }
+
+ /**
+ * **Logs a Debug Log**
+ *
+ * *Logs to Logcat if [PinLog.setDevLogging] is set to `true`*
+ *
+ * *Stores the log if [PinLog.setDoStoreLogs] is set to `true`*
+ *
+ *
+ * */
+ @JvmStatic
+ fun logD(TAG: String, m: String) {
+ if (isDevLoggingEnabled) Log.d(TAG, m)
+ storeLog(LogData(TAG, m, null, LogLevel.DEBUG))
+ }
+
+ /*STACKTRACE*/
+ /**
+ * **Returns Stack-Trace for the provided [Throwable]**
+ *
+ * */
+ @JvmStatic
+ fun getStackTraceString(tr: Throwable): String {
+ return Log.getStackTraceString(tr)
+ }
+
+ private fun storeLog(data: LogData) {
+ //Filter PinLog Class's logs as they are intended only for Logcat logging
+ when {
+ data.TAG == CLASS_TAG -> return
+ !isAppInitialised() -> {
+ return
+ }
+ else -> {
+ val log = loggingStyle!!.getFormattedLogData(
+ data.TAG, data.m ?: "",
+ data.e,
+ data.dateLong,
+ data.level,
+ callerVersionName,
+ callerVersionCode,
+ callerPackageName
+ )
+
+ if (pinLoggerService == null) {
+ pinLoggerService = Executors.newSingleThreadExecutor()
+ }
+
+ if (shouldStoreLogs) {
+ val runnable = Runnable {
+ try {
+ applicationLogDataSource?.insertPinLog(
+ ApplicationLogModel(
+ id = 0,
+ LOG = log,
+ LOG_LEVEL = data.level.LEVEL_INT
+ )
+ )
+ } catch (ex: java.lang.Exception) {
+ ex.printStackTrace()
+ }
+ }
+ pinLoggerService?.submit(runnable)
+ }
+
+ listeners.submitLog(log)
+ }
+ }
+ }
+
+ enum class LogLevel(val LEVEL_INT: Int, val SHORT_NAME: String) {
+ ERROR(0, "E"),
+ WARN(1, "W"),
+ INFO(2, "I"),
+ DEBUG(3, "D")
+ }
+
+ interface OnLogAddedListener {
+ fun onLogAdded(log: String)
+ }
+
+ /**
+ * Call this to add a [OnLogAddedListener] which notifies you when a new log is added
+ * @return [Boolean] whether listener was added.
+ *
+ * */
+ @JvmStatic
+ fun addListener(onLogAddedListener: OnLogAddedListener): Boolean {
+ return if (listeners.size >= 5) {
+ logWarning("Could not add new listener as max limit(5) has reached")
+ false
+ } else {
+ listeners.add(onLogAddedListener)
+ true
+ }
+ }
+
+ /**
+ * Call this to remove a previously added [OnLogAddedListener]
+ * @return [Boolean] whether listener was removed.
+ *
+ * */
+ @JvmStatic
+ fun removeListener(onLogAddedListener: OnLogAddedListener): Boolean {
+ return if (!listeners.contains(onLogAddedListener)) {
+ logWarning("Could not remove listener as it was not found")
+ false
+ } else {
+ listeners.remove(onLogAddedListener)
+ true
+ }
+ }
+
+ /**
+ *
+ * @return [Int] Total Count of Logs that are stored in PinLog's database
+ * May return 0 if PinLog was not initialised.
+ *
+ * */
+ @JvmStatic
+ fun getPinLogsCount(): Int {
+ return when {
+ !isAppInitialised() -> {
+ 0
+ }
+ else -> {
+ applicationLogDataSource!!.getPinLogsCount()
+ }
+ }
+ }
+
+ /**
+ * Call this to Get Logs
+ *
+ * *Generally you should call this method in
+ * a CoroutineJob or another Thread so it does not block main-ui thread*
+ *
+ * @return List of all logs that are stored in PinLog's database
+ * May return emptyList if PinLog was not initialised.
+ *
+ * */
+ @JvmStatic
+ fun getAllPinLogs(): List {
+ return getAllPinLogs(false, 0)
+ }
+
+
+ /**
+ * Call this to Get Logs
+ *
+ * *Generally you should call this method in
+ * a CoroutineJob or another Thread so it does not block main-ui thread*
+ *
+ * @param maxLogsLimit The max number of logs that should be returned.
+ * By default all logs are returned.
+ * @return List of all logs that are stored in PinLog's database
+ * May return emptyList if PinLog was not initialised.
+ *
+ * */
+ @JvmStatic
+ fun getAllPinLogs(maxLogsLimit: Int): List {
+ return getAllPinLogs(false, maxLogsLimit)
+ }
+
+ /**
+ * Call this to Get Logs
+ *
+ * *Generally you should call this method in
+ * a CoroutineJob or another Thread so it does not block main-ui thread*
+ *
+ * @param shouldDeleteExistingLogs Whether the logs returned should be deleted from
+ * the database. *By Default logs are deleted.*
+ * @param maxLogsLimit The max number of logs that should be returned.
+ * By default all logs are returned.
+ * @return List of all logs that are stored in PinLog's database
+ * May return emptyList if PinLog was not initialised.
+ *
+ * */
+ @JvmStatic
+ fun getAllPinLogs(
+ shouldDeleteExistingLogs: Boolean = true,
+ maxLogsLimit: Int
+ ): List {
+ val logs: MutableList = mutableListOf()
+
+ if (!isAppInitialised()) {
+ return emptyList()
+ }
+
+ if (applicationLogDataSource != null && app != null) {
+ logs.addAll(applicationLogDataSource!!.getAllPinLogs())
+ if (shouldDeleteExistingLogs) deleteAllPinLogs()
+ }
+
+ if (maxLogsLimit >= 1 && logs.size > maxLogsLimit) {
+ return logs.dropLast(logs.size - maxLogsLimit).toMutableList()
+ }
+
+ return logs
+ }
+
+ /**
+ * Call this to Get Logs as a String List
+ *
+ * *Generally you should call this method in
+ * a CoroutineJob or another Thread so it does not block main-ui thread*
+ *
+ * @return List of all logs that are stored in PinLog's database
+ * May return emptyList if PinLog was not initialised.
+ *
+ * */
+ @JvmStatic
+ fun getAllPinLogsAsStringList(): List {
+ return getAllPinLogsAsStringList(false)
+ }
+
+ /**
+ * Call this to Get Logs as a String List
+ *
+ * *Generally you should call this method in
+ * a CoroutineJob or another Thread so it does not block main-ui thread*
+ *
+ * @param shouldDeleteExistingLogs Whether the logs returned should be deleted from
+ * the database. *By Default logs are deleted.*
+ * @return List of all logs that are stored in PinLog's database
+ * May return emptyList if PinLog was not initialised.
+ *
+ * */
+ @JvmStatic
+ fun getAllPinLogsAsStringList(shouldDeleteExistingLogs: Boolean = true): List {
+ val logs: MutableList = mutableListOf()
+
+ if (!isAppInitialised()) {
+ return emptyList()
+ }
+
+ if (applicationLogDataSource != null) {
+ logs.addAll(applicationLogDataSource!!.getAllPinLogsAsStringList())
+ if (shouldDeleteExistingLogs) deleteAllPinLogs()
+ }
+ return logs
+ }
+
+ /**
+ * Call this to Get Logs as a single String.
+ *
+ * *Generally you should call this method in
+ * a CoroutineJob or another Thread so it does not block main-ui thread*
+ *
+ * @return String containing all logs that are stored in PinLog's database.
+ * The log element is appended by \n. May return `null` if PinLog was not initialised
+ *
+ *
+ * */
+ @JvmStatic
+ fun getAllPinLogsAsString(): String? {
+ var logs: String? = null
+ if (!isAppInitialised()) {
+ return null
+ } else {
+ val list = (applicationLogDataSource!!.getAllPinLogsAsStringList())
+ logs = ""
+ list.forEach {
+ logs += it + "\n"
+ }
+ }
+ return logs
+ }
+
+ /**
+ * Call this to Get all saved Logs in a File.
+ *
+ *
+ * @return A (.txt) File that contains all logs or
+ * null if [PinLog] was not initialised or if some exception occured
+ * while creating that file.
+ *
+ * */
+ fun getAllPinLogsInFileWithName(fileName: String?): File? {
+ return getAllPinLogsInFile(fileName = fileName, extraEndingLine = null)
+ }
+
+ /**
+ * Call this to Get all saved Logs in a File.
+ *
+ *
+ * @param extraEndingLine An extra line (String) that you want to add at the last line of file like
+ * *userIdentifier or sharedPrefsData or anything.*
+ *
+ * Pass null if not interested.
+ *
+ * @return A (.txt) File that contains all logs or
+ * null if [PinLog] was not initialised or if some exception occured
+ * while creating that file.
+ *
+ * */
+ fun getAllPinLogsInFile(extraEndingLine: String?): File? {
+ return getAllPinLogsInFile(fileName = null, extraEndingLine = extraEndingLine)
+ }
+
+ /**
+ * Call this to Get all saved Logs in a File.
+ *
+ *
+ * @param fileName a File Name. Default is
+ * "`{YOUR_APP_PACKAGE_NAME}_LOG_FILE_{DATE}`"
+ *
+ * @param extraEndingLine An extra line (String) that you want to add at the last line of file like
+ * *userIdentifier or sharedPrefsData or anything.*
+ *
+ * Pass null if not interested.
+ *
+ * @return A (.txt) File that contains all logs or
+ * null if [PinLog] was not initialised or if some exception occured
+ * while creating that file.
+ *
+ * */
+ @JvmStatic
+ fun getAllPinLogsInFile(fileName: String?, extraEndingLine: String?): File? {
+ if (!isAppInitialised()) {
+ return null
+ }
+ val logsList = getAllPinLogsAsStringList()
+ if (logsList.isNotEmpty()) {
+ val dirPath: String =
+ getContext().getExternalFilesDir(null)!!.absolutePath + "/ApplicationLogFiles"
+ try {
+ //Create a directory if it doesn't already exist.
+ val filePath = File(dirPath)
+ if (!filePath.exists()) {
+ if (!filePath.mkdirs()) {
+ logW(
+ CLASS_TAG,
+ "Error occurred while creating directory for log files."
+ )
+ return null
+ }
+ }
+
+ //Create a new file with file name
+ val logFile: File = if (fileName == null) {
+ File(
+ filePath,
+ "${callerPackageName}_LOG_FILE_${System.currentTimeMillis()}"
+ )
+ } else File(filePath, fileName)
+
+ val writer = FileWriter(logFile, true)
+ val bufferedWriter = BufferedWriter(writer, 4 * 1024.0.pow(2.0).toInt())
+ for (logString in logsList) {
+ bufferedWriter.write(logString + "\n")
+ }
+
+ extraEndingLine?.let {
+ bufferedWriter.write(it + "\n")
+ }
+
+ writer.flush()
+ writer.close()
+
+ logI(CLASS_TAG, "The logs are save in a file - ${logFile.absolutePath}")
+
+ return logFile
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ return null
+ }
+
+ /**
+ *
+ * Call this to **delete all** of the logs stored in PinLog's database
+ *
+ * */
+ @JvmStatic
+ fun deleteAllPinLogs() {
+ if (!isAppInitialised()) {
+ return
+ }
+ if (applicationLogDataSource == null) return
+ else {
+ logWarning("Delete Logs request was called")
+ applicationLogDataSource!!.deleteAllPinLogs()
+ }
+ }
+
+ /**
+ * if set to `true` Enables dev logs
+ *
+ * Uses built-in Android [Log] util to log to Logcat
+ * *Defaults to `false`
+ *
+ * @param enabled Whether Dev Logs are enabled defaults to `false`
+ * */
+ fun setDevLogging(enabled: Boolean) {
+ isDevLoggingEnabled = enabled
+ }
+
+ /**
+ * Uses built-in room to store logs
+ * *Defaults to `true`
+ *
+ * *Note : Logs are stored for max time-period of seven days of logging.
+ * After that they are deleted by [PinLog] service.*
+ *
+ * @param boolean Whether to store the logs
+ *
+ * */
+ fun setDoStoreLogs(boolean: Boolean) {
+ shouldStoreLogs = boolean
+ }
+
+ /**
+ * Useful if you want build info in pre-logs
+ * @param buildConfig The BuildConfig generated by gradle
+ * */
+ @JvmStatic
+ fun setBuildConfigClass(buildConfig: Class<*>) {
+ buildConfigClass = buildConfig
+ }
+
+ /**
+ * Initialises [PinLog]
+ * Defaults :
+ * - [isDevLoggingEnabled] = `false`
+ * - [shouldStoreLogs] = `true`
+ *
+ * *Note : that the properties will be overwritten by new one if [PinLog] was previously initialised.*
+ * @param application Application. Commonly used inside [Application.onCreate] method of your [Application] class
+ * @return [Boolean] - If [PinLog] was previously initialised then `false` else `true`.
+ *
+ * */
+ @JvmStatic
+ fun initialise(@NonNull application: Application): Boolean {
+ return initialise(
+ application = application,
+ setDevLoggingEnabled = false,
+ setDoStoreLogs = true,
+ buildConfig = null
+ )
+ }
+
+ /**
+ * Initialises [PinLog]
+ *
+ *
+ * *Note : that the properties will be overwritten by new one if [PinLog] was previously initialised.*
+ * @param application Application. Commonly used inside [Application.onCreate] method of your [Application] class
+ * @return [Boolean] - If [PinLog] was previously initialised then `false` else `true`.
+ *
+ * */
+ @JvmStatic
+ fun initialise(@NonNull application: Application, setDevLoggingEnabled: Boolean): Boolean {
+ return initialise(
+ application = application,
+ setDevLoggingEnabled = setDevLoggingEnabled,
+ setDoStoreLogs = true,
+ buildConfig = null
+ )
+ }
+
+ /**
+ * Initialises [PinLog]
+ *
+ * *Note : that the properties will be overwritten by new one if [PinLog] was previously initialised.*
+ * @param application Application. Commonly used inside [Application.onCreate] method of your [Application] class
+ * @return [Boolean] - If [PinLog] was previously initialised then `false` else `true`.
+ *
+ * */
+ @JvmStatic
+ fun initialise(
+ @NonNull application: Application,
+ setDevLoggingEnabled: Boolean,
+ buildConfig: Class<*>?
+ ): Boolean {
+ return initialise(
+ application = application,
+ setDevLoggingEnabled = setDevLoggingEnabled,
+ setDoStoreLogs = true,
+ buildConfig = buildConfig
+ )
+ }
+
+ /**
+ * Initialises [PinLog]
+ *
+ * *Note : that the properties will be overwritten by new one if [PinLog] was previously initialised.*
+ * @param application Application. Commonly used inside [Application.onCreate] method of your [Application] class
+ * @return [Boolean] - If [PinLog] was previously initialised then `false` else `true`.
+ *
+ * */
+ @JvmStatic
+ fun initialise(
+ @NonNull application: Application,
+ setDevLoggingEnabled: Boolean,
+ setDoStoreLogs: Boolean,
+ buildConfig: Class<*>?
+ ): Boolean {
+ if (loggingStyle == null) {
+ loggingStyle = DefaultApplicationLoggingStyle()
+ }
+ return instance(application)
+ }
+
+
+ /**
+ * Uses built-in database to store logs
+ * *Defaults to [customLoggingStyle] = [DefaultApplicationLoggingStyle]
+ *
+ * @param customLoggingStyle To set this property to custom string log format override [LoggingStyle.getFormattedLogData] method
+ *
+ * */
+ fun setLogFormatting(customLoggingStyle: LoggingStyle?) {
+ loggingStyle = customLoggingStyle
+ }
+
+ /**
+ * If set to `true`, uses built-in PinLog [Thread.UncaughtExceptionHandler] , this
+ * is created in a way that it starts a Share Intent with logs.
+ * *Defaults to `false`
+ *
+ * If you are setting [enabled] to `true` then also add toEmail
+ *
+ * */
+ @JvmStatic
+ fun setPinLogExceptionHandlerEnabled(enabled: Boolean, toEmail: String?) {
+ if (exceptionHandlerEnabled && !enabled) {
+ if (Thread.getDefaultUncaughtExceptionHandler() == PinLogUncaughtExceptionHandler()) {
+ Thread.setDefaultUncaughtExceptionHandler(defaultExceptionHandler)
+ }
+ } else {
+ exceptionHandlerEnabled = enabled
+ sendToEmail = toEmail
+ logInfo("Exception handling is setTo PinLog")
+ setupExceptionHandler()
+ }
+ }
+
+ /**
+ * Initialises [PinLog] in Debug mode that is with all Debug Properties like [isDevLoggingEnabled] to true.
+ * More suitable **for Debug Builds**.
+ *
+ * *Note : that the properties will be overwritten by new one if [PinLog] was previously initialised.*
+ * @param application Application. Commonly used inside [Application.onCreate] method of your [Application] class.
+ * @param buildConfig The BuildConfig class generated by Gradle when you build the project.
+ * @param logingStyle Custom implementation of the [LoggingStyle] class. Default is [DefaultApplicationLoggingStyle]
+ * @return [Boolean] - If [PinLog] was previously initialised then `false` else `true`.
+ *
+ * */
+ @JvmStatic
+ fun initialiseDebug(
+ @NonNull application: Application,
+ buildConfig: Class<*>? = null,
+ logingStyle: LoggingStyle? = null
+ ): Boolean {
+ isDevLoggingEnabled = true
+ shouldStoreLogs = true
+ buildConfig?.let {
+ buildConfigClass = it
+ }
+ loggingStyle = if (loggingStyle == null) {
+ DefaultApplicationLoggingStyle()
+ } else logingStyle
+
+ return instance(application)
+ }
+
+ /**
+ * Initialises [PinLog] in Release mode that is with all Non-Debug Properties like [setDevLogging] to `false`.
+ * This mode is great **for Release Builds** where your app should not
+ * print any logs with the [Log] util class.
+ *
+ * **This means PinLog will not print the Logs to the Logcat, but store them instead
+ * which you can get by [PinLog.getAllPinLogs] method**
+ *
+ * *Note : that the properties will be overwritten by new one if [PinLog] was previously initialised.*
+ * @param application Application. Commonly used inside [Application.onCreate] method of your [Application] class.
+ * @param buildConfig The BuildConfig class generated by Gradle when you build the project.
+ * @param logingStyle Custom implementation of the [LoggingStyle] class. Default is [DefaultApplicationLoggingStyle]
+ * @return [Boolean] - If [PinLog] was previously initialised then `false` else `true`.
+ *
+ * */
+ @JvmStatic
+ fun initialiseRelease(
+ @NonNull application: Application,
+ buildConfig: Class<*>? = null,
+ logingStyle: LoggingStyle? = null
+ ): Boolean {
+ isDevLoggingEnabled = false
+ shouldStoreLogs = true
+ buildConfig?.let {
+ buildConfigClass = it
+ }
+ loggingStyle = if (loggingStyle == null) {
+ DefaultApplicationLoggingStyle()
+ } else logingStyle
+ return instance(application)
+ }
+
+ private fun collectBuildConfigData() {
+ val data = JSONObject()
+ buildConfigClass?.let { someClass ->
+ val fields = someClass.fields
+ for (field in fields) {
+ try {
+ val value: Any? = field[null]
+
+ value?.let {
+ logI("BuildConfig", " ${field.name} -> $it ")
+ if (field.type.isArray) {
+ data.put(field.name, JSONArray(listOf(*it as Array<*>)))
+ } else {
+ data.put(field.name, it)
+ }
+ }
+
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ if (data.has("DEBUG")) {
+ val isDebug = data.getBoolean("DEBUG")
+ if (isDebug) logI(CLASS_TAG, "Application Build is of Type Debug")
+ }
+ }
+
+ private class PinLogUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
+
+ override fun uncaughtException(t: Thread, e: Throwable) {
+ logWarning("An UncaughtException was caught by PinLog in ${t.name}")
+ val stackTrace = getStackTraceString(e)
+ logE("PinLogExceptionInfo", stackTrace, e)
+ try {
+ val i = Intent(Intent.ACTION_SEND)
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ sendToEmail?.let {
+ i.putExtra(Intent.EXTRA_EMAIL, it)
+ }
+ val text =
+ "\n$callerAppName crashed due to an unknown error on ${Date()}" +
+ "\n\n" +
+ "$callerAppName crashed unexpectedly" +
+ "\n\n" +
+ "**Crash Information**\n" +
+ "------------------------beginning of crash\n" +
+ stackTrace +
+ "\n------------------------end of crash\n"
+ i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ i.putExtra(Intent.EXTRA_TEXT, text)
+ i.type = "text/plain"
+ getContext().applicationContext.startActivity(i)
+ exitProcess(0)
+
+ } catch (e: Exception) {
+ e.printStackTrace()
+ defaultExceptionHandler?.uncaughtException(t, e)
+ }
+ }
+ }
+
+ /**
+ *
+ * **Warning : Do not use this value as your TAG property as logs
+ * having this [CLASS_TAG] are ignored.**
+ *
+ * */
+ internal const val CLASS_TAG = "PinLog"
+
+}
+
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogDataSource.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogDataSource.kt
new file mode 100644
index 0000000..0bc6065
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogDataSource.kt
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog.database
+
+internal interface ApplicationLogDataSource {
+ fun getPinLogsCount(): Int
+ fun getPinLogsGroupCount(): Int
+ fun insertPinLog(applicationLog: ApplicationLogModel) : Boolean
+ fun deleteAllPinLogs()
+ fun getPinLogs(group: Int): List?
+ fun getAllPinLogs(): List
+ fun getAllPinLogsAsStringList() : List
+ fun deletedExpiredPinLogs(expiryTimeInSeconds: Int)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogDatabaseHelper.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogDatabaseHelper.kt
new file mode 100644
index 0000000..25d0d25
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogDatabaseHelper.kt
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog.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 com.adityaamolbavadekar.pinlog.ApplicationLogsContract
+import com.adityaamolbavadekar.pinlog.ApplicationLogsContract.ApplicationLogEntry.COLUMN_NAME_ID
+import com.adityaamolbavadekar.pinlog.ApplicationLogsContract.ApplicationLogEntry.COLUMN_NAME_LOG
+import com.adityaamolbavadekar.pinlog.ApplicationLogsContract.ApplicationLogEntry.COLUMN_NAME_LOG_LEVEL
+import com.adityaamolbavadekar.pinlog.ApplicationLogsContract.ApplicationLogEntry.DATABASE_VERSION
+import com.adityaamolbavadekar.pinlog.ApplicationLogsContract.ApplicationLogEntry.TABLE_NAME
+import com.adityaamolbavadekar.pinlog.PinLog
+import com.adityaamolbavadekar.pinlog.PinLog.CLASS_TAG
+
+internal class ApplicationLogDatabaseHelper(c: Context) : SQLiteOpenHelper(
+ c,
+ TABLE_NAME,
+ null,
+ DATABASE_VERSION
+), ApplicationLogDataSource {
+
+ private var database: SQLiteDatabase? = null
+
+ private fun initializePinLogsDatabase() {
+ if (database == null) {
+ database = this.writableDatabase
+ }
+ }
+
+ /*START [SQLiteOpenHelper]*/
+ override fun onCreate(db: SQLiteDatabase?) {
+ ApplicationLogsContract.onCreate(db)
+ }
+
+ override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
+ ApplicationLogsContract.onUpgrade(db, oldVersion, newVersion)
+ }
+
+ /*END [SQLiteOpenHelper]*/
+
+ override fun getPinLogsCount(): Int {
+ // Initialize SQLiteDatabase if it is null
+ initializePinLogsDatabase()
+ var count = 0
+ try {
+ if (database != null) {
+ count = DatabaseUtils.queryNumEntries(database, TABLE_NAME).toInt()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ PinLog.logError(
+ "PinLogTable: Exception occurred in Database while executing getCount: $e", e
+ )
+ }
+ return count
+ }
+
+ override fun getPinLogsGroupCount(): Int {
+ val c = getPinLogsCount()
+ return if (c <= 0) {
+ 0
+ } else {
+ (c / 5000)
+ }
+ }
+
+ override fun insertPinLog(applicationLog: ApplicationLogModel): Boolean {
+ val errorCode: Long = -1
+ var rowId: Long = -1
+ database?.let { db ->
+ val contentValues = ContentValues()
+ contentValues.put(COLUMN_NAME_LOG, applicationLog.LOG)
+ contentValues.put(COLUMN_NAME_LOG_LEVEL, applicationLog.LOG_LEVEL)
+
+ try {
+ rowId = db.insert(TABLE_NAME, null, contentValues)
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ PinLog.logError(
+ "PinLogTable: Exception occurred in Database while executing insertPinLog: $e",
+ e
+ )
+ }
+ }
+ return (rowId != errorCode)
+ }
+
+ override fun deleteAllPinLogs() {
+ database?.let { db ->
+
+ try {
+ db.delete(TABLE_NAME, null, null)
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ PinLog.logError(
+ "PinLogTable: Exception occurred in Database while executing deleteAllPinLogs: $e",
+ e
+ )
+ }
+
+ }
+ }
+
+ override fun getPinLogs(group: Int): List {
+ return emptyList()//Not implemented
+ }
+
+ private fun getCursor(db: SQLiteDatabase): Cursor? {
+ return db.query(
+ TABLE_NAME,
+ arrayOf(
+ COLUMN_NAME_ID,//0
+ COLUMN_NAME_LOG,//1
+ COLUMN_NAME_LOG_LEVEL//2
+ ),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ )
+ }
+
+ override fun getAllPinLogs(): List {
+ val applicationLogsList: MutableList = mutableListOf()
+ database?.let { db ->
+ val c = getCursor(db)
+
+ if (c == null || c.isClosed) return emptyList()
+ else try {
+ if (c.moveToFirst()) {
+ do {
+ if (c.isClosed) {
+ break
+ }
+ val id: Int = c.getInt(0)
+ val log: String = c.getString(1)
+ val level: Int = c.getInt(2)
+ val pinLog = ApplicationLogModel(id, log, level)
+ applicationLogsList.add(pinLog)
+
+ } while (c.moveToNext())
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ PinLog.logE(
+ CLASS_TAG,
+ "PinLogTable: Exception occurred in Database while executing getPinLogs: $e"
+ )
+ }
+ c.close()
+ return applicationLogsList.toList()
+
+ }
+ return applicationLogsList.toList()
+ }
+
+ override fun getAllPinLogsAsStringList(): List {
+ val pinLogsList: MutableList = mutableListOf()
+ database?.let { db ->
+ val c = getCursor(db)
+
+ if (c == null || c.isClosed) return emptyList()
+ else try {
+ if (c.moveToFirst()) {
+ do {
+ if (c.isClosed) {
+ break
+ }
+ val log: String = c.getString(1)
+ pinLogsList.add(log)
+ } while (c.moveToNext())
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ PinLog.logError(
+ "PinLogTable: Exception occurred in Database while executing getAllPinLogsAsStringList: $e",
+ e
+ )
+ }
+ c.close()
+ return pinLogsList.toList()
+
+ }
+ return pinLogsList.toList()
+ }
+
+ override fun deletedExpiredPinLogs(expiryTimeInSeconds: Int) {
+ if (database == null) {
+ return
+ }
+ //Not yet implemented
+ }
+
+ init {
+ initializePinLogsDatabase()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogModel.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogModel.kt
new file mode 100644
index 0000000..f91a503
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/database/ApplicationLogModel.kt
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog.database
+
+data class ApplicationLogModel(val id: Int, var LOG: String, var LOG_LEVEL: Int)
\ No newline at end of file
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/extensions/Extensions.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/extensions/Extensions.kt
new file mode 100644
index 0000000..cc47ad0
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/extensions/Extensions.kt
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog.extensions
+
+import android.app.Application
+import com.adityaamolbavadekar.pinlog.PinLog
+import com.adityaamolbavadekar.pinlog.PinLog.CLASS_TAG
+
+fun Application.initPinLogger(): Boolean {
+ return PinLog.initialise(this)
+}
+
+fun Application.initPinLoggerInDebugMode(): Boolean {
+ return PinLog.initialiseDebug(this)
+}
+
+fun Application.initPinLoggerInReleaseMode(): Boolean {
+ return PinLog.initialiseRelease(this)
+}
+
+inline fun debug(m: () -> String) {
+ PinLog.logD("?", m.invoke())
+}
+
+inline fun warn(m: () -> String) {
+ PinLog.logW("?", m.invoke())
+}
+
+inline fun info(m: () -> String) {
+ PinLog.logI("?", m.invoke())
+}
+
+inline fun error(m: () -> String) {
+ PinLog.logE("?", m.invoke())
+}
+
+inline fun error(e: Exception, m: () -> String) {
+ PinLog.logE("?", m.invoke(), e)
+}
+
+fun MutableList.submitLog(log: String) {
+ for (it in this) {
+ try {
+ it.onLogAdded(log)
+ } catch (e: Exception) {
+ PinLog.logW(CLASS_TAG, "Could not notify $it about newly added log")
+ }
+ }
+
+}
+
diff --git a/app/src/main/java/com/adityaamolbavadekar/pinlog/extensions/PinLoggingExtensions.kt b/app/src/main/java/com/adityaamolbavadekar/pinlog/extensions/PinLoggingExtensions.kt
new file mode 100644
index 0000000..cc6a64f
--- /dev/null
+++ b/app/src/main/java/com/adityaamolbavadekar/pinlog/extensions/PinLoggingExtensions.kt
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Aditya Bavadekar
+ *
+ * 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.adityaamolbavadekar.pinlog.extensions
+
+import android.content.Context
+import com.adityaamolbavadekar.pinlog.PinLog
+
+/*ERROR LOGGING*/
+
+/**
+ * **Logs an Error Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logE]
+ *
+ * */
+fun Context.logE(s: String?) {
+ PinLog.logE(getName(), s ?: "")
+}
+
+/**
+ * **Logs an Error Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logE]
+ *
+ * */
+fun Context.logE(s: String?, throwable: Throwable?) {
+ PinLog.logE(getName(), s, throwable ?: Exception(""))
+}
+
+/*INFO LOGGING*/
+
+/**
+ * **Logs an Info Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logI]
+ *
+ * */
+fun Context.logI(s: String?) {
+ PinLog.logI(getName(), s ?: "")
+}
+
+/**
+ * **Logs an Info Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logI]
+ *
+ * */
+fun Context.logI(s: String?, throwable: Throwable?) {
+ PinLog.logI(getName(), s, throwable ?: Exception(""))
+}
+
+/*WARNING LOGGING*/
+
+/**
+ * **Logs an Warn Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logW]
+ *
+ * */
+fun Context.logW(s: String?) {
+ PinLog.logW(getName(), s ?: "")
+}
+
+/**
+ * **Logs an Warn Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logW]
+ *
+ * */
+fun Context.logW(s: String?, throwable: Throwable?) {
+ PinLog.logW(getName(), s, throwable ?: Exception(""))
+}
+
+/*DEBUG LOGGING*/
+
+/**
+ * **Logs an Debug Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logD]
+ *
+ * */
+fun Context.logD(s: String?) {
+ PinLog.logD(getName(), s ?: "")
+}
+
+/**
+ * **Logs an Debug Log**
+ *
+ * **Logs to Logcat if [PinLog.setDevLogging] is set to `true`**
+ *
+ * **Stores the log if [PinLog.setDoStoreLogs] is set to `true`**
+ *
+ * @see [PinLog.logD]
+ *
+ * */
+fun Context.logD(s: String?, throwable: Throwable?) {
+ PinLog.logD(getName(), s, throwable ?: Exception(""))
+}
+
+
+/*Others*/
+
+private fun Context.getName(): String {
+ return this.javaClass.simpleName
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..28c73bc
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,26 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext.kotlin_version = "1.4.21"
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.0.0-alpha09"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..e6a071d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# 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=-Xmx1024m
+# 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
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7b55fd6
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu May 26 12:29:15 IST 2022
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@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
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..823f94d
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name = "PinLog Library"
\ No newline at end of file