diff --git a/app/build.gradle b/app/build.gradle
index 0c1b9155..07caa487 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,90 +6,90 @@ apply plugin: 'kotlin-kapt'
def fileProvider = "file_provider"
android {
- compileSdkVersion 29
- buildToolsVersion "29.0.3"
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
- defaultConfig {
- applicationId "com.dp.logcatapp"
- minSdkVersion 16
- targetSdkVersion 29
- versionCode 32
- versionName "1.7.1"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- vectorDrawables.useSupportLibrary = true
- }
+ defaultConfig {
+ applicationId "com.dp.logcatapp"
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 32
+ versionName "1.7.1"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables.useSupportLibrary = true
+ }
- signingConfigs {
- release {
- try {
- def keystoreProperties = new Properties()
- keystoreProperties.load(new FileInputStream(rootProject.file("keystore.properties")))
- keyAlias keystoreProperties['keyAlias']
- keyPassword keystoreProperties['keyPassword']
- storeFile file(keystoreProperties['storeFile'])
- storePassword keystoreProperties['storePassword']
- } catch (FileNotFoundException ignored) {
- println("keystore file not found.")
- }
- }
+ signingConfigs {
+ release {
+ try {
+ def keystoreProperties = new Properties()
+ keystoreProperties.load(new FileInputStream(rootProject.file("keystore.properties")))
+ keyAlias keystoreProperties['keyAlias']
+ keyPassword keystoreProperties['keyPassword']
+ storeFile file(keystoreProperties['storeFile'])
+ storePassword keystoreProperties['storePassword']
+ } catch (FileNotFoundException ignored) {
+ println("keystore file not found.")
+ }
}
+ }
- dexOptions {
- javaMaxHeapSize "2g"
- }
+ dexOptions {
+ javaMaxHeapSize "2g"
+ }
- lintOptions {
- abortOnError false
- disable 'MissingTranslation'
- }
+ lintOptions {
+ abortOnError false
+ disable 'MissingTranslation'
+ }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_1_8
- }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
- buildTypes {
- buildTypes.each {
- it.buildConfigField("String", "FILE_PROVIDER", "\"$fileProvider\"")
- it.addManifestPlaceholders(["fileProvider": fileProvider])
- }
- release {
- signingConfig signingConfigs.release
- minifyEnabled false
- shrinkResources false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
+ buildTypes {
+ buildTypes.each {
+ it.buildConfigField("String", "FILE_PROVIDER", "\"$fileProvider\"")
+ it.addManifestPlaceholders(["fileProvider": fileProvider])
+ }
+ release {
+ signingConfig signingConfigs.release
+ minifyEnabled false
+ shrinkResources false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
+ }
}
dependencies {
- implementation fileTree(include: ['*.jar'], dir: 'libs')
- implementation project(':collections')
- implementation project(':logcat')
- implementation project(':logger')
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
- implementation "androidx.core:core-ktx:$core_ktx_version"
- implementation "androidx.fragment:fragment-ktx:$fragment_ktx_version"
- implementation "androidx.appcompat:appcompat:$appcompat_version"
- implementation "com.google.android.material:material:$material_version"
- implementation "androidx.preference:preference:$preference_version"
- implementation "androidx.legacy:legacy-preference-v14:$legacy_preference_v14_version"
- implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_common_java8_version"
- implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_extensions_version"
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_viewmodel_ktx_version"
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_runtime_version"
- implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version"
- implementation "androidx.room:room-runtime:$room_version"
- implementation "androidx.documentfile:documentfile:$documentfile_version"
- kapt "androidx.room:room-compiler:$room_version"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_core_version"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_android_version"
- testImplementation "junit:junit:$junit_version"
- androidTestImplementation "androidx.test:runner:$runner_version"
- androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_core_version"
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation project(':collections')
+ implementation project(':logcat')
+ implementation project(':logger')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ implementation "androidx.core:core-ktx:$core_ktx_version"
+ implementation "androidx.fragment:fragment-ktx:$fragment_ktx_version"
+ implementation "androidx.appcompat:appcompat:$appcompat_version"
+ implementation "com.google.android.material:material:$material_version"
+ implementation "androidx.preference:preference:$preference_version"
+ implementation "androidx.legacy:legacy-preference-v14:$legacy_preference_v14_version"
+ implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_common_java8_version"
+ implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_extensions_version"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_viewmodel_ktx_version"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_runtime_version"
+ implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version"
+ implementation "androidx.room:room-runtime:$room_version"
+ implementation "androidx.documentfile:documentfile:$documentfile_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_core_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_android_version"
+ testImplementation "junit:junit:$junit_version"
+ androidTestImplementation "androidx.test:runner:$runner_version"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_core_version"
}
diff --git a/app/src/androidTest/java/com/dp/logcatapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/dp/logcatapp/ExampleInstrumentedTest.kt
index 7e08adc0..87dbe895 100644
--- a/app/src/androidTest/java/com/dp/logcatapp/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/com/dp/logcatapp/ExampleInstrumentedTest.kt
@@ -2,12 +2,10 @@ package com.dp.logcatapp
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
-
+import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -15,10 +13,10 @@ import org.junit.Assert.*
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
- @Test
- fun useAppContext() {
- // Context of the app under test.
- val appContext = InstrumentationRegistry.getTargetContext()
- assertEquals("com.dp.logcatapp", appContext.packageName)
- }
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getTargetContext()
+ assertEquals("com.dp.logcatapp", appContext.packageName)
+ }
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8a45b707..3d4a86a3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,100 +3,100 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.dp.logcatapp">
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/LogcatApp.kt b/app/src/main/java/com/dp/logcatapp/LogcatApp.kt
index 71664e50..84587aaa 100644
--- a/app/src/main/java/com/dp/logcatapp/LogcatApp.kt
+++ b/app/src/main/java/com/dp/logcatapp/LogcatApp.kt
@@ -5,8 +5,8 @@ import com.dp.logger.Logger
@Suppress("unused")
class LogcatApp : Application() {
- override fun onCreate() {
- super.onCreate()
- Logger.init("LogcatReader")
- }
+ override fun onCreate() {
+ super.onCreate()
+ Logger.init("LogcatReader")
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/activities/BaseActivityWithToolbar.kt b/app/src/main/java/com/dp/logcatapp/activities/BaseActivityWithToolbar.kt
index fb938936..d1381668 100644
--- a/app/src/main/java/com/dp/logcatapp/activities/BaseActivityWithToolbar.kt
+++ b/app/src/main/java/com/dp/logcatapp/activities/BaseActivityWithToolbar.kt
@@ -21,70 +21,70 @@ import com.dp.logcatapp.util.setTheme
@SuppressLint("Registered")
abstract class BaseActivityWithToolbar : AppCompatActivity() {
- companion object {
- init {
- AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
- }
+ companion object {
+ init {
+ AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
}
+ }
- lateinit var toolbar: Toolbar
- private set
- protected val handler = Handler()
+ lateinit var toolbar: Toolbar
+ private set
+ protected val handler = Handler()
- override fun onCreate(savedInstanceState: Bundle?) {
- PreferenceManager.setDefaultValues(this, R.xml.settings, false)
- setTheme()
- if (Build.VERSION.SDK_INT >= 21) {
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- }
- super.onCreate(savedInstanceState)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ PreferenceManager.setDefaultValues(this, R.xml.settings, false)
+ setTheme()
+ if (Build.VERSION.SDK_INT >= 21) {
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
+ super.onCreate(savedInstanceState)
+ }
- protected fun setupToolbar() {
- toolbar = findViewById(getToolbarIdRes())
- if (Build.VERSION.SDK_INT == 19) {
- setAppBarPaddingForKitkat(toolbar.parent as ViewGroup)
- } else if (Build.VERSION.SDK_INT >= 21) {
- ViewCompat.setOnApplyWindowInsetsListener(toolbar) { view, insets ->
- val viewGroup = view.parent as ViewGroup
- viewGroup.setPadding(0, insets.systemWindowInsetTop, 0, 0)
- insets.consumeSystemWindowInsets()
- }
- }
- setSupportActionBar(toolbar)
- supportActionBar?.title = getToolbarTitle()
+ protected fun setupToolbar() {
+ toolbar = findViewById(getToolbarIdRes())
+ if (Build.VERSION.SDK_INT == 19) {
+ setAppBarPaddingForKitkat(toolbar.parent as ViewGroup)
+ } else if (Build.VERSION.SDK_INT >= 21) {
+ ViewCompat.setOnApplyWindowInsetsListener(toolbar) { view, insets ->
+ val viewGroup = view.parent as ViewGroup
+ viewGroup.setPadding(0, insets.systemWindowInsetTop, 0, 0)
+ insets.consumeSystemWindowInsets()
+ }
}
+ setSupportActionBar(toolbar)
+ supportActionBar?.title = getToolbarTitle()
+ }
- private fun setAppBarPaddingForKitkat(viewGroup: ViewGroup) {
- val dm = DisplayMetrics()
- windowManager.defaultDisplay.getMetrics(dm)
- val topPadding = (dm.scaledDensity * 25).toInt()
- viewGroup.setPadding(0, topPadding, 0, 0)
- }
+ private fun setAppBarPaddingForKitkat(viewGroup: ViewGroup) {
+ val dm = DisplayMetrics()
+ windowManager.defaultDisplay.getMetrics(dm)
+ val topPadding = (dm.scaledDensity * 25).toInt()
+ viewGroup.setPadding(0, topPadding, 0, 0)
+ }
- protected abstract fun getToolbarIdRes(): Int
+ protected abstract fun getToolbarIdRes(): Int
- protected abstract fun getToolbarTitle(): String
+ protected abstract fun getToolbarTitle(): String
- protected fun enableDisplayHomeAsUp() {
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- }
+ protected fun enableDisplayHomeAsUp() {
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ }
- override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
- android.R.id.home -> {
- val upIntent = NavUtils.getParentActivityIntent(this)
- if (upIntent != null) {
- if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
- TaskStackBuilder.create(this)
- .addNextIntentWithParentStack(upIntent)
- .startActivities()
- } else {
- NavUtils.navigateUpTo(this, upIntent)
- }
- }
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
+ override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
+ android.R.id.home -> {
+ val upIntent = NavUtils.getParentActivityIntent(this)
+ if (upIntent != null) {
+ if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
+ TaskStackBuilder.create(this)
+ .addNextIntentWithParentStack(upIntent)
+ .startActivities()
+ } else {
+ NavUtils.navigateUpTo(this, upIntent)
+ }
+ }
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/activities/FiltersActivity.kt b/app/src/main/java/com/dp/logcatapp/activities/FiltersActivity.kt
index 744b6bab..cf4ee1e4 100644
--- a/app/src/main/java/com/dp/logcatapp/activities/FiltersActivity.kt
+++ b/app/src/main/java/com/dp/logcatapp/activities/FiltersActivity.kt
@@ -8,35 +8,37 @@ import com.dp.logcatapp.fragments.filters.FiltersFragment
class FiltersActivity : BaseActivityWithToolbar() {
- companion object {
- val TAG = FiltersActivity::class.qualifiedName
- val EXTRA_EXCLUSIONS = TAG + "_exclusions"
- val KEY_LOG = TAG + "_key_log"
+ companion object {
+ val TAG = FiltersActivity::class.qualifiedName
+ val EXTRA_EXCLUSIONS = TAG + "_exclusions"
+ val KEY_LOG = TAG + "_key_log"
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_filters)
+ setupToolbar()
+ enableDisplayHomeAsUp()
+
+ if (savedInstanceState == null) {
+ supportFragmentManager.commit {
+ replace(
+ R.id.content_frame, FiltersFragment.newInstance(getLog(), isExclusions()),
+ FiltersFragment.TAG
+ )
+ }
}
+ }
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_filters)
- setupToolbar()
- enableDisplayHomeAsUp()
-
- if (savedInstanceState == null) {
- supportFragmentManager.commit {
- replace(R.id.content_frame, FiltersFragment.newInstance(getLog(), isExclusions()),
- FiltersFragment.TAG)
- }
- }
- }
-
- private fun isExclusions() = intent != null && intent.getBooleanExtra(EXTRA_EXCLUSIONS, false)
+ private fun isExclusions() = intent != null && intent.getBooleanExtra(EXTRA_EXCLUSIONS, false)
- private fun getLog() = intent.getParcelableExtra(KEY_LOG)
+ private fun getLog() = intent.getParcelableExtra(KEY_LOG)
- override fun getToolbarIdRes() = R.id.toolbar
+ override fun getToolbarIdRes() = R.id.toolbar
- override fun getToolbarTitle(): String = if (isExclusions()) {
- getString(R.string.exclusions)
- } else {
- getString(R.string.filters)
- }
+ override fun getToolbarTitle(): String = if (isExclusions()) {
+ getString(R.string.exclusions)
+ } else {
+ getString(R.string.filters)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/activities/MainActivity.kt b/app/src/main/java/com/dp/logcatapp/activities/MainActivity.kt
index 39208197..534cbb9e 100644
--- a/app/src/main/java/com/dp/logcatapp/activities/MainActivity.kt
+++ b/app/src/main/java/com/dp/logcatapp/activities/MainActivity.kt
@@ -15,107 +15,115 @@ import com.dp.logcatapp.util.setKeepScreenOn
import com.dp.logcatapp.util.showToast
class MainActivity : BaseActivityWithToolbar() {
- private var canExit = false
+ private var canExit = false
- private val exitRunnable = Runnable {
- canExit = false
- }
+ private val exitRunnable = Runnable {
+ canExit = false
+ }
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- setupToolbar()
-
- if (checkShouldTheAppExit(intent)) {
- return
- }
-
- val logcatServiceIntent = Intent(this, LogcatService::class.java)
- if (Build.VERSION.SDK_INT >= 26) {
- startForegroundService(logcatServiceIntent)
- } else {
- startService(logcatServiceIntent)
- }
-
- if (savedInstanceState == null) {
- val stopRecording = intent?.getBooleanExtra(STOP_RECORDING_EXTRA,
- false) == true
- supportFragmentManager.beginTransaction()
- .replace(R.id.content_frame, LogcatLiveFragment.newInstance(stopRecording),
- LogcatLiveFragment.TAG)
- .commit()
- }
- }
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ setupToolbar()
- override fun getToolbarIdRes(): Int = R.id.toolbar
-
- override fun getToolbarTitle(): String = getString(R.string.device_logs)
-
- private fun checkShouldTheAppExit(intent: Intent?): Boolean =
- if (intent?.getBooleanExtra(EXIT_EXTRA, false) == true) {
- canExit = true
- ActivityCompat.finishAfterTransition(this)
- true
- } else {
- false
- }
-
- private fun handleStopRecordingIntent(intent: Intent?) {
- if (intent?.getBooleanExtra(STOP_RECORDING_EXTRA, false) == true) {
- val fragment = supportFragmentManager.findFragmentByTag(LogcatLiveFragment.TAG)
- if (fragment != null) {
- (fragment as LogcatLiveFragment).tryStopRecording()
- }
- }
+ if (checkShouldTheAppExit(intent)) {
+ return
}
- override fun onNewIntent(intent: Intent?) {
- super.onNewIntent(intent)
- checkShouldTheAppExit(intent)
- handleStopRecordingIntent(intent)
+ val logcatServiceIntent = Intent(this, LogcatService::class.java)
+ if (Build.VERSION.SDK_INT >= 26) {
+ startForegroundService(logcatServiceIntent)
+ } else {
+ startService(logcatServiceIntent)
}
- override fun onResume() {
- super.onResume()
- setKeepScreenOn(getDefaultSharedPreferences().getBoolean(PreferenceKeys.General.KEY_KEEP_SCREEN_ON,
- PreferenceKeys.General.Default.KEY_KEEP_SCREEN_ON))
+ if (savedInstanceState == null) {
+ val stopRecording = intent?.getBooleanExtra(
+ STOP_RECORDING_EXTRA,
+ false
+ ) == true
+ supportFragmentManager.beginTransaction()
+ .replace(
+ R.id.content_frame, LogcatLiveFragment.newInstance(stopRecording),
+ LogcatLiveFragment.TAG
+ )
+ .commit()
}
+ }
- override fun onDestroy() {
- super.onDestroy()
- if (canExit) {
- stopService(Intent(this, LogcatService::class.java))
- }
- }
+ override fun getToolbarIdRes(): Int = R.id.toolbar
- override fun onBackPressed() {
- if (canExit) {
- handler.removeCallbacks(exitRunnable)
- super.onBackPressed()
- } else {
- canExit = true
- showToast(getString(R.string.press_back_again_to_exit))
- handler.postDelayed(exitRunnable, EXIT_DOUBLE_PRESS_DELAY)
- }
- }
+ override fun getToolbarTitle(): String = getString(R.string.device_logs)
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.main, menu)
- return super.onCreateOptionsMenu(menu)
+ private fun checkShouldTheAppExit(intent: Intent?): Boolean =
+ if (intent?.getBooleanExtra(EXIT_EXTRA, false) == true) {
+ canExit = true
+ ActivityCompat.finishAfterTransition(this)
+ true
+ } else {
+ false
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
- R.id.action_settings -> {
- startActivity(Intent(this, SettingsActivity::class.java))
- true
- }
- else -> super.onOptionsItemSelected(item)
+ private fun handleStopRecordingIntent(intent: Intent?) {
+ if (intent?.getBooleanExtra(STOP_RECORDING_EXTRA, false) == true) {
+ val fragment = supportFragmentManager.findFragmentByTag(LogcatLiveFragment.TAG)
+ if (fragment != null) {
+ (fragment as LogcatLiveFragment).tryStopRecording()
+ }
}
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ checkShouldTheAppExit(intent)
+ handleStopRecordingIntent(intent)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ setKeepScreenOn(
+ getDefaultSharedPreferences().getBoolean(
+ PreferenceKeys.General.KEY_KEEP_SCREEN_ON,
+ PreferenceKeys.General.Default.KEY_KEEP_SCREEN_ON
+ )
+ )
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ if (canExit) {
+ stopService(Intent(this, LogcatService::class.java))
+ }
+ }
+
+ override fun onBackPressed() {
+ if (canExit) {
+ handler.removeCallbacks(exitRunnable)
+ super.onBackPressed()
+ } else {
+ canExit = true
+ showToast(getString(R.string.press_back_again_to_exit))
+ handler.postDelayed(exitRunnable, EXIT_DOUBLE_PRESS_DELAY)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.main, menu)
+ return super.onCreateOptionsMenu(menu)
+ }
- companion object {
- val TAG = MainActivity::class.qualifiedName
- val EXIT_EXTRA = TAG + "_extra_exit"
- val STOP_RECORDING_EXTRA = TAG + "_stop_recording_extra"
- private const val EXIT_DOUBLE_PRESS_DELAY: Long = 2000
+ override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
+ R.id.action_settings -> {
+ startActivity(Intent(this, SettingsActivity::class.java))
+ true
}
+ else -> super.onOptionsItemSelected(item)
+ }
+
+ companion object {
+ val TAG = MainActivity::class.qualifiedName
+ val EXIT_EXTRA = TAG + "_extra_exit"
+ val STOP_RECORDING_EXTRA = TAG + "_stop_recording_extra"
+ private const val EXIT_DOUBLE_PRESS_DELAY: Long = 2000
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/activities/SavedLogsActivity.kt b/app/src/main/java/com/dp/logcatapp/activities/SavedLogsActivity.kt
index e2d95245..e899b5e3 100644
--- a/app/src/main/java/com/dp/logcatapp/activities/SavedLogsActivity.kt
+++ b/app/src/main/java/com/dp/logcatapp/activities/SavedLogsActivity.kt
@@ -13,103 +13,105 @@ import com.dp.logcatapp.fragments.savedlogs.SavedLogsFragment
class SavedLogsActivity : BaseActivityWithToolbar() {
- companion object {
- val TAG = SavedLogsActivity::class.qualifiedName
+ companion object {
+ val TAG = SavedLogsActivity::class.qualifiedName
+ }
+
+ lateinit var cabToolbar: Toolbar
+ private set
+ private var cabToolbarCallback: CabToolbarCallback? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_saved_logs)
+ setupToolbar()
+ enableDisplayHomeAsUp()
+
+ cabToolbar = findViewById(R.id.cabToolbar)
+ cabToolbar.inflateMenu(R.menu.saved_logs_cab)
+ cabToolbar.isClickable = true
+ cabToolbar.setNavigationIcon(R.drawable.ic_clear_white_24dp)
+ cabToolbar.setNavigationOnClickListener {
+ closeCabToolbar()
}
- lateinit var cabToolbar: Toolbar
- private set
- private var cabToolbarCallback: CabToolbarCallback? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_saved_logs)
- setupToolbar()
- enableDisplayHomeAsUp()
-
- cabToolbar = findViewById(R.id.cabToolbar)
- cabToolbar.inflateMenu(R.menu.saved_logs_cab)
- cabToolbar.isClickable = true
- cabToolbar.setNavigationIcon(R.drawable.ic_clear_white_24dp)
- cabToolbar.setNavigationOnClickListener {
- closeCabToolbar()
- }
-
- if (savedInstanceState == null) {
- supportFragmentManager.commit {
- replace(R.id.content_frame, SavedLogsFragment(), SavedLogsFragment.TAG)
- }
- }
+ if (savedInstanceState == null) {
+ supportFragmentManager.commit {
+ replace(R.id.content_frame, SavedLogsFragment(), SavedLogsFragment.TAG)
+ }
}
+ }
- override fun onBackPressed() {
- if (cabToolbar.visibility == View.VISIBLE) {
- closeCabToolbar()
- return
- }
-
- super.onBackPressed()
+ override fun onBackPressed() {
+ if (cabToolbar.visibility == View.VISIBLE) {
+ closeCabToolbar()
+ return
}
- fun openCabToolbar(callback: CabToolbarCallback,
- @NonNull menuItemClickListener: Toolbar.OnMenuItemClickListener): Boolean {
- cabToolbar.setOnMenuItemClickListener(menuItemClickListener)
- cabToolbarCallback = callback
-
- cabToolbarCallback?.onCabToolbarOpen(cabToolbar)
- cabToolbarCallback?.onCabToolbarInvalidate(cabToolbar)
-
- cabToolbar.alpha = 0.0f
- cabToolbar.scaleX = 1.25f
- cabToolbar.scaleY = 0.75f
- cabToolbar.visibility = View.VISIBLE
- cabToolbar.animate()
- .alpha(1.0f)
- .scaleX(1.0f)
- .scaleY(1.0f)
- .setDuration(resources.getInteger(android.R.integer.config_shortAnimTime).toLong())
- .setInterpolator(DecelerateInterpolator())
- .setListener(null)
- return true
- }
+ super.onBackPressed()
+ }
+
+ fun openCabToolbar(
+ callback: CabToolbarCallback,
+ @NonNull menuItemClickListener: Toolbar.OnMenuItemClickListener
+ ): Boolean {
+ cabToolbar.setOnMenuItemClickListener(menuItemClickListener)
+ cabToolbarCallback = callback
+
+ cabToolbarCallback?.onCabToolbarOpen(cabToolbar)
+ cabToolbarCallback?.onCabToolbarInvalidate(cabToolbar)
+
+ cabToolbar.alpha = 0.0f
+ cabToolbar.scaleX = 1.25f
+ cabToolbar.scaleY = 0.75f
+ cabToolbar.visibility = View.VISIBLE
+ cabToolbar.animate()
+ .alpha(1.0f)
+ .scaleX(1.0f)
+ .scaleY(1.0f)
+ .setDuration(resources.getInteger(android.R.integer.config_shortAnimTime).toLong())
+ .setInterpolator(DecelerateInterpolator())
+ .setListener(null)
+ return true
+ }
+
+ fun closeCabToolbar() {
+ cabToolbarCallback?.onCabToolbarClose(cabToolbar)
+ cabToolbarCallback = null
+
+ cabToolbar.setOnMenuItemClickListener(null)
+ cabToolbar.animate()
+ .alpha(0.0f)
+ .scaleX(1.25f)
+ .scaleY(0.75f)
+ .setDuration(resources.getInteger(android.R.integer.config_shortAnimTime).toLong())
+ .setInterpolator(DecelerateInterpolator())
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ cabToolbar.visibility = View.GONE
+ }
+ })
+ }
- fun closeCabToolbar() {
- cabToolbarCallback?.onCabToolbarClose(cabToolbar)
- cabToolbarCallback = null
-
- cabToolbar.setOnMenuItemClickListener(null)
- cabToolbar.animate()
- .alpha(0.0f)
- .scaleX(1.25f)
- .scaleY(0.75f)
- .setDuration(resources.getInteger(android.R.integer.config_shortAnimTime).toLong())
- .setInterpolator(DecelerateInterpolator())
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- cabToolbar.visibility = View.GONE
- }
- })
- }
+ fun invalidateCabToolbarMenu() {
+ cabToolbarCallback?.onCabToolbarInvalidate(cabToolbar)
+ }
- fun invalidateCabToolbarMenu() {
- cabToolbarCallback?.onCabToolbarInvalidate(cabToolbar)
- }
+ fun isCabToolbarActive() = cabToolbar.visibility == View.VISIBLE
- fun isCabToolbarActive() = cabToolbar.visibility == View.VISIBLE
-
- fun setCabToolbarTitle(title: String) {
- cabToolbar.title = title
- }
+ fun setCabToolbarTitle(title: String) {
+ cabToolbar.title = title
+ }
- override fun getToolbarIdRes(): Int = R.id.toolbar
+ override fun getToolbarIdRes(): Int = R.id.toolbar
- override fun getToolbarTitle(): String = getString(R.string.saved_logs)
+ override fun getToolbarTitle(): String = getString(R.string.saved_logs)
}
interface CabToolbarCallback {
- fun onCabToolbarOpen(toolbar: Toolbar)
+ fun onCabToolbarOpen(toolbar: Toolbar)
- fun onCabToolbarInvalidate(toolbar: Toolbar)
+ fun onCabToolbarInvalidate(toolbar: Toolbar)
- fun onCabToolbarClose(toolbar: Toolbar)
+ fun onCabToolbarClose(toolbar: Toolbar)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/activities/SavedLogsViewerActivity.kt b/app/src/main/java/com/dp/logcatapp/activities/SavedLogsViewerActivity.kt
index 838a7da1..8a9d455f 100644
--- a/app/src/main/java/com/dp/logcatapp/activities/SavedLogsViewerActivity.kt
+++ b/app/src/main/java/com/dp/logcatapp/activities/SavedLogsViewerActivity.kt
@@ -10,38 +10,42 @@ import com.dp.logcatapp.util.setKeepScreenOn
class SavedLogsViewerActivity : BaseActivityWithToolbar() {
- companion object {
- val TAG = SavedLogsViewerActivity::class.qualifiedName
+ companion object {
+ val TAG = SavedLogsViewerActivity::class.qualifiedName
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_saved_logs_viewer)
+ setupToolbar()
+ enableDisplayHomeAsUp()
+
+ if (intent.data == null) {
+ finish()
+ return
}
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_saved_logs_viewer)
- setupToolbar()
- enableDisplayHomeAsUp()
-
- if (intent.data == null) {
- finish()
- return
- }
-
- toolbar.title = getFileNameFromUri(intent.data!!)
-
- if (savedInstanceState == null) {
- val frag = SavedLogsViewerFragment.newInstance(intent.data!!)
- supportFragmentManager.beginTransaction()
- .replace(R.id.content_frame, frag, SavedLogsViewerFragment.TAG)
- .commit()
- }
+ toolbar.title = getFileNameFromUri(intent.data!!)
+
+ if (savedInstanceState == null) {
+ val frag = SavedLogsViewerFragment.newInstance(intent.data!!)
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content_frame, frag, SavedLogsViewerFragment.TAG)
+ .commit()
}
+ }
- override fun getToolbarIdRes(): Int = R.id.toolbar
+ override fun getToolbarIdRes(): Int = R.id.toolbar
- override fun getToolbarTitle(): String = getString(R.string.saved_logs)
+ override fun getToolbarTitle(): String = getString(R.string.saved_logs)
- override fun onResume() {
- super.onResume()
- setKeepScreenOn(getDefaultSharedPreferences().getBoolean(PreferenceKeys.General.KEY_KEEP_SCREEN_ON,
- PreferenceKeys.General.Default.KEY_KEEP_SCREEN_ON))
- }
+ override fun onResume() {
+ super.onResume()
+ setKeepScreenOn(
+ getDefaultSharedPreferences().getBoolean(
+ PreferenceKeys.General.KEY_KEEP_SCREEN_ON,
+ PreferenceKeys.General.Default.KEY_KEEP_SCREEN_ON
+ )
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/activities/SettingsActivity.kt b/app/src/main/java/com/dp/logcatapp/activities/SettingsActivity.kt
index c04de78b..d369e490 100644
--- a/app/src/main/java/com/dp/logcatapp/activities/SettingsActivity.kt
+++ b/app/src/main/java/com/dp/logcatapp/activities/SettingsActivity.kt
@@ -5,25 +5,24 @@ import com.dp.logcatapp.R
import com.dp.logcatapp.fragments.settings.SettingsFragment
class SettingsActivity : BaseActivityWithToolbar() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_settings)
- setupToolbar()
- enableDisplayHomeAsUp()
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_settings)
+ setupToolbar()
+ enableDisplayHomeAsUp()
- if (savedInstanceState == null) {
- supportFragmentManager.beginTransaction()
- .replace(R.id.content_frame, SettingsFragment(), SettingsFragment.TAG)
- .commit()
- }
+ if (savedInstanceState == null) {
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content_frame, SettingsFragment(), SettingsFragment.TAG)
+ .commit()
}
+ }
- override fun getToolbarIdRes(): Int = R.id.toolbar
+ override fun getToolbarIdRes(): Int = R.id.toolbar
- override fun getToolbarTitle(): String = getString(R.string.settings)
-
- companion object {
- val TAG = SettingsActivity::class.qualifiedName
- }
+ override fun getToolbarTitle(): String = getString(R.string.settings)
+ companion object {
+ val TAG = SettingsActivity::class.qualifiedName
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/activities/SplashActivity.kt b/app/src/main/java/com/dp/logcatapp/activities/SplashActivity.kt
index 24fcd77d..7ac1a852 100644
--- a/app/src/main/java/com/dp/logcatapp/activities/SplashActivity.kt
+++ b/app/src/main/java/com/dp/logcatapp/activities/SplashActivity.kt
@@ -5,13 +5,13 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
open class SplashActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- startActivity(Intent(this, MainActivity::class.java))
- finish()
- }
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ startActivity(Intent(this, MainActivity::class.java))
+ finish()
+ }
- override fun onBackPressed() {
- // prevent back press
- }
+ override fun onBackPressed() {
+ // prevent back press
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/db/MyDB.kt b/app/src/main/java/com/dp/logcatapp/db/MyDB.kt
index 322805e3..1afed81d 100644
--- a/app/src/main/java/com/dp/logcatapp/db/MyDB.kt
+++ b/app/src/main/java/com/dp/logcatapp/db/MyDB.kt
@@ -2,100 +2,131 @@ package com.dp.logcatapp.db
import android.content.Context
import androidx.annotation.GuardedBy
-import androidx.room.*
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Delete
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.Room
+import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.dp.logcatapp.fragments.filters.FilterType
-@Entity(primaryKeys = ["type", "value", "exclude"], tableName = "filters", indices = [Index(name = "index_filters", value = ["exclude"])])
-data class FilterInfo(@ColumnInfo(name = "type") val type: Int,
- @ColumnInfo(name = "value") val content: String,
- @ColumnInfo(name = "exclude") val exclude: Boolean)
+@Entity(
+ primaryKeys = ["type", "value", "exclude"], tableName = "filters",
+ indices = [Index(name = "index_filters", value = ["exclude"])]
+)
+data class FilterInfo(
+ @ColumnInfo(name = "type") val type: Int,
+ @ColumnInfo(name = "value") val content: String,
+ @ColumnInfo(name = "exclude") val exclude: Boolean
+)
@Dao
interface FilterDao {
- @Query("SELECT * FROM filters WHERE `exclude` = 0")
- fun getFilters(): List
+ @Query("SELECT * FROM filters WHERE `exclude` = 0")
+ fun getFilters(): List
- @Query("SELECT * FROM filters WHERE `exclude` = 1")
- fun getExclusions(): List
+ @Query("SELECT * FROM filters WHERE `exclude` = 1")
+ fun getExclusions(): List
- @Query("SELECT * FROM filters")
- fun getAll(): List
+ @Query("SELECT * FROM filters")
+ fun getAll(): List
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insert(vararg info: FilterInfo)
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insert(vararg info: FilterInfo)
- @Delete
- fun delete(vararg info: FilterInfo)
+ @Delete
+ fun delete(vararg info: FilterInfo)
- @Query("DELETE FROM filters WHERE `exclude` = :exclusions")
- fun deleteAll(exclusions: Boolean)
+ @Query("DELETE FROM filters WHERE `exclude` = :exclusions")
+ fun deleteAll(exclusions: Boolean)
}
@Entity(tableName = "saved_logs_info")
-data class SavedLogInfo(@ColumnInfo(name = "name") val fileName: String,
- @PrimaryKey @ColumnInfo(name = "path") val path: String,
- @ColumnInfo(name = "is_custom") val isCustom: Boolean)
+data class SavedLogInfo(
+ @ColumnInfo(name = "name") val fileName: String,
+ @PrimaryKey @ColumnInfo(name = "path") val path: String,
+ @ColumnInfo(name = "is_custom") val isCustom: Boolean
+)
@Dao
interface SavedLogsDao {
- @Query("SELECT * FROM saved_logs_info")
- fun getAllSync(): List
+ @Query("SELECT * FROM saved_logs_info")
+ fun getAllSync(): List
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insert(vararg savedLogInfo: SavedLogInfo)
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insert(vararg savedLogInfo: SavedLogInfo)
- @Delete
- fun delete(vararg savedLogInfo: SavedLogInfo)
+ @Delete
+ fun delete(vararg savedLogInfo: SavedLogInfo)
}
@Database(entities = [FilterInfo::class, SavedLogInfo::class], exportSchema = false, version = 2)
abstract class MyDB : RoomDatabase() {
- abstract fun filterDao(): FilterDao
-
- abstract fun savedLogsDao(): SavedLogsDao
-
- companion object {
- private const val DB_NAME = "logcat_reader_db"
-
- private val instanceLock = Any()
- @GuardedBy("instanceLock")
- @Volatile
- private var instance: MyDB? = null
-
- fun getInstance(context: Context): MyDB {
- val tmp = instance
- if (tmp != null) {
- return tmp
- }
-
- synchronized(instanceLock) {
- if (instance == null) {
- instance = Room.databaseBuilder(context.applicationContext,
- MyDB::class.java, DB_NAME)
- .addMigrations(MIGRATION_1_2)
- .fallbackToDestructiveMigration()
- .build()
- }
- return instance!!
- }
+ abstract fun filterDao(): FilterDao
+
+ abstract fun savedLogsDao(): SavedLogsDao
+
+ companion object {
+ private const val DB_NAME = "logcat_reader_db"
+
+ private val instanceLock = Any()
+
+ @GuardedBy("instanceLock")
+ @Volatile
+ private var instance: MyDB? = null
+
+ fun getInstance(context: Context): MyDB {
+ val tmp = instance
+ if (tmp != null) {
+ return tmp
+ }
+
+ synchronized(instanceLock) {
+ if (instance == null) {
+ instance = Room.databaseBuilder(
+ context.applicationContext,
+ MyDB::class.java, DB_NAME
+ )
+ .addMigrations(MIGRATION_1_2)
+ .fallbackToDestructiveMigration()
+ .build()
}
+ return instance!!
+ }
+ }
- private val MIGRATION_1_2 = object : Migration(1, 2) {
- override fun migrate(db: SupportSQLiteDatabase) {
- db.execSQL("CREATE TABLE `filters_new` (`type` INTEGER NOT NULL, `value` TEXT NOT NULL, `exclude` INTEGER NOT NULL, PRIMARY KEY (`type`, `value`, `exclude`))")
- db.execSQL("INSERT OR IGNORE INTO `filters_new` (`type`, `value`, `exclude`) SELECT ${FilterType.KEYWORD}, `keyword`, `exclude` FROM `filters` WHERE `keyword` != ''")
- db.execSQL("INSERT OR IGNORE INTO `filters_new` (`type`, `value`, `exclude`) SELECT ${FilterType.TAG}, `tag`, `exclude` FROM `filters` WHERE `tag` != ''")
- db.execSQL("INSERT OR IGNORE INTO `filters_new` (`type`, `value`, `exclude`) SELECT ${FilterType.LOG_LEVELS}, `log_priorities`, `exclude` FROM `filters` WHERE `log_priorities` != ''")
-
- db.execSQL("DROP TABLE `filters`")
- db.execSQL("ALTER TABLE `filters_new` RENAME TO `filters`")
- db.execSQL("CREATE INDEX `index_filters` ON `filters` (`exclude`)")
- db.execSQL("CREATE TABLE IF NOT EXISTS `saved_logs_info` (`name` TEXT NOT NULL, `path` TEXT NOT NULL, `is_custom` INTEGER NOT NULL, PRIMARY KEY (`path`))")
- }
- }
+ private val MIGRATION_1_2 = object : Migration(1, 2) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ "CREATE TABLE `filters_new` (`type` INTEGER NOT NULL, `value` TEXT NOT NULL, `exclude` INTEGER NOT NULL, PRIMARY KEY (`type`, `value`, `exclude`))"
+ )
+ db.execSQL(
+ "INSERT OR IGNORE INTO `filters_new` (`type`, `value`, `exclude`) SELECT ${FilterType.KEYWORD}, `keyword`, `exclude` FROM `filters` WHERE `keyword` != ''"
+ )
+ db.execSQL(
+ "INSERT OR IGNORE INTO `filters_new` (`type`, `value`, `exclude`) SELECT ${FilterType.TAG}, `tag`, `exclude` FROM `filters` WHERE `tag` != ''"
+ )
+ db.execSQL(
+ "INSERT OR IGNORE INTO `filters_new` (`type`, `value`, `exclude`) SELECT ${FilterType.LOG_LEVELS}, `log_priorities`, `exclude` FROM `filters` WHERE `log_priorities` != ''"
+ )
+
+ db.execSQL("DROP TABLE `filters`")
+ db.execSQL("ALTER TABLE `filters_new` RENAME TO `filters`")
+ db.execSQL("CREATE INDEX `index_filters` ON `filters` (`exclude`)")
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS `saved_logs_info` (`name` TEXT NOT NULL, `path` TEXT NOT NULL, `is_custom` INTEGER NOT NULL, PRIMARY KEY (`path`))"
+ )
+ }
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/base/BaseDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/base/BaseDialogFragment.kt
index 3d079db4..9384dd04 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/base/BaseDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/base/BaseDialogFragment.kt
@@ -8,47 +8,51 @@ import android.view.animation.AnimationUtils
import androidx.fragment.app.DialogFragment
open class BaseDialogFragment : DialogFragment() {
- private val handler = Handler()
+ private val handler = Handler()
- override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
- var animation: Animation? = super.onCreateAnimation(transit, enter, nextAnim)
+ override fun onCreateAnimation(
+ transit: Int,
+ enter: Boolean,
+ nextAnim: Int
+ ): Animation? {
+ var animation: Animation? = super.onCreateAnimation(transit, enter, nextAnim)
- if (animation == null && nextAnim != 0) {
- animation = AnimationUtils.loadAnimation(activity, nextAnim)
- }
-
- if (animation != null) {
- val view = view
- if (view != null) {
- val layerType = view.layerType
- view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ if (animation == null && nextAnim != 0) {
+ animation = AnimationUtils.loadAnimation(activity, nextAnim)
+ }
- animation.setAnimationListener(object : Animation.AnimationListener {
- override fun onAnimationStart(animation: Animation) {}
+ if (animation != null) {
+ val view = view
+ if (view != null) {
+ val layerType = view.layerType
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
- override fun onAnimationEnd(animation: Animation) {
- view.setLayerType(layerType, null)
- }
+ animation.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationStart(animation: Animation) {}
- override fun onAnimationRepeat(animation: Animation) {}
- })
- }
- }
+ override fun onAnimationEnd(animation: Animation) {
+ view.setLayerType(layerType, null)
+ }
- return animation
+ override fun onAnimationRepeat(animation: Animation) {}
+ })
+ }
}
- override fun onDestroyView() {
- if (dialog != null && retainInstance)
- dialog?.setDismissMessage(null)
- super.onDestroyView()
- }
+ return animation
+ }
+
+ override fun onDestroyView() {
+ if (dialog != null && retainInstance)
+ dialog?.setDismissMessage(null)
+ super.onDestroyView()
+ }
- protected fun runOnUIThread(runnable: () -> Unit) {
- if (Thread.currentThread() == Looper.getMainLooper().thread) {
- runnable()
- } else {
- handler.post(runnable)
- }
+ protected fun runOnUIThread(runnable: () -> Unit) {
+ if (Thread.currentThread() == Looper.getMainLooper().thread) {
+ runnable()
+ } else {
+ handler.post(runnable)
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/base/BaseFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/base/BaseFragment.kt
index 5121d0fd..147c2aeb 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/base/BaseFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/base/BaseFragment.kt
@@ -8,27 +8,27 @@ import androidx.lifecycle.lifecycleScope
open class BaseFragment : Fragment() {
- protected val scope
- get() = viewLifecycleOwner.lifecycleScope
+ protected val scope
+ get() = viewLifecycleOwner.lifecycleScope
- protected lateinit var handler: Handler
- private set
+ protected lateinit var handler: Handler
+ private set
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- handler = Handler()
- }
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ handler = Handler()
+ }
- override fun onDestroy() {
- super.onDestroy()
- handler.removeCallbacksAndMessages(null)
- }
+ override fun onDestroy() {
+ super.onDestroy()
+ handler.removeCallbacksAndMessages(null)
+ }
- protected fun runOnUIThread(runnable: () -> Unit) {
- if (Thread.currentThread() == Looper.getMainLooper().thread) {
- runnable()
- } else {
- handler.post(runnable)
- }
+ protected fun runOnUIThread(runnable: () -> Unit) {
+ if (Thread.currentThread() == Looper.getMainLooper().thread) {
+ runnable()
+ } else {
+ handler.post(runnable)
}
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/filters/FilterType.kt b/app/src/main/java/com/dp/logcatapp/fragments/filters/FilterType.kt
index a1becdec..c5c7231c 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/filters/FilterType.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/filters/FilterType.kt
@@ -1,9 +1,9 @@
package com.dp.logcatapp.fragments.filters
object FilterType {
- const val KEYWORD = 0
- const val TAG = 1
- const val PID = 2
- const val TID = 3
- const val LOG_LEVELS = 4
+ const val KEYWORD = 0
+ const val TAG = 1
+ const val PID = 2
+ const val TID = 3
+ const val LOG_LEVELS = 4
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersFragment.kt
index df7c32c5..18d951d1 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersFragment.kt
@@ -2,7 +2,12 @@ package com.dp.logcatapp.fragments.filters
import android.annotation.SuppressLint
import android.os.Bundle
-import android.view.*
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.lifecycle.Observer
@@ -21,203 +26,240 @@ import com.dp.logcatapp.util.inflateLayout
class FiltersFragment : BaseFragment() {
- companion object {
- val TAG = FiltersFragment::class.qualifiedName
- private val KEY_EXCLUSIONS = TAG + "_key_exclusions"
- private val KEY_LOG = TAG + "_key_log"
-
- fun newInstance(log: Log?, exclusions: Boolean): FiltersFragment {
- val frag = FiltersFragment()
- val bundle = Bundle()
- bundle.putBoolean(KEY_EXCLUSIONS, exclusions)
- bundle.putParcelable(KEY_LOG, log)
- frag.arguments = bundle
- return frag
- }
+ companion object {
+ val TAG = FiltersFragment::class.qualifiedName
+ private val KEY_EXCLUSIONS = TAG + "_key_exclusions"
+ private val KEY_LOG = TAG + "_key_log"
+
+ fun newInstance(
+ log: Log?,
+ exclusions: Boolean
+ ): FiltersFragment {
+ val frag = FiltersFragment()
+ val bundle = Bundle()
+ bundle.putBoolean(KEY_EXCLUSIONS, exclusions)
+ bundle.putParcelable(KEY_LOG, log)
+ frag.arguments = bundle
+ return frag
}
-
- private lateinit var viewModel: FiltersViewModel
- private lateinit var recyclerViewAdapter: MyRecyclerViewAdapter
- private lateinit var linearLayoutManager: LinearLayoutManager
- private lateinit var emptyMessage: TextView
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
- viewModel = requireActivity().getAndroidViewModel()
- recyclerViewAdapter = MyRecyclerViewAdapter {
- onRemoveClicked(it)
+ }
+
+ private lateinit var viewModel: FiltersViewModel
+ private lateinit var recyclerViewAdapter: MyRecyclerViewAdapter
+ private lateinit var linearLayoutManager: LinearLayoutManager
+ private lateinit var emptyMessage: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ viewModel = requireActivity().getAndroidViewModel()
+ recyclerViewAdapter = MyRecyclerViewAdapter {
+ onRemoveClicked(it)
+ }
+ }
+
+ override fun onViewCreated(
+ view: View,
+ savedInstanceState: Bundle?
+ ) {
+ super.onViewCreated(view, savedInstanceState)
+ viewModel.getFilters(isExclusions()).observe(viewLifecycleOwner, Observer {
+ if (it != null) {
+ if (it.isEmpty()) {
+ emptyMessage.visibility = View.VISIBLE
+ } else {
+ emptyMessage.visibility = View.GONE
}
+ recyclerViewAdapter.setData(it)
+ }
+ })
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (getLog() != null) showAddFilter()
+ }
+
+ fun isExclusions() = arguments?.getBoolean(KEY_EXCLUSIONS) ?: false
+
+ private fun getLog() = arguments?.getParcelable(KEY_LOG)
+
+ @SuppressLint("CheckResult")
+ private fun onRemoveClicked(v: View) {
+ val pos = linearLayoutManager.getPosition(v)
+ if (pos != RecyclerView.NO_POSITION) {
+ val item = recyclerViewAdapter[pos]
+ viewModel.deleteFilter(item.info, isExclusions())
}
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- viewModel.getFilters(isExclusions()).observe(viewLifecycleOwner, Observer {
- if (it != null) {
- if (it.isEmpty()) {
- emptyMessage.visibility = View.VISIBLE
- } else {
- emptyMessage.visibility = View.GONE
- }
- recyclerViewAdapter.setData(it)
- }
- })
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val rootView = inflateLayout(R.layout.filters_fragment)
+
+ emptyMessage = rootView.findViewById(R.id.textViewEmpty)
+
+ val activity = requireActivity()
+ val recyclerView = rootView.findViewById(R.id.recyclerView)
+ recyclerView.addItemDecoration(
+ DividerItemDecoration(
+ activity,
+ DividerItemDecoration.VERTICAL
+ )
+ )
+ linearLayoutManager = LinearLayoutManager(activity)
+ recyclerView.layoutManager = linearLayoutManager
+ recyclerView.adapter = recyclerViewAdapter
+
+ return rootView
+ }
+
+ override fun onCreateOptionsMenu(
+ menu: Menu,
+ inflater: MenuInflater
+ ) {
+ inflater.inflate(R.menu.filters, menu)
+ super.onCreateOptionsMenu(menu, inflater)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.add_action -> {
+ showAddFilter()
+ true
+ }
+ R.id.clear_action -> {
+ viewModel.deleteAllFilters(isExclusions())
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
}
+ }
- override fun onResume() {
- super.onResume()
- if (getLog() != null) showAddFilter()
+ private fun showAddFilter() {
+ var frag =
+ parentFragmentManager.findFragmentByTag(FilterDialogFragment.TAG) as? FilterDialogFragment
+ if (frag == null) {
+ frag = FilterDialogFragment.newInstance(getLog())
}
- fun isExclusions() = arguments?.getBoolean(KEY_EXCLUSIONS) ?: false
+ frag.setTargetFragment(this, 0)
+ frag.show(parentFragmentManager, FilterDialogFragment.TAG)
+ }
- private fun getLog() = arguments?.getParcelable(KEY_LOG)
+ @SuppressLint("CheckResult")
+ fun addFilter(logcatMsg: LogcatMsg) {
+ val list = mutableListOf()
+ val exclude = isExclusions()
- @SuppressLint("CheckResult")
- private fun onRemoveClicked(v: View) {
- val pos = linearLayoutManager.getPosition(v)
- if (pos != RecyclerView.NO_POSITION) {
- val item = recyclerViewAdapter[pos]
- viewModel.deleteFilter(item.info, isExclusions())
- }
+ if (logcatMsg.keyword.isNotEmpty()) {
+ list.add(FilterInfo(FilterType.KEYWORD, logcatMsg.keyword, exclude))
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? {
- val rootView = inflateLayout(R.layout.filters_fragment)
-
- emptyMessage = rootView.findViewById(R.id.textViewEmpty)
-
- val activity = requireActivity()
- val recyclerView = rootView.findViewById(R.id.recyclerView)
- recyclerView.addItemDecoration(DividerItemDecoration(activity,
- DividerItemDecoration.VERTICAL))
- linearLayoutManager = LinearLayoutManager(activity)
- recyclerView.layoutManager = linearLayoutManager
- recyclerView.adapter = recyclerViewAdapter
-
- return rootView
+ if (logcatMsg.tag.isNotEmpty()) {
+ list.add(FilterInfo(FilterType.TAG, logcatMsg.tag, exclude))
}
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- inflater.inflate(R.menu.filters, menu)
- super.onCreateOptionsMenu(menu, inflater)
+ if (logcatMsg.pid.isNotEmpty()) {
+ list.add(FilterInfo(FilterType.PID, logcatMsg.pid, exclude))
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.add_action -> {
- showAddFilter()
- true
- }
- R.id.clear_action -> {
- viewModel.deleteAllFilters(isExclusions())
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
+ if (logcatMsg.tid.isNotEmpty()) {
+ list.add(FilterInfo(FilterType.TID, logcatMsg.tid, exclude))
}
- private fun showAddFilter() {
- var frag = parentFragmentManager.findFragmentByTag(FilterDialogFragment.TAG) as? FilterDialogFragment
- if (frag == null) {
- frag = FilterDialogFragment.newInstance(getLog())
- }
-
- frag.setTargetFragment(this, 0)
- frag.show(parentFragmentManager, FilterDialogFragment.TAG)
+ if (logcatMsg.logLevels.isNotEmpty()) {
+ list.add(
+ FilterInfo(
+ FilterType.LOG_LEVELS,
+ logcatMsg.logLevels.sorted().joinToString(","), exclude
+ )
+ )
}
- @SuppressLint("CheckResult")
- fun addFilter(logcatMsg: LogcatMsg) {
- val list = mutableListOf()
- val exclude = isExclusions()
-
- if (logcatMsg.keyword.isNotEmpty()) {
- list.add(FilterInfo(FilterType.KEYWORD, logcatMsg.keyword, exclude))
- }
-
- if (logcatMsg.tag.isNotEmpty()) {
- list.add(FilterInfo(FilterType.TAG, logcatMsg.tag, exclude))
- }
-
- if (logcatMsg.pid.isNotEmpty()) {
- list.add(FilterInfo(FilterType.PID, logcatMsg.pid, exclude))
- }
-
- if (logcatMsg.tid.isNotEmpty()) {
- list.add(FilterInfo(FilterType.TID, logcatMsg.tid, exclude))
- }
-
- if (logcatMsg.logLevels.isNotEmpty()) {
- list.add(FilterInfo(FilterType.LOG_LEVELS,
- logcatMsg.logLevels.sorted().joinToString(","), exclude))
- }
-
- if (list.isNotEmpty()) {
- viewModel.addFilters(list, isExclusions())
- }
+ if (list.isNotEmpty()) {
+ viewModel.addFilters(list, isExclusions())
}
+ }
}
-data class FilterListItem(val type: String,
- val content: String,
- val info: FilterInfo)
+data class FilterListItem(
+ val type: String,
+ val content: String,
+ val info: FilterInfo
+)
internal class MyRecyclerViewAdapter(private val onRemoveListener: (View) -> Unit) :
- RecyclerView.Adapter() {
+ RecyclerView.Adapter() {
- private val data = mutableListOf()
+ private val data = mutableListOf()
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
- val view = parent.context.inflateLayout(R.layout.filters_fragment_list_item, parent)
- view.findViewById(R.id.removeFilterIcon).setOnClickListener {
- onRemoveListener(it.parent as ViewGroup)
- }
- return MyViewHolder(view)
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MyViewHolder {
+ val view = parent.context.inflateLayout(R.layout.filters_fragment_list_item, parent)
+ view.findViewById(R.id.removeFilterIcon).setOnClickListener {
+ onRemoveListener(it.parent as ViewGroup)
}
-
- override fun getItemCount() = data.size
-
- operator fun get(index: Int) = data[index]
-
- fun setData(data: List) {
- val diffCallback = DataDiffCallback(this.data, data)
- val diffResult = DiffUtil.calculateDiff(diffCallback)
-
- this.data.clear()
- this.data.addAll(data)
- diffResult.dispatchUpdatesTo(this)
+ return MyViewHolder(view)
+ }
+
+ override fun getItemCount() = data.size
+
+ operator fun get(index: Int) = data[index]
+
+ fun setData(data: List) {
+ val diffCallback = DataDiffCallback(this.data, data)
+ val diffResult = DiffUtil.calculateDiff(diffCallback)
+
+ this.data.clear()
+ this.data.addAll(data)
+ diffResult.dispatchUpdatesTo(this)
+ }
+
+ override fun onBindViewHolder(
+ holder: MyViewHolder,
+ position: Int
+ ) {
+ val item = data[position]
+ holder.content.text = item.content
+ holder.type.text = item.type
+ }
+
+ class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val content: TextView = itemView.findViewById(R.id.content)
+ val type: TextView = itemView.findViewById(R.id.type)
+ }
+
+ private class DataDiffCallback(
+ private val old: List,
+ private val new: List
+ ) : DiffUtil.Callback() {
+
+ override fun getOldListSize(): Int {
+ return old.size
}
- override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
- val item = data[position]
- holder.content.text = item.content
- holder.type.text = item.type
+ override fun getNewListSize(): Int {
+ return new.size
}
- class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val content: TextView = itemView.findViewById(R.id.content)
- val type: TextView = itemView.findViewById(R.id.type)
+ override fun areItemsTheSame(
+ p0: Int,
+ p1: Int
+ ): Boolean {
+ return old[p0].info == new[p1].info
}
- private class DataDiffCallback(private val old: List,
- private val new: List) : DiffUtil.Callback() {
-
- override fun getOldListSize(): Int {
- return old.size
- }
-
- override fun getNewListSize(): Int {
- return new.size
- }
-
- override fun areItemsTheSame(p0: Int, p1: Int): Boolean {
- return old[p0].info == new[p1].info
- }
-
- override fun areContentsTheSame(p0: Int, p1: Int): Boolean {
- return old[p0] == new[p1]
- }
+ override fun areContentsTheSame(
+ p0: Int,
+ p1: Int
+ ): Boolean {
+ return old[p0] == new[p1]
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersViewModel.kt b/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersViewModel.kt
index 3292fb65..40572209 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersViewModel.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/filters/FiltersViewModel.kt
@@ -14,101 +14,107 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class FiltersViewModel(application: Application) : ScopedAndroidViewModel(application) {
- private val context = application
+ private val context = application
- private lateinit var filters: MutableLiveData>
+ private lateinit var filters: MutableLiveData>
- private var loadFiltersJob: Job? = null
+ private var loadFiltersJob: Job? = null
- fun getFilters(isExclusions: Boolean): LiveData> {
- if (this::filters.isInitialized) {
- return filters
- }
-
- filters = MutableLiveData()
- reloadFilters(isExclusions)
- return filters
+ fun getFilters(isExclusions: Boolean): LiveData> {
+ if (this::filters.isInitialized) {
+ return filters
}
- fun addFilters(filters: List, isExclusions: Boolean) {
- val dao = MyDB.getInstance(context).filterDao()
- launch {
- withContext(IO) {
- dao.insert(*filters.toTypedArray())
- }
+ filters = MutableLiveData()
+ reloadFilters(isExclusions)
+ return filters
+ }
- reloadFilters(isExclusions)
- }
+ fun addFilters(
+ filters: List,
+ isExclusions: Boolean
+ ) {
+ val dao = MyDB.getInstance(context).filterDao()
+ launch {
+ withContext(IO) {
+ dao.insert(*filters.toTypedArray())
+ }
+
+ reloadFilters(isExclusions)
}
+ }
- fun deleteFilter(filter: FilterInfo, isExclusions: Boolean) {
- val dao = MyDB.getInstance(context).filterDao()
- launch {
- withContext(IO) {
- dao.delete(filter)
- }
+ fun deleteFilter(
+ filter: FilterInfo,
+ isExclusions: Boolean
+ ) {
+ val dao = MyDB.getInstance(context).filterDao()
+ launch {
+ withContext(IO) {
+ dao.delete(filter)
+ }
- reloadFilters(isExclusions)
- }
+ reloadFilters(isExclusions)
}
+ }
- fun deleteAllFilters(isExclusions: Boolean) {
- val dao = MyDB.getInstance(context).filterDao()
- launch {
- withContext(IO) {
- dao.deleteAll(isExclusions)
- }
+ fun deleteAllFilters(isExclusions: Boolean) {
+ val dao = MyDB.getInstance(context).filterDao()
+ launch {
+ withContext(IO) {
+ dao.deleteAll(isExclusions)
+ }
- reloadFilters(isExclusions)
- }
+ reloadFilters(isExclusions)
}
+ }
- private fun reloadFilters(isExclusions: Boolean) {
- val dao = MyDB.getInstance(context).filterDao()
+ private fun reloadFilters(isExclusions: Boolean) {
+ val dao = MyDB.getInstance(context).filterDao()
- loadFiltersJob?.cancel()
- loadFiltersJob = launch {
- filters.value = withContext(IO) {
- val list = if (isExclusions) {
- dao.getExclusions()
- } else {
- dao.getFilters()
- }
+ loadFiltersJob?.cancel()
+ loadFiltersJob = launch {
+ filters.value = withContext(IO) {
+ val list = if (isExclusions) {
+ dao.getExclusions()
+ } else {
+ dao.getFilters()
+ }
- list.map {
- val displayText: String
- val type: String
- when (it.type) {
- FilterType.LOG_LEVELS -> {
- type = context.getString(R.string.log_level)
- displayText = it.content.split(",")
- .joinToString(", ") { s ->
- when (s) {
- LogPriority.ASSERT -> "Assert"
- LogPriority.ERROR -> "Error"
- LogPriority.DEBUG -> "Debug"
- LogPriority.FATAL -> "Fatal"
- LogPriority.INFO -> "Info"
- LogPriority.VERBOSE -> "Verbose"
- LogPriority.WARNING -> "warning"
- else -> ""
- }
- }
- }
- else -> {
- displayText = it.content
- type = when (it.type) {
- FilterType.KEYWORD -> context.getString(R.string.keyword)
- FilterType.TAG -> context.getString(R.string.tag)
- FilterType.PID -> context.getString(R.string.process_id)
- FilterType.TID -> context.getString(R.string.thread_id)
- else -> throw IllegalStateException("invalid type: ${it.type}")
- }
- }
- }
- FilterListItem(type, displayText, it)
+ list.map {
+ val displayText: String
+ val type: String
+ when (it.type) {
+ FilterType.LOG_LEVELS -> {
+ type = context.getString(R.string.log_level)
+ displayText = it.content.split(",")
+ .joinToString(", ") { s ->
+ when (s) {
+ LogPriority.ASSERT -> "Assert"
+ LogPriority.ERROR -> "Error"
+ LogPriority.DEBUG -> "Debug"
+ LogPriority.FATAL -> "Fatal"
+ LogPriority.INFO -> "Info"
+ LogPriority.VERBOSE -> "Verbose"
+ LogPriority.WARNING -> "warning"
+ else -> ""
+ }
}
}
+ else -> {
+ displayText = it.content
+ type = when (it.type) {
+ FilterType.KEYWORD -> context.getString(R.string.keyword)
+ FilterType.TAG -> context.getString(R.string.tag)
+ FilterType.PID -> context.getString(R.string.process_id)
+ FilterType.TID -> context.getString(R.string.thread_id)
+ else -> throw IllegalStateException("invalid type: ${it.type}")
+ }
+ }
+ }
+ FilterListItem(type, displayText, it)
}
+ }
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/filters/dialogs/FilterDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/filters/dialogs/FilterDialogFragment.kt
index 1cc2c4df..5aa106e0 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/filters/dialogs/FilterDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/filters/dialogs/FilterDialogFragment.kt
@@ -19,153 +19,193 @@ import com.dp.logcatapp.util.inflateLayout
class FilterDialogFragment : BaseDialogFragment() {
- companion object {
- val TAG = FilterDialogFragment::class.qualifiedName
- private val KEY_LOG = TAG + "_key_log"
+ companion object {
+ val TAG = FilterDialogFragment::class.qualifiedName
+ private val KEY_LOG = TAG + "_key_log"
- fun newInstance(log: Log?): FilterDialogFragment {
- val bundle = Bundle()
- bundle.putParcelable(KEY_LOG, log)
+ fun newInstance(log: Log?): FilterDialogFragment {
+ val bundle = Bundle()
+ bundle.putParcelable(KEY_LOG, log)
- val fragment = FilterDialogFragment()
- fragment.arguments = bundle
- return fragment
- }
- }
-
- private lateinit var viewModel: MyViewModel
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- viewModel = getViewModel()
- initViewModel(getLog())
+ val fragment = FilterDialogFragment()
+ fragment.arguments = bundle
+ return fragment
}
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val rootView = inflateLayout(R.layout.filter_dialog)
-
- val editTextKeyword = rootView.findViewById(R.id.keyword)
- editTextKeyword.setText(viewModel.keyword)
- editTextKeyword.addTextChangedListener(object : TextWatcher {
- override fun afterTextChanged(s: Editable) {
- viewModel.keyword = s.toString()
- }
-
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
- }
-
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
- }
- })
-
- val editTextTag = rootView.findViewById(R.id.tag)
- editTextTag.setText(viewModel.tag)
- editTextTag.addTextChangedListener(object : TextWatcher {
- override fun afterTextChanged(s: Editable) {
- viewModel.tag = s.toString()
- }
-
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
- }
-
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
- }
- })
-
- val editTextPid = rootView.findViewById(R.id.pid)
- editTextPid.setText(viewModel.pid)
- editTextPid.addTextChangedListener(object : TextWatcher {
- override fun afterTextChanged(s: Editable) {
- viewModel.pid = s.toString()
- }
-
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
- }
-
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
- }
- })
-
- val editTextTid = rootView.findViewById(R.id.tid)
- editTextTid.setText(viewModel.tid)
- editTextTid.addTextChangedListener(object : TextWatcher {
- override fun afterTextChanged(s: Editable) {
- viewModel.tid = s.toString()
- }
-
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
- }
-
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
- }
- })
-
- val checkBoxMap = mutableMapOf()
- checkBoxMap[rootView.findViewById(R.id.checkboxAssert)] = LogPriority.ASSERT
- checkBoxMap[rootView.findViewById(R.id.checkboxDebug)] = LogPriority.DEBUG
- checkBoxMap[rootView.findViewById(R.id.checkboxError)] = LogPriority.ERROR
- checkBoxMap[rootView.findViewById(R.id.checkboxFatal)] = LogPriority.FATAL
- checkBoxMap[rootView.findViewById(R.id.checkboxInfo)] = LogPriority.INFO
- checkBoxMap[rootView.findViewById(R.id.checkboxVerbose)] = LogPriority.VERBOSE
- checkBoxMap[rootView.findViewById(R.id.checkboxWarning)] = LogPriority.WARNING
-
- for ((k, v) in checkBoxMap) {
- k.isChecked = v in viewModel.logPriorities
- val logPriority = checkBoxMap[k]!!
- k.setOnCheckedChangeListener { _, isChecked ->
- if (isChecked) {
- viewModel.logPriorities += logPriority
- } else {
- viewModel.logPriorities -= logPriority
- }
- }
- }
-
- val title = if ((targetFragment as FiltersFragment).isExclusions()) {
- getString(R.string.exclusion)
+ }
+
+ private lateinit var viewModel: MyViewModel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ viewModel = getViewModel()
+ initViewModel(getLog())
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val rootView = inflateLayout(R.layout.filter_dialog)
+
+ val editTextKeyword = rootView.findViewById(R.id.keyword)
+ editTextKeyword.setText(viewModel.keyword)
+ editTextKeyword.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable) {
+ viewModel.keyword = s.toString()
+ }
+
+ override fun beforeTextChanged(
+ s: CharSequence,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(
+ s: CharSequence,
+ start: Int,
+ before: Int,
+ count: Int
+ ) {
+ }
+ })
+
+ val editTextTag = rootView.findViewById(R.id.tag)
+ editTextTag.setText(viewModel.tag)
+ editTextTag.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable) {
+ viewModel.tag = s.toString()
+ }
+
+ override fun beforeTextChanged(
+ s: CharSequence,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(
+ s: CharSequence,
+ start: Int,
+ before: Int,
+ count: Int
+ ) {
+ }
+ })
+
+ val editTextPid = rootView.findViewById(R.id.pid)
+ editTextPid.setText(viewModel.pid)
+ editTextPid.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable) {
+ viewModel.pid = s.toString()
+ }
+
+ override fun beforeTextChanged(
+ s: CharSequence,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(
+ s: CharSequence,
+ start: Int,
+ before: Int,
+ count: Int
+ ) {
+ }
+ })
+
+ val editTextTid = rootView.findViewById(R.id.tid)
+ editTextTid.setText(viewModel.tid)
+ editTextTid.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable) {
+ viewModel.tid = s.toString()
+ }
+
+ override fun beforeTextChanged(
+ s: CharSequence,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(
+ s: CharSequence,
+ start: Int,
+ before: Int,
+ count: Int
+ ) {
+ }
+ })
+
+ val checkBoxMap = mutableMapOf()
+ checkBoxMap[rootView.findViewById(R.id.checkboxAssert)] = LogPriority.ASSERT
+ checkBoxMap[rootView.findViewById(R.id.checkboxDebug)] = LogPriority.DEBUG
+ checkBoxMap[rootView.findViewById(R.id.checkboxError)] = LogPriority.ERROR
+ checkBoxMap[rootView.findViewById(R.id.checkboxFatal)] = LogPriority.FATAL
+ checkBoxMap[rootView.findViewById(R.id.checkboxInfo)] = LogPriority.INFO
+ checkBoxMap[rootView.findViewById(R.id.checkboxVerbose)] = LogPriority.VERBOSE
+ checkBoxMap[rootView.findViewById(R.id.checkboxWarning)] = LogPriority.WARNING
+
+ for ((k, v) in checkBoxMap) {
+ k.isChecked = v in viewModel.logPriorities
+ val logPriority = checkBoxMap[k]!!
+ k.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) {
+ viewModel.logPriorities += logPriority
} else {
- getString(R.string.filter)
+ viewModel.logPriorities -= logPriority
}
+ }
+ }
- return AlertDialog.Builder(requireActivity())
- .setTitle(title)
- .setView(rootView)
- .setPositiveButton(android.R.string.ok) { _, _ ->
- var logcatMsg = LogcatMsg()
- logcatMsg.logLevels = mutableSetOf()
- logcatMsg.keyword = editTextKeyword.text.toString().trim()
- logcatMsg.tag = editTextTag.text.toString().trim()
- logcatMsg.pid = editTextPid.text.toString().trim()
- logcatMsg.tid = editTextTid.text.toString().trim()
- for ((k, v) in checkBoxMap) {
- if (k.isChecked) {
- logcatMsg.logLevels.add(v)
- }
- }
- (targetFragment as FiltersFragment).addFilter(logcatMsg)
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- dismiss()
- }
- .create()
+ val title = if ((targetFragment as FiltersFragment).isExclusions()) {
+ getString(R.string.exclusion)
+ } else {
+ getString(R.string.filter)
}
- fun initViewModel(log: Log?) {
- log?.let {
- viewModel.tag = log.tag
- viewModel.pid = log.pid
- viewModel.tid = log.tid
- viewModel.logPriorities.add(log.priority)
+ return AlertDialog.Builder(requireActivity())
+ .setTitle(title)
+ .setView(rootView)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ var logcatMsg = LogcatMsg()
+ logcatMsg.logLevels = mutableSetOf()
+ logcatMsg.keyword = editTextKeyword.text.toString().trim()
+ logcatMsg.tag = editTextTag.text.toString().trim()
+ logcatMsg.pid = editTextPid.text.toString().trim()
+ logcatMsg.tid = editTextTid.text.toString().trim()
+ for ((k, v) in checkBoxMap) {
+ if (k.isChecked) {
+ logcatMsg.logLevels.add(v)
+ }
}
+ (targetFragment as FiltersFragment).addFilter(logcatMsg)
+ }
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ dismiss()
+ }
+ .create()
+ }
+
+ fun initViewModel(log: Log?) {
+ log?.let {
+ viewModel.tag = log.tag
+ viewModel.pid = log.pid
+ viewModel.tid = log.tid
+ viewModel.logPriorities.add(log.priority)
}
+ }
- fun getLog() = arguments?.getParcelable(KEY_LOG)
+ fun getLog() = arguments?.getParcelable(KEY_LOG)
}
internal class MyViewModel : ViewModel() {
- var keyword = ""
- var tag = ""
- var pid = ""
- var tid = ""
- val logPriorities = mutableSetOf()
+ var keyword = ""
+ var tag = ""
+ var pid = ""
+ var tid = ""
+ val logPriorities = mutableSetOf()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveFragment.kt
index 267d2cfe..c493d4b7 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveFragment.kt
@@ -7,7 +7,12 @@ import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.IBinder
-import android.view.*
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
@@ -27,12 +32,24 @@ import com.dp.logcatapp.activities.SavedLogsActivity
import com.dp.logcatapp.db.FilterInfo
import com.dp.logcatapp.fragments.base.BaseFragment
import com.dp.logcatapp.fragments.filters.FilterType
-import com.dp.logcatapp.fragments.logcatlive.dialogs.*
+import com.dp.logcatapp.fragments.logcatlive.dialogs.AskingForRootAccessDialogFragment
+import com.dp.logcatapp.fragments.logcatlive.dialogs.ManualMethodToGrantPermissionDialogFragment
+import com.dp.logcatapp.fragments.logcatlive.dialogs.NeedPermissionDialogFragment
+import com.dp.logcatapp.fragments.logcatlive.dialogs.OnSavedBottomSheetDialogFragment
+import com.dp.logcatapp.fragments.logcatlive.dialogs.RestartAppMessageDialogFragment
import com.dp.logcatapp.fragments.shared.dialogs.CopyToClipboardDialogFragment
import com.dp.logcatapp.fragments.shared.dialogs.FilterExclusionDialogFragment
import com.dp.logcatapp.services.LogcatService
import com.dp.logcatapp.services.getService
-import com.dp.logcatapp.util.*
+import com.dp.logcatapp.util.PreferenceKeys
+import com.dp.logcatapp.util.ServiceBinder
+import com.dp.logcatapp.util.SuCommander
+import com.dp.logcatapp.util.containsIgnoreCase
+import com.dp.logcatapp.util.getAndroidViewModel
+import com.dp.logcatapp.util.getDefaultSharedPreferences
+import com.dp.logcatapp.util.inflateLayout
+import com.dp.logcatapp.util.showSnackbar
+import com.dp.logcatapp.util.showToast
import com.dp.logcatapp.views.IndeterminateProgressSnackBar
import com.dp.logger.Logger
import com.google.android.material.floatingactionbutton.FloatingActionButton
@@ -45,665 +62,710 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LogcatLiveFragment : BaseFragment(), ServiceConnection, LogsReceivedListener {
- companion object {
- val TAG = LogcatLiveFragment::class.qualifiedName
- const val LOGCAT_DIR = "logcat"
-
- private const val SEARCH_FILTER_TAG = "search_filter_tag"
-
- private val STOP_RECORDING = TAG + "_stop_recording"
-
- fun newInstance(stopRecording: Boolean): LogcatLiveFragment {
- val bundle = Bundle()
- bundle.putBoolean(STOP_RECORDING, stopRecording)
- val frag = LogcatLiveFragment()
- frag.arguments = bundle
- return frag
- }
- }
-
- private lateinit var serviceBinder: ServiceBinder
- private lateinit var recyclerView: RecyclerView
- private lateinit var linearLayoutManager: LinearLayoutManager
- private lateinit var viewModel: LogcatLiveViewModel
- private lateinit var adapter: MyRecyclerViewAdapter
- private lateinit var fabUp: FloatingActionButton
- private lateinit var fabDown: FloatingActionButton
- private lateinit var snackBarProgress: IndeterminateProgressSnackBar
- private var logcatService: LogcatService? = null
- private var ignoreScrollEvent = false
- private var searchViewActive = false
- private var lastLogId = -1
- private var lastSearchRunnable: Runnable? = null
- private var searchTask: Job? = null
-
- private val hideFabUpRunnable: Runnable = Runnable {
- fabUp.hide()
- }
-
- private val hideFabDownRunnable: Runnable = Runnable {
- fabDown.hide()
- }
-
- private val onScrollListener = object : RecyclerView.OnScrollListener() {
- var lastDy = 0
-
- override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
- if (dy > 0 && lastDy <= 0) {
- hideFabUp()
- showFabDown()
- } else if (dy < 0 && lastDy >= 0) {
- showFabUp()
- hideFabDown()
- }
- lastDy = dy
- }
-
- override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
- when (newState) {
- RecyclerView.SCROLL_STATE_DRAGGING -> {
- viewModel.autoScroll = false
- if (lastDy > 0) {
- hideFabUp()
- showFabDown()
- } else if (lastDy < 0) {
- showFabUp()
- hideFabDown()
- }
- }
- else -> {
- var firstPos = -1
- if (searchViewActive && !viewModel.autoScroll &&
- newState == RecyclerView.SCROLL_STATE_IDLE) {
- firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
- if (firstPos != RecyclerView.NO_POSITION) {
- val log = adapter[firstPos]
- lastLogId = log.id
- }
- }
-
- val pos = linearLayoutManager.findLastCompletelyVisibleItemPosition()
- if (pos == RecyclerView.NO_POSITION) {
- viewModel.autoScroll = false
- return
- }
-
- if (ignoreScrollEvent) {
- if (pos == adapter.itemCount) {
- ignoreScrollEvent = false
- }
- return
- }
-
- if (pos == 0) {
- hideFabUp()
- }
-
- if (firstPos == RecyclerView.NO_POSITION) {
- firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
- }
-
- viewModel.scrollPosition = firstPos
- viewModel.autoScroll = pos == adapter.itemCount - 1
-
- if (viewModel.autoScroll) {
- hideFabUp()
- hideFabDown()
- }
- }
- }
- }
- }
-
- private fun showFabUp() {
- handler.removeCallbacks(hideFabUpRunnable)
- fabUp.show()
- handler.postDelayed(hideFabUpRunnable, 2000)
- }
-
- private fun hideFabUp() {
- handler.removeCallbacks(hideFabUpRunnable)
- fabUp.hide()
- }
-
- private fun showFabDown() {
- handler.removeCallbacks(hideFabDownRunnable)
- fabDown.show()
- handler.postDelayed(hideFabDownRunnable, 2000)
- }
-
- private fun hideFabDown() {
- handler.removeCallbacks(hideFabDownRunnable)
- fabDown.hide()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
- serviceBinder = ServiceBinder(LogcatService::class.java, this)
-
- val activity = requireActivity()
- val maxLogs = activity.getDefaultSharedPreferences()
- .getString(PreferenceKeys.Logcat.KEY_MAX_LOGS,
- PreferenceKeys.Logcat.Default.MAX_LOGS)!!.trim().toInt()
- adapter = MyRecyclerViewAdapter(activity, maxLogs)
- activity.getDefaultSharedPreferences().registerOnSharedPreferenceChangeListener(adapter)
-
- viewModel = activity.getAndroidViewModel()
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? =
- inflateLayout(R.layout.fragment_logcat_live)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- recyclerView = view.findViewById(R.id.recyclerView)
- linearLayoutManager = LinearLayoutManager(activity)
- recyclerView.layoutManager = linearLayoutManager
- recyclerView.itemAnimator = null
- recyclerView.addItemDecoration(DividerItemDecoration(activity,
- linearLayoutManager.orientation))
- recyclerView.adapter = adapter
-
- recyclerView.addOnScrollListener(onScrollListener)
-
- fabDown = view.findViewById(R.id.fabDown)
- fabDown.setOnClickListener {
- logcatService?.logcat?.pause()
- hideFabDown()
- ignoreScrollEvent = true
- viewModel.autoScroll = true
- linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
- resumeLogcat()
- }
-
- snackBarProgress = IndeterminateProgressSnackBar(view, getString(R.string.saving))
-
- fabUp = view.findViewById(R.id.fabUp)
- fabUp.setOnClickListener {
- logcatService?.logcat?.pause()
- hideFabUp()
- viewModel.autoScroll = false
- linearLayoutManager.scrollToPositionWithOffset(0, 0)
- resumeLogcat()
- }
-
+ companion object {
+ val TAG = LogcatLiveFragment::class.qualifiedName
+ const val LOGCAT_DIR = "logcat"
+
+ private const val SEARCH_FILTER_TAG = "search_filter_tag"
+
+ private val STOP_RECORDING = TAG + "_stop_recording"
+
+ fun newInstance(stopRecording: Boolean): LogcatLiveFragment {
+ val bundle = Bundle()
+ bundle.putBoolean(STOP_RECORDING, stopRecording)
+ val frag = LogcatLiveFragment()
+ frag.arguments = bundle
+ return frag
+ }
+ }
+
+ private lateinit var serviceBinder: ServiceBinder
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var linearLayoutManager: LinearLayoutManager
+ private lateinit var viewModel: LogcatLiveViewModel
+ private lateinit var adapter: MyRecyclerViewAdapter
+ private lateinit var fabUp: FloatingActionButton
+ private lateinit var fabDown: FloatingActionButton
+ private lateinit var snackBarProgress: IndeterminateProgressSnackBar
+ private var logcatService: LogcatService? = null
+ private var ignoreScrollEvent = false
+ private var searchViewActive = false
+ private var lastLogId = -1
+ private var lastSearchRunnable: Runnable? = null
+ private var searchTask: Job? = null
+
+ private val hideFabUpRunnable: Runnable = Runnable {
+ fabUp.hide()
+ }
+
+ private val hideFabDownRunnable: Runnable = Runnable {
+ fabDown.hide()
+ }
+
+ private val onScrollListener = object : RecyclerView.OnScrollListener() {
+ var lastDy = 0
+
+ override fun onScrolled(
+ recyclerView: RecyclerView,
+ dx: Int,
+ dy: Int
+ ) {
+ if (dy > 0 && lastDy <= 0) {
hideFabUp()
+ showFabDown()
+ } else if (dy < 0 && lastDy >= 0) {
+ showFabUp()
hideFabDown()
-
- adapter.setOnClickListener { v ->
- val pos = linearLayoutManager.getPosition(v)
- if (pos >= 0) {
- viewModel.autoScroll = false
- val log = adapter[pos]
- CopyToClipboardDialogFragment.newInstance(log)
- .show(parentFragmentManager, CopyToClipboardDialogFragment.TAG)
- }
- }
-
- adapter.setOnLongClickListener { v ->
- val pos = linearLayoutManager.getPosition(v)
- if (pos >= 0) {
- viewModel.autoScroll = false
- val log = adapter[pos]
- FilterExclusionDialogFragment.newInstance(log)
- .show(parentFragmentManager, FilterExclusionDialogFragment.TAG)
- }
+ }
+ lastDy = dy
+ }
+
+ override fun onScrollStateChanged(
+ recyclerView: RecyclerView,
+ newState: Int
+ ) {
+ when (newState) {
+ RecyclerView.SCROLL_STATE_DRAGGING -> {
+ viewModel.autoScroll = false
+ if (lastDy > 0) {
+ hideFabUp()
+ showFabDown()
+ } else if (lastDy < 0) {
+ showFabUp()
+ hideFabDown()
+ }
}
-
- if (!checkReadLogsPermission() && !viewModel.showedGrantPermissionInstruction) {
- viewModel.showedGrantPermissionInstruction = true
- NeedPermissionDialogFragment().let {
- it.setTargetFragment(this, 0)
- it.show(parentFragmentManager, NeedPermissionDialogFragment.TAG)
+ else -> {
+ var firstPos = -1
+ if (searchViewActive && !viewModel.autoScroll &&
+ newState == RecyclerView.SCROLL_STATE_IDLE
+ ) {
+ firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
+ if (firstPos != RecyclerView.NO_POSITION) {
+ val log = adapter[firstPos]
+ lastLogId = log.id
}
- }
+ }
- viewModel.getFilters().observe(viewLifecycleOwner, Observer { filters ->
- if (filters != null) {
- logcatService?.let {
- val logcat = it.logcat
- logcat.pause()
- logcat.clearFilters(exclude = SEARCH_FILTER_TAG)
- logcat.clearExclusions()
-
- for (filter in filters) {
- if (filter.exclude) {
- logcat.addExclusion("${filter.hashCode()}",
- LogFilter(filter))
- } else {
- logcat.addFilter("${filter.hashCode()}",
- LogFilter(filter))
- }
- }
-
- adapter.setItems(logcat.getLogsFiltered())
- updateToolbarSubtitle(adapter.itemCount)
- scrollRecyclerView()
- resumeLogcat()
- }
- }
- })
- }
-
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
-
- viewModel.getFileSaveNotifier().observe(viewLifecycleOwner, Observer { saveInfo ->
- saveInfo?.let {
- if (viewModel.alreadySaved) {
- return@Observer
- }
- when (it.result) {
- SaveInfo.IN_PROGRESS -> {
- snackBarProgress.show()
- }
- else -> {
- snackBarProgress.dismiss()
- when (it.result) {
- SaveInfo.SUCCESS -> {
- OnSavedBottomSheetDialogFragment.newInstance(it.fileName!!, it.uri!!)
- .show(parentFragmentManager, OnSavedBottomSheetDialogFragment.TAG)
- }
- SaveInfo.ERROR_EMPTY_LOGS -> {
- showSnackbar(view, getString(R.string.nothing_to_save))
- }
- else -> {
- showSnackbar(view, getString(R.string.failed_to_save_logs))
- }
- }
- }
- }
- }
- })
- }
+ val pos = linearLayoutManager.findLastCompletelyVisibleItemPosition()
+ if (pos == RecyclerView.NO_POSITION) {
+ viewModel.autoScroll = false
+ return
+ }
- private fun checkReadLogsPermission() = ContextCompat.checkSelfPermission(requireContext(),
- Manifest.permission.READ_LOGS) == PackageManager.PERMISSION_GRANTED
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- super.onCreateOptionsMenu(menu, inflater)
-
- inflater.inflate(R.menu.logcat_live, menu)
- val searchItem = menu.findItem(R.id.action_search)
- val searchView = searchItem.actionView as SearchView
-
- var reachedBlank = false
- searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
- override fun onQueryTextChange(newText: String): Boolean {
- searchViewActive = true
- removeLastSearchRunnableCallback()
-
- if (newText.isBlank()) {
- reachedBlank = true
- onSearchViewClose()
- } else {
- reachedBlank = false
- logcatService?.logcat?.let {
- lastSearchRunnable = Runnable {
- runSearchTask(it, newText)
- }.also {
- handler.postDelayed(it, 100)
- }
- }
-
- }
- return true
+ if (ignoreScrollEvent) {
+ if (pos == adapter.itemCount) {
+ ignoreScrollEvent = false
}
+ return
+ }
- override fun onQueryTextSubmit(query: String) = false
- })
+ if (pos == 0) {
+ hideFabUp()
+ }
- val playPauseItem = menu.findItem(R.id.action_play_pause)
- val recordToggleItem = menu.findItem(R.id.action_record_toggle)
+ if (firstPos == RecyclerView.NO_POSITION) {
+ firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
+ }
- searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
- playPauseItem.isVisible = !hasFocus
- recordToggleItem.isVisible = !hasFocus
- }
+ viewModel.scrollPosition = firstPos
+ viewModel.autoScroll = pos == adapter.itemCount - 1
- searchView.setOnCloseListener {
- removeLastSearchRunnableCallback()
- searchViewActive = false
- if (!reachedBlank) {
- onSearchViewClose()
- }
- playPauseItem.isVisible = true
- recordToggleItem.isVisible = true
- false
- }
- }
-
- private fun onSearchViewClose() {
- logcatService?.logcat?.let {
- it.pause()
- it.removeFilter(SEARCH_FILTER_TAG)
-
- adapter.clear()
- addAllLogs(it.getLogsFiltered())
- if (lastLogId == -1) {
- scrollRecyclerView()
- } else {
- viewModel.autoScroll = linearLayoutManager.findLastCompletelyVisibleItemPosition() ==
- adapter.itemCount - 1
- if (!viewModel.autoScroll) {
- viewModel.scrollPosition = lastLogId
- linearLayoutManager.scrollToPositionWithOffset(lastLogId, 0)
- }
- lastLogId = -1
- }
+ if (viewModel.autoScroll) {
+ hideFabUp()
+ hideFabDown()
+ }
}
-
- resumeLogcat()
- }
-
- override fun onPrepareOptionsMenu(menu: Menu) {
- super.onPrepareOptionsMenu(menu)
-
- val playPauseItem = menu.findItem(R.id.action_play_pause)
- val recordToggleItem = menu.findItem(R.id.action_record_toggle)
-
- val context = requireContext()
+ }
+ }
+ }
+
+ private fun showFabUp() {
+ handler.removeCallbacks(hideFabUpRunnable)
+ fabUp.show()
+ handler.postDelayed(hideFabUpRunnable, 2000)
+ }
+
+ private fun hideFabUp() {
+ handler.removeCallbacks(hideFabUpRunnable)
+ fabUp.hide()
+ }
+
+ private fun showFabDown() {
+ handler.removeCallbacks(hideFabDownRunnable)
+ fabDown.show()
+ handler.postDelayed(hideFabDownRunnable, 2000)
+ }
+
+ private fun hideFabDown() {
+ handler.removeCallbacks(hideFabDownRunnable)
+ fabDown.hide()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ serviceBinder = ServiceBinder(LogcatService::class.java, this)
+
+ val activity = requireActivity()
+ val maxLogs = activity.getDefaultSharedPreferences()
+ .getString(
+ PreferenceKeys.Logcat.KEY_MAX_LOGS,
+ PreferenceKeys.Logcat.Default.MAX_LOGS
+ )!!.trim().toInt()
+ adapter = MyRecyclerViewAdapter(activity, maxLogs)
+ activity.getDefaultSharedPreferences().registerOnSharedPreferenceChangeListener(adapter)
+
+ viewModel = activity.getAndroidViewModel()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? =
+ inflateLayout(R.layout.fragment_logcat_live)
+
+ override fun onViewCreated(
+ view: View,
+ savedInstanceState: Bundle?
+ ) {
+ super.onViewCreated(view, savedInstanceState)
+ recyclerView = view.findViewById(R.id.recyclerView)
+ linearLayoutManager = LinearLayoutManager(activity)
+ recyclerView.layoutManager = linearLayoutManager
+ recyclerView.itemAnimator = null
+ recyclerView.addItemDecoration(
+ DividerItemDecoration(
+ activity,
+ linearLayoutManager.orientation
+ )
+ )
+ recyclerView.adapter = adapter
+
+ recyclerView.addOnScrollListener(onScrollListener)
+
+ fabDown = view.findViewById(R.id.fabDown)
+ fabDown.setOnClickListener {
+ logcatService?.logcat?.pause()
+ hideFabDown()
+ ignoreScrollEvent = true
+ viewModel.autoScroll = true
+ linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
+ resumeLogcat()
+ }
+
+ snackBarProgress = IndeterminateProgressSnackBar(view, getString(R.string.saving))
+
+ fabUp = view.findViewById(R.id.fabUp)
+ fabUp.setOnClickListener {
+ logcatService?.logcat?.pause()
+ hideFabUp()
+ viewModel.autoScroll = false
+ linearLayoutManager.scrollToPositionWithOffset(0, 0)
+ resumeLogcat()
+ }
+
+ hideFabUp()
+ hideFabDown()
+
+ adapter.setOnClickListener { v ->
+ val pos = linearLayoutManager.getPosition(v)
+ if (pos >= 0) {
+ viewModel.autoScroll = false
+ val log = adapter[pos]
+ CopyToClipboardDialogFragment.newInstance(log)
+ .show(parentFragmentManager, CopyToClipboardDialogFragment.TAG)
+ }
+ }
+
+ adapter.setOnLongClickListener { v ->
+ val pos = linearLayoutManager.getPosition(v)
+ if (pos >= 0) {
+ viewModel.autoScroll = false
+ val log = adapter[pos]
+ FilterExclusionDialogFragment.newInstance(log)
+ .show(parentFragmentManager, FilterExclusionDialogFragment.TAG)
+ }
+ }
+
+ if (!checkReadLogsPermission() && !viewModel.showedGrantPermissionInstruction) {
+ viewModel.showedGrantPermissionInstruction = true
+ NeedPermissionDialogFragment().let {
+ it.setTargetFragment(this, 0)
+ it.show(parentFragmentManager, NeedPermissionDialogFragment.TAG)
+ }
+ }
+
+ viewModel.getFilters().observe(viewLifecycleOwner, Observer { filters ->
+ if (filters != null) {
logcatService?.let {
- if (it.paused) {
- playPauseItem.icon = ContextCompat.getDrawable(context,
- R.drawable.ic_play_arrow_white_24dp)
- playPauseItem.title = getString(R.string.resume)
+ val logcat = it.logcat
+ logcat.pause()
+ logcat.clearFilters(exclude = SEARCH_FILTER_TAG)
+ logcat.clearExclusions()
+
+ for (filter in filters) {
+ if (filter.exclude) {
+ logcat.addExclusion(
+ "${filter.hashCode()}",
+ LogFilter(filter)
+ )
} else {
- playPauseItem.icon = ContextCompat.getDrawable(context,
- R.drawable.ic_pause_white_24dp)
- playPauseItem.title = getString(R.string.pause)
+ logcat.addFilter(
+ "${filter.hashCode()}",
+ LogFilter(filter)
+ )
}
+ }
- if (it.recording) {
- recordToggleItem.icon = ContextCompat.getDrawable(context,
- R.drawable.ic_stop_white_24dp)
- recordToggleItem.title = getString(R.string.stop_recording)
- } else {
- recordToggleItem.icon = ContextCompat.getDrawable(context,
- R.drawable.ic_fiber_manual_record_white_24dp)
- recordToggleItem.title = getString(R.string.start_recording)
- }
- }
-
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.action_search -> {
- true
- }
- R.id.action_play_pause -> {
- logcatService?.let {
- val newPausedState = !it.paused
- if (newPausedState) {
- it.logcat.pause()
- } else {
- it.logcat.resume()
- }
- it.paused = newPausedState
- activity?.invalidateOptionsMenu()
- }
- true
- }
- R.id.action_record_toggle -> {
- logcatService?.let {
- val recording = !it.recording
-
- if (recording && it.paused) {
- return@let
- }
-
- it.updateNotification(recording)
-
- val logcat = it.logcat
- if (recording) {
- Snackbar.make(requireView(), getString(R.string.started_recording),
- Snackbar.LENGTH_SHORT).show()
- logcat.startRecording()
- } else {
- saveToFile(true)
- }
-
- it.recording = recording
- activity?.invalidateOptionsMenu()
- }
- true
- }
- R.id.clear_action -> {
- logcatService?.logcat?.clearLogs {
- adapter.clear()
- updateToolbarSubtitle(adapter.itemCount)
- }
- true
- }
- R.id.filters_action -> {
- moveToFilterActivity(false)
- true
- }
- R.id.exclusions_action -> {
- moveToFilterActivity(true)
- true
- }
- R.id.action_save -> {
- saveToFile(false)
- true
- }
- R.id.action_view_saved_logs -> {
- startActivity(Intent(activity, SavedLogsActivity::class.java))
- true
- }
- R.id.action_restart_logcat -> {
- adapter.clear()
- logcatService?.logcat?.restart()
- true
- }
- else -> return super.onOptionsItemSelected(item)
+ adapter.setItems(logcat.getLogsFiltered())
+ updateToolbarSubtitle(adapter.itemCount)
+ scrollRecyclerView()
+ resumeLogcat()
}
- }
+ }
+ })
+ }
- private fun moveToFilterActivity(isExclusion: Boolean) {
- val intent = Intent(requireActivity(), FiltersActivity::class.java)
- intent.putExtra(FiltersActivity.EXTRA_EXCLUSIONS, isExclusion)
- startActivity(intent)
- }
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
- fun tryStopRecording() {
- viewModel.stopRecording = true
- if (logcatService != null) {
- stopRecording()
+ viewModel.getFileSaveNotifier().observe(viewLifecycleOwner, Observer { saveInfo ->
+ saveInfo?.let {
+ if (viewModel.alreadySaved) {
+ return@Observer
}
- }
-
- private fun stopRecording() {
- logcatService?.let {
- if (it.recording) {
- it.updateNotification(false)
- saveToFile(true)
- it.recording = false
- activity?.invalidateOptionsMenu()
+ when (it.result) {
+ SaveInfo.IN_PROGRESS -> {
+ snackBarProgress.show()
+ }
+ else -> {
+ snackBarProgress.dismiss()
+ when (it.result) {
+ SaveInfo.SUCCESS -> {
+ OnSavedBottomSheetDialogFragment.newInstance(it.fileName!!, it.uri!!)
+ .show(parentFragmentManager, OnSavedBottomSheetDialogFragment.TAG)
+ }
+ SaveInfo.ERROR_EMPTY_LOGS -> {
+ showSnackbar(view, getString(R.string.nothing_to_save))
+ }
+ else -> {
+ showSnackbar(view, getString(R.string.failed_to_save_logs))
+ }
}
+ }
}
- viewModel.stopRecording = false
- }
+ }
+ })
+ }
+
+ private fun checkReadLogsPermission() = ContextCompat.checkSelfPermission(
+ requireContext(),
+ Manifest.permission.READ_LOGS
+ ) == PackageManager.PERMISSION_GRANTED
+
+ override fun onCreateOptionsMenu(
+ menu: Menu,
+ inflater: MenuInflater
+ ) {
+ super.onCreateOptionsMenu(menu, inflater)
+
+ inflater.inflate(R.menu.logcat_live, menu)
+ val searchItem = menu.findItem(R.id.action_search)
+ val searchView = searchItem.actionView as SearchView
+
+ var reachedBlank = false
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextChange(newText: String): Boolean {
+ searchViewActive = true
+ removeLastSearchRunnableCallback()
- private fun saveToFile(recorded: Boolean) {
- viewModel.save {
- if (recorded) {
- logcatService?.logcat?.stopRecording()
- } else {
- logcatService?.logcat?.getLogsFiltered()
+ if (newText.isBlank()) {
+ reachedBlank = true
+ onSearchViewClose()
+ } else {
+ reachedBlank = false
+ logcatService?.logcat?.let {
+ lastSearchRunnable = Runnable {
+ runSearchTask(it, newText)
+ }.also {
+ handler.postDelayed(it, 100)
}
+ }
}
- }
+ return true
+ }
+ override fun onQueryTextSubmit(query: String) = false
+ })
- override fun onStart() {
- super.onStart()
- serviceBinder.bind(requireActivity())
- }
+ val playPauseItem = menu.findItem(R.id.action_play_pause)
+ val recordToggleItem = menu.findItem(R.id.action_record_toggle)
- override fun onStop() {
- super.onStop()
- serviceBinder.unbind(requireActivity())
+ searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
+ playPauseItem.isVisible = !hasFocus
+ recordToggleItem.isVisible = !hasFocus
}
- private fun removeLastSearchRunnableCallback() {
- lastSearchRunnable?.let {
- handler.removeCallbacks(it)
- }
- lastSearchRunnable = null
- }
-
- override fun onDestroy() {
- super.onDestroy()
- requireContext().getDefaultSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(adapter)
-
- removeLastSearchRunnableCallback()
- searchTask?.cancel()
-
- recyclerView.removeOnScrollListener(onScrollListener)
- logcatService?.let {
- it.logcat.removeEventListener(this)
- it.logcat.unbind(activity as AppCompatActivity)
- }
- serviceBinder.close()
+ searchView.setOnCloseListener {
+ removeLastSearchRunnableCallback()
+ searchViewActive = false
+ if (!reachedBlank) {
+ onSearchViewClose()
+ }
+ playPauseItem.isVisible = true
+ recordToggleItem.isVisible = true
+ false
}
+ }
- override fun onServiceConnected(name: ComponentName, service: IBinder) {
- Logger.debug(LogcatLiveFragment::class, "onServiceConnected")
- logcatService = service.getService()
- val logcat = logcatService!!.logcat
- logcat.pause() // resume on updateFilters callback
-
- if (adapter.itemCount == 0) {
- Logger.debug(LogcatLiveFragment::class, "Added all logs")
- addAllLogs(logcat.getLogsFiltered())
- } else if (logcatService!!.restartedLogcat) {
- Logger.debug(LogcatLiveFragment::class, "Logcat restarted")
- logcatService!!.restartedLogcat = false
- adapter.clear()
- }
+ private fun onSearchViewClose() {
+ logcatService?.logcat?.let {
+ it.pause()
+ it.removeFilter(SEARCH_FILTER_TAG)
+ adapter.clear()
+ addAllLogs(it.getLogsFiltered())
+ if (lastLogId == -1) {
scrollRecyclerView()
-
- logcat.addEventListener(this)
- logcat.bind(activity as AppCompatActivity)
-
- if (viewModel.stopRecording || arguments?.getBoolean(STOP_RECORDING) == true) {
- arguments?.putBoolean(STOP_RECORDING, false)
- stopRecording()
+ } else {
+ viewModel.autoScroll = linearLayoutManager.findLastCompletelyVisibleItemPosition() ==
+ adapter.itemCount - 1
+ if (!viewModel.autoScroll) {
+ viewModel.scrollPosition = lastLogId
+ linearLayoutManager.scrollToPositionWithOffset(lastLogId, 0)
}
-
- viewModel.reloadFilters()
- }
-
- override fun onReceivedLogs(logs: List) {
- adapter.addItems(logs)
- updateToolbarSubtitle(adapter.itemCount)
- if (viewModel.autoScroll) {
- linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
- }
- }
-
- private fun addAllLogs(logs: List) {
- adapter.addItems(logs)
- updateToolbarSubtitle(adapter.itemCount)
- }
-
- private fun scrollRecyclerView() {
- if (viewModel.autoScroll) {
- linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
- } else {
- linearLayoutManager.scrollToPositionWithOffset(viewModel.scrollPosition, 0)
- }
- }
-
- private fun updateToolbarSubtitle(count: Int) {
- if (count > 1) {
- (activity as BaseActivityWithToolbar).toolbar.subtitle = "$count"
- } else {
- (activity as BaseActivityWithToolbar).toolbar.subtitle = null
- }
- }
-
- private fun resumeLogcat() {
+ lastLogId = -1
+ }
+ }
+
+ resumeLogcat()
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ super.onPrepareOptionsMenu(menu)
+
+ val playPauseItem = menu.findItem(R.id.action_play_pause)
+ val recordToggleItem = menu.findItem(R.id.action_record_toggle)
+
+ val context = requireContext()
+ logcatService?.let {
+ if (it.paused) {
+ playPauseItem.icon = ContextCompat.getDrawable(
+ context,
+ R.drawable.ic_play_arrow_white_24dp
+ )
+ playPauseItem.title = getString(R.string.resume)
+ } else {
+ playPauseItem.icon = ContextCompat.getDrawable(
+ context,
+ R.drawable.ic_pause_white_24dp
+ )
+ playPauseItem.title = getString(R.string.pause)
+ }
+
+ if (it.recording) {
+ recordToggleItem.icon = ContextCompat.getDrawable(
+ context,
+ R.drawable.ic_stop_white_24dp
+ )
+ recordToggleItem.title = getString(R.string.stop_recording)
+ } else {
+ recordToggleItem.icon = ContextCompat.getDrawable(
+ context,
+ R.drawable.ic_fiber_manual_record_white_24dp
+ )
+ recordToggleItem.title = getString(R.string.start_recording)
+ }
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.action_search -> {
+ true
+ }
+ R.id.action_play_pause -> {
logcatService?.let {
- if (!it.paused) {
- it.logcat.resume()
- }
+ val newPausedState = !it.paused
+ if (newPausedState) {
+ it.logcat.pause()
+ } else {
+ it.logcat.resume()
+ }
+ it.paused = newPausedState
+ activity?.invalidateOptionsMenu()
}
- }
-
- override fun onServiceDisconnected(name: ComponentName) {
- Logger.debug(LogcatLiveFragment::class, "onServiceDisconnected")
- logcatService = null
- }
-
- private class SearchFilter(private val searchText: String) : Filter {
- override fun apply(log: Log): Boolean {
- return log.tag.containsIgnoreCase(searchText) ||
- log.msg.containsIgnoreCase(searchText)
- }
-
- }
-
- private fun runSearchTask(logcat: Logcat, searchText: String) {
- searchTask?.cancel()
- searchTask = scope.launch {
- logcat.pause()
- logcat.addFilter(SEARCH_FILTER_TAG, SearchFilter(searchText))
-
- val filteredLogs = withContext(Default) { logcat.getLogsFiltered() }
- adapter.setItems(filteredLogs)
- viewModel.autoScroll = false
- linearLayoutManager.scrollToPositionWithOffset(0, 0)
-
- resumeLogcat()
- }
- }
-
- fun useRootToGrantPermission() {
- scope.launch {
- val dialog = AskingForRootAccessDialogFragment()
- dialog.show(parentFragmentManager, AskingForRootAccessDialogFragment.TAG)
-
- val result = withContext(IO) {
- val cmd = "pm grant ${BuildConfig.APPLICATION_ID} ${Manifest.permission.READ_LOGS}"
- SuCommander(cmd).run()
- }
-
- dialog.dismissAllowingStateLoss()
- if (result) {
- RestartAppMessageDialogFragment.newInstance().show(parentFragmentManager,
- RestartAppMessageDialogFragment.TAG)
- } else {
- requireActivity().showToast(getString(R.string.fail))
- ManualMethodToGrantPermissionDialogFragment().show(parentFragmentManager,
- ManualMethodToGrantPermissionDialogFragment.TAG)
- }
+ true
+ }
+ R.id.action_record_toggle -> {
+ logcatService?.let {
+ val recording = !it.recording
+
+ if (recording && it.paused) {
+ return@let
+ }
+
+ it.updateNotification(recording)
+
+ val logcat = it.logcat
+ if (recording) {
+ Snackbar.make(
+ requireView(), getString(R.string.started_recording),
+ Snackbar.LENGTH_SHORT
+ ).show()
+ logcat.startRecording()
+ } else {
+ saveToFile(true)
+ }
+
+ it.recording = recording
+ activity?.invalidateOptionsMenu()
}
- }
-
- private class LogFilter(filterInfo: FilterInfo) : Filter {
- private val type = filterInfo.type
- private val content = filterInfo.content
-
- override fun apply(log: Log): Boolean {
- if (content.isEmpty()) {
- return true
- }
-
- return when (type) {
- FilterType.LOG_LEVELS -> log.priority == content
- FilterType.KEYWORD -> log.msg.containsIgnoreCase(content)
- FilterType.TAG -> log.tag.containsIgnoreCase(content)
- FilterType.PID -> log.pid.containsIgnoreCase(content)
- FilterType.TID -> log.tid.containsIgnoreCase(content)
- else -> false
- }
+ true
+ }
+ R.id.clear_action -> {
+ logcatService?.logcat?.clearLogs {
+ adapter.clear()
+ updateToolbarSubtitle(adapter.itemCount)
}
- }
+ true
+ }
+ R.id.filters_action -> {
+ moveToFilterActivity(false)
+ true
+ }
+ R.id.exclusions_action -> {
+ moveToFilterActivity(true)
+ true
+ }
+ R.id.action_save -> {
+ saveToFile(false)
+ true
+ }
+ R.id.action_view_saved_logs -> {
+ startActivity(Intent(activity, SavedLogsActivity::class.java))
+ true
+ }
+ R.id.action_restart_logcat -> {
+ adapter.clear()
+ logcatService?.logcat?.restart()
+ true
+ }
+ else -> return super.onOptionsItemSelected(item)
+ }
+ }
+
+ private fun moveToFilterActivity(isExclusion: Boolean) {
+ val intent = Intent(requireActivity(), FiltersActivity::class.java)
+ intent.putExtra(FiltersActivity.EXTRA_EXCLUSIONS, isExclusion)
+ startActivity(intent)
+ }
+
+ fun tryStopRecording() {
+ viewModel.stopRecording = true
+ if (logcatService != null) {
+ stopRecording()
+ }
+ }
+
+ private fun stopRecording() {
+ logcatService?.let {
+ if (it.recording) {
+ it.updateNotification(false)
+ saveToFile(true)
+ it.recording = false
+ activity?.invalidateOptionsMenu()
+ }
+ }
+ viewModel.stopRecording = false
+ }
+
+ private fun saveToFile(recorded: Boolean) {
+ viewModel.save {
+ if (recorded) {
+ logcatService?.logcat?.stopRecording()
+ } else {
+ logcatService?.logcat?.getLogsFiltered()
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ serviceBinder.bind(requireActivity())
+ }
+
+ override fun onStop() {
+ super.onStop()
+ serviceBinder.unbind(requireActivity())
+ }
+
+ private fun removeLastSearchRunnableCallback() {
+ lastSearchRunnable?.let {
+ handler.removeCallbacks(it)
+ }
+ lastSearchRunnable = null
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ requireContext().getDefaultSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(adapter)
+
+ removeLastSearchRunnableCallback()
+ searchTask?.cancel()
+
+ recyclerView.removeOnScrollListener(onScrollListener)
+ logcatService?.let {
+ it.logcat.removeEventListener(this)
+ it.logcat.unbind(activity as AppCompatActivity)
+ }
+ serviceBinder.close()
+ }
+
+ override fun onServiceConnected(
+ name: ComponentName,
+ service: IBinder
+ ) {
+ Logger.debug(LogcatLiveFragment::class, "onServiceConnected")
+ logcatService = service.getService()
+ val logcat = logcatService!!.logcat
+ logcat.pause() // resume on updateFilters callback
+
+ if (adapter.itemCount == 0) {
+ Logger.debug(LogcatLiveFragment::class, "Added all logs")
+ addAllLogs(logcat.getLogsFiltered())
+ } else if (logcatService!!.restartedLogcat) {
+ Logger.debug(LogcatLiveFragment::class, "Logcat restarted")
+ logcatService!!.restartedLogcat = false
+ adapter.clear()
+ }
+
+ scrollRecyclerView()
+
+ logcat.addEventListener(this)
+ logcat.bind(activity as AppCompatActivity)
+
+ if (viewModel.stopRecording || arguments?.getBoolean(STOP_RECORDING) == true) {
+ arguments?.putBoolean(STOP_RECORDING, false)
+ stopRecording()
+ }
+
+ viewModel.reloadFilters()
+ }
+
+ override fun onReceivedLogs(logs: List) {
+ adapter.addItems(logs)
+ updateToolbarSubtitle(adapter.itemCount)
+ if (viewModel.autoScroll) {
+ linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
+ }
+ }
+
+ private fun addAllLogs(logs: List) {
+ adapter.addItems(logs)
+ updateToolbarSubtitle(adapter.itemCount)
+ }
+
+ private fun scrollRecyclerView() {
+ if (viewModel.autoScroll) {
+ linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
+ } else {
+ linearLayoutManager.scrollToPositionWithOffset(viewModel.scrollPosition, 0)
+ }
+ }
+
+ private fun updateToolbarSubtitle(count: Int) {
+ if (count > 1) {
+ (activity as BaseActivityWithToolbar).toolbar.subtitle = "$count"
+ } else {
+ (activity as BaseActivityWithToolbar).toolbar.subtitle = null
+ }
+ }
+
+ private fun resumeLogcat() {
+ logcatService?.let {
+ if (!it.paused) {
+ it.logcat.resume()
+ }
+ }
+ }
+
+ override fun onServiceDisconnected(name: ComponentName) {
+ Logger.debug(LogcatLiveFragment::class, "onServiceDisconnected")
+ logcatService = null
+ }
+
+ private class SearchFilter(private val searchText: String) : Filter {
+ override fun apply(log: Log): Boolean {
+ return log.tag.containsIgnoreCase(searchText) ||
+ log.msg.containsIgnoreCase(searchText)
+ }
+ }
+
+ private fun runSearchTask(
+ logcat: Logcat,
+ searchText: String
+ ) {
+ searchTask?.cancel()
+ searchTask = scope.launch {
+ logcat.pause()
+ logcat.addFilter(SEARCH_FILTER_TAG, SearchFilter(searchText))
+
+ val filteredLogs = withContext(Default) { logcat.getLogsFiltered() }
+ adapter.setItems(filteredLogs)
+ viewModel.autoScroll = false
+ linearLayoutManager.scrollToPositionWithOffset(0, 0)
+
+ resumeLogcat()
+ }
+ }
+
+ fun useRootToGrantPermission() {
+ scope.launch {
+ val dialog = AskingForRootAccessDialogFragment()
+ dialog.show(parentFragmentManager, AskingForRootAccessDialogFragment.TAG)
+
+ val result = withContext(IO) {
+ val cmd = "pm grant ${BuildConfig.APPLICATION_ID} ${Manifest.permission.READ_LOGS}"
+ SuCommander(cmd).run()
+ }
+
+ dialog.dismissAllowingStateLoss()
+ if (result) {
+ RestartAppMessageDialogFragment.newInstance().show(
+ parentFragmentManager,
+ RestartAppMessageDialogFragment.TAG
+ )
+ } else {
+ requireActivity().showToast(getString(R.string.fail))
+ ManualMethodToGrantPermissionDialogFragment().show(
+ parentFragmentManager,
+ ManualMethodToGrantPermissionDialogFragment.TAG
+ )
+ }
+ }
+ }
+
+ private class LogFilter(filterInfo: FilterInfo) : Filter {
+ private val type = filterInfo.type
+ private val content = filterInfo.content
+
+ override fun apply(log: Log): Boolean {
+ if (content.isEmpty()) {
+ return true
+ }
+
+ return when (type) {
+ FilterType.LOG_LEVELS -> log.priority == content
+ FilterType.KEYWORD -> log.msg.containsIgnoreCase(content)
+ FilterType.TAG -> log.tag.containsIgnoreCase(content)
+ FilterType.PID -> log.pid.containsIgnoreCase(content)
+ FilterType.TID -> log.tid.containsIgnoreCase(content)
+ else -> false
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveViewModel.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveViewModel.kt
index 70376b44..a6b84ed7 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveViewModel.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/LogcatLiveViewModel.kt
@@ -18,132 +18,143 @@ import com.dp.logcatapp.util.PreferenceKeys
import com.dp.logcatapp.util.ScopedAndroidViewModel
import com.dp.logcatapp.util.Utils
import com.dp.logcatapp.util.getDefaultSharedPreferences
-import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.io.File
import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
+import java.util.Locale
internal class LogcatLiveViewModel(application: Application) : ScopedAndroidViewModel(application) {
- var autoScroll = true
- var scrollPosition = 0
- var showedGrantPermissionInstruction = false
- var stopRecording = false
+ var autoScroll = true
+ var scrollPosition = 0
+ var showedGrantPermissionInstruction = false
+ var stopRecording = false
- private val fileSaveNotifier = MutableLiveData()
- var alreadySaved = true
- private set
+ private val fileSaveNotifier = MutableLiveData()
+ var alreadySaved = true
+ private set
- private lateinit var filters: MutableLiveData>
- private var loadFiltersJob: Job? = null
+ private lateinit var filters: MutableLiveData>
+ private var loadFiltersJob: Job? = null
- fun getFilters(): LiveData> {
- if (this::filters.isInitialized) {
- return filters
- }
-
- filters = MutableLiveData()
- reloadFilters()
- return filters
+ fun getFilters(): LiveData> {
+ if (this::filters.isInitialized) {
+ return filters
}
- fun reloadFilters() {
- loadFiltersJob?.cancel()
- loadFiltersJob = launch {
- val db = MyDB.getInstance(getApplication())
- filters.value = withContext(IO) {
- db.filterDao().getAll()
- }
- }
+ filters = MutableLiveData()
+ reloadFilters()
+ return filters
+ }
+
+ fun reloadFilters() {
+ loadFiltersJob?.cancel()
+ loadFiltersJob = launch {
+ val db = MyDB.getInstance(getApplication())
+ filters.value = withContext(IO) {
+ db.filterDao().getAll()
+ }
}
+ }
- fun getFileSaveNotifier(): LiveData = fileSaveNotifier
+ fun getFileSaveNotifier(): LiveData = fileSaveNotifier
- fun save(f: () -> List?) {
- launch {
- alreadySaved = false
- fileSaveNotifier.value = SaveInfo(SaveInfo.IN_PROGRESS)
- fileSaveNotifier.value = withContext(IO) { saveAsync(f) }
- alreadySaved = true
- }
+ fun save(f: () -> List?) {
+ launch {
+ alreadySaved = false
+ fileSaveNotifier.value = SaveInfo(SaveInfo.IN_PROGRESS)
+ fileSaveNotifier.value = withContext(IO) { saveAsync(f) }
+ alreadySaved = true
}
+ }
+
+ private suspend fun saveAsync(f: () -> List?): SaveInfo = coroutineScope {
+ val saveInfo = SaveInfo(SaveInfo.ERROR_SAVING)
- private suspend fun saveAsync(f: () -> List?): SaveInfo = coroutineScope {
- val saveInfo = SaveInfo(SaveInfo.ERROR_SAVING)
+ val logs = f().orEmpty()
+ if (logs.isEmpty()) {
+ saveInfo.result = SaveInfo.ERROR_EMPTY_LOGS
+ } else {
+ getUri()?.let { uri ->
+ saveInfo.uri = uri
- val logs = f().orEmpty()
- if (logs.isEmpty()) {
- saveInfo.result = SaveInfo.ERROR_EMPTY_LOGS
+ val context = getApplication()
+ val isUsingCustomLocation = Utils.isUsingCustomSaveLocation(context)
+ val result = if (isUsingCustomLocation && Build.VERSION.SDK_INT >= 21) {
+ Logcat.writeToFile(context, logs, uri)
} else {
- getUri()?.let { uri ->
- saveInfo.uri = uri
-
- val context = getApplication()
- val isUsingCustomLocation = Utils.isUsingCustomSaveLocation(context)
- val result = if (isUsingCustomLocation && Build.VERSION.SDK_INT >= 21) {
- Logcat.writeToFile(context, logs, uri)
- } else {
- Logcat.writeToFile(logs, uri.toFile())
- }
-
- saveInfo.result = SaveInfo.ERROR_SAVING
- if (result) {
- val fileName = if (isUsingCustomLocation && Build.VERSION.SDK_INT >= 21) {
- DocumentFile.fromSingleUri(context, uri)?.name
- } else {
- uri.toFile().name
- }
-
- if (fileName != null) {
- val db = MyDB.getInstance(context)
- db.savedLogsDao().insert(SavedLogInfo(fileName,
- uri.toString(), isUsingCustomLocation))
-
- saveInfo.result = SaveInfo.SUCCESS
- saveInfo.fileName = fileName
- }
- }
- }
+ Logcat.writeToFile(logs, uri.toFile())
}
- saveInfo
+ saveInfo.result = SaveInfo.ERROR_SAVING
+ if (result) {
+ val fileName = if (isUsingCustomLocation && Build.VERSION.SDK_INT >= 21) {
+ DocumentFile.fromSingleUri(context, uri)?.name
+ } else {
+ uri.toFile().name
+ }
+
+ if (fileName != null) {
+ val db = MyDB.getInstance(context)
+ db.savedLogsDao().insert(
+ SavedLogInfo(
+ fileName,
+ uri.toString(), isUsingCustomLocation
+ )
+ )
+
+ saveInfo.result = SaveInfo.SUCCESS
+ saveInfo.fileName = fileName
+ }
+ }
+ }
}
- private fun getUri(): Uri? {
- val timeStamp = SimpleDateFormat("MM-dd-yyyy_HH-mm-ss", Locale.getDefault())
- .format(Date())
- val fileName = "logcat_$timeStamp"
-
- val context = getApplication()
- val saveLocationPref = context.getDefaultSharedPreferences().getString(
- PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
- PreferenceKeys.Logcat.Default.SAVE_LOCATION
- )!!
-
- return if (saveLocationPref.isEmpty()) {
- val file = File(context.filesDir, LOGCAT_DIR)
- file.mkdirs()
- File(file, "$fileName.txt").toUri()
- } else {
- if (Build.VERSION.SDK_INT >= 21) {
- val documentFile = DocumentFile.fromTreeUri(context, Uri.parse(saveLocationPref))
- val file = documentFile?.createFile("text/plain", fileName)
- file?.uri
- } else {
- val file = File(saveLocationPref, LOGCAT_DIR)
- file.mkdirs()
- File(file, "$fileName.txt").toUri()
- }
- }
+ saveInfo
+ }
+
+ private fun getUri(): Uri? {
+ val timeStamp = SimpleDateFormat("MM-dd-yyyy_HH-mm-ss", Locale.getDefault())
+ .format(Date())
+ val fileName = "logcat_$timeStamp"
+
+ val context = getApplication()
+ val saveLocationPref = context.getDefaultSharedPreferences().getString(
+ PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
+ PreferenceKeys.Logcat.Default.SAVE_LOCATION
+ )!!
+
+ return if (saveLocationPref.isEmpty()) {
+ val file = File(context.filesDir, LOGCAT_DIR)
+ file.mkdirs()
+ File(file, "$fileName.txt").toUri()
+ } else {
+ if (Build.VERSION.SDK_INT >= 21) {
+ val documentFile = DocumentFile.fromTreeUri(context, Uri.parse(saveLocationPref))
+ val file = documentFile?.createFile("text/plain", fileName)
+ file?.uri
+ } else {
+ val file = File(saveLocationPref, LOGCAT_DIR)
+ file.mkdirs()
+ File(file, "$fileName.txt").toUri()
+ }
}
+ }
}
-internal data class SaveInfo(var result: Int, var fileName: String? = null,
- var uri: Uri? = null) {
- companion object {
- const val SUCCESS = 0
- const val ERROR_EMPTY_LOGS = 1
- const val ERROR_SAVING = 2
- const val IN_PROGRESS = 4
- }
+internal data class SaveInfo(
+ var result: Int,
+ var fileName: String? = null,
+ var uri: Uri? = null
+) {
+ companion object {
+ const val SUCCESS = 0
+ const val ERROR_EMPTY_LOGS = 1
+ const val ERROR_SAVING = 2
+ const val IN_PROGRESS = 4
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/MyRecyclerViewAdapter.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/MyRecyclerViewAdapter.kt
index a48ba385..ce15f6e5 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/MyRecyclerViewAdapter.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/MyRecyclerViewAdapter.kt
@@ -14,124 +14,138 @@ import com.dp.logcat.Logcat
import com.dp.logcatapp.R
import com.dp.logcatapp.util.PreferenceKeys
import com.logcat.collections.FixedCircularArray
-import kotlin.math.max
-
-internal class MyRecyclerViewAdapter(context: Context, initialCapacity: Int) :
- RecyclerView.Adapter(),
- View.OnClickListener, View.OnLongClickListener, SharedPreferences.OnSharedPreferenceChangeListener {
-
-
- private var list = FixedCircularArray(initialCapacity, Logcat.INITIAL_LOG_SIZE)
- private var onClickListener: ((View) -> Unit)? = null
- private var onLongClickListener: ((View) -> Unit)? = null
-
- private val priorityColorAssert = ContextCompat.getColor(context, R.color.priority_assert)
- private val priorityColorDebug = ContextCompat.getColor(context, R.color.priority_debug)
- private val priorityColorError = ContextCompat.getColor(context, R.color.priority_error)
- private val priorityColorInfo = ContextCompat.getColor(context, R.color.priority_info)
- private val priorityColorVerbose = ContextCompat.getColor(context, R.color.priority_verbose)
- private val priorityColorWarning = ContextCompat.getColor(context, R.color.priority_warning)
- private val priorityColorFatal = ContextCompat.getColor(context, R.color.priority_fatal)
- private val priorityColorSilent = ContextCompat.getColor(context, R.color.priority_silent)
-
- private fun getPriorityColor(priority: String) = when (priority) {
- LogPriority.ASSERT -> priorityColorAssert
- LogPriority.DEBUG -> priorityColorDebug
- LogPriority.ERROR -> priorityColorError
- LogPriority.FATAL -> priorityColorFatal
- LogPriority.INFO -> priorityColorInfo
- LogPriority.VERBOSE -> priorityColorVerbose
- LogPriority.WARNING -> priorityColorWarning
- else -> priorityColorSilent
- }
-
- override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
- val log = list[position]
- holder.date.text = log.date
- holder.time.text = log.time
- holder.pid.text = log.pid
- holder.tid.text = log.tid
- holder.priority.text = log.priority
- holder.tag.text = log.tag
- holder.message.text = log.msg
-
- holder.priority.setBackgroundColor(getPriorityColor(log.priority))
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.fragment_logcat_live_list_item, parent, false)
- view.setOnClickListener(this)
- view.setOnLongClickListener(this)
- return MyViewHolder(view)
- }
- override fun onClick(v: View) {
- when (v.id) {
- R.id.list_item_root -> onClickListener?.invoke(v)
- }
+internal class MyRecyclerViewAdapter(
+ context: Context,
+ initialCapacity: Int
+) :
+ RecyclerView.Adapter(),
+ View.OnClickListener,
+ View.OnLongClickListener,
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private var list = FixedCircularArray(initialCapacity, Logcat.INITIAL_LOG_SIZE)
+ private var onClickListener: ((View) -> Unit)? = null
+ private var onLongClickListener: ((View) -> Unit)? = null
+
+ private val priorityColorAssert = ContextCompat.getColor(context, R.color.priority_assert)
+ private val priorityColorDebug = ContextCompat.getColor(context, R.color.priority_debug)
+ private val priorityColorError = ContextCompat.getColor(context, R.color.priority_error)
+ private val priorityColorInfo = ContextCompat.getColor(context, R.color.priority_info)
+ private val priorityColorVerbose = ContextCompat.getColor(context, R.color.priority_verbose)
+ private val priorityColorWarning = ContextCompat.getColor(context, R.color.priority_warning)
+ private val priorityColorFatal = ContextCompat.getColor(context, R.color.priority_fatal)
+ private val priorityColorSilent = ContextCompat.getColor(context, R.color.priority_silent)
+
+ private fun getPriorityColor(priority: String) = when (priority) {
+ LogPriority.ASSERT -> priorityColorAssert
+ LogPriority.DEBUG -> priorityColorDebug
+ LogPriority.ERROR -> priorityColorError
+ LogPriority.FATAL -> priorityColorFatal
+ LogPriority.INFO -> priorityColorInfo
+ LogPriority.VERBOSE -> priorityColorVerbose
+ LogPriority.WARNING -> priorityColorWarning
+ else -> priorityColorSilent
+ }
+
+ override fun onBindViewHolder(
+ holder: MyViewHolder,
+ position: Int
+ ) {
+ val log = list[position]
+ holder.date.text = log.date
+ holder.time.text = log.time
+ holder.pid.text = log.pid
+ holder.tid.text = log.tid
+ holder.priority.text = log.priority
+ holder.tag.text = log.tag
+ holder.message.text = log.msg
+
+ holder.priority.setBackgroundColor(getPriorityColor(log.priority))
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MyViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.fragment_logcat_live_list_item, parent, false)
+ view.setOnClickListener(this)
+ view.setOnLongClickListener(this)
+ return MyViewHolder(view)
+ }
+
+ override fun onClick(v: View) {
+ when (v.id) {
+ R.id.list_item_root -> onClickListener?.invoke(v)
}
+ }
- override fun onLongClick(v: View): Boolean {
- when (v.id) {
- R.id.list_item_root -> onLongClickListener?.invoke(v)
- }
- return true
+ override fun onLongClick(v: View): Boolean {
+ when (v.id) {
+ R.id.list_item_root -> onLongClickListener?.invoke(v)
}
-
- override fun getItemCount() = list.size
-
- internal fun addItems(items: List) {
- val startPosition = list.size
- list.add(items)
- if (list.size < list.capacity) {
- notifyItemRangeInserted(startPosition, items.size)
- } else {
- notifyItemRangeChanged(0, list.size)
- }
+ return true
+ }
+
+ override fun getItemCount() = list.size
+
+ internal fun addItems(items: List) {
+ val startPosition = list.size
+ list.add(items)
+ if (list.size < list.capacity) {
+ notifyItemRangeInserted(startPosition, items.size)
+ } else {
+ notifyItemRangeChanged(0, list.size)
}
-
- internal fun setItems(items: List) {
- clear()
- addItems(items)
- }
-
- operator fun get(index: Int) = list[index]
-
- internal fun clear() {
- val count = list.size
- list.clear()
- notifyItemRangeRemoved(0, count)
- }
-
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
- when (key) {
- PreferenceKeys.Logcat.KEY_MAX_LOGS -> {
- val newCapacity = sharedPreferences.getString(PreferenceKeys.Logcat.KEY_MAX_LOGS,
- PreferenceKeys.Logcat.Default.MAX_LOGS)!!.trim().toInt()
- val newData = FixedCircularArray(newCapacity, Logcat.INITIAL_LOG_SIZE)
- newData.add(list)
- list = newData
- notifyDataSetChanged()
- }
- }
- }
-
- internal fun setOnClickListener(onClickListener: (View) -> Unit) {
- this.onClickListener = onClickListener
- }
-
- internal fun setOnLongClickListener(onLongClickListener: (View) -> Unit) {
- this.onLongClickListener = onLongClickListener
- }
-
- class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val date: TextView = itemView.findViewById(R.id.date)
- val time: TextView = itemView.findViewById(R.id.time)
- val pid: TextView = itemView.findViewById(R.id.pid)
- val tid: TextView = itemView.findViewById(R.id.tid)
- val priority: TextView = itemView.findViewById(R.id.priority)
- val tag: TextView = itemView.findViewById(R.id.tag)
- val message: TextView = itemView.findViewById(R.id.message)
+ }
+
+ internal fun setItems(items: List) {
+ clear()
+ addItems(items)
+ }
+
+ operator fun get(index: Int) = list[index]
+
+ internal fun clear() {
+ val count = list.size
+ list.clear()
+ notifyItemRangeRemoved(0, count)
+ }
+
+ override fun onSharedPreferenceChanged(
+ sharedPreferences: SharedPreferences,
+ key: String
+ ) {
+ when (key) {
+ PreferenceKeys.Logcat.KEY_MAX_LOGS -> {
+ val newCapacity = sharedPreferences.getString(
+ PreferenceKeys.Logcat.KEY_MAX_LOGS,
+ PreferenceKeys.Logcat.Default.MAX_LOGS
+ )!!.trim().toInt()
+ val newData = FixedCircularArray(newCapacity, Logcat.INITIAL_LOG_SIZE)
+ newData.add(list)
+ list = newData
+ notifyDataSetChanged()
+ }
}
+ }
+
+ internal fun setOnClickListener(onClickListener: (View) -> Unit) {
+ this.onClickListener = onClickListener
+ }
+
+ internal fun setOnLongClickListener(onLongClickListener: (View) -> Unit) {
+ this.onLongClickListener = onLongClickListener
+ }
+
+ class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val date: TextView = itemView.findViewById(R.id.date)
+ val time: TextView = itemView.findViewById(R.id.time)
+ val pid: TextView = itemView.findViewById(R.id.pid)
+ val tid: TextView = itemView.findViewById(R.id.tid)
+ val priority: TextView = itemView.findViewById(R.id.priority)
+ val tag: TextView = itemView.findViewById(R.id.tag)
+ val message: TextView = itemView.findViewById(R.id.message)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/AskingForRootAccessDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/AskingForRootAccessDialogFragment.kt
index 27e163e0..83cd9a21 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/AskingForRootAccessDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/AskingForRootAccessDialogFragment.kt
@@ -9,17 +9,17 @@ import com.dp.logcatapp.util.inflateLayout
class AskingForRootAccessDialogFragment : BaseDialogFragment() {
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val view = inflateLayout(R.layout.asking_for_root_access_dialog_fragment)
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val view = inflateLayout(R.layout.asking_for_root_access_dialog_fragment)
- isCancelable = false
- return AlertDialog.Builder(requireActivity())
- .setView(view)
- .setCancelable(false)
- .create()
- }
+ isCancelable = false
+ return AlertDialog.Builder(requireActivity())
+ .setView(view)
+ .setCancelable(false)
+ .create()
+ }
- companion object {
- val TAG = AskingForRootAccessDialogFragment::class.qualifiedName
- }
+ companion object {
+ val TAG = AskingForRootAccessDialogFragment::class.qualifiedName
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/ManualMethodToGrantPermissionDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/ManualMethodToGrantPermissionDialogFragment.kt
index 63f661a4..65d0595f 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/ManualMethodToGrantPermissionDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/ManualMethodToGrantPermissionDialogFragment.kt
@@ -13,23 +13,23 @@ import com.dp.logcatapp.util.inflateLayout
class ManualMethodToGrantPermissionDialogFragment : BaseDialogFragment() {
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val view = inflateLayout(R.layout.manual_method_dialog_fragment)
- return AlertDialog.Builder(requireActivity())
- .setTitle(getString(R.string.manual_method))
- .setView(view)
- .setPositiveButton(R.string.copy_adb_command) { _, _ ->
- val activity = requireActivity()
- val cmd = "adb shell pm grant ${activity.packageName} " +
- Manifest.permission.READ_LOGS
- val cm = activity.getSystemService(Context.CLIPBOARD_SERVICE)
- as ClipboardManager
- cm.setPrimaryClip(ClipData.newPlainText("Adb command", cmd))
- }
- .create()
- }
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val view = inflateLayout(R.layout.manual_method_dialog_fragment)
+ return AlertDialog.Builder(requireActivity())
+ .setTitle(getString(R.string.manual_method))
+ .setView(view)
+ .setPositiveButton(R.string.copy_adb_command) { _, _ ->
+ val activity = requireActivity()
+ val cmd = "adb shell pm grant ${activity.packageName} " +
+ Manifest.permission.READ_LOGS
+ val cm = activity.getSystemService(Context.CLIPBOARD_SERVICE)
+ as ClipboardManager
+ cm.setPrimaryClip(ClipData.newPlainText("Adb command", cmd))
+ }
+ .create()
+ }
- companion object {
- val TAG = ManualMethodToGrantPermissionDialogFragment::class.qualifiedName
- }
+ companion object {
+ val TAG = ManualMethodToGrantPermissionDialogFragment::class.qualifiedName
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/NeedPermissionDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/NeedPermissionDialogFragment.kt
index 5004ada8..dc595af4 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/NeedPermissionDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/NeedPermissionDialogFragment.kt
@@ -9,21 +9,23 @@ import com.dp.logcatapp.fragments.logcatlive.LogcatLiveFragment
class NeedPermissionDialogFragment : BaseDialogFragment() {
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- return AlertDialog.Builder(requireActivity())
- .setTitle(R.string.read_logs_permission_required)
- .setMessage(R.string.read_logs_permission_required_msg)
- .setPositiveButton(R.string.manual_method) { _, _ ->
- ManualMethodToGrantPermissionDialogFragment().show(parentFragmentManager,
- ManualMethodToGrantPermissionDialogFragment.TAG)
- }
- .setNegativeButton(R.string.root_method) { _, _ ->
- (targetFragment as LogcatLiveFragment).useRootToGrantPermission()
- }
- .create()
- }
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.read_logs_permission_required)
+ .setMessage(R.string.read_logs_permission_required_msg)
+ .setPositiveButton(R.string.manual_method) { _, _ ->
+ ManualMethodToGrantPermissionDialogFragment().show(
+ parentFragmentManager,
+ ManualMethodToGrantPermissionDialogFragment.TAG
+ )
+ }
+ .setNegativeButton(R.string.root_method) { _, _ ->
+ (targetFragment as LogcatLiveFragment).useRootToGrantPermission()
+ }
+ .create()
+ }
- companion object {
- val TAG = NeedPermissionDialogFragment::class.qualifiedName
- }
+ companion object {
+ val TAG = NeedPermissionDialogFragment::class.qualifiedName
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/OnSavedBottomSheetDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/OnSavedBottomSheetDialogFragment.kt
index 0deda6fc..668ed688 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/OnSavedBottomSheetDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/OnSavedBottomSheetDialogFragment.kt
@@ -15,51 +15,57 @@ import com.dp.logcatapp.util.showSnackbar
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class OnSavedBottomSheetDialogFragment : BottomSheetDialogFragment() {
- companion object {
- val TAG = OnSavedBottomSheetDialogFragment::class.qualifiedName
+ companion object {
+ val TAG = OnSavedBottomSheetDialogFragment::class.qualifiedName
- private val KEY_URI = TAG + "_uri"
- private val KEY_FILE_NAME = TAG + "_file_name"
+ private val KEY_URI = TAG + "_uri"
+ private val KEY_FILE_NAME = TAG + "_file_name"
- fun newInstance(fileName: String, uri: Uri): OnSavedBottomSheetDialogFragment {
- val fragment = OnSavedBottomSheetDialogFragment()
- val bundle = Bundle()
- bundle.putString(KEY_URI, uri.toString())
- bundle.putString(KEY_FILE_NAME, fileName)
- fragment.arguments = bundle
- return fragment
- }
+ fun newInstance(
+ fileName: String,
+ uri: Uri
+ ): OnSavedBottomSheetDialogFragment {
+ val fragment = OnSavedBottomSheetDialogFragment()
+ val bundle = Bundle()
+ bundle.putString(KEY_URI, uri.toString())
+ bundle.putString(KEY_FILE_NAME, fileName)
+ fragment.arguments = bundle
+ return fragment
}
+ }
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
- val rootView = inflater.inflate(R.layout.saved_log_bottom_sheet, container, false)
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val rootView = inflater.inflate(R.layout.saved_log_bottom_sheet, container, false)
- val arguments = requireArguments()
- val fileName = arguments.getString(KEY_FILE_NAME)
- val uri = Uri.parse(arguments.getString(KEY_URI))
+ val arguments = requireArguments()
+ val fileName = arguments.getString(KEY_FILE_NAME)
+ val uri = Uri.parse(arguments.getString(KEY_URI))
- rootView.findViewById(R.id.savedFileName).text = fileName
- rootView.findViewById(R.id.actionView).setOnClickListener {
- if (!viewSavedLog(uri)) {
- showSnackbar(view, getString(R.string.could_not_open_log_file))
- }
- dismiss()
- }
-
- rootView.findViewById(R.id.actionShare).setOnClickListener {
- val context = requireContext()
- ShareUtils.shareSavedLogs(context, uri, Utils.isUsingCustomSaveLocation(context))
- dismiss()
- }
-
- return rootView
+ rootView.findViewById(R.id.savedFileName).text = fileName
+ rootView.findViewById(R.id.actionView).setOnClickListener {
+ if (!viewSavedLog(uri)) {
+ showSnackbar(view, getString(R.string.could_not_open_log_file))
+ }
+ dismiss()
}
- private fun viewSavedLog(uri: Uri): Boolean {
- val intent = Intent(context, SavedLogsViewerActivity::class.java)
- intent.setDataAndType(uri, "text/plain")
- startActivity(intent)
- return true
+ rootView.findViewById(R.id.actionShare).setOnClickListener {
+ val context = requireContext()
+ ShareUtils.shareSavedLogs(context, uri, Utils.isUsingCustomSaveLocation(context))
+ dismiss()
}
+
+ return rootView
+ }
+
+ private fun viewSavedLog(uri: Uri): Boolean {
+ val intent = Intent(context, SavedLogsViewerActivity::class.java)
+ intent.setDataAndType(uri, "text/plain")
+ startActivity(intent)
+ return true
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/RestartAppMessageDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/RestartAppMessageDialogFragment.kt
index f8c38aae..7a178ae0 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/RestartAppMessageDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/logcatlive/dialogs/RestartAppMessageDialogFragment.kt
@@ -11,25 +11,25 @@ import com.dp.logcatapp.services.LogcatService
class RestartAppMessageDialogFragment : BaseDialogFragment() {
- companion object {
- val TAG = RestartAppMessageDialogFragment::class.qualifiedName
+ companion object {
+ val TAG = RestartAppMessageDialogFragment::class.qualifiedName
- fun newInstance(): RestartAppMessageDialogFragment {
- return RestartAppMessageDialogFragment()
- }
+ fun newInstance(): RestartAppMessageDialogFragment {
+ return RestartAppMessageDialogFragment()
}
+ }
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- isCancelable = false
- return AlertDialog.Builder(requireActivity())
- .setTitle(R.string.app_restart_dialog_title)
- .setMessage(getString(R.string.app_restart_dialog_msg_body))
- .setCancelable(false)
- .setPositiveButton(android.R.string.ok) { _, _ ->
- val context = requireContext()
- context.stopService(Intent(context, LogcatService::class.java))
- Process.killProcess(Process.myPid())
- }
- .create()
- }
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ isCancelable = false
+ return AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.app_restart_dialog_title)
+ .setMessage(getString(R.string.app_restart_dialog_msg_body))
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ val context = requireContext()
+ context.stopService(Intent(context, LogcatService::class.java))
+ Process.killProcess(Process.myPid())
+ }
+ .create()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsFragment.kt
index a9750ca6..30ab7215 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsFragment.kt
@@ -37,536 +37,589 @@ import com.dp.logcatapp.db.SavedLogInfo
import com.dp.logcatapp.fragments.base.BaseDialogFragment
import com.dp.logcatapp.fragments.base.BaseFragment
import com.dp.logcatapp.fragments.logcatlive.LogcatLiveFragment
-import com.dp.logcatapp.util.*
+import com.dp.logcatapp.util.ShareUtils
+import com.dp.logcatapp.util.closeQuietly
+import com.dp.logcatapp.util.getAndroidViewModel
+import com.dp.logcatapp.util.inflateLayout
+import com.dp.logcatapp.util.showToast
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.io.*
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.OutputStreamWriter
class SavedLogsFragment : BaseFragment(), View.OnClickListener, View.OnLongClickListener,
- Toolbar.OnMenuItemClickListener, CabToolbarCallback {
- companion object {
- val TAG = SavedLogsFragment::class.qualifiedName
- private const val SAVE_REQ = 12
- }
-
- private lateinit var viewModel: SavedLogsViewModel
- private lateinit var recyclerView: RecyclerView
- private lateinit var emptyView: View
- private lateinit var recyclerViewAdapter: MyRecyclerViewAdapter
- private lateinit var linearLayoutManager: LinearLayoutManager
- private lateinit var progressBar: ProgressBar
-
- private var exportFormat: ExportFormat? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- viewModel = getAndroidViewModel()
-
- recyclerViewAdapter = MyRecyclerViewAdapter(requireActivity(),
- this, this, viewModel.selectedItems)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? =
- inflateLayout(R.layout.fragment_saved_logs)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- emptyView = view.findViewById(R.id.textViewEmpty)
- progressBar = view.findViewById(R.id.progressBar)
-
- recyclerView = view.findViewById(R.id.recyclerView)
- linearLayoutManager = LinearLayoutManager(context)
- recyclerView.itemAnimator = null
- recyclerView.layoutManager = linearLayoutManager
- recyclerView.adapter = recyclerViewAdapter
-
- parentFragmentManager.findFragmentByTag(RenameDialogFragment.TAG)
- ?.setTargetFragment(this, 0)
-
- viewModel.getFileNames().observe(viewLifecycleOwner, Observer {
- progressBar.visibility = View.GONE
- if (it != null) {
- if (it.logFiles.isNotEmpty()) {
- emptyView.visibility = View.GONE
- recyclerView.visibility = View.VISIBLE
- recyclerViewAdapter.setItems(it.logFiles)
-
- if (it.totalSize.isEmpty()) {
- (activity as BaseActivityWithToolbar).toolbar.subtitle =
- "${it.logFiles.size}"
- } else {
- val totalLogCountFmt = if (it.totalLogCount == 1L) {
- resources.getString(R.string.log_count_fmt)
- } else {
- resources.getString(R.string.log_count_fmt_plural)
- }
-
- val totalLogCountStr = totalLogCountFmt.format(it.totalLogCount)
- (activity as BaseActivityWithToolbar).toolbar.subtitle =
- "${it.logFiles.size} ($totalLogCountStr, ${it.totalSize})"
- }
-
- if (viewModel.selectedItems.isNotEmpty()) {
- (activity as SavedLogsActivity).openCabToolbar(this,
- this)
- (activity as SavedLogsActivity).setCabToolbarTitle(viewModel
- .selectedItems.size.toString())
- }
- } else {
- recyclerView.visibility = View.GONE
- emptyView.visibility = View.VISIBLE
- recyclerViewAdapter.setItems(emptyList())
- (activity as BaseActivityWithToolbar).toolbar.subtitle = null
- }
+ Toolbar.OnMenuItemClickListener, CabToolbarCallback {
+ companion object {
+ val TAG = SavedLogsFragment::class.qualifiedName
+ private const val SAVE_REQ = 12
+ }
+
+ private lateinit var viewModel: SavedLogsViewModel
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var emptyView: View
+ private lateinit var recyclerViewAdapter: MyRecyclerViewAdapter
+ private lateinit var linearLayoutManager: LinearLayoutManager
+ private lateinit var progressBar: ProgressBar
+
+ private var exportFormat: ExportFormat? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ viewModel = getAndroidViewModel()
+
+ recyclerViewAdapter = MyRecyclerViewAdapter(
+ requireActivity(),
+ this, this, viewModel.selectedItems
+ )
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? =
+ inflateLayout(R.layout.fragment_saved_logs)
+
+ override fun onViewCreated(
+ view: View,
+ savedInstanceState: Bundle?
+ ) {
+ super.onViewCreated(view, savedInstanceState)
+ emptyView = view.findViewById(R.id.textViewEmpty)
+ progressBar = view.findViewById(R.id.progressBar)
+
+ recyclerView = view.findViewById(R.id.recyclerView)
+ linearLayoutManager = LinearLayoutManager(context)
+ recyclerView.itemAnimator = null
+ recyclerView.layoutManager = linearLayoutManager
+ recyclerView.adapter = recyclerViewAdapter
+
+ parentFragmentManager.findFragmentByTag(RenameDialogFragment.TAG)
+ ?.setTargetFragment(this, 0)
+
+ viewModel.getFileNames().observe(viewLifecycleOwner, Observer {
+ progressBar.visibility = View.GONE
+ if (it != null) {
+ if (it.logFiles.isNotEmpty()) {
+ emptyView.visibility = View.GONE
+ recyclerView.visibility = View.VISIBLE
+ recyclerViewAdapter.setItems(it.logFiles)
+
+ if (it.totalSize.isEmpty()) {
+ (activity as BaseActivityWithToolbar).toolbar.subtitle =
+ "${it.logFiles.size}"
+ } else {
+ val totalLogCountFmt = if (it.totalLogCount == 1L) {
+ resources.getString(R.string.log_count_fmt)
} else {
- emptyView.visibility = View.VISIBLE
+ resources.getString(R.string.log_count_fmt_plural)
}
- })
- }
- override fun onClick(v: View) {
- when (v.id) {
- R.id.list_item_root -> {
- val pos = linearLayoutManager.getPosition(v)
- if (pos != RecyclerView.NO_POSITION) {
- if ((activity as SavedLogsActivity).isCabToolbarActive()) {
- onSelect(pos)
- if (viewModel.selectedItems.isEmpty()) {
- (activity as SavedLogsActivity).closeCabToolbar()
- }
- } else {
- val fileInfo = recyclerViewAdapter.getItem(pos)
- val intent = Intent(context, SavedLogsViewerActivity::class.java)
- intent.setDataAndType(Uri.parse(fileInfo.info.path), "text/plain")
- startActivity(intent)
- }
- }
- }
- }
- }
-
- private fun onSelect(pos: Int) {
- if (pos in viewModel.selectedItems) {
- viewModel.selectedItems -= pos
+ val totalLogCountStr = totalLogCountFmt.format(it.totalLogCount)
+ (activity as BaseActivityWithToolbar).toolbar.subtitle =
+ "${it.logFiles.size} ($totalLogCountStr, ${it.totalSize})"
+ }
+
+ if (viewModel.selectedItems.isNotEmpty()) {
+ (activity as SavedLogsActivity).openCabToolbar(
+ this,
+ this
+ )
+ (activity as SavedLogsActivity).setCabToolbarTitle(
+ viewModel
+ .selectedItems.size.toString()
+ )
+ }
} else {
- viewModel.selectedItems += pos
+ recyclerView.visibility = View.GONE
+ emptyView.visibility = View.VISIBLE
+ recyclerViewAdapter.setItems(emptyList())
+ (activity as BaseActivityWithToolbar).toolbar.subtitle = null
}
- (activity as SavedLogsActivity).setCabToolbarTitle(viewModel.selectedItems.size.toString())
- (activity as SavedLogsActivity).invalidateCabToolbarMenu()
- recyclerViewAdapter.notifyItemChanged(pos)
-
- }
-
- override fun onLongClick(v: View): Boolean {
- when (v.id) {
- R.id.list_item_root -> {
- if ((activity as SavedLogsActivity).isCabToolbarActive()) {
- return false
- }
-
- val pos = linearLayoutManager.getPosition(v)
- if (pos != RecyclerView.NO_POSITION) {
- viewModel.selectedItems.clear()
- onSelect(pos)
- return (activity as SavedLogsActivity).openCabToolbar(this,
- this)
- }
+ } else {
+ emptyView.visibility = View.VISIBLE
+ }
+ })
+ }
+
+ override fun onClick(v: View) {
+ when (v.id) {
+ R.id.list_item_root -> {
+ val pos = linearLayoutManager.getPosition(v)
+ if (pos != RecyclerView.NO_POSITION) {
+ if ((activity as SavedLogsActivity).isCabToolbarActive()) {
+ onSelect(pos)
+ if (viewModel.selectedItems.isEmpty()) {
+ (activity as SavedLogsActivity).closeCabToolbar()
}
+ } else {
+ val fileInfo = recyclerViewAdapter.getItem(pos)
+ val intent = Intent(context, SavedLogsViewerActivity::class.java)
+ intent.setDataAndType(Uri.parse(fileInfo.info.path), "text/plain")
+ startActivity(intent)
+ }
}
- return false
+ }
}
+ }
- private fun selectAll() {
- viewModel.selectedItems.clear()
- for (i in 0 until recyclerViewAdapter.itemCount) {
- onSelect(i)
- }
+ private fun onSelect(pos: Int) {
+ if (pos in viewModel.selectedItems) {
+ viewModel.selectedItems -= pos
+ } else {
+ viewModel.selectedItems += pos
}
+ (activity as SavedLogsActivity).setCabToolbarTitle(viewModel.selectedItems.size.toString())
+ (activity as SavedLogsActivity).invalidateCabToolbarMenu()
+ recyclerViewAdapter.notifyItemChanged(pos)
+ }
+
+ override fun onLongClick(v: View): Boolean {
+ when (v.id) {
+ R.id.list_item_root -> {
+ if ((activity as SavedLogsActivity).isCabToolbarActive()) {
+ return false
+ }
- override fun onMenuItemClick(item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.action_rename -> {
- val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
- val frag = RenameDialogFragment.newInstance(fileInfo.info.fileName, fileInfo.info.path)
- frag.setTargetFragment(this, 0)
- frag.show(parentFragmentManager, RenameDialogFragment.TAG)
- true
- }
- R.id.action_export -> {
- val dialog = ChooseExportFormatTypeDialogFragment()
- dialog.setTargetFragment(this, 0)
- dialog.show(parentFragmentManager, ChooseExportFormatTypeDialogFragment.TAG)
- true
- }
- R.id.action_share -> {
- val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
- ShareUtils.shareSavedLogs(requireContext(), Uri.parse(fileInfo.info.path),
- fileInfo.info.isCustom)
- true
- }
- R.id.action_select_all -> {
- selectAll()
- true
- }
- R.id.action_delete -> {
- deleteSelectedLogFiles()
- true
- }
- else -> false
+ val pos = linearLayoutManager.getPosition(v)
+ if (pos != RecyclerView.NO_POSITION) {
+ viewModel.selectedItems.clear()
+ onSelect(pos)
+ return (activity as SavedLogsActivity).openCabToolbar(
+ this,
+ this
+ )
}
+ }
}
+ return false
+ }
- private fun handleExportAction(exportFormat: ExportFormat) {
- this.exportFormat = exportFormat
- if (Build.VERSION.SDK_INT >= 19) {
- saveToDeviceKitkat()
- } else {
- saveToDeviceFallback()
- }
+ private fun selectAll() {
+ viewModel.selectedItems.clear()
+ for (i in 0 until recyclerViewAdapter.itemCount) {
+ onSelect(i)
}
+ }
- private fun deleteSelectedLogFiles() {
- val deleteList = viewModel.selectedItems
- .map { recyclerViewAdapter.data[it] }
- .toList()
-
- val db = MyDB.getInstance(context!!)
- scope.launch {
- withContext(IO) {
- val deleted = deleteList
- .filter {
- with(Uri.parse(it.info.path)) {
- if (it.info.isCustom && Build.VERSION.SDK_INT >= 21) {
- val file = DocumentFile.fromSingleUri(context!!, this)
- file != null && file.delete()
- } else {
- this.toFile().delete()
- }
- }
- }
- .map { it.info.fileName }
- .toList()
-
- val deletedSavedLogInfoList = deleteList
- .map { it.info }
- .filter { it.fileName in deleted }
- .toTypedArray()
- db.savedLogsDao().delete(*deletedSavedLogInfoList)
+ override fun onMenuItemClick(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.action_rename -> {
+ val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+ val frag = RenameDialogFragment.newInstance(fileInfo.info.fileName, fileInfo.info.path)
+ frag.setTargetFragment(this, 0)
+ frag.show(parentFragmentManager, RenameDialogFragment.TAG)
+ true
+ }
+ R.id.action_export -> {
+ val dialog = ChooseExportFormatTypeDialogFragment()
+ dialog.setTargetFragment(this, 0)
+ dialog.show(parentFragmentManager, ChooseExportFormatTypeDialogFragment.TAG)
+ true
+ }
+ R.id.action_share -> {
+ val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+ ShareUtils.shareSavedLogs(
+ requireContext(), Uri.parse(fileInfo.info.path),
+ fileInfo.info.isCustom
+ )
+ true
+ }
+ R.id.action_select_all -> {
+ selectAll()
+ true
+ }
+ R.id.action_delete -> {
+ deleteSelectedLogFiles()
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun handleExportAction(exportFormat: ExportFormat) {
+ this.exportFormat = exportFormat
+ if (Build.VERSION.SDK_INT >= 19) {
+ saveToDeviceKitkat()
+ } else {
+ saveToDeviceFallback()
+ }
+ }
+
+ private fun deleteSelectedLogFiles() {
+ val deleteList = viewModel.selectedItems
+ .map { recyclerViewAdapter.data[it] }
+ .toList()
+
+ val db = MyDB.getInstance(context!!)
+ scope.launch {
+ withContext(IO) {
+ val deleted = deleteList
+ .filter {
+ with(Uri.parse(it.info.path)) {
+ if (it.info.isCustom && Build.VERSION.SDK_INT >= 21) {
+ val file = DocumentFile.fromSingleUri(context!!, this)
+ file != null && file.delete()
+ } else {
+ this.toFile().delete()
+ }
}
+ }
+ .map { it.info.fileName }
+ .toList()
+
+ val deletedSavedLogInfoList = deleteList
+ .map { it.info }
+ .filter { it.fileName in deleted }
+ .toTypedArray()
+ db.savedLogsDao().delete(*deletedSavedLogInfoList)
+ }
+
+ viewModel.selectedItems.clear()
+ viewModel.load()
+ }
- viewModel.selectedItems.clear()
- viewModel.load()
- }
+ (activity as SavedLogsActivity).closeCabToolbar()
+ }
- (activity as SavedLogsActivity).closeCabToolbar()
+ private fun saveToDeviceFallback() {
+ val activity = requireActivity()
+ if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
+ activity.showToast(getString(R.string.external_storage_not_mounted_error))
+ return
}
- private fun saveToDeviceFallback() {
- val activity = requireActivity()
- if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
- activity.showToast(getString(R.string.external_storage_not_mounted_error))
- return
- }
+ val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+ val fileName = fileInfo.info.fileName
+ val srcFolder = File(activity.filesDir, LogcatLiveFragment.LOGCAT_DIR)
+ val src = File(srcFolder, fileName)
- val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
- val fileName = fileInfo.info.fileName
- val srcFolder = File(activity.filesDir, LogcatLiveFragment.LOGCAT_DIR)
- val src = File(srcFolder, fileName)
-
- @Suppress("DEPRECATION")
- val documentsFolder = Environment.getExternalStoragePublicDirectory("Documents")
-
- val destFolder = File(documentsFolder, "LogcatReader")
- if (!destFolder.exists()) {
- if (!destFolder.mkdirs()) {
- activity.showToast(getString(R.string.error_saving))
- return
- }
- }
+ @Suppress("DEPRECATION")
+ val documentsFolder = Environment.getExternalStoragePublicDirectory("Documents")
- val dest = File(destFolder, fileName)
- runSaveFileTask(FileInputStream(src), FileOutputStream(dest))
+ val destFolder = File(documentsFolder, "LogcatReader")
+ if (!destFolder.exists()) {
+ if (!destFolder.mkdirs()) {
+ activity.showToast(getString(R.string.error_saving))
+ return
+ }
}
- @TargetApi(19)
- private fun saveToDeviceKitkat() {
- try {
- val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
- val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
- intent.addCategory(Intent.CATEGORY_OPENABLE)
- intent.type = "text/plain"
- intent.putExtra(Intent.EXTRA_TITLE, fileInfo.info.fileName)
- startActivityForResult(intent, SAVE_REQ)
- } catch (e: Exception) {
- context?.showToast(getString(R.string.error))
- }
+ val dest = File(destFolder, fileName)
+ runSaveFileTask(FileInputStream(src), FileOutputStream(dest))
+ }
+
+ @TargetApi(19)
+ private fun saveToDeviceKitkat() {
+ try {
+ val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+ val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "text/plain"
+ intent.putExtra(Intent.EXTRA_TITLE, fileInfo.info.fileName)
+ startActivityForResult(intent, SAVE_REQ)
+ } catch (e: Exception) {
+ context?.showToast(getString(R.string.error))
}
-
- private fun onSaveCallback(uri: Uri) {
- val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
- val folder = File(context!!.filesDir, LogcatLiveFragment.LOGCAT_DIR)
- val file = File(folder, fileInfo.info.fileName)
-
- try {
- val src = FileInputStream(file)
- val dest = context!!.contentResolver.openOutputStream(uri)
- runSaveFileTask(src, dest!!)
- } catch (e: IOException) {
- activity!!.showToast(getString(R.string.error_saving))
- }
+ }
+
+ private fun onSaveCallback(uri: Uri) {
+ val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+ val folder = File(context!!.filesDir, LogcatLiveFragment.LOGCAT_DIR)
+ val file = File(folder, fileInfo.info.fileName)
+
+ try {
+ val src = FileInputStream(file)
+ val dest = context!!.contentResolver.openOutputStream(uri)
+ runSaveFileTask(src, dest!!)
+ } catch (e: IOException) {
+ activity!!.showToast(getString(R.string.error_saving))
}
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- when (requestCode) {
- SAVE_REQ -> {
- if (resultCode == Activity.RESULT_OK) {
- if (data != null) {
- onSaveCallback(data.data!!)
- }
- }
- }
+ }
+
+ override fun onActivityResult(
+ requestCode: Int,
+ resultCode: Int,
+ data: Intent?
+ ) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ SAVE_REQ -> {
+ if (resultCode == Activity.RESULT_OK) {
+ if (data != null) {
+ onSaveCallback(data.data!!)
+ }
}
+ }
}
+ }
- override fun onCabToolbarOpen(toolbar: Toolbar) {
- }
+ override fun onCabToolbarOpen(toolbar: Toolbar) {
+ }
- override fun onCabToolbarInvalidate(toolbar: Toolbar) {
- val share = toolbar.menu.findItem(R.id.action_share)
- val save = toolbar.menu.findItem(R.id.action_export)
- val rename = toolbar.menu.findItem(R.id.action_rename)
+ override fun onCabToolbarInvalidate(toolbar: Toolbar) {
+ val share = toolbar.menu.findItem(R.id.action_share)
+ val save = toolbar.menu.findItem(R.id.action_export)
+ val rename = toolbar.menu.findItem(R.id.action_rename)
- val visible = viewModel.selectedItems.size == 1
- share.isVisible = visible
- save.isVisible = visible
+ val visible = viewModel.selectedItems.size == 1
+ share.isVisible = visible
+ save.isVisible = visible
- if (visible) {
- val info = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
- rename.isVisible = !info.info.isCustom
- } else {
- rename.isVisible = false
- }
+ if (visible) {
+ val info = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+ rename.isVisible = !info.info.isCustom
+ } else {
+ rename.isVisible = false
}
-
- override fun onCabToolbarClose(toolbar: Toolbar) {
- viewModel.selectedItems.clear()
- recyclerViewAdapter.notifyDataSetChanged()
+ }
+
+ override fun onCabToolbarClose(toolbar: Toolbar) {
+ viewModel.selectedItems.clear()
+ recyclerViewAdapter.notifyDataSetChanged()
+ }
+
+ private fun onRename(
+ newName: String,
+ newPath: Uri
+ ) {
+ val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+
+ val db = MyDB.getInstance(context!!)
+ scope.launch {
+ withContext(IO) {
+ db.savedLogsDao().delete(fileInfo.info)
+ db.savedLogsDao().insert(SavedLogInfo(newName, newPath.toString(), fileInfo.info.isCustom))
+ }
+
+ viewModel.load()
}
- private fun onRename(newName: String, newPath: Uri) {
- val fileInfo = recyclerViewAdapter.getItem(viewModel.selectedItems.toIntArray()[0])
+ (activity as SavedLogsActivity).closeCabToolbar()
+ }
- val db = MyDB.getInstance(context!!)
- scope.launch {
- withContext(IO) {
- db.savedLogsDao().delete(fileInfo.info)
- db.savedLogsDao().insert(SavedLogInfo(newName, newPath.toString(), fileInfo.info.isCustom))
- }
-
- viewModel.load()
- }
+ class ChooseExportFormatTypeDialogFragment : BaseDialogFragment() {
+ private var exportFormat = ExportFormat.DEFAULT
- (activity as SavedLogsActivity).closeCabToolbar()
- }
-
- class ChooseExportFormatTypeDialogFragment : BaseDialogFragment() {
- private var exportFormat = ExportFormat.DEFAULT
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- return AlertDialog.Builder(activity!!)
- .setTitle(getString(R.string.select_export_format))
- .setSingleChoiceItems(R.array.export_format, 0) { _, which ->
- exportFormat = ExportFormat.values()[which]
- }
- .setPositiveButton(R.string.export) { _, _ ->
- (targetFragment as SavedLogsFragment).handleExportAction(exportFormat)
- }
- .create()
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return AlertDialog.Builder(activity!!)
+ .setTitle(getString(R.string.select_export_format))
+ .setSingleChoiceItems(R.array.export_format, 0) { _, which ->
+ exportFormat = ExportFormat.values()[which]
}
-
- companion object {
- val TAG = ChooseExportFormatTypeDialogFragment::class.qualifiedName
+ .setPositiveButton(R.string.export) { _, _ ->
+ (targetFragment as SavedLogsFragment).handleExportAction(exportFormat)
}
+ .create()
}
- private enum class ExportFormat {
- DEFAULT,
- SINGLE
+ companion object {
+ val TAG = ChooseExportFormatTypeDialogFragment::class.qualifiedName
+ }
+ }
+
+ private enum class ExportFormat {
+ DEFAULT,
+ SINGLE
+ }
+
+ private class MyRecyclerViewAdapter(
+ context: Context,
+ val onClickListener: View.OnClickListener,
+ val onLongClickListener: View.OnLongClickListener,
+ val selectedItems: Set
+ ) : RecyclerView.Adapter() {
+
+ val data = mutableListOf()
+ val logFormat: String = context.resources.getString(R.string.log_count_fmt)
+ val logsFormat: String = context.resources.getString(R.string.log_count_fmt_plural)
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MyViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.fragment_saved_logs_list_item, parent, false)
+ view.setOnClickListener(onClickListener)
+ view.setOnLongClickListener(onLongClickListener)
+ return MyViewHolder(view)
}
- private class MyRecyclerViewAdapter(
- context: Context,
- val onClickListener: View.OnClickListener,
- val onLongClickListener: View.OnLongClickListener,
- val selectedItems: Set
- ) : RecyclerView.Adapter() {
-
- val data = mutableListOf()
- val logFormat: String = context.resources.getString(R.string.log_count_fmt)
- val logsFormat: String = context.resources.getString(R.string.log_count_fmt_plural)
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.fragment_saved_logs_list_item, parent, false)
- view.setOnClickListener(onClickListener)
- view.setOnLongClickListener(onLongClickListener)
- return MyViewHolder(view)
- }
-
- override fun getItemCount(): Int = data.size
-
- override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
- val fileInfo = data[position]
- holder.fileName.text = fileInfo.info.fileName
- holder.fileSize.text = fileInfo.sizeStr
-
- // no need to check for 0 as the app does not allow for saving empty logs
- if (fileInfo.count == 1L) {
- holder.logCount.text = logFormat.format(fileInfo.count)
- } else {
- holder.logCount.text = logsFormat.format(fileInfo.count)
- }
-
- holder.itemView.isSelected = selectedItems.contains(position)
- }
-
- fun getItem(index: Int) = data[index]
+ override fun getItemCount(): Int = data.size
- fun setItems(data: List) {
- val callback = DataDiffCallback(this.data, data)
- val result = DiffUtil.calculateDiff(callback)
+ override fun onBindViewHolder(
+ holder: MyViewHolder,
+ position: Int
+ ) {
+ val fileInfo = data[position]
+ holder.fileName.text = fileInfo.info.fileName
+ holder.fileSize.text = fileInfo.sizeStr
- this.data.clear()
- this.data += data
- result.dispatchUpdatesTo(this)
- }
+ // no need to check for 0 as the app does not allow for saving empty logs
+ if (fileInfo.count == 1L) {
+ holder.logCount.text = logFormat.format(fileInfo.count)
+ } else {
+ holder.logCount.text = logsFormat.format(fileInfo.count)
+ }
- class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val fileName: TextView = itemView.findViewById(R.id.fileName)
- val fileSize: TextView = itemView.findViewById(R.id.fileSize)
- val logCount: TextView = itemView.findViewById(R.id.logCount)
- }
-
- private class DataDiffCallback(private val old: List,
- private val new: List) : DiffUtil.Callback() {
+ holder.itemView.isSelected = selectedItems.contains(position)
+ }
- override fun areItemsTheSame(p0: Int, p1: Int): Boolean {
- return old[p0].info.path == new[p1].info.path
- }
+ fun getItem(index: Int) = data[index]
- override fun getOldListSize(): Int {
- return old.size
- }
+ fun setItems(data: List) {
+ val callback = DataDiffCallback(this.data, data)
+ val result = DiffUtil.calculateDiff(callback)
- override fun getNewListSize(): Int {
- return new.size
- }
+ this.data.clear()
+ this.data += data
+ result.dispatchUpdatesTo(this)
+ }
- override fun areContentsTheSame(p0: Int, p1: Int): Boolean {
- return old[p0] == new[p1]
- }
- }
+ class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val fileName: TextView = itemView.findViewById(R.id.fileName)
+ val fileSize: TextView = itemView.findViewById(R.id.fileSize)
+ val logCount: TextView = itemView.findViewById(R.id.logCount)
}
- private fun runSaveFileTask(src: InputStream, dest: OutputStream) {
- scope.launch {
- val result = withContext(IO) {
- try {
- when (this@SavedLogsFragment.exportFormat ?: return@withContext false) {
- ExportFormat.DEFAULT -> {
- src.copyTo(dest)
- }
- ExportFormat.SINGLE -> {
- val bufferedWriter = BufferedWriter(OutputStreamWriter(dest))
- LogcatStreamReader(src).use {
- for (log in it) {
- val metadata = log.metadataToString()
- val msgTokens = log.msg.split("\n")
- for (msg in msgTokens) {
- bufferedWriter.write(metadata)
- bufferedWriter.write(" ")
- bufferedWriter.write(msg)
- bufferedWriter.newLine()
- }
- }
- }
- }
- }
- true
- } catch (e: IOException) {
- false
- } finally {
- src.closeQuietly()
- dest.closeQuietly()
- }
+ private class DataDiffCallback(
+ private val old: List,
+ private val new: List
+ ) : DiffUtil.Callback() {
+
+ override fun areItemsTheSame(
+ p0: Int,
+ p1: Int
+ ): Boolean {
+ return old[p0].info.path == new[p1].info.path
+ }
+
+ override fun getOldListSize(): Int {
+ return old.size
+ }
+
+ override fun getNewListSize(): Int {
+ return new.size
+ }
+
+ override fun areContentsTheSame(
+ p0: Int,
+ p1: Int
+ ): Boolean {
+ return old[p0] == new[p1]
+ }
+ }
+ }
+
+ private fun runSaveFileTask(
+ src: InputStream,
+ dest: OutputStream
+ ) {
+ scope.launch {
+ val result = withContext(IO) {
+ try {
+ when (this@SavedLogsFragment.exportFormat ?: return@withContext false) {
+ ExportFormat.DEFAULT -> {
+ src.copyTo(dest)
}
-
- activity?.let {
- if (result) {
- it.showToast(it.getString(R.string.saved))
- } else {
- it.showToast(it.getString(R.string.error_saving))
+ ExportFormat.SINGLE -> {
+ val bufferedWriter = BufferedWriter(OutputStreamWriter(dest))
+ LogcatStreamReader(src).use {
+ for (log in it) {
+ val metadata = log.metadataToString()
+ val msgTokens = log.msg.split("\n")
+ for (msg in msgTokens) {
+ bufferedWriter.write(metadata)
+ bufferedWriter.write(" ")
+ bufferedWriter.write(msg)
+ bufferedWriter.newLine()
+ }
}
+ }
}
+ }
+ true
+ } catch (e: IOException) {
+ false
+ } finally {
+ src.closeQuietly()
+ dest.closeQuietly()
}
- }
+ }
- class RenameDialogFragment : BaseDialogFragment() {
+ activity?.let {
+ if (result) {
+ it.showToast(it.getString(R.string.saved))
+ } else {
+ it.showToast(it.getString(R.string.error_saving))
+ }
+ }
+ }
+ }
- companion object {
- val TAG = RenameDialogFragment::class.qualifiedName
+ class RenameDialogFragment : BaseDialogFragment() {
- private val KEY_FILENAME = TAG + "_key_filename"
- private val KEY_PATH = TAG + "_key_path"
+ companion object {
+ val TAG = RenameDialogFragment::class.qualifiedName
+
+ private val KEY_FILENAME = TAG + "_key_filename"
+ private val KEY_PATH = TAG + "_key_path"
+
+ fun newInstance(
+ fileName: String,
+ path: String
+ ): RenameDialogFragment {
+ val bundle = Bundle()
+ bundle.putString(KEY_FILENAME, fileName)
+ bundle.putString(KEY_PATH, path)
+ val frag = RenameDialogFragment()
+ frag.arguments = bundle
+ return frag
+ }
+ }
- fun newInstance(fileName: String, path: String): RenameDialogFragment {
- val bundle = Bundle()
- bundle.putString(KEY_FILENAME, fileName)
- bundle.putString(KEY_PATH, path)
- val frag = RenameDialogFragment()
- frag.arguments = bundle
- return frag
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val view = inflateLayout(R.layout.rename_dialog)
+ val editText = view.findViewById(R.id.editText)
+ editText.setText(arguments!!.getString(KEY_FILENAME))
+ editText.selectAll()
+
+ val dialog = AlertDialog.Builder(activity!!)
+ .setTitle(R.string.rename)
+ .setView(view)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ val newName = editText.text.toString()
+ if (newName.isNotEmpty()) {
+ val file = arguments!!.getString(KEY_PATH)!!.toUri().toFile()
+ val newFile = File(file.parent, newName)
+ if (file.renameTo(newFile)) {
+ (targetFragment as SavedLogsFragment).onRename(newName, newFile.toUri())
+ } else {
+ activity!!.showToast(getString(R.string.error))
}
+ }
+ dismiss()
}
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ dismiss()
+ }
+ .create()
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val view = inflateLayout(R.layout.rename_dialog)
- val editText = view.findViewById(R.id.editText)
- editText.setText(arguments!!.getString(KEY_FILENAME))
- editText.selectAll()
-
- val dialog = AlertDialog.Builder(activity!!)
- .setTitle(R.string.rename)
- .setView(view)
- .setPositiveButton(android.R.string.ok) { _, _ ->
- val newName = editText.text.toString()
- if (newName.isNotEmpty()) {
- val file = arguments!!.getString(KEY_PATH)!!.toUri().toFile()
- val newFile = File(file.parent, newName)
- if (file.renameTo(newFile)) {
- (targetFragment as SavedLogsFragment).onRename(newName, newFile.toUri())
- } else {
- activity!!.showToast(getString(R.string.error))
- }
- }
- dismiss()
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- dismiss()
- }
- .create()
-
- dialog.setOnShowListener {
- editText.requestFocus()
- val imm = activity!!.getSystemService(Context.INPUT_METHOD_SERVICE) as
- InputMethodManager
- imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
- }
+ dialog.setOnShowListener {
+ editText.requestFocus()
+ val imm = activity!!.getSystemService(Context.INPUT_METHOD_SERVICE) as
+ InputMethodManager
+ imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
+ }
- return dialog
- }
+ return dialog
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsViewModel.kt b/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsViewModel.kt
index ec2e4e7a..7dbc74ea 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsViewModel.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/savedlogs/SavedLogsViewModel.kt
@@ -26,129 +26,135 @@ import java.io.FileInputStream
import java.io.IOException
internal class SavedLogsViewModel(application: Application) : ScopedAndroidViewModel(application) {
- private val fileNames = MutableLiveData()
- val selectedItems = mutableSetOf()
+ private val fileNames = MutableLiveData()
+ val selectedItems = mutableSetOf()
- private var job: Job? = null
+ private var job: Job? = null
- init {
- load()
- }
+ init {
+ load()
+ }
- fun load() {
- job?.cancel()
- job = launch {
- val db = MyDB.getInstance(getApplication())
- val result = withContext(IO) { loadAsync(db) }
- fileNames.value = result
- }
+ fun load() {
+ job?.cancel()
+ job = launch {
+ val db = MyDB.getInstance(getApplication())
+ val result = withContext(IO) { loadAsync(db) }
+ fileNames.value = result
}
-
- private suspend fun loadAsync(db: MyDB): SavedLogsResult = coroutineScope {
- val savedLogsResult = SavedLogsResult()
- var totalSize = 0L
-
- updateDBWithExistingInternalLogFiles(db)
-
- val application = getApplication()
- val savedLogInfoList = db.savedLogsDao().getAllSync()
- for (info in savedLogInfoList) {
- if (info.isCustom && Build.VERSION.SDK_INT >= 21) {
- val file = DocumentFile.fromSingleUri(application, Uri.parse(info.path))
- if (file == null || file.name == null) {
- Logger.debug(this::class, "file name is null")
- continue
- }
-
- val size = file.length()
- val count = countLogs(application, file)
- val fileInfo = LogFileInfo(info, size, Utils.bytesToString(size), count)
- savedLogsResult.logFiles += fileInfo
- totalSize += fileInfo.size
- } else {
- val file = Uri.parse(info.path).toFile()
- val size = file.length()
- val count = countLogs(file)
- val fileInfo = LogFileInfo(info, size, Utils.bytesToString(size), count)
- savedLogsResult.logFiles += fileInfo
- totalSize += fileInfo.size
- }
+ }
+
+ private suspend fun loadAsync(db: MyDB): SavedLogsResult = coroutineScope {
+ val savedLogsResult = SavedLogsResult()
+ var totalSize = 0L
+
+ updateDBWithExistingInternalLogFiles(db)
+
+ val application = getApplication()
+ val savedLogInfoList = db.savedLogsDao().getAllSync()
+ for (info in savedLogInfoList) {
+ if (info.isCustom && Build.VERSION.SDK_INT >= 21) {
+ val file = DocumentFile.fromSingleUri(application, Uri.parse(info.path))
+ if (file == null || file.name == null) {
+ Logger.debug(this::class, "file name is null")
+ continue
}
- savedLogsResult.totalLogCount = savedLogsResult.logFiles
- .foldRight(0L) { logFileInfo, acc ->
- acc + logFileInfo.count
- }
- savedLogsResult.logFiles.sortBy { it.info.fileName }
+ val size = file.length()
+ val count = countLogs(application, file)
+ val fileInfo = LogFileInfo(info, size, Utils.bytesToString(size), count)
+ savedLogsResult.logFiles += fileInfo
+ totalSize += fileInfo.size
+ } else {
+ val file = Uri.parse(info.path).toFile()
+ val size = file.length()
+ val count = countLogs(file)
+ val fileInfo = LogFileInfo(info, size, Utils.bytesToString(size), count)
+ savedLogsResult.logFiles += fileInfo
+ totalSize += fileInfo.size
+ }
+ }
- if (totalSize > 0) {
- savedLogsResult.totalSize = Utils.bytesToString(totalSize)
- }
+ savedLogsResult.totalLogCount = savedLogsResult.logFiles
+ .foldRight(0L) { logFileInfo, acc ->
+ acc + logFileInfo.count
+ }
+ savedLogsResult.logFiles.sortBy { it.info.fileName }
- savedLogsResult
+ if (totalSize > 0) {
+ savedLogsResult.totalSize = Utils.bytesToString(totalSize)
}
- private fun updateDBWithExistingInternalLogFiles(db: MyDB) {
- val files = File(getApplication().cacheDir, LogcatLiveFragment.LOGCAT_DIR).listFiles()
- if (files != null) {
- val savedLogInfoArray = files.map {
- SavedLogInfo(it.name, it.absolutePath, false)
- }.toTypedArray()
- db.savedLogsDao().insert(*savedLogInfoArray)
- }
+ savedLogsResult
+ }
+
+ private fun updateDBWithExistingInternalLogFiles(db: MyDB) {
+ val files =
+ File(getApplication().cacheDir, LogcatLiveFragment.LOGCAT_DIR).listFiles()
+ if (files != null) {
+ val savedLogInfoArray = files.map {
+ SavedLogInfo(it.name, it.absolutePath, false)
+ }.toTypedArray()
+ db.savedLogsDao().insert(*savedLogInfoArray)
}
+ }
- private fun countLogs(file: File): Long {
- val logCount = Logcat.getLogCountFromHeader(file)
- if (logCount != -1L) {
- return logCount
- }
+ private fun countLogs(file: File): Long {
+ val logCount = Logcat.getLogCountFromHeader(file)
+ if (logCount != -1L) {
+ return logCount
+ }
- return try {
- val reader = LogcatStreamReader(FileInputStream(file))
- val logs = reader.asSequence().toList()
+ return try {
+ val reader = LogcatStreamReader(FileInputStream(file))
+ val logs = reader.asSequence().toList()
- if (!Logcat.writeToFile(logs, file)) {
- Logger.debug(SavedLogsViewModel::class, "Failed to write log header")
- }
+ if (!Logcat.writeToFile(logs, file)) {
+ Logger.debug(SavedLogsViewModel::class, "Failed to write log header")
+ }
- logs.size.toLong()
- } catch (e: IOException) {
- 0L
- }
+ logs.size.toLong()
+ } catch (e: IOException) {
+ 0L
+ }
+ }
+
+ private fun countLogs(
+ context: Context,
+ file: DocumentFile
+ ): Long {
+ val logCount = Logcat.getLogCountFromHeader(context, file)
+ if (logCount != -1L) {
+ return logCount
}
- private fun countLogs(context: Context, file: DocumentFile): Long {
- val logCount = Logcat.getLogCountFromHeader(context, file)
- if (logCount != -1L) {
- return logCount
- }
-
- return try {
- val inputStream = context.contentResolver.openInputStream(file.uri)
- val reader = LogcatStreamReader(inputStream!!)
- val logs = reader.asSequence().toList()
+ return try {
+ val inputStream = context.contentResolver.openInputStream(file.uri)
+ val reader = LogcatStreamReader(inputStream!!)
+ val logs = reader.asSequence().toList()
- if (!Logcat.writeToFile(context, logs, file.uri)) {
- Logger.debug(SavedLogsViewModel::class, "Failed to write log header")
- }
+ if (!Logcat.writeToFile(context, logs, file.uri)) {
+ Logger.debug(SavedLogsViewModel::class, "Failed to write log header")
+ }
- logs.size.toLong()
- } catch (e: IOException) {
- 0L
- }
+ logs.size.toLong()
+ } catch (e: IOException) {
+ 0L
}
+ }
- fun getFileNames(): LiveData = fileNames
+ fun getFileNames(): LiveData = fileNames
}
-data class LogFileInfo(val info: SavedLogInfo,
- val size: Long,
- val sizeStr: String,
- val count: Long)
+data class LogFileInfo(
+ val info: SavedLogInfo,
+ val size: Long,
+ val sizeStr: String,
+ val count: Long
+)
internal class SavedLogsResult {
- var totalSize = ""
- var totalLogCount = 0L
- val logFiles = mutableListOf()
+ var totalSize = ""
+ var totalLogCount = 0L
+ val logFiles = mutableListOf()
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/MyRecyclerViewAdapter.kt b/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/MyRecyclerViewAdapter.kt
index 7f4640ea..95c945f3 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/MyRecyclerViewAdapter.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/MyRecyclerViewAdapter.kt
@@ -12,83 +12,89 @@ import com.dp.logcat.LogPriority
import com.dp.logcatapp.R
internal class MyRecyclerViewAdapter(context: Context) : RecyclerView.Adapter(),
- View.OnClickListener {
- private val data = mutableListOf()
- private var onClickListener: ((View) -> Unit)? = null
+ View.OnClickListener {
+ private val data = mutableListOf()
+ private var onClickListener: ((View) -> Unit)? = null
- private val priorityColorAssert = ContextCompat.getColor(context, R.color.priority_assert)
- private val priorityColorDebug = ContextCompat.getColor(context, R.color.priority_debug)
- private val priorityColorError = ContextCompat.getColor(context, R.color.priority_error)
- private val priorityColorInfo = ContextCompat.getColor(context, R.color.priority_info)
- private val priorityColorVerbose = ContextCompat.getColor(context, R.color.priority_verbose)
- private val priorityColorWarning = ContextCompat.getColor(context, R.color.priority_warning)
- private val priorityColorFatal = ContextCompat.getColor(context, R.color.priority_fatal)
- private val priorityColorSilent = ContextCompat.getColor(context, R.color.priority_silent)
+ private val priorityColorAssert = ContextCompat.getColor(context, R.color.priority_assert)
+ private val priorityColorDebug = ContextCompat.getColor(context, R.color.priority_debug)
+ private val priorityColorError = ContextCompat.getColor(context, R.color.priority_error)
+ private val priorityColorInfo = ContextCompat.getColor(context, R.color.priority_info)
+ private val priorityColorVerbose = ContextCompat.getColor(context, R.color.priority_verbose)
+ private val priorityColorWarning = ContextCompat.getColor(context, R.color.priority_warning)
+ private val priorityColorFatal = ContextCompat.getColor(context, R.color.priority_fatal)
+ private val priorityColorSilent = ContextCompat.getColor(context, R.color.priority_silent)
- private fun getPriorityColor(priority: String) = when (priority) {
- LogPriority.ASSERT -> priorityColorAssert
- LogPriority.DEBUG -> priorityColorDebug
- LogPriority.ERROR -> priorityColorError
- LogPriority.FATAL -> priorityColorFatal
- LogPriority.INFO -> priorityColorInfo
- LogPriority.VERBOSE -> priorityColorVerbose
- LogPriority.WARNING -> priorityColorWarning
- else -> priorityColorSilent
- }
+ private fun getPriorityColor(priority: String) = when (priority) {
+ LogPriority.ASSERT -> priorityColorAssert
+ LogPriority.DEBUG -> priorityColorDebug
+ LogPriority.ERROR -> priorityColorError
+ LogPriority.FATAL -> priorityColorFatal
+ LogPriority.INFO -> priorityColorInfo
+ LogPriority.VERBOSE -> priorityColorVerbose
+ LogPriority.WARNING -> priorityColorWarning
+ else -> priorityColorSilent
+ }
- override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
- val log = data[position]
- holder.date.text = log.date
- holder.time.text = log.time
- holder.pid.text = log.pid
- holder.tid.text = log.tid
- holder.priority.text = log.priority
- holder.tag.text = log.tag
- holder.message.text = log.msg
+ override fun onBindViewHolder(
+ holder: MyViewHolder,
+ position: Int
+ ) {
+ val log = data[position]
+ holder.date.text = log.date
+ holder.time.text = log.time
+ holder.pid.text = log.pid
+ holder.tid.text = log.tid
+ holder.priority.text = log.priority
+ holder.tag.text = log.tag
+ holder.message.text = log.msg
- holder.priority.setBackgroundColor(getPriorityColor(log.priority))
- }
+ holder.priority.setBackgroundColor(getPriorityColor(log.priority))
+ }
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.fragment_saved_logs_viewer_list_item, parent, false)
- view.setOnClickListener(this)
- return MyViewHolder(view)
- }
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MyViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.fragment_saved_logs_viewer_list_item, parent, false)
+ view.setOnClickListener(this)
+ return MyViewHolder(view)
+ }
- override fun onClick(v: View) {
- when (v.id) {
- R.id.list_item_root -> onClickListener?.invoke(v)
- }
+ override fun onClick(v: View) {
+ when (v.id) {
+ R.id.list_item_root -> onClickListener?.invoke(v)
}
+ }
- override fun getItemCount() = data.size
+ override fun getItemCount() = data.size
- internal fun setItems(items: List) {
- clear()
- data += items
- notifyItemRangeInserted(0, items.size)
- }
+ internal fun setItems(items: List) {
+ clear()
+ data += items
+ notifyItemRangeInserted(0, items.size)
+ }
- operator fun get(index: Int) = data[index]
+ operator fun get(index: Int) = data[index]
- private fun clear() {
- val size = data.size
- data.clear()
- notifyItemRangeRemoved(0, size)
- }
+ private fun clear() {
+ val size = data.size
+ data.clear()
+ notifyItemRangeRemoved(0, size)
+ }
- internal fun setOnClickListener(onClickListener: (View) -> Unit) {
- this.onClickListener = onClickListener
- }
+ internal fun setOnClickListener(onClickListener: (View) -> Unit) {
+ this.onClickListener = onClickListener
+ }
- class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val date: TextView = itemView.findViewById(R.id.date)
- val time: TextView = itemView.findViewById(R.id.time)
- val pid: TextView = itemView.findViewById(R.id.pid)
- val tid: TextView = itemView.findViewById(R.id.tid)
- val priority: TextView = itemView.findViewById(R.id.priority)
- val tag: TextView = itemView.findViewById(R.id.tag)
- val message: TextView = itemView.findViewById(R.id.message)
- }
+ class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val date: TextView = itemView.findViewById(R.id.date)
+ val time: TextView = itemView.findViewById(R.id.time)
+ val pid: TextView = itemView.findViewById(R.id.pid)
+ val tid: TextView = itemView.findViewById(R.id.tid)
+ val priority: TextView = itemView.findViewById(R.id.priority)
+ val tag: TextView = itemView.findViewById(R.id.tag)
+ val message: TextView = itemView.findViewById(R.id.message)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerFragment.kt
index 70d043bf..242390c2 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerFragment.kt
@@ -2,7 +2,12 @@ package com.dp.logcatapp.fragments.savedlogsviewer
import android.net.Uri
import android.os.Bundle
-import android.view.*
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.widget.SearchView
@@ -26,343 +31,366 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SavedLogsViewerFragment : BaseFragment() {
- companion object {
- val TAG = SavedLogsViewerFragment::class.qualifiedName
+ companion object {
+ val TAG = SavedLogsViewerFragment::class.qualifiedName
- private val KEY_FILE_URI = TAG + "_key_filename"
+ private val KEY_FILE_URI = TAG + "_key_filename"
- fun newInstance(uri: Uri): SavedLogsViewerFragment {
- val bundle = Bundle()
- bundle.putString(KEY_FILE_URI, uri.toString())
- val frag = SavedLogsViewerFragment()
- frag.arguments = bundle
- return frag
- }
- }
-
- private lateinit var recyclerView: RecyclerView
- private lateinit var linearLayoutManager: LinearLayoutManager
- private lateinit var viewModel: SavedLogsViewerViewModel
- private lateinit var adapter: MyRecyclerViewAdapter
- private lateinit var fabUp: FloatingActionButton
- private lateinit var fabDown: FloatingActionButton
- private lateinit var progressBar: ProgressBar
- private lateinit var textViewEmpty: TextView
- private var ignoreScrollEvent = false
- private var searchViewActive = false
- private var lastLogId = -1
- private var lastSearchRunnable: Runnable? = null
- private var searchTask: Job? = null
-
- private val hideFabUpRunnable: Runnable = Runnable {
- fabUp.hide()
+ fun newInstance(uri: Uri): SavedLogsViewerFragment {
+ val bundle = Bundle()
+ bundle.putString(KEY_FILE_URI, uri.toString())
+ val frag = SavedLogsViewerFragment()
+ frag.arguments = bundle
+ return frag
}
-
- private val hideFabDownRunnable: Runnable = Runnable {
- fabDown.hide()
+ }
+
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var linearLayoutManager: LinearLayoutManager
+ private lateinit var viewModel: SavedLogsViewerViewModel
+ private lateinit var adapter: MyRecyclerViewAdapter
+ private lateinit var fabUp: FloatingActionButton
+ private lateinit var fabDown: FloatingActionButton
+ private lateinit var progressBar: ProgressBar
+ private lateinit var textViewEmpty: TextView
+ private var ignoreScrollEvent = false
+ private var searchViewActive = false
+ private var lastLogId = -1
+ private var lastSearchRunnable: Runnable? = null
+ private var searchTask: Job? = null
+
+ private val hideFabUpRunnable: Runnable = Runnable {
+ fabUp.hide()
+ }
+
+ private val hideFabDownRunnable: Runnable = Runnable {
+ fabDown.hide()
+ }
+
+ private val onScrollListener = object : RecyclerView.OnScrollListener() {
+ var lastDy = 0
+
+ override fun onScrolled(
+ recyclerView: RecyclerView,
+ dx: Int,
+ dy: Int
+ ) {
+ if (dy > 0 && lastDy <= 0) {
+ hideFabUp()
+ showFabDown()
+ } else if (dy < 0 && lastDy >= 0) {
+ showFabUp()
+ hideFabDown()
+ }
+ lastDy = dy
}
- private val onScrollListener = object : RecyclerView.OnScrollListener() {
- var lastDy = 0
-
- override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
- if (dy > 0 && lastDy <= 0) {
- hideFabUp()
- showFabDown()
- } else if (dy < 0 && lastDy >= 0) {
- showFabUp()
- hideFabDown()
- }
- lastDy = dy
+ override fun onScrollStateChanged(
+ recyclerView: RecyclerView,
+ newState: Int
+ ) {
+ when (newState) {
+ RecyclerView.SCROLL_STATE_DRAGGING -> {
+ viewModel.autoScroll = false
+ if (lastDy > 0) {
+ hideFabUp()
+ showFabDown()
+ } else if (lastDy < 0) {
+ showFabUp()
+ hideFabDown()
+ }
}
-
- override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
- when (newState) {
- RecyclerView.SCROLL_STATE_DRAGGING -> {
- viewModel.autoScroll = false
- if (lastDy > 0) {
- hideFabUp()
- showFabDown()
- } else if (lastDy < 0) {
- showFabUp()
- hideFabDown()
- }
- }
- else -> {
- var firstPos = -1
- if (searchViewActive && !viewModel.autoScroll &&
- newState == RecyclerView.SCROLL_STATE_IDLE) {
- firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
- if (firstPos != RecyclerView.NO_POSITION) {
- val log = adapter[firstPos]
- lastLogId = log.id
- }
- }
-
- val pos = linearLayoutManager.findLastCompletelyVisibleItemPosition()
- if (pos == RecyclerView.NO_POSITION) {
- viewModel.autoScroll = false
- return
- }
-
- if (ignoreScrollEvent) {
- if (pos == adapter.itemCount) {
- ignoreScrollEvent = false
- }
- return
- }
-
- if (pos == 0) {
- hideFabUp()
- }
-
- if (firstPos == RecyclerView.NO_POSITION) {
- firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
- }
-
- viewModel.scrollPosition = firstPos
- viewModel.autoScroll = pos == adapter.itemCount - 1
-
- if (viewModel.autoScroll) {
- hideFabUp()
- hideFabDown()
- }
- }
+ else -> {
+ var firstPos = -1
+ if (searchViewActive && !viewModel.autoScroll &&
+ newState == RecyclerView.SCROLL_STATE_IDLE
+ ) {
+ firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
+ if (firstPos != RecyclerView.NO_POSITION) {
+ val log = adapter[firstPos]
+ lastLogId = log.id
}
- }
- }
-
- private fun showFabUp() {
- handler.removeCallbacks(hideFabUpRunnable)
- fabUp.show()
- handler.postDelayed(hideFabUpRunnable, 2000)
- }
-
- private fun hideFabUp() {
- handler.removeCallbacks(hideFabUpRunnable)
- fabUp.hide()
- }
-
- private fun showFabDown() {
- handler.removeCallbacks(hideFabDownRunnable)
- fabDown.show()
- handler.postDelayed(hideFabDownRunnable, 2000)
- }
-
- private fun hideFabDown() {
- handler.removeCallbacks(hideFabDownRunnable)
- fabDown.hide()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
- adapter = MyRecyclerViewAdapter(requireActivity())
- viewModel = getAndroidViewModel()
- viewModel.init(Uri.parse(requireArguments().getString(KEY_FILE_URI)))
- }
+ }
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? =
- inflateLayout(R.layout.fragment_saved_logs_viewer)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ val pos = linearLayoutManager.findLastCompletelyVisibleItemPosition()
+ if (pos == RecyclerView.NO_POSITION) {
+ viewModel.autoScroll = false
+ return
+ }
- progressBar = view.findViewById(R.id.progressBar)
- textViewEmpty = view.findViewById(R.id.textViewEmpty)
+ if (ignoreScrollEvent) {
+ if (pos == adapter.itemCount) {
+ ignoreScrollEvent = false
+ }
+ return
+ }
- recyclerView = view.findViewById(R.id.recyclerView)
- linearLayoutManager = LinearLayoutManager(activity)
- recyclerView.layoutManager = linearLayoutManager
- recyclerView.itemAnimator = null
- recyclerView.addItemDecoration(DividerItemDecoration(activity,
- linearLayoutManager.orientation))
- recyclerView.adapter = adapter
+ if (pos == 0) {
+ hideFabUp()
+ }
- recyclerView.addOnScrollListener(onScrollListener)
+ if (firstPos == RecyclerView.NO_POSITION) {
+ firstPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
+ }
- fabDown = view.findViewById(R.id.fabDown)
- fabDown.setOnClickListener {
- hideFabDown()
- ignoreScrollEvent = true
- viewModel.autoScroll = true
- linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
- }
+ viewModel.scrollPosition = firstPos
+ viewModel.autoScroll = pos == adapter.itemCount - 1
- fabUp = view.findViewById(R.id.fabUp)
- fabUp.setOnClickListener {
+ if (viewModel.autoScroll) {
hideFabUp()
- viewModel.autoScroll = false
- linearLayoutManager.scrollToPositionWithOffset(0, 0)
- }
-
- hideFabUp()
- hideFabDown()
-
- adapter.setOnClickListener { v ->
- val pos = linearLayoutManager.getPosition(v)
- if (pos >= 0) {
- viewModel.autoScroll = false
- val log = adapter[pos]
- CopyToClipboardDialogFragment.newInstance(log)
- .show(parentFragmentManager, CopyToClipboardDialogFragment.TAG)
- }
+ hideFabDown()
+ }
}
-
- viewModel.getLogs().observe(viewLifecycleOwner, Observer {
- progressBar.visibility = View.GONE
- if (it != null) {
- when (it) {
- is SavedLogsViewerViewModel.SavedLogsResult.Success -> {
- if (it.logs.isEmpty()) {
- textViewEmpty.visibility = View.VISIBLE
- } else {
- textViewEmpty.visibility = View.GONE
- setLogs(it.logs)
- scrollRecyclerView()
- }
- }
- is SavedLogsViewerViewModel.SavedLogsResult.FileOpenError -> {
- context?.showToast(getString(R.string.error_opening_source))
- }
- is SavedLogsViewerViewModel.SavedLogsResult.FileParseError -> {
- context?.showToast(getString(R.string.unsupported_source))
- }
- }
- } else {
- textViewEmpty.visibility = View.VISIBLE
- }
- })
+ }
+ }
+ }
+
+ private fun showFabUp() {
+ handler.removeCallbacks(hideFabUpRunnable)
+ fabUp.show()
+ handler.postDelayed(hideFabUpRunnable, 2000)
+ }
+
+ private fun hideFabUp() {
+ handler.removeCallbacks(hideFabUpRunnable)
+ fabUp.hide()
+ }
+
+ private fun showFabDown() {
+ handler.removeCallbacks(hideFabDownRunnable)
+ fabDown.show()
+ handler.postDelayed(hideFabDownRunnable, 2000)
+ }
+
+ private fun hideFabDown() {
+ handler.removeCallbacks(hideFabDownRunnable)
+ fabDown.hide()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ adapter = MyRecyclerViewAdapter(requireActivity())
+ viewModel = getAndroidViewModel()
+ viewModel.init(Uri.parse(requireArguments().getString(KEY_FILE_URI)))
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? =
+ inflateLayout(R.layout.fragment_saved_logs_viewer)
+
+ override fun onViewCreated(
+ view: View,
+ savedInstanceState: Bundle?
+ ) {
+ super.onViewCreated(view, savedInstanceState)
+
+ progressBar = view.findViewById(R.id.progressBar)
+ textViewEmpty = view.findViewById(R.id.textViewEmpty)
+
+ recyclerView = view.findViewById(R.id.recyclerView)
+ linearLayoutManager = LinearLayoutManager(activity)
+ recyclerView.layoutManager = linearLayoutManager
+ recyclerView.itemAnimator = null
+ recyclerView.addItemDecoration(
+ DividerItemDecoration(
+ activity,
+ linearLayoutManager.orientation
+ )
+ )
+ recyclerView.adapter = adapter
+
+ recyclerView.addOnScrollListener(onScrollListener)
+
+ fabDown = view.findViewById(R.id.fabDown)
+ fabDown.setOnClickListener {
+ hideFabDown()
+ ignoreScrollEvent = true
+ viewModel.autoScroll = true
+ linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
}
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- super.onCreateOptionsMenu(menu, inflater)
-
- inflater.inflate(R.menu.saved_logs_viewer, menu)
- val searchItem = menu.findItem(R.id.action_search)
- val searchView = searchItem.actionView as SearchView
-
- var reachedBlank = false
- searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
- override fun onQueryTextChange(newText: String): Boolean {
- searchViewActive = true
- removeLastSearchRunnableCallback()
-
- if (newText.isBlank()) {
- reachedBlank = true
- onSearchViewClose()
- } else {
- reachedBlank = false
- lastSearchRunnable = Runnable {
- viewModel.getLogs().value?.let {
- if (it is SavedLogsViewerViewModel.SavedLogsResult.Success) {
- runSearchTask(it.logs, newText)
- }
- }
- }.also {
- handler.postDelayed(it, 300)
- }
-
- }
- return true
- }
-
- override fun onQueryTextSubmit(query: String) = false
- })
-
- searchView.setOnCloseListener {
- removeLastSearchRunnableCallback()
- searchViewActive = false
- if (!reachedBlank) {
- onSearchViewClose()
- }
- false
- }
+ fabUp = view.findViewById(R.id.fabUp)
+ fabUp.setOnClickListener {
+ hideFabUp()
+ viewModel.autoScroll = false
+ linearLayoutManager.scrollToPositionWithOffset(0, 0)
}
- private fun onSearchViewClose() {
- val result = viewModel.getLogs().value
+ hideFabUp()
+ hideFabDown()
+
+ adapter.setOnClickListener { v ->
+ val pos = linearLayoutManager.getPosition(v)
+ if (pos >= 0) {
+ viewModel.autoScroll = false
+ val log = adapter[pos]
+ CopyToClipboardDialogFragment.newInstance(log)
+ .show(parentFragmentManager, CopyToClipboardDialogFragment.TAG)
+ }
+ }
- val logs: List
- if (result !is SavedLogsViewerViewModel.SavedLogsResult.Success) {
- logs = emptyList()
- } else {
- logs = result.logs
+ viewModel.getLogs().observe(viewLifecycleOwner, Observer {
+ progressBar.visibility = View.GONE
+ if (it != null) {
+ when (it) {
+ is SavedLogsViewerViewModel.SavedLogsResult.Success -> {
+ if (it.logs.isEmpty()) {
+ textViewEmpty.visibility = View.VISIBLE
+ } else {
+ textViewEmpty.visibility = View.GONE
+ setLogs(it.logs)
+ scrollRecyclerView()
+ }
+ }
+ is SavedLogsViewerViewModel.SavedLogsResult.FileOpenError -> {
+ context?.showToast(getString(R.string.error_opening_source))
+ }
+ is SavedLogsViewerViewModel.SavedLogsResult.FileParseError -> {
+ context?.showToast(getString(R.string.unsupported_source))
+ }
}
- setLogs(logs)
+ } else {
+ textViewEmpty.visibility = View.VISIBLE
+ }
+ })
+ }
+
+ override fun onCreateOptionsMenu(
+ menu: Menu,
+ inflater: MenuInflater
+ ) {
+ super.onCreateOptionsMenu(menu, inflater)
+
+ inflater.inflate(R.menu.saved_logs_viewer, menu)
+ val searchItem = menu.findItem(R.id.action_search)
+ val searchView = searchItem.actionView as SearchView
+
+ var reachedBlank = false
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextChange(newText: String): Boolean {
+ searchViewActive = true
+ removeLastSearchRunnableCallback()
- if (lastLogId == -1) {
- scrollRecyclerView()
+ if (newText.isBlank()) {
+ reachedBlank = true
+ onSearchViewClose()
} else {
- viewModel.autoScroll = linearLayoutManager.findLastCompletelyVisibleItemPosition() ==
- adapter.itemCount - 1
- if (!viewModel.autoScroll) {
- viewModel.scrollPosition = lastLogId
- linearLayoutManager.scrollToPositionWithOffset(lastLogId, 0)
+ reachedBlank = false
+ lastSearchRunnable = Runnable {
+ viewModel.getLogs().value?.let {
+ if (it is SavedLogsViewerViewModel.SavedLogsResult.Success) {
+ runSearchTask(it.logs, newText)
+ }
}
- lastLogId = -1
+ }.also {
+ handler.postDelayed(it, 300)
+ }
}
+ return true
+ }
+
+ override fun onQueryTextSubmit(query: String) = false
+ })
+
+ searchView.setOnCloseListener {
+ removeLastSearchRunnableCallback()
+ searchViewActive = false
+ if (!reachedBlank) {
+ onSearchViewClose()
+ }
+ false
}
+ }
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.action_search -> {
- true
- }
- else -> return super.onOptionsItemSelected(item)
- }
- }
+ private fun onSearchViewClose() {
+ val result = viewModel.getLogs().value
- private fun removeLastSearchRunnableCallback() {
- lastSearchRunnable?.let {
- handler.removeCallbacks(it)
- }
- lastSearchRunnable = null
+ val logs: List
+ if (result !is SavedLogsViewerViewModel.SavedLogsResult.Success) {
+ logs = emptyList()
+ } else {
+ logs = result.logs
}
-
- override fun onDestroy() {
- super.onDestroy()
- (activity as BaseActivityWithToolbar).toolbar.subtitle = null
- removeLastSearchRunnableCallback()
- searchTask?.cancel()
- recyclerView.removeOnScrollListener(onScrollListener)
+ setLogs(logs)
+
+ if (lastLogId == -1) {
+ scrollRecyclerView()
+ } else {
+ viewModel.autoScroll = linearLayoutManager.findLastCompletelyVisibleItemPosition() ==
+ adapter.itemCount - 1
+ if (!viewModel.autoScroll) {
+ viewModel.scrollPosition = lastLogId
+ linearLayoutManager.scrollToPositionWithOffset(lastLogId, 0)
+ }
+ lastLogId = -1
}
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.action_search -> {
+ true
+ }
+ else -> return super.onOptionsItemSelected(item)
+ }
+ }
- private fun setLogs(logs: List) {
- if (adapter.itemCount == logs.size) {
- return
- }
+ private fun removeLastSearchRunnableCallback() {
+ lastSearchRunnable?.let {
+ handler.removeCallbacks(it)
+ }
+ lastSearchRunnable = null
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ (activity as BaseActivityWithToolbar).toolbar.subtitle = null
+ removeLastSearchRunnableCallback()
+ searchTask?.cancel()
+ recyclerView.removeOnScrollListener(onScrollListener)
+ }
+
+ private fun setLogs(logs: List) {
+ if (adapter.itemCount == logs.size) {
+ return
+ }
- adapter.setItems(logs)
+ adapter.setItems(logs)
- if (logs.isEmpty()) {
- (activity as BaseActivityWithToolbar).toolbar.subtitle = null
- } else {
- (activity as BaseActivityWithToolbar).toolbar.subtitle = "${logs.size}"
- }
+ if (logs.isEmpty()) {
+ (activity as BaseActivityWithToolbar).toolbar.subtitle = null
+ } else {
+ (activity as BaseActivityWithToolbar).toolbar.subtitle = "${logs.size}"
}
+ }
- private fun scrollRecyclerView() {
- if (viewModel.autoScroll) {
- linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
- } else {
- linearLayoutManager.scrollToPositionWithOffset(viewModel.scrollPosition, 0)
- }
+ private fun scrollRecyclerView() {
+ if (viewModel.autoScroll) {
+ linearLayoutManager.scrollToPosition(adapter.itemCount - 1)
+ } else {
+ linearLayoutManager.scrollToPositionWithOffset(viewModel.scrollPosition, 0)
}
-
- private fun runSearchTask(logs: List, searchText: String) {
- searchTask?.cancel()
- searchTask = scope.launch {
- val filteredLogs = withContext(IO) {
- logs.filter {
- it.tag.containsIgnoreCase(searchText) ||
- it.msg.containsIgnoreCase(searchText)
- }
- }
- adapter.setItems(filteredLogs)
- viewModel.autoScroll = false
- linearLayoutManager.scrollToPositionWithOffset(0, 0)
+ }
+
+ private fun runSearchTask(
+ logs: List,
+ searchText: String
+ ) {
+ searchTask?.cancel()
+ searchTask = scope.launch {
+ val filteredLogs = withContext(IO) {
+ logs.filter {
+ it.tag.containsIgnoreCase(searchText) ||
+ it.msg.containsIgnoreCase(searchText)
}
+ }
+ adapter.setItems(filteredLogs)
+ viewModel.autoScroll = false
+ linearLayoutManager.scrollToPositionWithOffset(0, 0)
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerViewModel.kt b/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerViewModel.kt
index 3bca0c60..015b24f6 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerViewModel.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/savedlogsviewer/SavedLogsViewerViewModel.kt
@@ -15,53 +15,58 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
-internal class SavedLogsViewerViewModel(application: Application) : ScopedAndroidViewModel(application) {
- var autoScroll = true
- var scrollPosition = 0
+internal class SavedLogsViewerViewModel(application: Application) : ScopedAndroidViewModel(
+ application
+) {
+ var autoScroll = true
+ var scrollPosition = 0
- private var logs = MutableLiveData()
+ private var logs = MutableLiveData()
- fun init(uri: Uri) {
- launch {
- logs.value = withContext(IO) { load(getApplication(), uri) }
- }
+ fun init(uri: Uri) {
+ launch {
+ logs.value = withContext(IO) { load(getApplication(), uri) }
}
+ }
- private suspend fun load(context: Context, uri: Uri) = coroutineScope {
- val logs = mutableListOf()
- var availableBytes = 0
+ private suspend fun load(
+ context: Context,
+ uri: Uri
+ ) = coroutineScope {
+ val logs = mutableListOf()
+ var availableBytes = 0
+ try {
+ context.contentResolver.openInputStream(uri)?.let {
try {
- context.contentResolver.openInputStream(uri)?.let {
- try {
- availableBytes = it.available()
- val reader = LogcatStreamReader(it)
- for (log in reader) {
- logs += log
- }
- } catch (e: IOException) {
- // ignore
- } finally {
- it.closeQuietly()
- }
- }
- } catch (e: Exception) {
- e.printStackTrace()
- return@coroutineScope SavedLogsResult.FileOpenError
- }
-
- if (logs.isEmpty() && availableBytes > 0) {
- return@coroutineScope SavedLogsResult.FileParseError
+ availableBytes = it.available()
+ val reader = LogcatStreamReader(it)
+ for (log in reader) {
+ logs += log
+ }
+ } catch (e: IOException) {
+ // ignore
+ } finally {
+ it.closeQuietly()
}
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return@coroutineScope SavedLogsResult.FileOpenError
+ }
- SavedLogsResult.Success(logs)
+ if (logs.isEmpty() && availableBytes > 0) {
+ return@coroutineScope SavedLogsResult.FileParseError
}
- fun getLogs(): LiveData = logs
+ SavedLogsResult.Success(logs)
+ }
- sealed class SavedLogsResult {
- data class Success(val logs: List) : SavedLogsResult()
- object FileOpenError : SavedLogsResult()
- object FileParseError : SavedLogsResult()
- }
+ fun getLogs(): LiveData = logs
+
+ sealed class SavedLogsResult {
+ data class Success(val logs: List) : SavedLogsResult()
+ object FileOpenError : SavedLogsResult()
+ object FileParseError : SavedLogsResult()
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/settings/SettingsFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/settings/SettingsFragment.kt
index c1ccf495..e996262d 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/settings/SettingsFragment.kt
@@ -31,315 +31,344 @@ import java.text.NumberFormat
class SettingsFragment : PreferenceFragmentCompat() {
- companion object {
- val TAG = SettingsFragment::class.qualifiedName
- private const val WRITE_STORAGE_PERMISSION_REQ = 12
- private const val SAVE_LOCATION_REQ = 123
- }
-
- private lateinit var prefSaveLocation: Preference
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- addPreferencesFromResource(R.xml.settings)
- setupAppearanceCategory()
- setupLogcatCategory()
- setupAboutCategory()
+ companion object {
+ val TAG = SettingsFragment::class.qualifiedName
+ private const val WRITE_STORAGE_PERMISSION_REQ = 12
+ private const val SAVE_LOCATION_REQ = 123
+ }
+
+ private lateinit var prefSaveLocation: Preference
+
+ override fun onCreatePreferences(
+ savedInstanceState: Bundle?,
+ rootKey: String?
+ ) {
+ addPreferencesFromResource(R.xml.settings)
+ setupAppearanceCategory()
+ setupLogcatCategory()
+ setupAboutCategory()
+ }
+
+ private fun setupAppearanceCategory() {
+ val sharedPrefs = preferenceScreen.sharedPreferences
+ val themePref = findPreference(PreferenceKeys.Appearance.KEY_THEME)!!
+ val useBlackThemePref =
+ findPreference(PreferenceKeys.Appearance.KEY_USE_BLACK_THEME)!!
+
+ val currTheme = sharedPrefs.getString(
+ PreferenceKeys.Appearance.KEY_THEME,
+ PreferenceKeys.Appearance.Default.THEME
+ )!!
+
+ when (currTheme) {
+ PreferenceKeys.Appearance.Theme.AUTO,
+ PreferenceKeys.Appearance.Theme.DARK -> useBlackThemePref.isEnabled = true
+ else -> useBlackThemePref.isEnabled = false
}
- private fun setupAppearanceCategory() {
- val sharedPrefs = preferenceScreen.sharedPreferences
- val themePref = findPreference(PreferenceKeys.Appearance.KEY_THEME)!!
- val useBlackThemePref = findPreference(PreferenceKeys.Appearance.KEY_USE_BLACK_THEME)!!
+ val themePrefEntries = resources.getStringArray(R.array.pref_appearance_theme_entries)
+ themePref.summary = themePrefEntries[currTheme.toInt()]
- val currTheme = sharedPrefs.getString(PreferenceKeys.Appearance.KEY_THEME,
- PreferenceKeys.Appearance.Default.THEME)!!
-
- when (currTheme) {
- PreferenceKeys.Appearance.Theme.AUTO,
- PreferenceKeys.Appearance.Theme.DARK -> useBlackThemePref.isEnabled = true
- else -> useBlackThemePref.isEnabled = false
- }
-
- val themePrefEntries = resources.getStringArray(R.array.pref_appearance_theme_entries)
- themePref.summary = themePrefEntries[currTheme.toInt()]
-
- themePref.onPreferenceChangeListener =
- Preference.OnPreferenceChangeListener { preference, newValue ->
- preference.summary = themePrefEntries[(newValue as String).toInt()]
- requireActivity().restartApp()
- true
- }
-
- useBlackThemePref.onPreferenceChangeListener = Preference
- .OnPreferenceChangeListener { _, _ ->
- val activity = requireActivity()
- if (activity.isDarkThemeOn()) {
- activity.restartApp()
- }
- true
- }
- }
+ themePref.onPreferenceChangeListener =
+ Preference.OnPreferenceChangeListener { preference, newValue ->
+ preference.summary = themePrefEntries[(newValue as String).toInt()]
+ requireActivity().restartApp()
+ true
+ }
- private fun setupLogcatCategory() {
- val prefPollInterval = findPreference(PreferenceKeys.Logcat.KEY_POLL_INTERVAL)!!
- val prefBuffers = findPreference(PreferenceKeys.Logcat.KEY_BUFFERS)!!
- val prefMaxLogs = findPreference(PreferenceKeys.Logcat.KEY_MAX_LOGS)!!
-
- prefPollInterval.summary = preferenceScreen.sharedPreferences
- .getString(PreferenceKeys.Logcat.KEY_POLL_INTERVAL,
- PreferenceKeys.Logcat.Default.POLL_INTERVAL)!! + " ms"
-
- prefPollInterval.onPreferenceChangeListener = Preference
- .OnPreferenceChangeListener { preference, newValue ->
- val activity = requireActivity()
- try {
- val v = newValue.toString().trim()
- val num = v.toLong()
- if (num <= 0) {
- activity.showToast(getString(R.string.value_must_be_greater_than_0))
- false
- } else {
- preference.summary = "$v ms"
- true
- }
- } catch (e: NumberFormatException) {
- activity.showToast(getString(R.string.value_must_be_a_positive_integer))
- false
- }
- }
-
- val availableBuffers = Logcat.AVAILABLE_BUFFERS
- val defaultBuffers = PreferenceKeys.Logcat.Default.BUFFERS
- if (availableBuffers.isNotEmpty() && defaultBuffers.isNotEmpty()) {
- val bufferValues = preferenceScreen.sharedPreferences
- .getStringSet(PreferenceKeys.Logcat.KEY_BUFFERS, defaultBuffers)!!
-
- val toSummary = { values: Set ->
- values.map { e -> availableBuffers[e.toInt()] }
- .sorted()
- .joinToString(", ")
- }
-
- prefBuffers.entries = availableBuffers.copyOf()
- val entryValues = mutableListOf()
- for (i in availableBuffers.indices) {
- entryValues += i.toString()
- }
- prefBuffers.entryValues = entryValues.toTypedArray()
- prefBuffers.summary = toSummary(bufferValues)
-
- @Suppress("unchecked_cast")
- prefBuffers.onPreferenceChangeListener = Preference
- .OnPreferenceChangeListener { preference, newValue ->
- val mp = preference as MultiSelectListPreference
- val values = newValue as Set
-
- if (values.isEmpty()) {
- false
- } else {
- mp.summary = toSummary(values)
- true
- }
- }
- } else {
- prefBuffers.isVisible = false
+ useBlackThemePref.onPreferenceChangeListener = Preference
+ .OnPreferenceChangeListener { _, _ ->
+ val activity = requireActivity()
+ if (activity.isDarkThemeOn()) {
+ activity.restartApp()
}
-
- val maxLogs = preferenceScreen.sharedPreferences.getString(
- PreferenceKeys.Logcat.KEY_MAX_LOGS,
- PreferenceKeys.Logcat.Default.MAX_LOGS
- )!!.trim().toInt()
-
- prefMaxLogs.summary = NumberFormat.getInstance().format(maxLogs)
- prefMaxLogs.onPreferenceChangeListener = Preference
- .OnPreferenceChangeListener callback@{ preference, newValue ->
- val activity = requireActivity()
- try {
- val oldValue = preferenceScreen.sharedPreferences.getString(
- PreferenceKeys.Logcat.KEY_MAX_LOGS,
- PreferenceKeys.Logcat.Default.MAX_LOGS
- )!!.trim().toInt()
-
- val newMaxLogs = (newValue as String).trim().toInt()
- if (newMaxLogs == oldValue) {
- return@callback false
- }
-
- if (newMaxLogs < 1000) {
- activity.showToast(getString(R.string.cannot_be_less_than_1000))
- return@callback false
- }
-
- preference.summary = NumberFormat.getInstance().format(newMaxLogs)
- true
- } catch (e: NumberFormatException) {
- activity.showToast(getString(R.string.not_a_valid_number))
- false
- }
- }
-
- setupSaveLocationOption()
- }
-
- private fun setupSaveLocationOption() {
- prefSaveLocation = findPreference(PreferenceKeys.Logcat.KEY_SAVE_LOCATION)!!
- val saveLocation = preferenceScreen.sharedPreferences.getString(
- PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
- PreferenceKeys.Logcat.Default.SAVE_LOCATION
- )!!.trim()
- if (saveLocation.isEmpty()) {
- prefSaveLocation.summary = getString(R.string.save_location_internal)
- } else {
- if (Build.VERSION.SDK_INT >= 21) {
- prefSaveLocation.summary = getString(R.string.save_location_custom)
- } else {
- prefSaveLocation.summary = "%s/%s".format(saveLocation,
- LogcatLiveFragment.LOGCAT_DIR)
- }
+ true
+ }
+ }
+
+ private fun setupLogcatCategory() {
+ val prefPollInterval = findPreference(PreferenceKeys.Logcat.KEY_POLL_INTERVAL)!!
+ val prefBuffers = findPreference(PreferenceKeys.Logcat.KEY_BUFFERS)!!
+ val prefMaxLogs = findPreference(PreferenceKeys.Logcat.KEY_MAX_LOGS)!!
+
+ prefPollInterval.summary = preferenceScreen.sharedPreferences
+ .getString(
+ PreferenceKeys.Logcat.KEY_POLL_INTERVAL,
+ PreferenceKeys.Logcat.Default.POLL_INTERVAL
+ )!! + " ms"
+
+ prefPollInterval.onPreferenceChangeListener = Preference
+ .OnPreferenceChangeListener { preference, newValue ->
+ val activity = requireActivity()
+ try {
+ val v = newValue.toString().trim()
+ val num = v.toLong()
+ if (num <= 0) {
+ activity.showToast(getString(R.string.value_must_be_greater_than_0))
+ false
+ } else {
+ preference.summary = "$v ms"
+ true
+ }
+ } catch (e: NumberFormatException) {
+ activity.showToast(getString(R.string.value_must_be_a_positive_integer))
+ false
}
-
- prefSaveLocation.setOnPreferenceClickListener {
- val frag = SaveLocationDialogFragment()
- frag.setTargetFragment(this@SettingsFragment, 0)
- frag.show(parentFragmentManager, SaveLocationDialogFragment.TAG)
+ }
+
+ val availableBuffers = Logcat.AVAILABLE_BUFFERS
+ val defaultBuffers = PreferenceKeys.Logcat.Default.BUFFERS
+ if (availableBuffers.isNotEmpty() && defaultBuffers.isNotEmpty()) {
+ val bufferValues = preferenceScreen.sharedPreferences
+ .getStringSet(PreferenceKeys.Logcat.KEY_BUFFERS, defaultBuffers)!!
+
+ val toSummary = { values: Set ->
+ values.map { e -> availableBuffers[e.toInt()] }
+ .sorted()
+ .joinToString(", ")
+ }
+
+ prefBuffers.entries = availableBuffers.copyOf()
+ val entryValues = mutableListOf()
+ for (i in availableBuffers.indices) {
+ entryValues += i.toString()
+ }
+ prefBuffers.entryValues = entryValues.toTypedArray()
+ prefBuffers.summary = toSummary(bufferValues)
+
+ @Suppress("unchecked_cast")
+ prefBuffers.onPreferenceChangeListener = Preference
+ .OnPreferenceChangeListener { preference, newValue ->
+ val mp = preference as MultiSelectListPreference
+ val values = newValue as Set
+
+ if (values.isEmpty()) {
+ false
+ } else {
+ mp.summary = toSummary(values)
true
+ }
}
-
- val frag = parentFragmentManager.findFragmentByTag(SaveLocationDialogFragment.TAG)
- frag?.setTargetFragment(this, 0)
-
- val folderChooserFragment = parentFragmentManager
- .findFragmentByTag(FolderChooserDialogFragment.TAG)
- folderChooserFragment?.setTargetFragment(this, 0)
+ } else {
+ prefBuffers.isVisible = false
}
- private fun isExternalStorageWritable() =
- Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
-
- private fun setupCustomSaveLocation() {
- if (Build.VERSION.SDK_INT >= 21) {
- setupCustomSaveLocationLollipop()
- } else {
- if (isExternalStorageWritable()) {
- val frag = FolderChooserDialogFragment()
- frag.setTargetFragment(this, 0)
- frag.show(parentFragmentManager, FolderChooserDialogFragment.TAG)
- } else {
- requireActivity().showToast(getString(R.string.err_msg_external_storage_not_writable))
- }
- }
- }
+ val maxLogs = preferenceScreen.sharedPreferences.getString(
+ PreferenceKeys.Logcat.KEY_MAX_LOGS,
+ PreferenceKeys.Logcat.Default.MAX_LOGS
+ )!!.trim().toInt()
- fun setupCustomSaveLocationPreLollipop(file: File?) {
+ prefMaxLogs.summary = NumberFormat.getInstance().format(maxLogs)
+ prefMaxLogs.onPreferenceChangeListener = Preference
+ .OnPreferenceChangeListener callback@{ preference, newValue ->
val activity = requireActivity()
- if (file == null) {
- activity.showToast("Folder not selected")
- } else {
- if (!file.canWrite()) {
- activity.showToast("Folder not writable")
- return
- }
-
- preferenceScreen.sharedPreferences.edit {
- putString(PreferenceKeys.Logcat.KEY_SAVE_LOCATION, file.absolutePath)
- }
- prefSaveLocation.summary = "%s/%s".format(file.absolutePath,
- LogcatLiveFragment.LOGCAT_DIR)
+ try {
+ val oldValue = preferenceScreen.sharedPreferences.getString(
+ PreferenceKeys.Logcat.KEY_MAX_LOGS,
+ PreferenceKeys.Logcat.Default.MAX_LOGS
+ )!!.trim().toInt()
+
+ val newMaxLogs = (newValue as String).trim().toInt()
+ if (newMaxLogs == oldValue) {
+ return@callback false
+ }
+
+ if (newMaxLogs < 1000) {
+ activity.showToast(getString(R.string.cannot_be_less_than_1000))
+ return@callback false
+ }
+
+ preference.summary = NumberFormat.getInstance().format(newMaxLogs)
+ true
+ } catch (e: NumberFormatException) {
+ activity.showToast(getString(R.string.not_a_valid_number))
+ false
}
+ }
+
+ setupSaveLocationOption()
+ }
+
+ private fun setupSaveLocationOption() {
+ prefSaveLocation = findPreference(PreferenceKeys.Logcat.KEY_SAVE_LOCATION)!!
+ val saveLocation = preferenceScreen.sharedPreferences.getString(
+ PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
+ PreferenceKeys.Logcat.Default.SAVE_LOCATION
+ )!!.trim()
+ if (saveLocation.isEmpty()) {
+ prefSaveLocation.summary = getString(R.string.save_location_internal)
+ } else {
+ if (Build.VERSION.SDK_INT >= 21) {
+ prefSaveLocation.summary = getString(R.string.save_location_custom)
+ } else {
+ prefSaveLocation.summary = "%s/%s".format(
+ saveLocation,
+ LogcatLiveFragment.LOGCAT_DIR
+ )
+ }
}
- @TargetApi(21)
- private fun setupCustomSaveLocationLollipop() {
- if (ContextCompat.checkSelfPermission(requireActivity(),
- Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
- PackageManager.PERMISSION_GRANTED) {
- requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
- WRITE_STORAGE_PERMISSION_REQ)
- } else {
- onPermissionGranted()
- }
+ prefSaveLocation.setOnPreferenceClickListener {
+ val frag = SaveLocationDialogFragment()
+ frag.setTargetFragment(this@SettingsFragment, 0)
+ frag.show(parentFragmentManager, SaveLocationDialogFragment.TAG)
+ true
}
- @TargetApi(21)
- private fun onPermissionGranted() {
- val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
- intent.putExtra("android.content.extra.SHOW_ADVANCED", true)
- startActivityForResult(intent, SAVE_LOCATION_REQ)
+ val frag = parentFragmentManager.findFragmentByTag(SaveLocationDialogFragment.TAG)
+ frag?.setTargetFragment(this, 0)
+
+ val folderChooserFragment = parentFragmentManager
+ .findFragmentByTag(FolderChooserDialogFragment.TAG)
+ folderChooserFragment?.setTargetFragment(this, 0)
+ }
+
+ private fun isExternalStorageWritable() =
+ Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
+
+ private fun setupCustomSaveLocation() {
+ if (Build.VERSION.SDK_INT >= 21) {
+ setupCustomSaveLocationLollipop()
+ } else {
+ if (isExternalStorageWritable()) {
+ val frag = FolderChooserDialogFragment()
+ frag.setTargetFragment(this, 0)
+ frag.show(parentFragmentManager, FolderChooserDialogFragment.TAG)
+ } else {
+ requireActivity().showToast(getString(R.string.err_msg_external_storage_not_writable))
+ }
}
-
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array,
- grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- when (requestCode) {
- WRITE_STORAGE_PERMISSION_REQ ->
- if (grantResults.isNotEmpty() &&
- grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- onPermissionGranted()
- }
- }
+ }
+
+ fun setupCustomSaveLocationPreLollipop(file: File?) {
+ val activity = requireActivity()
+ if (file == null) {
+ activity.showToast("Folder not selected")
+ } else {
+ if (!file.canWrite()) {
+ activity.showToast("Folder not writable")
+ return
+ }
+
+ preferenceScreen.sharedPreferences.edit {
+ putString(PreferenceKeys.Logcat.KEY_SAVE_LOCATION, file.absolutePath)
+ }
+ prefSaveLocation.summary = "%s/%s".format(
+ file.absolutePath,
+ LogcatLiveFragment.LOGCAT_DIR
+ )
}
-
- @TargetApi(21)
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- when (requestCode) {
- SAVE_LOCATION_REQ -> {
- val uri = data?.data
- if (uri != null) {
- activity!!.contentResolver.takePersistableUriPermission(uri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION or
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
- preferenceScreen.sharedPreferences.edit {
- putString(PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
- uri.toString())
- }
- prefSaveLocation.summary = getString(R.string.save_location_custom)
- }
- }
+ }
+
+ @TargetApi(21)
+ private fun setupCustomSaveLocationLollipop() {
+ if (ContextCompat.checkSelfPermission(
+ requireActivity(),
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ ) !=
+ PackageManager.PERMISSION_GRANTED
+ ) {
+ requestPermissions(
+ arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ WRITE_STORAGE_PERMISSION_REQ
+ )
+ } else {
+ onPermissionGranted()
+ }
+ }
+
+ @TargetApi(21)
+ private fun onPermissionGranted() {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ intent.putExtra("android.content.extra.SHOW_ADVANCED", true)
+ startActivityForResult(intent, SAVE_LOCATION_REQ)
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ when (requestCode) {
+ WRITE_STORAGE_PERMISSION_REQ ->
+ if (grantResults.isNotEmpty() &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED
+ ) {
+ onPermissionGranted()
}
}
-
- private fun setupDefaultSaveLocation() {
- preferenceScreen.sharedPreferences.edit {
- putString(PreferenceKeys.Logcat.KEY_SAVE_LOCATION, "")
+ }
+
+ @TargetApi(21)
+ override fun onActivityResult(
+ requestCode: Int,
+ resultCode: Int,
+ data: Intent?
+ ) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ SAVE_LOCATION_REQ -> {
+ val uri = data?.data
+ if (uri != null) {
+ activity!!.contentResolver.takePersistableUriPermission(
+ uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ )
+ preferenceScreen.sharedPreferences.edit {
+ putString(
+ PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
+ uri.toString()
+ )
+ }
+ prefSaveLocation.summary = getString(R.string.save_location_custom)
}
- prefSaveLocation.summary = getString(R.string.save_location_internal)
+ }
}
+ }
- private fun setupAboutCategory() {
- val prefAbout = findPreference(PreferenceKeys.About.KEY_VERSION_NAME)!!
- prefAbout.summary = "Version ${BuildConfig.VERSION_NAME}"
-
- val prefGitHubPage = findPreference(PreferenceKeys.About.KEY_GITHUB_PAGE)!!
- prefGitHubPage.setOnPreferenceClickListener { _ ->
- try {
- val url = "https://github.com/darshanparajuli/LogcatReader"
- val intent = Intent(Intent.ACTION_VIEW)
- intent.data = Uri.parse(url)
- startActivity(intent)
- true
- } catch (e: Exception) {
- false
- }
- }
+ private fun setupDefaultSaveLocation() {
+ preferenceScreen.sharedPreferences.edit {
+ putString(PreferenceKeys.Logcat.KEY_SAVE_LOCATION, "")
+ }
+ prefSaveLocation.summary = getString(R.string.save_location_internal)
+ }
+
+ private fun setupAboutCategory() {
+ val prefAbout = findPreference(PreferenceKeys.About.KEY_VERSION_NAME)!!
+ prefAbout.summary = "Version ${BuildConfig.VERSION_NAME}"
+
+ val prefGitHubPage = findPreference(PreferenceKeys.About.KEY_GITHUB_PAGE)!!
+ prefGitHubPage.setOnPreferenceClickListener { _ ->
+ try {
+ val url = "https://github.com/darshanparajuli/LogcatReader"
+ val intent = Intent(Intent.ACTION_VIEW)
+ intent.data = Uri.parse(url)
+ startActivity(intent)
+ true
+ } catch (e: Exception) {
+ false
+ }
}
+ }
- class SaveLocationDialogFragment : BaseDialogFragment() {
- companion object {
- val TAG = SaveLocationDialogFragment::class.qualifiedName
- }
+ class SaveLocationDialogFragment : BaseDialogFragment() {
+ companion object {
+ val TAG = SaveLocationDialogFragment::class.qualifiedName
+ }
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- return AlertDialog.Builder(activity!!)
- .setTitle(R.string.save_location)
- .setItems(R.array.save_location_options) { _, which ->
- if (which == 0) {
- (targetFragment as SettingsFragment).setupDefaultSaveLocation()
- } else {
- (targetFragment as SettingsFragment).setupCustomSaveLocation()
- }
- }
- .create()
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return AlertDialog.Builder(activity!!)
+ .setTitle(R.string.save_location)
+ .setItems(R.array.save_location_options) { _, which ->
+ if (which == 0) {
+ (targetFragment as SettingsFragment).setupDefaultSaveLocation()
+ } else {
+ (targetFragment as SettingsFragment).setupCustomSaveLocation()
+ }
}
+ .create()
}
+ }
}
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/settings/dialogs/FolderChooserDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/settings/dialogs/FolderChooserDialogFragment.kt
index ab98b486..d685ce26 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/settings/dialogs/FolderChooserDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/settings/dialogs/FolderChooserDialogFragment.kt
@@ -19,164 +19,180 @@ import androidx.recyclerview.widget.RecyclerView
import com.dp.logcatapp.R
import com.dp.logcatapp.fragments.base.BaseDialogFragment
import com.dp.logcatapp.fragments.settings.SettingsFragment
-import com.dp.logcatapp.util.*
+import com.dp.logcatapp.util.PreferenceKeys
+import com.dp.logcatapp.util.getAndroidViewModel
+import com.dp.logcatapp.util.getAttributeDrawable
+import com.dp.logcatapp.util.getDefaultSharedPreferences
+import com.dp.logcatapp.util.inflateLayout
import java.io.File
-
class FolderChooserDialogFragment : BaseDialogFragment(), View.OnClickListener {
- companion object {
- val TAG = FolderChooserDialogFragment::class.qualifiedName
- }
-
- private lateinit var recyclerViewAdapter: MyRecyclerViewAdapter
- private lateinit var layoutManager: LinearLayoutManager
- private lateinit var viewModel: MyViewModel
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- recyclerViewAdapter = MyRecyclerViewAdapter(requireContext(), this)
+ companion object {
+ val TAG = FolderChooserDialogFragment::class.qualifiedName
+ }
+
+ private lateinit var recyclerViewAdapter: MyRecyclerViewAdapter
+ private lateinit var layoutManager: LinearLayoutManager
+ private lateinit var viewModel: MyViewModel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ recyclerViewAdapter = MyRecyclerViewAdapter(requireContext(), this)
+
+ viewModel = getAndroidViewModel()
+ viewModel.files.observe(this, Observer> {
+ if (it != null) {
+ recyclerViewAdapter.setData(it)
+ }
+ })
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val rootView = inflateLayout(R.layout.folder_chooser)
+ val recyclerView = rootView.findViewById(R.id.recyclerView)
+
+ val activity = requireActivity()
+ layoutManager = LinearLayoutManager(activity)
+
+ recyclerView.layoutManager = layoutManager
+ recyclerView.adapter = recyclerViewAdapter
+
+ return AlertDialog.Builder(activity)
+ .setTitle("Select a folder")
+ .setView(rootView)
+ .setPositiveButton(getString(R.string.select)) { _, _ ->
+ val folder = if (recyclerViewAdapter.itemCount > 0) {
+ val fileHolder = recyclerViewAdapter[0]
+ if (fileHolder.isParent) {
+ fileHolder.file
+ } else {
+ null
+ }
+ } else {
+ null
+ }
- viewModel = getAndroidViewModel()
- viewModel.files.observe(this, Observer> {
- if (it != null) {
- recyclerViewAdapter.setData(it)
+ (targetFragment as SettingsFragment).setupCustomSaveLocationPreLollipop(folder)
+ }
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ dismiss()
+ }
+ .create()
+ }
+
+ override fun onClick(v: View) {
+ when (v.id) {
+ R.id.folderChooserListItem -> {
+ val pos = layoutManager.getPosition(v)
+ if (pos != RecyclerView.NO_POSITION) {
+ val fileHolder = recyclerViewAdapter[pos]
+ if (fileHolder.isParent) {
+ fileHolder.file.parentFile?.let {
+ viewModel.update(it)
}
- })
- }
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val rootView = inflateLayout(R.layout.folder_chooser)
- val recyclerView = rootView.findViewById(R.id.recyclerView)
-
- val activity = requireActivity()
- layoutManager = LinearLayoutManager(activity)
-
- recyclerView.layoutManager = layoutManager
- recyclerView.adapter = recyclerViewAdapter
-
- return AlertDialog.Builder(activity)
- .setTitle("Select a folder")
- .setView(rootView)
- .setPositiveButton(getString(R.string.select)) { _, _ ->
- val folder = if (recyclerViewAdapter.itemCount > 0) {
- val fileHolder = recyclerViewAdapter[0]
- if (fileHolder.isParent) {
- fileHolder.file
- } else {
- null
- }
- } else {
- null
- }
-
- (targetFragment as SettingsFragment).setupCustomSaveLocationPreLollipop(folder)
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- dismiss()
- }
- .create()
- }
-
- override fun onClick(v: View) {
- when (v.id) {
- R.id.folderChooserListItem -> {
- val pos = layoutManager.getPosition(v)
- if (pos != RecyclerView.NO_POSITION) {
- val fileHolder = recyclerViewAdapter[pos]
- if (fileHolder.isParent) {
- fileHolder.file.parentFile?.let {
- viewModel.update(it)
- }
- } else {
- if (fileHolder.file.isDirectory) {
- viewModel.update(fileHolder.file)
- }
- }
- }
+ } else {
+ if (fileHolder.file.isDirectory) {
+ viewModel.update(fileHolder.file)
}
+ }
}
+ }
}
+ }
}
-
internal class MyViewModel(application: Application) : AndroidViewModel(application) {
- val files = MutableLiveData>()
-
- init {
- val path = application.getDefaultSharedPreferences().getString(
- PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
- ""
- )!!
-
- val file = if (path.isEmpty()) {
- @Suppress("DEPRECATION")
- Environment.getExternalStorageDirectory()
- } else {
- File(path)
- }
-
- update(file)
+ val files = MutableLiveData>()
+
+ init {
+ val path = application.getDefaultSharedPreferences().getString(
+ PreferenceKeys.Logcat.KEY_SAVE_LOCATION,
+ ""
+ )!!
+
+ val file = if (path.isEmpty()) {
+ @Suppress("DEPRECATION")
+ Environment.getExternalStorageDirectory()
+ } else {
+ File(path)
}
- fun update(file: File) {
- val files = mutableListOf()
+ update(file)
+ }
- if (file.parentFile != null) {
- files.add(FileHolder(file, isParent = true))
- }
-
- file.listFiles()
- ?.map { FileHolder(it) }
- ?.sortedBy { it.file.name }
- ?.forEach { files.add(it) }
+ fun update(file: File) {
+ val files = mutableListOf()
- this.files.value = files
+ if (file.parentFile != null) {
+ files.add(FileHolder(file, isParent = true))
}
-}
-
-internal data class FileHolder(val file: File, val isParent: Boolean = false)
-private class MyRecyclerViewAdapter(context: Context, private val onClickListener: View.OnClickListener) :
- RecyclerView.Adapter() {
-
- private var data = listOf()
-
- private val drawableFolder = context.getAttributeDrawable(R.attr.ic_folder)!!
- private val drawableFile = context.getAttributeDrawable(R.attr.ic_insert_drive_file)!!
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
- val view = LayoutInflater.from(parent.context).inflate(R.layout.folder_chooser_list_item,
- parent, false)
- view.setOnClickListener(onClickListener)
- return MyViewHolder(view)
- }
+ file.listFiles()
+ ?.map { FileHolder(it) }
+ ?.sortedBy { it.file.name }
+ ?.forEach { files.add(it) }
- override fun getItemCount() = data.size
+ this.files.value = files
+ }
+}
- fun setData(items: List) {
- data = items
- notifyDataSetChanged()
+internal data class FileHolder(
+ val file: File,
+ val isParent: Boolean = false
+)
+
+private class MyRecyclerViewAdapter(
+ context: Context,
+ private val onClickListener: View.OnClickListener
+) :
+ RecyclerView.Adapter() {
+
+ private var data = listOf()
+
+ private val drawableFolder = context.getAttributeDrawable(R.attr.ic_folder)!!
+ private val drawableFile = context.getAttributeDrawable(R.attr.ic_insert_drive_file)!!
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MyViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(
+ R.layout.folder_chooser_list_item,
+ parent, false
+ )
+ view.setOnClickListener(onClickListener)
+ return MyViewHolder(view)
+ }
+
+ override fun getItemCount() = data.size
+
+ fun setData(items: List) {
+ data = items
+ notifyDataSetChanged()
+ }
+
+ operator fun get(index: Int) = data[index]
+
+ override fun onBindViewHolder(
+ holder: MyViewHolder,
+ position: Int
+ ) {
+ val fileHolder = data[position]
+ if (fileHolder.isParent) {
+ holder.fileName.text = ".."
+ } else {
+ holder.fileName.text = fileHolder.file.name
}
-
- operator fun get(index: Int) = data[index]
-
- override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
- val fileHolder = data[position]
- if (fileHolder.isParent) {
- holder.fileName.text = ".."
- } else {
- holder.fileName.text = fileHolder.file.name
- }
- if (fileHolder.file.isDirectory) {
- holder.fileIcon.setImageDrawable(drawableFolder)
- } else {
- holder.fileIcon.setImageDrawable(drawableFile)
- }
+ if (fileHolder.file.isDirectory) {
+ holder.fileIcon.setImageDrawable(drawableFolder)
+ } else {
+ holder.fileIcon.setImageDrawable(drawableFile)
}
+ }
- class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val fileIcon: ImageView = itemView.findViewById(R.id.fileIcon)
- val fileName: TextView = itemView.findViewById(R.id.fileName)
- }
+ class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val fileIcon: ImageView = itemView.findViewById(R.id.fileIcon)
+ val fileName: TextView = itemView.findViewById(R.id.fileName)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/CopyToClipboardDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/CopyToClipboardDialogFragment.kt
index 40a07b78..8b071c6b 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/CopyToClipboardDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/CopyToClipboardDialogFragment.kt
@@ -14,45 +14,53 @@ import com.dp.logcatapp.util.showToast
class CopyToClipboardDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
- companion object {
- val TAG = CopyToClipboardDialogFragment::class.qualifiedName
+ companion object {
+ val TAG = CopyToClipboardDialogFragment::class.qualifiedName
- private val KEY_LOG = TAG + "_key_log"
+ private val KEY_LOG = TAG + "_key_log"
- fun newInstance(log: Log): CopyToClipboardDialogFragment {
- val bundle = Bundle()
- bundle.putParcelable(KEY_LOG, log)
+ fun newInstance(log: Log): CopyToClipboardDialogFragment {
+ val bundle = Bundle()
+ bundle.putParcelable(KEY_LOG, log)
- val fragment = CopyToClipboardDialogFragment()
- fragment.arguments = bundle
- return fragment
- }
+ val fragment = CopyToClipboardDialogFragment()
+ fragment.arguments = bundle
+ return fragment
}
+ }
- private enum class LogContentType {
- TAG, MESSAGE, DATE, TIME, PID, TID
- }
+ private enum class LogContentType {
+ TAG,
+ MESSAGE,
+ DATE,
+ TIME,
+ PID,
+ TID
+ }
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- return AlertDialog.Builder(requireActivity())
- .setTitle(R.string.copy_to_clipboard)
- .setItems(R.array.log_content_types, this)
- .create()
- }
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.copy_to_clipboard)
+ .setItems(R.array.log_content_types, this)
+ .create()
+ }
- override fun onClick(dialog: DialogInterface, which: Int) {
- val log = requireArguments().getParcelable(KEY_LOG)!!
- val activity = requireActivity()
- val cm = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- val clip = when (which) {
- LogContentType.TAG.ordinal -> ClipData.newPlainText("Log Tag", log.tag)
- LogContentType.MESSAGE.ordinal -> ClipData.newPlainText("Log Msg", log.msg)
- LogContentType.DATE.ordinal -> ClipData.newPlainText("Log Date", log.date)
- LogContentType.TIME.ordinal -> ClipData.newPlainText("Log Time", log.time)
- LogContentType.PID.ordinal -> ClipData.newPlainText("Log PID", log.pid)
- else -> ClipData.newPlainText("Log TID", log.tid)
- }
- cm.setPrimaryClip(clip)
- activity.showToast(getString(R.string.copied_to_clipboard))
+ override fun onClick(
+ dialog: DialogInterface,
+ which: Int
+ ) {
+ val log = requireArguments().getParcelable(KEY_LOG)!!
+ val activity = requireActivity()
+ val cm = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val clip = when (which) {
+ LogContentType.TAG.ordinal -> ClipData.newPlainText("Log Tag", log.tag)
+ LogContentType.MESSAGE.ordinal -> ClipData.newPlainText("Log Msg", log.msg)
+ LogContentType.DATE.ordinal -> ClipData.newPlainText("Log Date", log.date)
+ LogContentType.TIME.ordinal -> ClipData.newPlainText("Log Time", log.time)
+ LogContentType.PID.ordinal -> ClipData.newPlainText("Log PID", log.pid)
+ else -> ClipData.newPlainText("Log TID", log.tid)
}
+ cm.setPrimaryClip(clip)
+ activity.showToast(getString(R.string.copied_to_clipboard))
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/FilterExclusionDialogFragment.kt b/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/FilterExclusionDialogFragment.kt
index d67ec5bb..d464b965 100644
--- a/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/FilterExclusionDialogFragment.kt
+++ b/app/src/main/java/com/dp/logcatapp/fragments/shared/dialogs/FilterExclusionDialogFragment.kt
@@ -10,48 +10,53 @@ import com.dp.logcatapp.R
import com.dp.logcatapp.activities.FiltersActivity
import com.dp.logcatapp.fragments.base.BaseDialogFragment
-
class FilterExclusionDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
- companion object {
- val TAG = FilterExclusionDialogFragment::class.qualifiedName
-
- private val KEY_LOG = TAG + "_key_log"
+ companion object {
+ val TAG = FilterExclusionDialogFragment::class.qualifiedName
- fun newInstance(log: Log): FilterExclusionDialogFragment {
- val bundle = Bundle()
- bundle.putParcelable(KEY_LOG, log)
+ private val KEY_LOG = TAG + "_key_log"
- val fragment = FilterExclusionDialogFragment()
- fragment.arguments = bundle
- return fragment
- }
- }
-
- private enum class LogContentType {
- FILTER, EXCLUDE
- }
+ fun newInstance(log: Log): FilterExclusionDialogFragment {
+ val bundle = Bundle()
+ bundle.putParcelable(KEY_LOG, log)
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- return AlertDialog.Builder(requireActivity())
- .setItems(R.array.filter_exclude, this)
- .create()
+ val fragment = FilterExclusionDialogFragment()
+ fragment.arguments = bundle
+ return fragment
}
-
- override fun onClick(dialog: DialogInterface, which: Int) {
- val log = requireArguments().getParcelable(KEY_LOG)!!
- when (which) {
- LogContentType.FILTER.ordinal -> moveToFilterActivity(log, false)
- LogContentType.EXCLUDE.ordinal -> moveToFilterActivity(log, true)
- }
- dismiss()
- }
-
-
- private fun moveToFilterActivity(log: Log, isExclusion: Boolean) {
- val intent = Intent(requireActivity(), FiltersActivity::class.java)
- intent.putExtra(FiltersActivity.EXTRA_EXCLUSIONS, isExclusion)
- intent.putExtra(FiltersActivity.KEY_LOG, log)
- startActivity(intent)
+ }
+
+ private enum class LogContentType {
+ FILTER,
+ EXCLUDE
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return AlertDialog.Builder(requireActivity())
+ .setItems(R.array.filter_exclude, this)
+ .create()
+ }
+
+ override fun onClick(
+ dialog: DialogInterface,
+ which: Int
+ ) {
+ val log = requireArguments().getParcelable(KEY_LOG)!!
+ when (which) {
+ LogContentType.FILTER.ordinal -> moveToFilterActivity(log, false)
+ LogContentType.EXCLUDE.ordinal -> moveToFilterActivity(log, true)
}
+ dismiss()
+ }
+
+ private fun moveToFilterActivity(
+ log: Log,
+ isExclusion: Boolean
+ ) {
+ val intent = Intent(requireActivity(), FiltersActivity::class.java)
+ intent.putExtra(FiltersActivity.EXTRA_EXCLUSIONS, isExclusion)
+ intent.putExtra(FiltersActivity.KEY_LOG, log)
+ startActivity(intent)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/model/LogcatMsg.kt b/app/src/main/java/com/dp/logcatapp/model/LogcatMsg.kt
index 07e459d2..46451299 100644
--- a/app/src/main/java/com/dp/logcatapp/model/LogcatMsg.kt
+++ b/app/src/main/java/com/dp/logcatapp/model/LogcatMsg.kt
@@ -1,11 +1,12 @@
package com.dp.logcatapp.model
+data class LogcatMsg(
+ var keyword: String,
+ var tag: String,
+ var pid: String,
+ var tid: String,
+ var logLevels: MutableSet
+) {
-data class LogcatMsg(var keyword: String,
- var tag: String,
- var pid: String,
- var tid: String,
- var logLevels: MutableSet) {
-
- constructor() : this("", "", "", "", mutableSetOf())
+ constructor() : this("", "", "", "", mutableSetOf())
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/services/BaseService.kt b/app/src/main/java/com/dp/logcatapp/services/BaseService.kt
index 624a931c..f7398e7d 100644
--- a/app/src/main/java/com/dp/logcatapp/services/BaseService.kt
+++ b/app/src/main/java/com/dp/logcatapp/services/BaseService.kt
@@ -8,36 +8,41 @@ import androidx.lifecycle.LifecycleService
import com.dp.logcatapp.util.getDefaultSharedPreferences
import com.dp.logcatapp.util.setTheme
-abstract class BaseService : LifecycleService(), SharedPreferences.OnSharedPreferenceChangeListener {
- private val localBinder = LocalBinder()
-
- override fun onCreate() {
- setTheme()
- super.onCreate()
- onPreRegisterSharedPreferenceChangeListener()
- getDefaultSharedPreferences().registerOnSharedPreferenceChangeListener(this)
- }
-
- protected open fun onPreRegisterSharedPreferenceChangeListener() {
- }
-
- override fun onDestroy() {
- getDefaultSharedPreferences().unregisterOnSharedPreferenceChangeListener(this)
- super.onDestroy()
- }
-
- override fun onBind(intent: Intent): IBinder? {
- super.onBind(intent)
- return localBinder
- }
-
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
- }
-
- inner class LocalBinder : Binder() {
- @Suppress("UNCHECKED_CAST")
- fun getService() = this@BaseService as T
- }
+abstract class BaseService : LifecycleService(),
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ private val localBinder = LocalBinder()
+
+ override fun onCreate() {
+ setTheme()
+ super.onCreate()
+ onPreRegisterSharedPreferenceChangeListener()
+ getDefaultSharedPreferences().registerOnSharedPreferenceChangeListener(this)
+ }
+
+ protected open fun onPreRegisterSharedPreferenceChangeListener() {
+ }
+
+ override fun onDestroy() {
+ getDefaultSharedPreferences().unregisterOnSharedPreferenceChangeListener(this)
+ super.onDestroy()
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ super.onBind(intent)
+ return localBinder
+ }
+
+ override fun onSharedPreferenceChanged(
+ sharedPreferences: SharedPreferences,
+ key: String
+ ) {
+ }
+
+ inner class LocalBinder : Binder() {
+ @Suppress("UNCHECKED_CAST")
+ fun getService() = this@BaseService as T
+ }
}
-inline fun IBinder.getService() = (this as BaseService.LocalBinder).getService()
\ No newline at end of file
+inline fun IBinder.getService() =
+ (this as BaseService.LocalBinder).getService()
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/services/LogcatService.kt b/app/src/main/java/com/dp/logcatapp/services/LogcatService.kt
index 04c0f3f4..2ceefac4 100644
--- a/app/src/main/java/com/dp/logcatapp/services/LogcatService.kt
+++ b/app/src/main/java/com/dp/logcatapp/services/LogcatService.kt
@@ -22,174 +22,206 @@ import com.dp.logcatapp.util.showToast
class LogcatService : BaseService() {
- companion object {
- val TAG = LogcatService::class.qualifiedName
- private const val NOTIFICATION_CHANNEL = "logcat_channel_01"
- private const val NOTIFICATION_ID = 1
+ companion object {
+ val TAG = LogcatService::class.qualifiedName
+ private const val NOTIFICATION_CHANNEL = "logcat_channel_01"
+ private const val NOTIFICATION_ID = 1
+ }
+
+ lateinit var logcat: Logcat
+ private set
+ var restartedLogcat = false
+
+ var paused = false
+ var recording = false
+
+ override fun onCreate() {
+ super.onCreate()
+ if (Build.VERSION.SDK_INT >= 26) {
+ createNotificationChannel()
}
- lateinit var logcat: Logcat
- private set
- var restartedLogcat = false
-
- var paused = false
- var recording = false
-
- override fun onCreate() {
- super.onCreate()
- if (Build.VERSION.SDK_INT >= 26) {
- createNotificationChannel()
- }
-
- initLogcat()
- }
-
- override fun onPreRegisterSharedPreferenceChangeListener() {
- val defaultBuffers = PreferenceKeys.Logcat.Default.BUFFERS
- if (defaultBuffers.isNotEmpty() && Logcat.AVAILABLE_BUFFERS.isNotEmpty()) {
- val buffers = getDefaultSharedPreferences()
- .getStringSet(PreferenceKeys.Logcat.KEY_BUFFERS, emptySet())
- if (buffers == null || buffers.isEmpty()) {
- getDefaultSharedPreferences().edit {
- putStringSet(PreferenceKeys.Logcat.KEY_BUFFERS, defaultBuffers)
- }
- }
+ initLogcat()
+ }
+
+ override fun onPreRegisterSharedPreferenceChangeListener() {
+ val defaultBuffers = PreferenceKeys.Logcat.Default.BUFFERS
+ if (defaultBuffers.isNotEmpty() && Logcat.AVAILABLE_BUFFERS.isNotEmpty()) {
+ val buffers = getDefaultSharedPreferences()
+ .getStringSet(PreferenceKeys.Logcat.KEY_BUFFERS, emptySet())
+ if (buffers == null || buffers.isEmpty()) {
+ getDefaultSharedPreferences().edit {
+ putStringSet(PreferenceKeys.Logcat.KEY_BUFFERS, defaultBuffers)
}
+ }
}
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- super.onStartCommand(intent, flags, startId)
- startForeground(NOTIFICATION_ID, createNotification(recording))
- return START_STICKY
- }
-
- fun updateNotification(showStopRecording: Boolean) {
- val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- nm.notify(NOTIFICATION_ID, createNotification(showStopRecording))
- }
-
- private fun createNotification(addStopRecordingAction: Boolean): Notification {
- val startIntent = Intent(this, MainActivity::class.java)
- startIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
- val contentIntent = PendingIntent.getActivity(this, 0, startIntent,
- PendingIntent.FLAG_UPDATE_CURRENT)
-
- val exitIntent = Intent(this, MainActivity::class.java)
- exitIntent.putExtra(MainActivity.EXIT_EXTRA, true)
- exitIntent.action = "exit"
- val exitPendingIntent = PendingIntent.getActivity(this, 1, exitIntent,
- PendingIntent.FLAG_UPDATE_CURRENT)
-
- val exitAction = NotificationCompat.Action.Builder(R.drawable.ic_clear_white_18dp,
- getString(R.string.exit), exitPendingIntent)
- .build()
-
- val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
- .setSmallIcon(R.drawable.ic_perm_device_information_white_24dp)
- .setColor(ContextCompat.getColor(applicationContext, R.color.color_primary))
- .setContentTitle(getString(R.string.app_name))
- .setTicker(getString(R.string.app_name))
- .setContentText(getString(R.string.logcat_service))
- .setWhen(System.currentTimeMillis())
- .setContentIntent(contentIntent)
- .setPriority(NotificationCompat.PRIORITY_HIGH)
- .setCategory(NotificationCompat.CATEGORY_SERVICE)
- .setAutoCancel(false)
- .addAction(exitAction)
-
- if (addStopRecordingAction) {
- val stopRecordingIntent = Intent(this, MainActivity::class.java)
- stopRecordingIntent.putExtra(MainActivity.STOP_RECORDING_EXTRA, true)
- stopRecordingIntent.action = "stop recording"
- val stopRecordingPendingIntent = PendingIntent.getActivity(this, 2,
- stopRecordingIntent, PendingIntent.FLAG_UPDATE_CURRENT)
- val stopRecordingAction = NotificationCompat.Action.Builder(R.drawable.ic_stop_white_18dp,
- getString(R.string.stop_recording), stopRecordingPendingIntent)
- .build()
-
- builder.addAction(stopRecordingAction)
- }
-
- if (Build.VERSION.SDK_INT < 21) {
- builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
- }
-
- return builder.build()
+ }
+
+ override fun onStartCommand(
+ intent: Intent?,
+ flags: Int,
+ startId: Int
+ ): Int {
+ super.onStartCommand(intent, flags, startId)
+ startForeground(NOTIFICATION_ID, createNotification(recording))
+ return START_STICKY
+ }
+
+ fun updateNotification(showStopRecording: Boolean) {
+ val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ nm.notify(NOTIFICATION_ID, createNotification(showStopRecording))
+ }
+
+ private fun createNotification(addStopRecordingAction: Boolean): Notification {
+ val startIntent = Intent(this, MainActivity::class.java)
+ startIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+ val contentIntent = PendingIntent.getActivity(
+ this, 0, startIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ val exitIntent = Intent(this, MainActivity::class.java)
+ exitIntent.putExtra(MainActivity.EXIT_EXTRA, true)
+ exitIntent.action = "exit"
+ val exitPendingIntent = PendingIntent.getActivity(
+ this, 1, exitIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ val exitAction = NotificationCompat.Action.Builder(
+ R.drawable.ic_clear_white_18dp,
+ getString(R.string.exit), exitPendingIntent
+ )
+ .build()
+
+ val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
+ .setSmallIcon(R.drawable.ic_perm_device_information_white_24dp)
+ .setColor(ContextCompat.getColor(applicationContext, R.color.color_primary))
+ .setContentTitle(getString(R.string.app_name))
+ .setTicker(getString(R.string.app_name))
+ .setContentText(getString(R.string.logcat_service))
+ .setWhen(System.currentTimeMillis())
+ .setContentIntent(contentIntent)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setCategory(NotificationCompat.CATEGORY_SERVICE)
+ .setAutoCancel(false)
+ .addAction(exitAction)
+
+ if (addStopRecordingAction) {
+ val stopRecordingIntent = Intent(this, MainActivity::class.java)
+ stopRecordingIntent.putExtra(MainActivity.STOP_RECORDING_EXTRA, true)
+ stopRecordingIntent.action = "stop recording"
+ val stopRecordingPendingIntent = PendingIntent.getActivity(
+ this, 2,
+ stopRecordingIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ val stopRecordingAction = NotificationCompat.Action.Builder(
+ R.drawable.ic_stop_white_18dp,
+ getString(R.string.stop_recording), stopRecordingPendingIntent
+ )
+ .build()
+
+ builder.addAction(stopRecordingAction)
}
- @TargetApi(26)
- private fun createNotificationChannel() {
- val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- val nc = NotificationChannel(NOTIFICATION_CHANNEL,
- getString(R.string.logcat_service_channel_name), NotificationManager.IMPORTANCE_LOW)
- nc.enableLights(false)
- nc.enableVibration(false)
- nm.createNotificationChannel(nc)
+ if (Build.VERSION.SDK_INT < 21) {
+ builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
}
- @TargetApi(26)
- private fun deleteNotificationChannel() {
- val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- nm.deleteNotificationChannel(NOTIFICATION_CHANNEL)
+ return builder.build()
+ }
+
+ @TargetApi(26)
+ private fun createNotificationChannel() {
+ val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ val nc = NotificationChannel(
+ NOTIFICATION_CHANNEL,
+ getString(R.string.logcat_service_channel_name), NotificationManager.IMPORTANCE_LOW
+ )
+ nc.enableLights(false)
+ nc.enableVibration(false)
+ nm.createNotificationChannel(nc)
+ }
+
+ @TargetApi(26)
+ private fun deleteNotificationChannel() {
+ val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ nm.deleteNotificationChannel(NOTIFICATION_CHANNEL)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ logcat.close()
+
+ if (Build.VERSION.SDK_INT >= 26) {
+ deleteNotificationChannel()
}
-
- override fun onDestroy() {
- super.onDestroy()
- logcat.close()
-
- if (Build.VERSION.SDK_INT >= 26) {
- deleteNotificationChannel()
- }
- }
-
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
- super.onSharedPreferenceChanged(sharedPreferences, key)
- when (key) {
- PreferenceKeys.Logcat.KEY_POLL_INTERVAL -> {
- val pollInterval = sharedPreferences.getString(key,
- PreferenceKeys.Logcat.Default.POLL_INTERVAL)!!.trim().toLong()
- logcat.setPollInterval(pollInterval)
- }
- PreferenceKeys.Logcat.KEY_BUFFERS -> handleBufferUpdate(sharedPreferences, key)
- PreferenceKeys.Logcat.KEY_MAX_LOGS -> {
- val newCapacity = sharedPreferences.getString(PreferenceKeys.Logcat.KEY_MAX_LOGS,
- PreferenceKeys.Logcat.Default.MAX_LOGS)!!.trim().toInt()
-
- showToast(getString(R.string.restarting_logcat))
-
- logcat.stop()
- restartedLogcat = true
- logcat.setMaxLogsCount(newCapacity)
- logcat.start()
- }
- }
- }
-
- private fun handleBufferUpdate(sharedPreferences: SharedPreferences, key: String) {
- val bufferValues = sharedPreferences.getStringSet(key, PreferenceKeys.Logcat.Default.BUFFERS)!!
- val buffers = Logcat.AVAILABLE_BUFFERS
+ }
+
+ override fun onSharedPreferenceChanged(
+ sharedPreferences: SharedPreferences,
+ key: String
+ ) {
+ super.onSharedPreferenceChanged(sharedPreferences, key)
+ when (key) {
+ PreferenceKeys.Logcat.KEY_POLL_INTERVAL -> {
+ val pollInterval = sharedPreferences.getString(
+ key,
+ PreferenceKeys.Logcat.Default.POLL_INTERVAL
+ )!!.trim().toLong()
+ logcat.setPollInterval(pollInterval)
+ }
+ PreferenceKeys.Logcat.KEY_BUFFERS -> handleBufferUpdate(sharedPreferences, key)
+ PreferenceKeys.Logcat.KEY_MAX_LOGS -> {
+ val newCapacity = sharedPreferences.getString(
+ PreferenceKeys.Logcat.KEY_MAX_LOGS,
+ PreferenceKeys.Logcat.Default.MAX_LOGS
+ )!!.trim().toInt()
showToast(getString(R.string.restarting_logcat))
+ logcat.stop()
restartedLogcat = true
- logcat.logcatBuffers = bufferValues.map { e -> buffers[e.toInt()].toLowerCase() }.toSet()
- logcat.restart()
- }
-
- private fun initLogcat() {
- val sharedPreferences = getDefaultSharedPreferences()
- val bufferValues = sharedPreferences.getStringSet(PreferenceKeys.Logcat.KEY_BUFFERS,
- PreferenceKeys.Logcat.Default.BUFFERS)!!
- val pollInterval = sharedPreferences.getString(PreferenceKeys.Logcat.KEY_POLL_INTERVAL,
- PreferenceKeys.Logcat.Default.POLL_INTERVAL)!!.trim().toLong()
- val maxLogs = sharedPreferences.getString(PreferenceKeys.Logcat.KEY_MAX_LOGS,
- PreferenceKeys.Logcat.Default.MAX_LOGS)!!.trim().toInt()
-
- logcat = Logcat(maxLogs)
- logcat.setPollInterval(pollInterval)
-
- val buffers = Logcat.AVAILABLE_BUFFERS
- logcat.logcatBuffers = bufferValues.map { e -> buffers[e.toInt()].toLowerCase() }.toSet()
+ logcat.setMaxLogsCount(newCapacity)
logcat.start()
+ }
}
+ }
+
+ private fun handleBufferUpdate(
+ sharedPreferences: SharedPreferences,
+ key: String
+ ) {
+ val bufferValues = sharedPreferences.getStringSet(key, PreferenceKeys.Logcat.Default.BUFFERS)!!
+ val buffers = Logcat.AVAILABLE_BUFFERS
+
+ showToast(getString(R.string.restarting_logcat))
+
+ restartedLogcat = true
+ logcat.logcatBuffers = bufferValues.map { e -> buffers[e.toInt()].toLowerCase() }.toSet()
+ logcat.restart()
+ }
+
+ private fun initLogcat() {
+ val sharedPreferences = getDefaultSharedPreferences()
+ val bufferValues = sharedPreferences.getStringSet(
+ PreferenceKeys.Logcat.KEY_BUFFERS,
+ PreferenceKeys.Logcat.Default.BUFFERS
+ )!!
+ val pollInterval = sharedPreferences.getString(
+ PreferenceKeys.Logcat.KEY_POLL_INTERVAL,
+ PreferenceKeys.Logcat.Default.POLL_INTERVAL
+ )!!.trim().toLong()
+ val maxLogs = sharedPreferences.getString(
+ PreferenceKeys.Logcat.KEY_MAX_LOGS,
+ PreferenceKeys.Logcat.Default.MAX_LOGS
+ )!!.trim().toInt()
+
+ logcat = Logcat(maxLogs)
+ logcat.setPollInterval(pollInterval)
+
+ val buffers = Logcat.AVAILABLE_BUFFERS
+ logcat.logcatBuffers = bufferValues.map { e -> buffers[e.toInt()].toLowerCase() }.toSet()
+ logcat.start()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/util/MyExtensions.kt b/app/src/main/java/com/dp/logcatapp/util/MyExtensions.kt
index d1cfbcd7..bc353575 100644
--- a/app/src/main/java/com/dp/logcatapp/util/MyExtensions.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/MyExtensions.kt
@@ -13,7 +13,11 @@ import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import android.util.TypedValue
-import android.view.*
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes
@@ -28,198 +32,222 @@ import com.google.android.material.snackbar.Snackbar
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
-import java.util.*
+import java.util.Calendar
//// BEGIN Activity
fun Activity.restartApp() {
- val taskBuilder = TaskStackBuilder.create(this)
- .addNextIntent(Intent(this, MainActivity::class.java))
- .addNextIntent(Intent(this, SettingsActivity::class.java))
- finish()
- overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
- taskBuilder.startActivities()
+ val taskBuilder = TaskStackBuilder.create(this)
+ .addNextIntent(Intent(this, MainActivity::class.java))
+ .addNextIntent(Intent(this, SettingsActivity::class.java))
+ finish()
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
+ taskBuilder.startActivities()
}
fun Activity.setKeepScreenOn(enabled: Boolean) {
- if (enabled) {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- } else {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- }
+ if (enabled) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ } else {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ }
}
//// END Activity
-
//// BEGIN Snackbar
-fun showSnackbar(view: View?, msg: String, length: Int = Snackbar.LENGTH_SHORT) {
- newSnakcbar(view, msg, length)?.show()
+fun showSnackbar(
+ view: View?,
+ msg: String,
+ length: Int = Snackbar.LENGTH_SHORT
+) {
+ newSnakcbar(view, msg, length)?.show()
}
-fun newSnakcbar(view: View?, msg: String, length: Int = Snackbar.LENGTH_SHORT): Snackbar? {
- if (view != null) {
- return Snackbar.make(view, msg, length)
- }
- return null
+fun newSnakcbar(
+ view: View?,
+ msg: String,
+ length: Int = Snackbar.LENGTH_SHORT
+): Snackbar? {
+ if (view != null) {
+ return Snackbar.make(view, msg, length)
+ }
+ return null
}
//// END Snackbar
//// BEGIN Fragment
-fun Fragment.inflateLayout(@LayoutRes layoutResId: Int, root: ViewGroup? = null,
- attachToRoot: Boolean = false): View =
- activity!!.inflateLayout(layoutResId, root, attachToRoot)
+fun Fragment.inflateLayout(
+ @LayoutRes layoutResId: Int,
+ root: ViewGroup? = null,
+ attachToRoot: Boolean = false
+): View =
+ activity!!.inflateLayout(layoutResId, root, attachToRoot)
//// END Fragment
-
//// BEGIN Context
private val typefaceCache = mutableMapOf()
// Bug find/workaround credit: https://github.com/drakeet/ToastCompat#why
-fun Context.showToast(msg: CharSequence, length: Int = Toast.LENGTH_SHORT) {
- val toast = Toast.makeText(this, msg, length)
- if (Build.VERSION.SDK_INT <= 25) {
- try {
- val field = View::class.java.getDeclaredField("mContext")
- field.isAccessible = true
- field.set(toast.view, ToastViewContextWrapper(this))
- } catch (e: Exception) {
- }
+fun Context.showToast(
+ msg: CharSequence,
+ length: Int = Toast.LENGTH_SHORT
+) {
+ val toast = Toast.makeText(this, msg, length)
+ if (Build.VERSION.SDK_INT <= 25) {
+ try {
+ val field = View::class.java.getDeclaredField("mContext")
+ field.isAccessible = true
+ field.set(toast.view, ToastViewContextWrapper(this))
+ } catch (e: Exception) {
}
- toast.show()
+ }
+ toast.show()
}
private class ToastViewContextWrapper(base: Context) : ContextWrapper(base) {
- override fun getApplicationContext(): Context =
- ToastViewApplicationContextWrapper(baseContext.applicationContext)
+ override fun getApplicationContext(): Context =
+ ToastViewApplicationContextWrapper(baseContext.applicationContext)
}
private class ToastViewApplicationContextWrapper(base: Context) : ContextWrapper(base) {
- override fun getSystemService(name: String): Any {
- return if (name == Context.WINDOW_SERVICE) {
- ToastWindowManager(baseContext.getSystemService(name) as WindowManager)
- } else {
- super.getSystemService(name)
- }
+ override fun getSystemService(name: String): Any {
+ return if (name == Context.WINDOW_SERVICE) {
+ ToastWindowManager(baseContext.getSystemService(name) as WindowManager)
+ } else {
+ super.getSystemService(name)
}
+ }
}
private class ToastWindowManager(val base: WindowManager) : WindowManager {
- override fun getDefaultDisplay(): Display = base.defaultDisplay
-
- override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
- try {
- base.addView(view, params)
- } catch (e: WindowManager.BadTokenException) {
- Logger.error("Toast", "caught BadTokenException crash")
- }
+ override fun getDefaultDisplay(): Display = base.defaultDisplay
+
+ override fun addView(
+ view: View?,
+ params: ViewGroup.LayoutParams?
+ ) {
+ try {
+ base.addView(view, params)
+ } catch (e: WindowManager.BadTokenException) {
+ Logger.error("Toast", "caught BadTokenException crash")
}
+ }
- override fun updateViewLayout(view: View?, params: ViewGroup.LayoutParams?) =
- base.updateViewLayout(view, params)
+ override fun updateViewLayout(
+ view: View?,
+ params: ViewGroup.LayoutParams?
+ ) =
+ base.updateViewLayout(view, params)
- override fun removeView(view: View?) = base.removeView(view)
+ override fun removeView(view: View?) = base.removeView(view)
- override fun removeViewImmediate(view: View?) = base.removeViewImmediate(view)
+ override fun removeViewImmediate(view: View?) = base.removeViewImmediate(view)
}
fun Context.getTypeface(name: String): Typeface? {
- val assetPath = "fonts/$name.ttf"
- var typeface = typefaceCache[assetPath]
- if (typeface == null) {
- typeface = Typeface.createFromAsset(assets, assetPath)
- typefaceCache[assetPath] = typeface
- }
- return typeface
+ val assetPath = "fonts/$name.ttf"
+ var typeface = typefaceCache[assetPath]
+ if (typeface == null) {
+ typeface = Typeface.createFromAsset(assets, assetPath)
+ typefaceCache[assetPath] = typeface
+ }
+ return typeface
}
fun Context.getAttributeDrawable(@AttrRes attrId: Int): Drawable? {
- val tv = TypedValue()
- theme.resolveAttribute(attrId, tv, true)
- return ContextCompat.getDrawable(this, tv.resourceId)
+ val tv = TypedValue()
+ theme.resolveAttribute(attrId, tv, true)
+ return ContextCompat.getDrawable(this, tv.resourceId)
}
fun Context.getDefaultSharedPreferences(): SharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(this)
+ PreferenceManager.getDefaultSharedPreferences(this)
private fun isDarkThemeTime() = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) !in 7..17
fun Context.isDarkThemeOn(): Boolean {
- val theme = getDefaultSharedPreferences()
- .getString(PreferenceKeys.Appearance.KEY_THEME, PreferenceKeys.Appearance.Default.THEME)
- return theme == PreferenceKeys.Appearance.Theme.DARK ||
- (theme == PreferenceKeys.Appearance.Theme.AUTO && isDarkThemeTime())
+ val theme = getDefaultSharedPreferences()
+ .getString(PreferenceKeys.Appearance.KEY_THEME, PreferenceKeys.Appearance.Default.THEME)
+ return theme == PreferenceKeys.Appearance.Theme.DARK ||
+ (theme == PreferenceKeys.Appearance.Theme.AUTO && isDarkThemeTime())
}
private fun Context.setThemeAuto() {
- if (isDarkThemeTime()) {
- setThemeDark()
- } else {
- setThemeLight()
- }
+ if (isDarkThemeTime()) {
+ setThemeDark()
+ } else {
+ setThemeLight()
+ }
}
private fun Context.setThemeDark() {
- val useBlackTheme = getDefaultSharedPreferences().getBoolean(PreferenceKeys
- .Appearance.KEY_USE_BLACK_THEME, PreferenceKeys.Appearance.Default.USE_BLACK_THEME)
- if (useBlackTheme) {
- setTheme(R.style.BlackTheme)
- } else {
- setTheme(R.style.DarkTheme)
- }
+ val useBlackTheme = getDefaultSharedPreferences().getBoolean(
+ PreferenceKeys
+ .Appearance.KEY_USE_BLACK_THEME, PreferenceKeys.Appearance.Default.USE_BLACK_THEME
+ )
+ if (useBlackTheme) {
+ setTheme(R.style.BlackTheme)
+ } else {
+ setTheme(R.style.DarkTheme)
+ }
}
private fun Context.setThemeLight() {
- setTheme(R.style.LightTheme)
+ setTheme(R.style.LightTheme)
}
fun Context.setTheme() {
- val prefs = getDefaultSharedPreferences()
- val theme = prefs.getString(PreferenceKeys.Appearance.KEY_THEME,
- PreferenceKeys.Appearance.Default.THEME)
- when (theme) {
- PreferenceKeys.Appearance.Theme.AUTO -> setThemeAuto()
- PreferenceKeys.Appearance.Theme.DARK -> setThemeDark()
- PreferenceKeys.Appearance.Theme.LIGHT -> setThemeLight()
- }
+ val prefs = getDefaultSharedPreferences()
+ val theme = prefs.getString(
+ PreferenceKeys.Appearance.KEY_THEME,
+ PreferenceKeys.Appearance.Default.THEME
+ )
+ when (theme) {
+ PreferenceKeys.Appearance.Theme.AUTO -> setThemeAuto()
+ PreferenceKeys.Appearance.Theme.DARK -> setThemeDark()
+ PreferenceKeys.Appearance.Theme.LIGHT -> setThemeLight()
+ }
}
fun Context.getFileNameFromUri(uri: Uri): String {
- var name: String? = null
- if (uri.scheme == "content") {
- val cursor = contentResolver.query(uri, null, null, null, null)
- name = cursor?.use {
- if (it.moveToFirst()) {
- it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
- } else {
- null
- }
- }
+ var name: String? = null
+ if (uri.scheme == "content") {
+ val cursor = contentResolver.query(uri, null, null, null, null)
+ name = cursor?.use {
+ if (it.moveToFirst()) {
+ it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+ } else {
+ null
+ }
}
+ }
- if (name == null) {
- name = uri.path!!
- val lastSlashIndex = name.lastIndexOf('/')
- if (lastSlashIndex != -1) {
- name = name.substring(lastSlashIndex + 1)
- }
+ if (name == null) {
+ name = uri.path!!
+ val lastSlashIndex = name.lastIndexOf('/')
+ if (lastSlashIndex != -1) {
+ name = name.substring(lastSlashIndex + 1)
}
+ }
- return name
+ return name
}
-fun Context.inflateLayout(@LayoutRes layoutResId: Int, root: ViewGroup? = null,
- attachToRoot: Boolean = false): View =
- LayoutInflater.from(this).inflate(layoutResId, root, attachToRoot)
+fun Context.inflateLayout(
+ @LayoutRes layoutResId: Int,
+ root: ViewGroup? = null,
+ attachToRoot: Boolean = false
+): View =
+ LayoutInflater.from(this).inflate(layoutResId, root, attachToRoot)
//// END Context
-
//// BEGIN String
@SuppressLint("DefaultLocale")
@@ -227,21 +255,20 @@ fun String.containsIgnoreCase(other: String) = toLowerCase().contains(other.toLo
//// END String
-
//// BEGIN Misc
fun InputStream.closeQuietly() {
- try {
- close()
- } catch (e: IOException) {
- }
+ try {
+ close()
+ } catch (e: IOException) {
+ }
}
fun OutputStream.closeQuietly() {
- try {
- close()
- } catch (e: IOException) {
- }
+ try {
+ close()
+ } catch (e: IOException) {
+ }
}
//// END Misc
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/util/PreferenceKeys.kt b/app/src/main/java/com/dp/logcatapp/util/PreferenceKeys.kt
index e97269b1..64729a33 100644
--- a/app/src/main/java/com/dp/logcatapp/util/PreferenceKeys.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/PreferenceKeys.kt
@@ -6,59 +6,59 @@ import com.dp.logcat.Logcat.Companion.INITIAL_LOG_CAPACITY
object PreferenceKeys {
- const val MAIN_PREF_SCREEN = "pref_key_main_screen"
+ const val MAIN_PREF_SCREEN = "pref_key_main_screen"
- object General {
- const val KEY_KEEP_SCREEN_ON = "pref_key_general_keep_screen_on"
+ object General {
+ const val KEY_KEEP_SCREEN_ON = "pref_key_general_keep_screen_on"
- object Default {
- const val KEY_KEEP_SCREEN_ON = false
- }
+ object Default {
+ const val KEY_KEEP_SCREEN_ON = false
}
+ }
- object Appearance {
+ object Appearance {
- const val KEY_THEME = "pref_key_appearance_theme"
- const val KEY_USE_BLACK_THEME = "pref_key_appearance_use_black_theme"
+ const val KEY_THEME = "pref_key_appearance_theme"
+ const val KEY_USE_BLACK_THEME = "pref_key_appearance_use_black_theme"
- object Theme {
+ object Theme {
- const val AUTO = "0"
- const val LIGHT = "1"
- const val DARK = "2"
- }
+ const val AUTO = "0"
+ const val LIGHT = "1"
+ const val DARK = "2"
+ }
- object Default {
+ object Default {
- const val THEME = Theme.AUTO
- const val USE_BLACK_THEME = false
- }
+ const val THEME = Theme.AUTO
+ const val USE_BLACK_THEME = false
}
+ }
- object Logcat {
- const val KEY_POLL_INTERVAL = "pref_key_logcat_poll_interval"
- const val KEY_BUFFERS = "pref_key_logcat_buffers"
- const val KEY_MAX_LOGS = "pref_key_logcat_max_logs"
- const val KEY_SAVE_LOCATION = "pref_key_logcat_save_location"
+ object Logcat {
+ const val KEY_POLL_INTERVAL = "pref_key_logcat_poll_interval"
+ const val KEY_BUFFERS = "pref_key_logcat_buffers"
+ const val KEY_MAX_LOGS = "pref_key_logcat_max_logs"
+ const val KEY_SAVE_LOCATION = "pref_key_logcat_save_location"
- object Default {
- const val POLL_INTERVAL = "250"
- val BUFFERS: Set = getDefaultBufferValues()
- const val MAX_LOGS = INITIAL_LOG_CAPACITY.toString()
- const val SAVE_LOCATION = ""
+ object Default {
+ const val POLL_INTERVAL = "250"
+ val BUFFERS: Set = getDefaultBufferValues()
+ const val MAX_LOGS = INITIAL_LOG_CAPACITY.toString()
+ const val SAVE_LOCATION = ""
- private fun getDefaultBufferValues(): Set {
- val bufferValues = mutableSetOf()
- DEFAULT_BUFFERS.map { AVAILABLE_BUFFERS.indexOf(it) }
- .filter { it != -1 }
- .forEach { bufferValues += it.toString() }
- return bufferValues
- }
- }
+ private fun getDefaultBufferValues(): Set {
+ val bufferValues = mutableSetOf()
+ DEFAULT_BUFFERS.map { AVAILABLE_BUFFERS.indexOf(it) }
+ .filter { it != -1 }
+ .forEach { bufferValues += it.toString() }
+ return bufferValues
+ }
}
+ }
- object About {
- const val KEY_VERSION_NAME = "pref_key_about_version_name"
- const val KEY_GITHUB_PAGE = "pref_key_about_github_repo"
- }
+ object About {
+ const val KEY_VERSION_NAME = "pref_key_about_version_name"
+ const val KEY_GITHUB_PAGE = "pref_key_about_github_repo"
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/util/ScopedViewModel.kt b/app/src/main/java/com/dp/logcatapp/util/ScopedViewModel.kt
index b2ed8e28..497ef4cd 100644
--- a/app/src/main/java/com/dp/logcatapp/util/ScopedViewModel.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/ScopedViewModel.kt
@@ -8,12 +8,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext
open class ScopedViewModel : ViewModel(), CoroutineScope {
- override val coroutineContext: CoroutineContext
- get() = viewModelScope.coroutineContext
+ override val coroutineContext: CoroutineContext
+ get() = viewModelScope.coroutineContext
}
-open class ScopedAndroidViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
+open class ScopedAndroidViewModel(application: Application) : AndroidViewModel(application),
+ CoroutineScope {
- override val coroutineContext: CoroutineContext
- get() = viewModelScope.coroutineContext
+ override val coroutineContext: CoroutineContext
+ get() = viewModelScope.coroutineContext
}
diff --git a/app/src/main/java/com/dp/logcatapp/util/ServiceBinder.kt b/app/src/main/java/com/dp/logcatapp/util/ServiceBinder.kt
index 5acbd544..31a3911e 100644
--- a/app/src/main/java/com/dp/logcatapp/util/ServiceBinder.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/ServiceBinder.kt
@@ -6,30 +6,32 @@ import android.content.ServiceConnection
import com.dp.logger.Logger
import java.io.Closeable
-class ServiceBinder(private val mClass: Class<*>,
- private val mServiceConnection: ServiceConnection) : Closeable {
- var isBound: Boolean = false
- private set
+class ServiceBinder(
+ private val mClass: Class<*>,
+ private val mServiceConnection: ServiceConnection
+) : Closeable {
+ var isBound: Boolean = false
+ private set
- private var closed: Boolean = false
+ private var closed: Boolean = false
- fun bind(context: Context) {
- check(!closed) { "This ServiceBinder has already been closed." }
+ fun bind(context: Context) {
+ check(!closed) { "This ServiceBinder has already been closed." }
- context.bindService(Intent(context, mClass), mServiceConnection, Context.BIND_ABOVE_CLIENT)
- isBound = true
- }
+ context.bindService(Intent(context, mClass), mServiceConnection, Context.BIND_ABOVE_CLIENT)
+ isBound = true
+ }
- fun unbind(context: Context) {
- if (isBound) {
- context.unbindService(mServiceConnection)
- isBound = false
- } else {
- Logger.warning(ServiceBinder::class, "service is not bound!")
- }
+ fun unbind(context: Context) {
+ if (isBound) {
+ context.unbindService(mServiceConnection)
+ isBound = false
+ } else {
+ Logger.warning(ServiceBinder::class, "service is not bound!")
}
+ }
- override fun close() {
- closed = true
- }
+ override fun close() {
+ closed = true
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/util/ShareUtils.kt b/app/src/main/java/com/dp/logcatapp/util/ShareUtils.kt
index b8bcc769..a9257f2d 100644
--- a/app/src/main/java/com/dp/logcatapp/util/ShareUtils.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/ShareUtils.kt
@@ -9,26 +9,32 @@ import com.dp.logcatapp.BuildConfig
import com.dp.logcatapp.R
object ShareUtils {
- fun shareSavedLogs(context: Context, uri: Uri, isCustom: Boolean): Boolean {
- try {
- val intent = Intent(Intent.ACTION_SEND)
+ fun shareSavedLogs(
+ context: Context,
+ uri: Uri,
+ isCustom: Boolean
+ ): Boolean {
+ try {
+ val intent = Intent(Intent.ACTION_SEND)
- val shareUri = if (isCustom) {
- uri
- } else {
- FileProvider.getUriForFile(context,
- "${context.packageName}.${BuildConfig.FILE_PROVIDER}", uri.toFile())
- }
+ val shareUri = if (isCustom) {
+ uri
+ } else {
+ FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.${BuildConfig.FILE_PROVIDER}", uri.toFile()
+ )
+ }
- intent.setDataAndType(shareUri, "text/plain")
- intent.putExtra(Intent.EXTRA_STREAM, shareUri)
- intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ intent.setDataAndType(shareUri, "text/plain")
+ intent.putExtra(Intent.EXTRA_STREAM, shareUri)
+ intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
- context.startActivity(Intent.createChooser(intent, context.getString(R.string.share)))
- return true
- } catch (e: Exception) {
- context.showToast(context.getString(R.string.unable_to_share))
- return false
- }
+ context.startActivity(Intent.createChooser(intent, context.getString(R.string.share)))
+ return true
+ } catch (e: Exception) {
+ context.showToast(context.getString(R.string.unable_to_share))
+ return false
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/util/SuCommander.kt b/app/src/main/java/com/dp/logcatapp/util/SuCommander.kt
index 99e3eb04..d8ca85cf 100644
--- a/app/src/main/java/com/dp/logcatapp/util/SuCommander.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/SuCommander.kt
@@ -7,72 +7,72 @@ import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.OutputStreamWriter
-import java.util.*
+import java.util.UUID
import java.util.concurrent.Executors
class SuCommander(private val cmd: String) {
- private fun BufferedWriter.writeCmd(cmd: String) {
- write(cmd)
- newLine()
- flush()
- }
-
- suspend fun run() = coroutineScope {
- try {
- val processBuilder = ProcessBuilder("su")
- val process = processBuilder.start()
+ private fun BufferedWriter.writeCmd(cmd: String) {
+ write(cmd)
+ newLine()
+ flush()
+ }
- val stdoutWriter = BufferedWriter(OutputStreamWriter(process.outputStream))
- val stdinReader = BufferedReader(InputStreamReader(process.inputStream))
- val stderrReader = BufferedReader(InputStreamReader(process.errorStream))
+ suspend fun run() = coroutineScope {
+ try {
+ val processBuilder = ProcessBuilder("su")
+ val process = processBuilder.start()
- val marker = "RESULT>>>${UUID.randomUUID()}>>>"
+ val stdoutWriter = BufferedWriter(OutputStreamWriter(process.outputStream))
+ val stdinReader = BufferedReader(InputStreamReader(process.inputStream))
+ val stderrReader = BufferedReader(InputStreamReader(process.errorStream))
- val stdoutStderrDispatcherContext = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
- val stdoutResult = async(stdoutStderrDispatcherContext) {
- var result = false
+ val marker = "RESULT>>>${UUID.randomUUID()}>>>"
- try {
- while (true) {
- val line = stdinReader.readLine()?.trim() ?: break
+ val stdoutStderrDispatcherContext = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
+ val stdoutResult = async(stdoutStderrDispatcherContext) {
+ var result = false
- val index = line.indexOf(marker)
- if (index != -1) {
- result = line.substring(index + marker.length) == "0"
- break
- }
- }
- } catch (e: Exception) {
- }
+ try {
+ while (true) {
+ val line = stdinReader.readLine()?.trim() ?: break
- result
+ val index = line.indexOf(marker)
+ if (index != -1) {
+ result = line.substring(index + marker.length) == "0"
+ break
}
+ }
+ } catch (e: Exception) {
+ }
- val stderrReaderResult = async(stdoutStderrDispatcherContext) {
- try {
- while (true) {
- stderrReader.readLine()?.trim() ?: break
- }
- } catch (e: Exception) {
- }
- }
+ result
+ }
- stdoutWriter.writeCmd(cmd)
- stdoutWriter.writeCmd("echo \"$marker$?\"")
+ val stderrReaderResult = async(stdoutStderrDispatcherContext) {
+ try {
+ while (true) {
+ stderrReader.readLine()?.trim() ?: break
+ }
+ } catch (e: Exception) {
+ }
+ }
- val finalResult = stdoutResult.await()
+ stdoutWriter.writeCmd(cmd)
+ stdoutWriter.writeCmd("echo \"$marker$?\"")
- stdoutWriter.writeCmd("exit")
+ val finalResult = stdoutResult.await()
- process.waitFor()
- process.destroy()
+ stdoutWriter.writeCmd("exit")
- stderrReaderResult.await()
+ process.waitFor()
+ process.destroy()
- finalResult
- } catch (e: Exception) {
- false
- }
+ stderrReaderResult.await()
+
+ finalResult
+ } catch (e: Exception) {
+ false
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/util/Utils.kt b/app/src/main/java/com/dp/logcatapp/util/Utils.kt
index 19eec3c4..8d704216 100644
--- a/app/src/main/java/com/dp/logcatapp/util/Utils.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/Utils.kt
@@ -3,23 +3,24 @@ package com.dp.logcatapp.util
import android.content.Context
object Utils {
- fun bytesToString(bytes: Long): String {
- val units = arrayOf("B", "KB", "MB", "GB", "TB")
- var unit = units[0]
- var totalSize = bytes.toDouble()
- for (i in 1 until units.size) {
- if (totalSize >= 1024) {
- totalSize /= 1024
- unit = units[i]
- } else {
- break
- }
- }
-
- return "%.2f %s".format(totalSize, unit)
+ fun bytesToString(bytes: Long): String {
+ val units = arrayOf("B", "KB", "MB", "GB", "TB")
+ var unit = units[0]
+ var totalSize = bytes.toDouble()
+ for (i in 1 until units.size) {
+ if (totalSize >= 1024) {
+ totalSize /= 1024
+ unit = units[i]
+ } else {
+ break
+ }
}
- fun isUsingCustomSaveLocation(context: Context) =
- context.getDefaultSharedPreferences().getString(
- PreferenceKeys.Logcat.KEY_SAVE_LOCATION, "")!!.isNotEmpty()
+ return "%.2f %s".format(totalSize, unit)
+ }
+
+ fun isUsingCustomSaveLocation(context: Context) =
+ context.getDefaultSharedPreferences().getString(
+ PreferenceKeys.Logcat.KEY_SAVE_LOCATION, ""
+ )!!.isNotEmpty()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/util/ViewModelUtil.kt b/app/src/main/java/com/dp/logcatapp/util/ViewModelUtil.kt
index f57403e8..f9954c67 100644
--- a/app/src/main/java/com/dp/logcatapp/util/ViewModelUtil.kt
+++ b/app/src/main/java/com/dp/logcatapp/util/ViewModelUtil.kt
@@ -7,19 +7,19 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
inline fun ComponentActivity.getAndroidViewModel(): T {
- val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
- return ViewModelProvider(this, factory).get(T::class.java)
+ val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+ return ViewModelProvider(this, factory).get(T::class.java)
}
inline fun Fragment.getAndroidViewModel(): T {
- val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(activity!!.application)
- return ViewModelProvider(this, factory).get(T::class.java)
+ val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(activity!!.application)
+ return ViewModelProvider(this, factory).get(T::class.java)
}
inline fun AppCompatActivity.getViewModel(): T {
- return ViewModelProvider(this).get(T::class.java)
+ return ViewModelProvider(this).get(T::class.java)
}
inline fun Fragment.getViewModel(): T {
- return ViewModelProvider(this).get(T::class.java)
+ return ViewModelProvider(this).get(T::class.java)
}
diff --git a/app/src/main/java/com/dp/logcatapp/views/CustomTextView.kt b/app/src/main/java/com/dp/logcatapp/views/CustomTextView.kt
index 65718891..60dfc6aa 100644
--- a/app/src/main/java/com/dp/logcatapp/views/CustomTextView.kt
+++ b/app/src/main/java/com/dp/logcatapp/views/CustomTextView.kt
@@ -9,26 +9,39 @@ import com.dp.logcatapp.util.getTypeface
class CustomTextView : AppCompatTextView {
- constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
- handleUseFont(context, attributeSet, 0)
- }
+ constructor(
+ context: Context,
+ attributeSet: AttributeSet
+ ) : super(context, attributeSet) {
+ handleUseFont(context, attributeSet, 0)
+ }
- constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) :
- super(context, attributeSet, defStyleAttr) {
- handleUseFont(context, attributeSet, defStyleAttr)
- }
+ constructor(
+ context: Context,
+ attributeSet: AttributeSet,
+ defStyleAttr: Int
+ ) :
+ super(context, attributeSet, defStyleAttr) {
+ handleUseFont(context, attributeSet, defStyleAttr)
+ }
- private fun handleUseFont(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) {
- val typedArray = context.theme.obtainStyledAttributes(attributeSet,
- R.styleable.CustomTextView, defStyleAttr, 0)
- try {
- val fontName = typedArray.getString(R.styleable.CustomTextView_useFont)
- if (fontName != null) {
- typeface = context.getTypeface(fontName)
- paintFlags = paintFlags.or(Paint.SUBPIXEL_TEXT_FLAG)
- }
- } finally {
- typedArray.recycle()
- }
+ private fun handleUseFont(
+ context: Context,
+ attributeSet: AttributeSet,
+ defStyleAttr: Int
+ ) {
+ val typedArray = context.theme.obtainStyledAttributes(
+ attributeSet,
+ R.styleable.CustomTextView, defStyleAttr, 0
+ )
+ try {
+ val fontName = typedArray.getString(R.styleable.CustomTextView_useFont)
+ if (fontName != null) {
+ typeface = context.getTypeface(fontName)
+ paintFlags = paintFlags.or(Paint.SUBPIXEL_TEXT_FLAG)
+ }
+ } finally {
+ typedArray.recycle()
}
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dp/logcatapp/views/IndeterminateProgressSnackBar.kt b/app/src/main/java/com/dp/logcatapp/views/IndeterminateProgressSnackBar.kt
index 75c3f7f0..93616fb7 100644
--- a/app/src/main/java/com/dp/logcatapp/views/IndeterminateProgressSnackBar.kt
+++ b/app/src/main/java/com/dp/logcatapp/views/IndeterminateProgressSnackBar.kt
@@ -7,30 +7,32 @@ import android.widget.TextView
import com.dp.logcatapp.R
import com.google.android.material.snackbar.Snackbar
-class IndeterminateProgressSnackBar(view: View,
- message: String) {
+class IndeterminateProgressSnackBar(
+ view: View,
+ message: String
+) {
- private val progressBar: ProgressBar
- private val textView: TextView
- private val snackBar = Snackbar.make(view, "", Snackbar.LENGTH_INDEFINITE)
+ private val progressBar: ProgressBar
+ private val textView: TextView
+ private val snackBar = Snackbar.make(view, "", Snackbar.LENGTH_INDEFINITE)
- init {
- val rootView = LayoutInflater.from(view.context)
- .inflate(R.layout.indeterminate_progress_snackbar, null)
- progressBar = rootView.findViewById(R.id.progressBar)
- textView = rootView.findViewById(R.id.message)
- textView.text = message
+ init {
+ val rootView = LayoutInflater.from(view.context)
+ .inflate(R.layout.indeterminate_progress_snackbar, null)
+ progressBar = rootView.findViewById(R.id.progressBar)
+ textView = rootView.findViewById(R.id.message)
+ textView.text = message
- val snackBarLayout = snackBar.view as Snackbar.SnackbarLayout
- snackBarLayout.removeAllViews()
- snackBarLayout.addView(rootView)
- }
+ val snackBarLayout = snackBar.view as Snackbar.SnackbarLayout
+ snackBarLayout.removeAllViews()
+ snackBarLayout.addView(rootView)
+ }
- fun show() {
- snackBar.show()
- }
+ fun show() {
+ snackBar.show()
+ }
- fun dismiss() {
- snackBar.dismiss()
- }
+ fun dismiss() {
+ snackBar.dismiss()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/list_item_selector_dark.xml b/app/src/main/res/drawable-v21/list_item_selector_dark.xml
index 49270cae..567ba8e3 100644
--- a/app/src/main/res/drawable-v21/list_item_selector_dark.xml
+++ b/app/src/main/res/drawable-v21/list_item_selector_dark.xml
@@ -1,14 +1,14 @@
- -
-
-
- -
-
-
-
-
+ -
+
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/list_item_selector_light.xml b/app/src/main/res/drawable-v21/list_item_selector_light.xml
index 5c7b2675..2015a01a 100644
--- a/app/src/main/res/drawable-v21/list_item_selector_light.xml
+++ b/app/src/main/res/drawable-v21/list_item_selector_light.xml
@@ -1,14 +1,14 @@
- -
-
-
- -
-
-
-
-
+ -
+
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
index c08da37d..5b563071 100644
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -1,34 +1,34 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index d5fccc53..c52defa6 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -4,167 +4,167 @@
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/list_item_selector_dark.xml b/app/src/main/res/drawable/list_item_selector_dark.xml
index 4bf1bd42..ab02f661 100644
--- a/app/src/main/res/drawable/list_item_selector_dark.xml
+++ b/app/src/main/res/drawable/list_item_selector_dark.xml
@@ -1,6 +1,6 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/list_item_selector_light.xml b/app/src/main/res/drawable/list_item_selector_light.xml
index 9ce70eb3..b0f6ce79 100644
--- a/app/src/main/res/drawable/list_item_selector_light.xml
+++ b/app/src/main/res/drawable/list_item_selector_light.xml
@@ -1,6 +1,6 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/splash_screen.xml b/app/src/main/res/drawable/splash_screen.xml
index eda65089..f36d4810 100644
--- a/app/src/main/res/drawable/splash_screen.xml
+++ b/app/src/main/res/drawable/splash_screen.xml
@@ -1,16 +1,16 @@
- -
-
-
-
-
- -
-
-
+ -
+
+
+
+
+ -
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/toolbar_shadow_dark.xml b/app/src/main/res/drawable/toolbar_shadow_dark.xml
index f1c3fa9b..f2dee43d 100644
--- a/app/src/main/res/drawable/toolbar_shadow_dark.xml
+++ b/app/src/main/res/drawable/toolbar_shadow_dark.xml
@@ -3,10 +3,10 @@
android:dither="true"
android:shape="rectangle">
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/toolbar_shadow_light.xml b/app/src/main/res/drawable/toolbar_shadow_light.xml
index 3da55310..ff9451d6 100644
--- a/app/src/main/res/drawable/toolbar_shadow_light.xml
+++ b/app/src/main/res/drawable/toolbar_shadow_light.xml
@@ -3,10 +3,10 @@
android:dither="true"
android:shape="rectangle">
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-v21/app_bar.xml b/app/src/main/res/layout-v21/app_bar.xml
index 21a477ad..62f71e87 100644
--- a/app/src/main/res/layout-v21/app_bar.xml
+++ b/app/src/main/res/layout-v21/app_bar.xml
@@ -5,12 +5,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
- android:elevation="@dimen/toolbar_shadow_height">
+ android:elevation="@dimen/toolbar_shadow_height"
+ >
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-v21/app_bar_cab.xml b/app/src/main/res/layout-v21/app_bar_cab.xml
index 18d7878c..bd5963d0 100644
--- a/app/src/main/res/layout-v21/app_bar_cab.xml
+++ b/app/src/main/res/layout-v21/app_bar_cab.xml
@@ -5,26 +5,30 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
- android:elevation="@dimen/toolbar_shadow_height">
+ android:elevation="@dimen/toolbar_shadow_height"
+ >
-
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_filters.xml b/app/src/main/res/layout/activity_filters.xml
index 0305b871..c5031690 100644
--- a/app/src/main/res/layout/activity_filters.xml
+++ b/app/src/main/res/layout/activity_filters.xml
@@ -1,24 +1,28 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 0305b871..c5031690 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,24 +1,28 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_saved_logs.xml b/app/src/main/res/layout/activity_saved_logs.xml
index 8d34c75d..3689c714 100644
--- a/app/src/main/res/layout/activity_saved_logs.xml
+++ b/app/src/main/res/layout/activity_saved_logs.xml
@@ -1,24 +1,28 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_saved_logs_viewer.xml b/app/src/main/res/layout/activity_saved_logs_viewer.xml
index 0305b871..c5031690 100644
--- a/app/src/main/res/layout/activity_saved_logs_viewer.xml
+++ b/app/src/main/res/layout/activity_saved_logs_viewer.xml
@@ -1,24 +1,28 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index 0305b871..c5031690 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -1,24 +1,28 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/app_bar.xml b/app/src/main/res/layout/app_bar.xml
index bc0a6e47..9e427414 100644
--- a/app/src/main/res/layout/app_bar.xml
+++ b/app/src/main/res/layout/app_bar.xml
@@ -4,12 +4,14 @@
android:id="@+id/toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/colorPrimary">
+ android:background="?attr/colorPrimary"
+ >
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/app_bar_cab.xml b/app/src/main/res/layout/app_bar_cab.xml
index 20063b56..de2a31c6 100644
--- a/app/src/main/res/layout/app_bar_cab.xml
+++ b/app/src/main/res/layout/app_bar_cab.xml
@@ -4,26 +4,30 @@
android:id="@+id/toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/colorPrimary">
+ android:background="?attr/colorPrimary"
+ >
-
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/asking_for_root_access_dialog_fragment.xml b/app/src/main/res/layout/asking_for_root_access_dialog_fragment.xml
index b550460c..535ba0b8 100644
--- a/app/src/main/res/layout/asking_for_root_access_dialog_fragment.xml
+++ b/app/src/main/res/layout/asking_for_root_access_dialog_fragment.xml
@@ -4,20 +4,23 @@
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
- android:padding="24dp">
+ android:padding="24dp"
+ >
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/filter_dialog.xml b/app/src/main/res/layout/filter_dialog.xml
index eb56b154..f74ab48b 100644
--- a/app/src/main/res/layout/filter_dialog.xml
+++ b/app/src/main/res/layout/filter_dialog.xml
@@ -1,92 +1,105 @@
+ android:layout_height="match_parent"
+ >
-
+
+
-
-
+ android:hint="@string/keyword"
+ />
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/app/src/main/res/layout/filters_fragment.xml b/app/src/main/res/layout/filters_fragment.xml
index 47f12d89..b6a1c31d 100644
--- a/app/src/main/res/layout/filters_fragment.xml
+++ b/app/src/main/res/layout/filters_fragment.xml
@@ -2,27 +2,31 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/filters_fragment_list_item.xml b/app/src/main/res/layout/filters_fragment_list_item.xml
index f87bc89f..53363de4 100644
--- a/app/src/main/res/layout/filters_fragment_list_item.xml
+++ b/app/src/main/res/layout/filters_fragment_list_item.xml
@@ -8,57 +8,61 @@
android:background="?attr/selectableItemBackground"
android:descendantFocusability="blocksDescendants"
android:paddingBottom="8dp"
- android:paddingTop="8dp">
+ android:paddingTop="8dp"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/folder_chooser.xml b/app/src/main/res/layout/folder_chooser.xml
index 4dabe5c7..5f018998 100644
--- a/app/src/main/res/layout/folder_chooser.xml
+++ b/app/src/main/res/layout/folder_chooser.xml
@@ -4,11 +4,13 @@
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
- android:orientation="vertical">
+ android:orientation="vertical"
+ >
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/folder_chooser_list_item.xml b/app/src/main/res/layout/folder_chooser_list_item.xml
index 359dbaa5..03fbf773 100644
--- a/app/src/main/res/layout/folder_chooser_list_item.xml
+++ b/app/src/main/res/layout/folder_chooser_list_item.xml
@@ -8,19 +8,22 @@
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground"
android:descendantFocusability="blocksDescendants"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ >
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_logcat_live.xml b/app/src/main/res/layout/fragment_logcat_live.xml
index 45236f31..d581ae14 100644
--- a/app/src/main/res/layout/fragment_logcat_live.xml
+++ b/app/src/main/res/layout/fragment_logcat_live.xml
@@ -2,34 +2,38 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_logcat_live_list_item.xml b/app/src/main/res/layout/fragment_logcat_live_list_item.xml
index 1a345c73..d414371c 100644
--- a/app/src/main/res/layout/fragment_logcat_live_list_item.xml
+++ b/app/src/main/res/layout/fragment_logcat_live_list_item.xml
@@ -8,140 +8,149 @@
android:background="?attr/list_item_selector"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical"
- android:orientation="horizontal">
-
-
+ android:orientation="horizontal"
+ >
+
-
-
-
-
+
+ android:textSize="@dimen/text_size_smaller2"
+ app:useFont="@string/font_RobotoMono_Medium"
+ />
+
-
-
+
-
+
-
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_saved_logs.xml b/app/src/main/res/layout/fragment_saved_logs.xml
index c3092ecc..e7665c6a 100644
--- a/app/src/main/res/layout/fragment_saved_logs.xml
+++ b/app/src/main/res/layout/fragment_saved_logs.xml
@@ -2,33 +2,37 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_saved_logs_list_item.xml b/app/src/main/res/layout/fragment_saved_logs_list_item.xml
index 6bf8b27b..16bf1b0d 100644
--- a/app/src/main/res/layout/fragment_saved_logs_list_item.xml
+++ b/app/src/main/res/layout/fragment_saved_logs_list_item.xml
@@ -9,43 +9,48 @@
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingLeft="16dp"
- android:paddingRight="16dp">
+ android:paddingRight="16dp"
+ >
+
+
+
+
+ android:layout_marginEnd="8dp"
+ android:layout_marginRight="8dp"
+ android:text="100 logs"
+ android:textColor="?attr/secondary_text_color"
+ android:textSize="@dimen/text_size_small"
+ app:useFont="@string/font_Roboto_Regular"
+ />
-
-
-
-
-
+ android:text="20 MB"
+ android:textColor="?attr/secondary_text_color"
+ android:textSize="@dimen/text_size_small"
+ app:useFont="@string/font_Roboto_Regular"
+ />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_saved_logs_viewer.xml b/app/src/main/res/layout/fragment_saved_logs_viewer.xml
index e5ccc6da..a99aff74 100644
--- a/app/src/main/res/layout/fragment_saved_logs_viewer.xml
+++ b/app/src/main/res/layout/fragment_saved_logs_viewer.xml
@@ -2,54 +2,60 @@
+ android:layout_height="match_parent"
+ >
-
+
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_saved_logs_viewer_list_item.xml b/app/src/main/res/layout/fragment_saved_logs_viewer_list_item.xml
index 1a345c73..d414371c 100644
--- a/app/src/main/res/layout/fragment_saved_logs_viewer_list_item.xml
+++ b/app/src/main/res/layout/fragment_saved_logs_viewer_list_item.xml
@@ -8,140 +8,149 @@
android:background="?attr/list_item_selector"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical"
- android:orientation="horizontal">
-
-
+ android:orientation="horizontal"
+ >
+
-
-
-
-
+
+ android:textSize="@dimen/text_size_smaller2"
+ app:useFont="@string/font_RobotoMono_Medium"
+ />
+
-
-
+
-
+
-
+
+
+
+
+
diff --git a/app/src/main/res/layout/indeterminate_progress_snackbar.xml b/app/src/main/res/layout/indeterminate_progress_snackbar.xml
index 42c924da..1ce32884 100644
--- a/app/src/main/res/layout/indeterminate_progress_snackbar.xml
+++ b/app/src/main/res/layout/indeterminate_progress_snackbar.xml
@@ -8,25 +8,28 @@
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
- android:theme="@style/ThemeOverlay.AppCompat.Dark">
+ android:theme="@style/ThemeOverlay.AppCompat.Dark"
+ >
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/manual_method_dialog_fragment.xml b/app/src/main/res/layout/manual_method_dialog_fragment.xml
index 6a5e8b78..b7b10512 100644
--- a/app/src/main/res/layout/manual_method_dialog_fragment.xml
+++ b/app/src/main/res/layout/manual_method_dialog_fragment.xml
@@ -2,72 +2,81 @@
+ android:layout_height="match_parent"
+ >
-
+
+
-
-
+ android:text="@string/permission_instruction0"
+ android:textSize="16sp"
+ app:useFont="@string/font_Roboto_Regular"
+ />
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/app/src/main/res/layout/rename_dialog.xml b/app/src/main/res/layout/rename_dialog.xml
index d9d92eef..4b7b623f 100644
--- a/app/src/main/res/layout/rename_dialog.xml
+++ b/app/src/main/res/layout/rename_dialog.xml
@@ -1,21 +1,24 @@
+ android:layout_height="match_parent"
+ >
-
+
+
-
-
-
+ />
+
diff --git a/app/src/main/res/layout/saved_log_bottom_sheet.xml b/app/src/main/res/layout/saved_log_bottom_sheet.xml
index 58c5e260..2aa0b03a 100644
--- a/app/src/main/res/layout/saved_log_bottom_sheet.xml
+++ b/app/src/main/res/layout/saved_log_bottom_sheet.xml
@@ -5,49 +5,53 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_color"
- android:orientation="vertical">
+ android:orientation="vertical"
+ >
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/filters.xml b/app/src/main/res/menu/filters.xml
index 3b6476d8..efaeb788 100644
--- a/app/src/main/res/menu/filters.xml
+++ b/app/src/main/res/menu/filters.xml
@@ -1,13 +1,13 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/logcat_live.xml b/app/src/main/res/menu/logcat_live.xml
index e2524913..8604a719 100644
--- a/app/src/main/res/menu/logcat_live.xml
+++ b/app/src/main/res/menu/logcat_live.xml
@@ -1,44 +1,44 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 8aa4e640..ee03cd1f 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -1,7 +1,7 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/saved_logs_cab.xml b/app/src/main/res/menu/saved_logs_cab.xml
index 75003372..3ef0cbc5 100644
--- a/app/src/main/res/menu/saved_logs_cab.xml
+++ b/app/src/main/res/menu/saved_logs_cab.xml
@@ -2,34 +2,34 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/saved_logs_viewer.xml b/app/src/main/res/menu/saved_logs_viewer.xml
index 34175259..5f73085e 100644
--- a/app/src/main/res/menu/saved_logs_viewer.xml
+++ b/app/src/main/res/menu/saved_logs_viewer.xml
@@ -1,10 +1,10 @@
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/prefs.xml b/app/src/main/res/values-ru/prefs.xml
index 60c89ced..78e879ce 100644
--- a/app/src/main/res/values-ru/prefs.xml
+++ b/app/src/main/res/values-ru/prefs.xml
@@ -1,8 +1,8 @@
-
- - Автоматическая
- - Светлая
- - Темная
-
+
+ - Автоматическая
+ - Светлая
+ - Темная
+
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index d0b3d99b..c0ebf710 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -3,98 +3,98 @@
- Logcat Reader
- Логи устройства
- Сохраненные логи
- Сохраненные логи
- Настройки
- Начать запись
- Остановить запись
- Logcat сервис
- Logcat сервис
- Нажмите ещё раз для выхода
- Внешний вид
- Тема
- Чёрная тема
- Выйти
- Поиск
- Пауза
- Продолжить
- О приложении
- Копировать
- Cкопировано
- Сохранить
- Logcat
- Интервал логов
- Посмотреть
- Не удалось открыть лог файл
- Не удалось сохранить логи
- Нечего сохранять
- Открыть с помощью
- Необходимо разрешение на чтение логов
- Чтобы просматривать логи, необходимо предоставить разрешение на чтение логов. Если ваше устройство рутировано, нажмите «Предоставить». Если это не работает или ваше устройство не рутировано, подключите ваше устройство к компьютеру через usb и введите команду запуска \'adb shell pm grant com.dp.logcatapp android.permission.READ_LOGS\'. В любом случае после этого вы должны запустить приложение и удалить его из списка недавних (удалить процесс). Он должен показывать все логи устройств при следующем запуске приложения. Сообщите, если он все еще не работает, спасибо!
- Предоставить
- Не удалось
- Буферы
- Перезапуск logcat
- Сохранено как %s
- Удалить процесс
- Разрешение было предоставлено. Logcat Reader нужно закрыть и открыть вручную, потому что автоматический перезапуск приложения не будет работать в этом случае. Это нужно сделать только один раз.
- Запись начата
- Приоритет логов
- GitHub репозиторий
- Посмотреть код, задать вопрос, и т.д.
- Фильтр
- Пусто
- Удалить
- Поделиться
- Сохранить
- Ошибка
- Сохраненный
- Ошибка при сохранении
- Внешнее хранилище не установлено
- Макс. кол-во последних логов для хранения в памяти
- Недействительный номер
- Не может быть меньше 1000
- Значение должно быть больше 0
- Значение должно быть целым числом
- Переименовать
- %d журнал
- %d журналы
- Выбрать все
- Сохранение…
- Расположение логов
- Внутренняя память приложения
- Выбрать свою папку
- Внешнее хранилище недоступно для записи
- Выбрать
- Фильтры
- "дескриптор"
- Добавить
- Тег
- исключать
- Исключения
- Перезапустить Logcat
- Исключения
- Очистить
- Уровень журнала
- Сообщение
- Дата
- Время
- Не удалось поделиться
- Общие
- Оставить экран включенным
- Скопировать в буфер обмена
- Успех
- Выберите формат экспорта
- Экспорт
- По умолчанию
- Одна строка
- Неудача
- Ручной метод
- Корневой способ
- Требуется перезапуск приложения
- Запрос разрешения на корневой доступ…
- Нажатие OK убьет приложение. Теперь вы увидите полный список журналов.
+ Logcat Reader
+ Логи устройства
+ Сохраненные логи
+ Сохраненные логи
+ Настройки
+ Начать запись
+ Остановить запись
+ Logcat сервис
+ Logcat сервис
+ Нажмите ещё раз для выхода
+ Внешний вид
+ Тема
+ Чёрная тема
+ Выйти
+ Поиск
+ Пауза
+ Продолжить
+ О приложении
+ Копировать
+ Cкопировано
+ Сохранить
+ Logcat
+ Интервал логов
+ Посмотреть
+ Не удалось открыть лог файл
+ Не удалось сохранить логи
+ Нечего сохранять
+ Открыть с помощью
+ Необходимо разрешение на чтение логов
+ Чтобы просматривать логи, необходимо предоставить разрешение на чтение логов. Если ваше устройство рутировано, нажмите «Предоставить». Если это не работает или ваше устройство не рутировано, подключите ваше устройство к компьютеру через usb и введите команду запуска \'adb shell pm grant com.dp.logcatapp android.permission.READ_LOGS\'. В любом случае после этого вы должны запустить приложение и удалить его из списка недавних (удалить процесс). Он должен показывать все логи устройств при следующем запуске приложения. Сообщите, если он все еще не работает, спасибо!
+ Предоставить
+ Не удалось
+ Буферы
+ Перезапуск logcat
+ Сохранено как %s
+ Удалить процесс
+ Разрешение было предоставлено. Logcat Reader нужно закрыть и открыть вручную, потому что автоматический перезапуск приложения не будет работать в этом случае. Это нужно сделать только один раз.
+ Запись начата
+ Приоритет логов
+ GitHub репозиторий
+ Посмотреть код, задать вопрос, и т.д.
+ Фильтр
+ Пусто
+ Удалить
+ Поделиться
+ Сохранить
+ Ошибка
+ Сохраненный
+ Ошибка при сохранении
+ Внешнее хранилище не установлено
+ Макс. кол-во последних логов для хранения в памяти
+ Недействительный номер
+ Не может быть меньше 1000
+ Значение должно быть больше 0
+ Значение должно быть целым числом
+ Переименовать
+ %d журнал
+ %d журналы
+ Выбрать все
+ Сохранение…
+ Расположение логов
+ Внутренняя память приложения
+ Выбрать свою папку
+ Внешнее хранилище недоступно для записи
+ Выбрать
+ Фильтры
+ "дескриптор"
+ Добавить
+ Тег
+ исключать
+ Исключения
+ Перезапустить Logcat
+ Исключения
+ Очистить
+ Уровень журнала
+ Сообщение
+ Дата
+ Время
+ Не удалось поделиться
+ Общие
+ Оставить экран включенным
+ Скопировать в буфер обмена
+ Успех
+ Выберите формат экспорта
+ Экспорт
+ По умолчанию
+ Одна строка
+ Неудача
+ Ручной метод
+ Корневой способ
+ Требуется перезапуск приложения
+ Запрос разрешения на корневой доступ…
+ Нажатие OK убьет приложение. Теперь вы увидите полный список журналов.
diff --git a/app/src/main/res/values-v19/styles.xml b/app/src/main/res/values-v19/styles.xml
index 30edc98d..84856cfd 100644
--- a/app/src/main/res/values-v19/styles.xml
+++ b/app/src/main/res/values-v19/styles.xml
@@ -1,20 +1,20 @@
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values-v21/dimens.xml b/app/src/main/res/values-v21/dimens.xml
index f7862a61..c7e6714a 100644
--- a/app/src/main/res/values-v21/dimens.xml
+++ b/app/src/main/res/values-v21/dimens.xml
@@ -1,4 +1,4 @@
- 0dp
+ 0dp
\ No newline at end of file
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
index baf1062a..9cebf4d5 100644
--- a/app/src/main/res/values-v21/styles.xml
+++ b/app/src/main/res/values-v21/styles.xml
@@ -1,20 +1,20 @@
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/prefs.xml b/app/src/main/res/values-zh-rCN/prefs.xml
index 1acceaf0..84a4041b 100644
--- a/app/src/main/res/values-zh-rCN/prefs.xml
+++ b/app/src/main/res/values-zh-rCN/prefs.xml
@@ -1,20 +1,18 @@
-
- - 自动
- - 亮色
- - 暗色
-
+
+ - 自动
+ - 亮色
+ - 暗色
+
-
- - 0
- - 1
- - 2
-
+
+ - 0
+ - 1
+ - 2
+
-
-
+
-
-
+
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 42c79e11..ce88dc63 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -1,118 +1,118 @@
- Logcat Reader
- 设备 Logs
- 保存 logs
- 保存 logs
- 设置
- 开始记录
- 停止记录
- Logcat 服务
- Logcat 服务
- 再次点击退出
- 通用
- 保持屏幕开启
- 出现
- 主题
- 暗色主题
- 退出
- 搜索
- 暂停
- 恢复
- 关于
- 复制ADB命令
- 复制到剪切板
- 复制
+ Logcat Reader
+ 设备 Logs
+ 保存 logs
+ 保存 logs
+ 设置
+ 开始记录
+ 停止记录
+ Logcat 服务
+ Logcat 服务
+ 再次点击退出
+ 通用
+ 保持屏幕开启
+ 出现
+ 主题
+ 暗色主题
+ 退出
+ 搜索
+ 暂停
+ 恢复
+ 关于
+ 复制ADB命令
+ 复制到剪切板
+ 复制
- 保存
- 导出
- Logcat
- 轮询间隔
- View
- 无法打开log文件
- 保存log文件错误
- 没有任何内容保存
- 用其它应用打开
- 权限请求
- 要查看所有日志,必须授予READ_LOGS权限.\n\n
+ 保存
+ 导出
+ Logcat
+ 轮询间隔
+ View
+ 无法打开log文件
+ 保存log文件错误
+ 没有任何内容保存
+ 用其它应用打开
+ 权限请求
+ 要查看所有日志,必须授予READ_LOGS权限.\n\n
1. 请通过USB将设备连接到计算机\n
2. 运行命令:\n\n
adb shell pm grant com.dp.logcatapp android.permission.READ_LOGS\n\n
3. 关闭应用程序(按两下返回键),然后将其的后台关闭.\n\n
如果有有任何问题请提issue,谢谢!
- Grant
- 失败
- Buffer
- 重新启动logcat
- 另存为 %s
- 结束
- 授权权限后请重新启动应用
+ Grant
+ 失败
+ Buffer
+ 重新启动logcat
+ 另存为 %s
+ 结束
+ 授权权限后请重新启动应用
- 开始记录
- Log priority
- GitHub Repository
- View source, create issues, etc
- 过滤
- Empty
- 删除
- 分享
- 保存
- 错误
- 保存
- 错误保存
- 没有检测到外置内存卡
-
+ 开始记录
+ Log priority
+ GitHub Repository
+ View source, create issues, etc
+ 过滤
+ Empty
+ 删除
+ 分享
+ 保存
+ 错误
+ 保存
+ 错误保存
+ 没有检测到外置内存卡
+
最多可保留的最近的日志
- 无效的数字
- 不能小于1000
- 值必须大于0
- 值必须是一个整数
- 重命名
- %d log
- %d logs
- 选择全部
- 保存中
- 保存 location
- 内部
- Custom
- 外置储存卡不可写入
- 选择
- 过滤
- 添加
- Keyword关键字
- Tag标记
- 排除
- 排除
- 重启 logcat
- 排除
- 清楚
- PID
- TID
- Log 等级
- 信息
- 日期
- 时间
- 无法分享
- 请按照以下步骤授予READ_LOGS权限(只需执行一次):
- 1.将手机连接到装有ADB工具的计算机.
- 2. 运行以下命令 (单行):
- adb shell pm grant com.dp.logcatapp android.permission.READ_LOGS
- 3. 关闭应用并清理后台.
- 4. 重新启动.
- 如果你有任何问题请提交 issues, 谢谢!
- 完成
- Fail
- ADB模式
- "READ_LOGS 权限已授权.\n\n如果您的设备是root用户,请选择'Root 模式',否则,要使用ADB手动授予权限,请选择'ADB 模式'.\n\n只需要执行一次!"
- Root模式
- 要求获得ROOT访问权限…
- 需要重启应用
- 点击确定将终止该应用程序。 从现在开始,您应该会看到完整的日志列表。
- 选择导出格式
- 默认
- Single line
- 来源打开错误
- 不支持的来源
+ 无效的数字
+ 不能小于1000
+ 值必须大于0
+ 值必须是一个整数
+ 重命名
+ %d log
+ %d logs
+ 选择全部
+ 保存中
+ 保存 location
+ 内部
+ Custom
+ 外置储存卡不可写入
+ 选择
+ 过滤
+ 添加
+ Keyword关键字
+ Tag标记
+ 排除
+ 排除
+ 重启 logcat
+ 排除
+ 清楚
+ PID
+ TID
+ Log 等级
+ 信息
+ 日期
+ 时间
+ 无法分享
+ 请按照以下步骤授予READ_LOGS权限(只需执行一次):
+ 1.将手机连接到装有ADB工具的计算机.
+ 2. 运行以下命令 (单行):
+ adb shell pm grant com.dp.logcatapp android.permission.READ_LOGS
+ 3. 关闭应用并清理后台.
+ 4. 重新启动.
+ 如果你有任何问题请提交 issues, 谢谢!
+ 完成
+ Fail
+ ADB模式
+ "READ_LOGS 权限已授权.\n\n如果您的设备是root用户,请选择'Root 模式',否则,要使用ADB手动授予权限,请选择'ADB 模式'.\n\n只需要执行一次!"
+ Root模式
+ 要求获得ROOT访问权限…
+ 需要重启应用
+ 点击确定将终止该应用程序。 从现在开始,您应该会看到完整的日志列表。
+ 选择导出格式
+ 默认
+ Single line
+ 来源打开错误
+ 不支持的来源
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 748dc254..4025a8a9 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -1,36 +1,36 @@
-
- - @string/tag
- - @string/message
- - @string/date
- - @string/time
- - @string/process_id
- - @string/thread_id
-
+
+ - @string/tag
+ - @string/message
+ - @string/date
+ - @string/time
+ - @string/process_id
+ - @string/thread_id
+
-
- - @string/filter
- - @string/exclude
-
+
+ - @string/filter
+ - @string/exclude
+
-
- - @string/save_location_internal
- - @string/save_location_custom
-
+
+ - @string/save_location_internal
+ - @string/save_location_custom
+
-
- - ASSERT
- - DEBUG
- - ERROR
- - FATAL
- - INFO
- - VERBOSE
- - WARNING
-
+
+ - ASSERT
+ - DEBUG
+ - ERROR
+ - FATAL
+ - INFO
+ - VERBOSE
+ - WARNING
+
-
- - @string/export_format_default
- - @string/export_format_single_line
-
+
+ - @string/export_format_default
+ - @string/export_format_single_line
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 65e7759b..ccadee66 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -1,21 +1,21 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ed3f8dec..c0b82b42 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,44 +1,44 @@
- #FF3D00
- #009688
- #00796B
- #00796B
- #004D40
-
- #4b000000
- #1e000000
- #64000000
- #28000000
-
- #de000000
- #FFFFFF
-
- #1fffffff
- #1f000000
-
- #8a000000
- #b2ffffff
-
- #32000000
- #32ffffff
-
- #212121
- #F5F5F5
- #000000
-
- #512DA8
- #1976D2
- #D32F2F
- #C2185B
- #388E3C
- #616161
- #E65100
- #546E7A
-
- #616161
- #BDBDBD
-
-
- @color/color_accent
+ #FF3D00
+ #009688
+ #00796B
+ #00796B
+ #004D40
+
+ #4b000000
+ #1e000000
+ #64000000
+ #28000000
+
+ #de000000
+ #FFFFFF
+
+ #1fffffff
+ #1f000000
+
+ #8a000000
+ #b2ffffff
+
+ #32000000
+ #32ffffff
+
+ #212121
+ #F5F5F5
+ #000000
+
+ #512DA8
+ #1976D2
+ #D32F2F
+ #C2185B
+ #388E3C
+ #616161
+ #E65100
+ #546E7A
+
+ #616161
+ #BDBDBD
+
+
+ @color/color_accent
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 4cefd560..64790873 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,17 +1,17 @@
- 5dp
- 5dp
+ 5dp
+ 5dp
- 4dp
- @dimen/toolbar_shadow_height
+ 4dp
+ @dimen/toolbar_shadow_height
- 24sp
- 20sp
- 18sp
- 16sp
- 14sp
- 13sp
- 12sp
- 10sp
+ 24sp
+ 20sp
+ 18sp
+ 16sp
+ 14sp
+ 13sp
+ 12sp
+ 10sp
diff --git a/app/src/main/res/values/fonts.xml b/app/src/main/res/values/fonts.xml
index d810b1bb..82324bb7 100644
--- a/app/src/main/res/values/fonts.xml
+++ b/app/src/main/res/values/fonts.xml
@@ -1,31 +1,31 @@
-
-
- Roboto-Bold
- Roboto-BoldItalic
- Roboto-Italic
- Roboto-Light
-
- Roboto-Medium
- Roboto-MediumItalic
- Roboto-Regular
-
-
- RobotoCondensed-Bold
- RobotoCondensed-BoldItalic
- RobotoCondensed-Italic
-
-
- RobotoCondensed-Regular
- RobotoMono-Bold
- RobotoMono-BoldItalic
- RobotoMono-Italic
-
-
- RobotoMono-Medium
- RobotoMono-MediumItalic
- RobotoMono-Regular
-
-
+
+
+ Roboto-Bold
+ Roboto-BoldItalic
+ Roboto-Italic
+ Roboto-Light
+
+ Roboto-Medium
+ Roboto-MediumItalic
+ Roboto-Regular
+
+
+ RobotoCondensed-Bold
+ RobotoCondensed-BoldItalic
+ RobotoCondensed-Italic
+
+
+ RobotoCondensed-Regular
+ RobotoMono-Bold
+ RobotoMono-BoldItalic
+ RobotoMono-Italic
+
+
+ RobotoMono-Medium
+ RobotoMono-MediumItalic
+ RobotoMono-Regular
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/prefs.xml b/app/src/main/res/values/prefs.xml
index f28401c3..54fb33ff 100644
--- a/app/src/main/res/values/prefs.xml
+++ b/app/src/main/res/values/prefs.xml
@@ -1,20 +1,18 @@
-
- - Automatic
- - Light Theme
- - Dark Theme
-
+
+ - Automatic
+ - Light Theme
+ - Dark Theme
+
-
- - 0
- - 1
- - 2
-
+
+ - 0
+ - 1
+ - 2
+
-
-
+
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fd322f6f..c64c85fb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,40 +1,40 @@
- Logcat Reader
- Device Logs
- Saved Logs
- Saved logs
- Settings
- Start recording
- Stop recording
- Logcat service
- Logcat service
- Press back again to exit
- General
- Keep screen on
- Appearance
- Theme
- Super Dark Theme
- Exit
- Search
- Pause
- Resume
- About
- Copy ADB Command
- Copy To Clipboard
- Copied
+ Logcat Reader
+ Device Logs
+ Saved Logs
+ Saved logs
+ Settings
+ Start recording
+ Stop recording
+ Logcat service
+ Logcat service
+ Press back again to exit
+ General
+ Keep screen on
+ Appearance
+ Theme
+ Super Dark Theme
+ Exit
+ Search
+ Pause
+ Resume
+ About
+ Copy ADB Command
+ Copy To Clipboard
+ Copied
- Save
- Export
- Logcat
- Poll Interval
- View
- Could not open the log file
- Failed to save logs
- Nothing to save
- Open with
- Permission required
- To view all of the logs,
+ Save
+ Export
+ Logcat
+ Poll Interval
+ View
+ Could not open the log file
+ Failed to save logs
+ Nothing to save
+ Open with
+ Permission required
+ To view all of the logs,
READ_LOGS permission must be granted.\n\n
1. Please connect your device to your computer via usb\n
2. Run command:\n\n
@@ -42,80 +42,80 @@
3. Close the app (double back press), and swipe it out of Recents.\n\n
Please report if there are any issues, thank you!
- Grant
- Failed
- Buffers
- Restarting logcat
- Saved as %s
- Kill
- Permission has been granted. Logcat Reader needs to be killed
+ Grant
+ Failed
+ Buffers
+ Restarting logcat
+ Saved as %s
+ Kill
+ Permission has been granted. Logcat Reader needs to be killed
and has to be opened manually because typical app restart won\'t work in this case.
This needs to be done only once.
- Started recording
- Log priority
- GitHub Repository
- View source, create issues, etc
- Filter
- Empty
- Delete
- Share
- Save
- Error
- Saved
- Error saving
- External storage is not mounted
-
+ Started recording
+ Log priority
+ GitHub Repository
+ View source, create issues, etc
+ Filter
+ Empty
+ Delete
+ Share
+ Save
+ Error
+ Saved
+ Error saving
+ External storage is not mounted
+
Max recent logs to keep in memory
- Not a valid number
- Cannot be less than 1000
- Value must be greater than 0
- Value must be a positive integer
- Rename
- %d log
- %d logs
- Select all
- Saving…
- Save location
- Internal
- Custom
- External storage not writable
- Select
- Filters
- Add
- Keyword
- Tag
- Exclude
- Exclusions
- Restart logcat
- Exclusion
- Clear
- PID
- TID
- Log level
- Message
- Date
- Time
- Unable to share
- Please follow the steps below to grant READ_LOGS permission (needs to be done only once):
- 1. Connect the phone to a computer with ADB tools installed.
- 2. Run the following command (single line):
- adb shell pm grant com.dp.logcatapp android.permission.READ_LOGS
- 3. Close the app by double pressing the back button and swiping it out from the Recents.
- 4. Start the app again, and now you should see all of the logs.
- Please report if there are any issues, thank you!
- Success
- Fail
- Manual method
- "READ_LOGS permission is required.\n\nIf your device is rooted, please select 'Root method', otherwise, to grant the permission manually using ADB, please select 'Manual method'.\n\nThis needs to be done only once!"
- Root method
- Asking permission for ROOT access…
- App restart required
- Tapping OK will kill the app. You should see a complete list of logs from now on.
- Select export format
- Default
- Single line
- Error opening source
- Unsupported source
+ Not a valid number
+ Cannot be less than 1000
+ Value must be greater than 0
+ Value must be a positive integer
+ Rename
+ %d log
+ %d logs
+ Select all
+ Saving…
+ Save location
+ Internal
+ Custom
+ External storage not writable
+ Select
+ Filters
+ Add
+ Keyword
+ Tag
+ Exclude
+ Exclusions
+ Restart logcat
+ Exclusion
+ Clear
+ PID
+ TID
+ Log level
+ Message
+ Date
+ Time
+ Unable to share
+ Please follow the steps below to grant READ_LOGS permission (needs to be done only once):
+ 1. Connect the phone to a computer with ADB tools installed.
+ 2. Run the following command (single line):
+ adb shell pm grant com.dp.logcatapp android.permission.READ_LOGS
+ 3. Close the app by double pressing the back button and swiping it out from the Recents.
+ 4. Start the app again, and now you should see all of the logs.
+ Please report if there are any issues, thank you!
+ Success
+ Fail
+ Manual method
+ "READ_LOGS permission is required.\n\nIf your device is rooted, please select 'Root method', otherwise, to grant the permission manually using ADB, please select 'Manual method'.\n\nThis needs to be done only once!"
+ Root method
+ Asking permission for ROOT access…
+ App restart required
+ Tapping OK will kill the app. You should see a complete list of logs from now on.
+ Select export format
+ Default
+ Single line
+ Error opening source
+ Unsupported source
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 05beee11..455483f9 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -2,88 +2,88 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/file_provider_paths.xml b/app/src/main/res/xml/file_provider_paths.xml
index 1e6e5a4f..438578f4 100644
--- a/app/src/main/res/xml/file_provider_paths.xml
+++ b/app/src/main/res/xml/file_provider_paths.xml
@@ -1,12 +1,12 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
index 63a74e39..263c3c16 100644
--- a/app/src/main/res/xml/settings.xml
+++ b/app/src/main/res/xml/settings.xml
@@ -1,68 +1,68 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/dp/logcatapp/ExampleUnitTest.kt b/app/src/test/java/com/dp/logcatapp/ExampleUnitTest.kt
index 449fc5c4..44814cfe 100644
--- a/app/src/test/java/com/dp/logcatapp/ExampleUnitTest.kt
+++ b/app/src/test/java/com/dp/logcatapp/ExampleUnitTest.kt
@@ -1,17 +1,16 @@
package com.dp.logcatapp
+import org.junit.Assert.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
}
diff --git a/build.gradle b/build.gradle
index a69e0f54..799ec560 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,50 +1,50 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.4.0'
- repositories {
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:4.0.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ ext.kotlin_version = '1.4.0'
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.0.1'
+ 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
- }
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
}
allprojects {
- repositories {
- google()
- jcenter()
- }
+ repositories {
+ google()
+ jcenter()
+ }
}
ext {
- appcompat_version = "1.2.0"
- constraint_layout_version = "2.0.1"
- core_ktx_version = "1.3.1"
- documentfile_version = "1.0.1"
- fragment_ktx_version = "1.2.5"
- kotlin_coroutines_android_version = "1.3.7"
- kotlin_coroutines_core_version = "1.3.7"
- legacy_preference_v14_version = "1.0.0"
- lifecycle_common_java8_version = "2.2.0"
- lifecycle_extensions_version = "2.2.0"
- lifecycle_runtime_version = "2.2.0"
- lifecycle_viewmodel_ktx_version = "2.2.0"
- material_version = "1.2.0"
- preference_version = "1.1.1"
- room_version = "2.2.5"
+ appcompat_version = "1.2.0"
+ constraint_layout_version = "2.0.1"
+ core_ktx_version = "1.3.1"
+ documentfile_version = "1.0.1"
+ fragment_ktx_version = "1.2.5"
+ kotlin_coroutines_android_version = "1.3.7"
+ kotlin_coroutines_core_version = "1.3.7"
+ legacy_preference_v14_version = "1.0.0"
+ lifecycle_common_java8_version = "2.2.0"
+ lifecycle_extensions_version = "2.2.0"
+ lifecycle_runtime_version = "2.2.0"
+ lifecycle_viewmodel_ktx_version = "2.2.0"
+ material_version = "1.2.0"
+ preference_version = "1.1.1"
+ room_version = "2.2.5"
- // test dependencies
- junit_version = "4.13"
- runner_version = "1.3.0"
- espresso_core_version = "3.3.0"
+ // test dependencies
+ junit_version = "4.13"
+ runner_version = "1.3.0"
+ espresso_core_version = "3.3.0"
}
task clean(type: Delete) {
- delete rootProject.buildDir
+ delete rootProject.buildDir
}
diff --git a/collections/build.gradle b/collections/build.gradle
index a90da536..56579858 100644
--- a/collections/build.gradle
+++ b/collections/build.gradle
@@ -5,33 +5,31 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 29
+ compileSdkVersion 29
- defaultConfig {
- minSdkVersion 16
- versionCode 1
- versionName "1.0"
+ defaultConfig {
+ minSdkVersion 16
+ versionCode 1
+ versionName "1.0"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-
+ }
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation project(':logger')
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
- implementation "androidx.appcompat:appcompat:$appcompat_version"
- testImplementation "junit:junit:$junit_version"
- androidTestImplementation "androidx.test:runner:$runner_version"
- androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_core_version"
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':logger')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ implementation "androidx.appcompat:appcompat:$appcompat_version"
+ testImplementation "junit:junit:$junit_version"
+ androidTestImplementation "androidx.test:runner:$runner_version"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_core_version"
}
diff --git a/collections/src/androidTest/java/com/logcat/collections/ExampleInstrumentedTest.java b/collections/src/androidTest/java/com/logcat/collections/ExampleInstrumentedTest.java
index 50c760c9..15d13825 100644
--- a/collections/src/androidTest/java/com/logcat/collections/ExampleInstrumentedTest.java
+++ b/collections/src/androidTest/java/com/logcat/collections/ExampleInstrumentedTest.java
@@ -3,19 +3,18 @@
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("com.trakam.collections.test", appContext.getPackageName());
- }
+ assertEquals("com.trakam.collections.test", appContext.getPackageName());
+ }
}
diff --git a/collections/src/main/AndroidManifest.xml b/collections/src/main/AndroidManifest.xml
index 13fb22c0..3d8273f4 100644
--- a/collections/src/main/AndroidManifest.xml
+++ b/collections/src/main/AndroidManifest.xml
@@ -1,2 +1 @@
-
+
diff --git a/collections/src/main/java/com/logcat/collections/FixedCircularArray.kt b/collections/src/main/java/com/logcat/collections/FixedCircularArray.kt
index f390870e..8ece6996 100644
--- a/collections/src/main/java/com/logcat/collections/FixedCircularArray.kt
+++ b/collections/src/main/java/com/logcat/collections/FixedCircularArray.kt
@@ -2,156 +2,159 @@ package com.logcat.collections
import kotlin.math.min
-class FixedCircularArray(val capacity: Int, initialSize: Int = INITIAL_SIZE) : Iterable {
-
- companion object {
- private const val INITIAL_SIZE = 16
+class FixedCircularArray(
+ val capacity: Int,
+ initialSize: Int = INITIAL_SIZE
+) : Iterable {
+
+ companion object {
+ private const val INITIAL_SIZE = 16
+ }
+
+ private var array = arrayOfNulls(min(capacity, initialSize))
+ private var head = 0
+ private var next = 0
+
+ init {
+ resetHead()
+ if (capacity <= 0) {
+ throw IllegalStateException("capacity (= $capacity) must be > 0")
}
-
- private var array = arrayOfNulls(min(capacity, initialSize))
- private var head = 0
- private var next = 0
-
- init {
- resetHead()
- if (capacity <= 0) {
- throw IllegalStateException("capacity (= $capacity) must be > 0")
- }
+ }
+
+ val size: Int
+ get() {
+ return when {
+ head == -1 -> 0
+ next <= head -> (capacity - head) + next
+ else -> next - head
+ }
}
- val size: Int
- get() {
- return when {
- head == -1 -> 0
- next <= head -> (capacity - head) + next
- else -> next - head
- }
- }
-
- fun add(list: Iterable) {
- for (e in list) {
- add(e)
- }
+ fun add(list: Iterable) {
+ for (e in list) {
+ add(e)
}
+ }
- fun add(e: E) {
- tryGrow()
-
- if (head < 0) {
- head = 0
- } else if (next == head) {
- head = ++head % capacity
- }
+ fun add(e: E) {
+ tryGrow()
- array[next] = e
- next = ++next % capacity
+ if (head < 0) {
+ head = 0
+ } else if (next == head) {
+ head = ++head % capacity
}
- fun remove(e: E): E? {
- if (e == null) {
- return null
- }
+ array[next] = e
+ next = ++next % capacity
+ }
- val index = indexOf(e)
- if (index != -1) {
- return removeAt(index)
- }
- return null
+ fun remove(e: E): E? {
+ if (e == null) {
+ return null
}
- @Suppress("unchecked_cast")
- fun removeAt(index: Int): E {
- checkIOBAndThrow(index)
- val result = array[(head + index) % capacity] as E
- for (i in index until (size - 1)) {
- array[(head + i) % capacity] = array[(head + i + 1) % capacity]
- }
- array[(head + size - 1) % capacity] = null
-
- next = (head + size - 1) % capacity
- if (next == head) {
- // empty array, reset
- resetHead()
- }
-
- return result
+ val index = indexOf(e)
+ if (index != -1) {
+ return removeAt(index)
}
+ return null
+ }
+
+ @Suppress("unchecked_cast")
+ fun removeAt(index: Int): E {
+ checkIOBAndThrow(index)
+ val result = array[(head + index) % capacity] as E
+ for (i in index until (size - 1)) {
+ array[(head + i) % capacity] = array[(head + i + 1) % capacity]
+ }
+ array[(head + size - 1) % capacity] = null
- fun indexOf(e: E): Int {
- if (e == null) {
- return -1
- }
+ next = (head + size - 1) % capacity
+ if (next == head) {
+ // empty array, reset
+ resetHead()
+ }
- for (i in 0 until size) {
- if (array[(head + i) % capacity] == e) {
- return i
- }
- }
+ return result
+ }
- return -1
+ fun indexOf(e: E): Int {
+ if (e == null) {
+ return -1
}
- @Suppress("unchecked_cast")
- operator fun get(index: Int): E {
- checkIOBAndThrow(index)
- return array[(head + index) % capacity] as E
+ for (i in 0 until size) {
+ if (array[(head + i) % capacity] == e) {
+ return i
+ }
}
- private fun checkIOBAndThrow(index: Int) {
- if (index < 0 || index >= size) {
- throw IndexOutOfBoundsException("index = $index, size = $size")
- }
- }
+ return -1
+ }
- private fun tryGrow() {
- if (size < array.size || array.size == capacity) {
- return
- }
+ @Suppress("unchecked_cast")
+ operator fun get(index: Int): E {
+ checkIOBAndThrow(index)
+ return array[(head + index) % capacity] as E
+ }
- val newSize = min(array.size * 2, capacity)
- val newArray = arrayOfNulls(newSize)
- System.arraycopy(array, 0, newArray, 0, array.size)
- array = newArray
+ private fun checkIOBAndThrow(index: Int) {
+ if (index < 0 || index >= size) {
+ throw IndexOutOfBoundsException("index = $index, size = $size")
}
+ }
- operator fun plusAssign(e: E) = add(e)
+ private fun tryGrow() {
+ if (size < array.size || array.size == capacity) {
+ return
+ }
- operator fun plusAssign(list: List) = add(list)
+ val newSize = min(array.size * 2, capacity)
+ val newArray = arrayOfNulls(newSize)
+ System.arraycopy(array, 0, newArray, 0, array.size)
+ array = newArray
+ }
- operator fun plusAssign(list: FixedCircularArray) = add(list)
+ operator fun plusAssign(e: E) = add(e)
- fun clear() {
- resetHead()
- array.fill(null)
- }
+ operator fun plusAssign(list: List) = add(list)
- private fun resetHead() {
- head = -1
- next = 0
- }
+ operator fun plusAssign(list: FixedCircularArray) = add(list)
- fun isEmpty() = size == 0
+ fun clear() {
+ resetHead()
+ array.fill(null)
+ }
- fun isNotEmpty() = size != 0
+ private fun resetHead() {
+ head = -1
+ next = 0
+ }
- fun isFull() = size == capacity
+ fun isEmpty() = size == 0
- operator fun contains(element: E): Boolean {
- for (i in 0 until size) {
- if (element == array[(head + i) % capacity]) {
- return true
- }
- }
- return false
+ fun isNotEmpty() = size != 0
+
+ fun isFull() = size == capacity
+
+ operator fun contains(element: E): Boolean {
+ for (i in 0 until size) {
+ if (element == array[(head + i) % capacity]) {
+ return true
+ }
}
+ return false
+ }
- override fun iterator(): Iterator = FixedCircularArrayIterator()
+ override fun iterator(): Iterator = FixedCircularArrayIterator()
- private inner class FixedCircularArrayIterator : Iterator {
- var index = 0
+ private inner class FixedCircularArrayIterator : Iterator {
+ var index = 0
- override fun hasNext(): Boolean = index < size
+ override fun hasNext(): Boolean = index < size
- override fun next(): E = get(index++)
- }
+ override fun next(): E = get(index++)
+ }
}
\ No newline at end of file
diff --git a/collections/src/main/res/values/strings.xml b/collections/src/main/res/values/strings.xml
index 1c0f4c50..60633487 100644
--- a/collections/src/main/res/values/strings.xml
+++ b/collections/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
- collections
+ collections
diff --git a/collections/src/test/java/com/logcat/collections/FixedCircularArrayTest.kt b/collections/src/test/java/com/logcat/collections/FixedCircularArrayTest.kt
index 56112a81..91845756 100644
--- a/collections/src/test/java/com/logcat/collections/FixedCircularArrayTest.kt
+++ b/collections/src/test/java/com/logcat/collections/FixedCircularArrayTest.kt
@@ -1,179 +1,181 @@
package com.logcat.collections
-import org.junit.Assert.*
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Test
class FixedCircularArrayTest {
- @Test
- fun testAdd() {
- val array = FixedCircularArray(12)
- assertTrue(array.isEmpty())
+ @Test
+ fun testAdd() {
+ val array = FixedCircularArray(12)
+ assertTrue(array.isEmpty())
- array.add(1)
- assertTrue(array.isNotEmpty())
+ array.add(1)
+ assertTrue(array.isNotEmpty())
- array.clear()
- assertTrue(array.isEmpty())
+ array.clear()
+ assertTrue(array.isEmpty())
- for (i in 0..100) {
- array.add(i)
- }
- assertEquals(12, array.size)
-
- val expectedValues = arrayOf(89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)
- for (i in 0 until array.size) {
- assertEquals(expectedValues[i], array[i])
- }
+ for (i in 0..100) {
+ array.add(i)
}
+ assertEquals(12, array.size)
- @Test
- fun testRemove() {
- val array = FixedCircularArray(10)
- array.add(5)
- assertEquals(5, array.remove(5))
- assertTrue(array.isEmpty())
-
- for (i in 0..100) {
- array.add(i)
- }
-
- println("================================")
- assertEquals(10, array.size)
- var index = 0
- while (array.isNotEmpty()) {
- assertEquals(91 + index++, array.removeAt(0))
- }
- assertTrue(array.isEmpty())
-
- for (i in 0 until 5) {
- array.add(i)
- }
- assertEquals(5, array.size)
-
- for (i in 0 until 20) {
- array.add(i)
- }
- assertEquals(10, array.size)
-
- index = 0
- while (array.isNotEmpty()) {
- assertEquals(10 + index++, array.removeAt(0))
- }
- assertTrue(array.isEmpty())
+ val expectedValues = arrayOf(89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)
+ for (i in 0 until array.size) {
+ assertEquals(expectedValues[i], array[i])
}
+ }
- @Test
- fun testIndexOf() {
- val array = FixedCircularArray(10)
- assertEquals(-1, array.indexOf(12))
+ @Test
+ fun testRemove() {
+ val array = FixedCircularArray(10)
+ array.add(5)
+ assertEquals(5, array.remove(5))
+ assertTrue(array.isEmpty())
- for (i in 0..1000) {
- array += i
- }
+ for (i in 0..100) {
+ array.add(i)
+ }
- assertEquals(8, array.indexOf(999))
+ println("================================")
+ assertEquals(10, array.size)
+ var index = 0
+ while (array.isNotEmpty()) {
+ assertEquals(91 + index++, array.removeAt(0))
+ }
+ assertTrue(array.isEmpty())
- array.clear()
- assertTrue(array.isEmpty())
+ for (i in 0 until 5) {
+ array.add(i)
+ }
+ assertEquals(5, array.size)
- array += 1
- array += 5
+ for (i in 0 until 20) {
+ array.add(i)
+ }
+ assertEquals(10, array.size)
- assertEquals(1, array.indexOf(5))
+ index = 0
+ while (array.isNotEmpty()) {
+ assertEquals(10 + index++, array.removeAt(0))
}
+ assertTrue(array.isEmpty())
+ }
+
+ @Test
+ fun testIndexOf() {
+ val array = FixedCircularArray(10)
+ assertEquals(-1, array.indexOf(12))
- @Test(expected = IllegalStateException::class)
- fun testCapacityZero() {
- FixedCircularArray(0)
+ for (i in 0..1000) {
+ array += i
}
- @Test
- fun testGet() {
- val array = FixedCircularArray(10)
+ assertEquals(8, array.indexOf(999))
+
+ array.clear()
+ assertTrue(array.isEmpty())
+
+ array += 1
+ array += 5
- array += 3
- array += 5
+ assertEquals(1, array.indexOf(5))
+ }
- assertEquals(3, array[0])
- assertEquals(5, array[1])
+ @Test(expected = IllegalStateException::class)
+ fun testCapacityZero() {
+ FixedCircularArray(0)
+ }
- for (i in 0..100) {
- array += i
- }
+ @Test
+ fun testGet() {
+ val array = FixedCircularArray(10)
- assertEquals(100, array[array.size - 1])
- assertEquals(91, array[0])
+ array += 3
+ array += 5
+
+ assertEquals(3, array[0])
+ assertEquals(5, array[1])
+
+ for (i in 0..100) {
+ array += i
}
- @Test
- fun testIsFull() {
- val array = FixedCircularArray(100)
- assertTrue(array.isEmpty())
- assertFalse(array.isFull())
+ assertEquals(100, array[array.size - 1])
+ assertEquals(91, array[0])
+ }
- for (i in 0..98) {
- array += 1
- }
+ @Test
+ fun testIsFull() {
+ val array = FixedCircularArray(100)
+ assertTrue(array.isEmpty())
+ assertFalse(array.isFull())
+
+ for (i in 0..98) {
+ array += 1
+ }
- assertTrue(array.isNotEmpty())
- assertFalse(array.isFull())
+ assertTrue(array.isNotEmpty())
+ assertFalse(array.isFull())
- array += 1
- assertTrue(array.isFull())
+ array += 1
+ assertTrue(array.isFull())
+ }
+
+ @Test
+ fun testIterator() {
+ val array = FixedCircularArray(10)
+ val expected = mutableListOf()
+ for (i in 0 until array.size) {
+ array += i * i
+ expected += i * i
}
- @Test
- fun testIterator() {
- val array = FixedCircularArray(10)
- val expected = mutableListOf()
- for (i in 0 until array.size) {
- array += i * i
- expected += i * i
- }
-
- val actual = mutableListOf()
- for (i in array) {
- actual += i
- }
-
- assertEquals(expected, actual)
+ val actual = mutableListOf()
+ for (i in array) {
+ actual += i
}
- @Test
- fun testContains() {
- val array = FixedCircularArray(100)
- array += 10
- array += 20
+ assertEquals(expected, actual)
+ }
- assertTrue(10 in array)
- assertTrue(20 in array)
- assertTrue(30 !in array)
+ @Test
+ fun testContains() {
+ val array = FixedCircularArray(100)
+ array += 10
+ array += 20
- for (i in 0..1000) {
- array += i
- }
+ assertTrue(10 in array)
+ assertTrue(20 in array)
+ assertTrue(30 !in array)
- assertTrue(901 in array)
- assertTrue(1000 in array)
- assertTrue(900 !in array)
+ for (i in 0..1000) {
+ array += i
}
- @Test
- fun testAddAfterRemove() {
- val array = FixedCircularArray(5)
+ assertTrue(901 in array)
+ assertTrue(1000 in array)
+ assertTrue(900 !in array)
+ }
+
+ @Test
+ fun testAddAfterRemove() {
+ val array = FixedCircularArray(5)
- for (i in 1..5) {
- array += i
- }
- assertTrue(array.isNotEmpty())
+ for (i in 1..5) {
+ array += i
+ }
+ assertTrue(array.isNotEmpty())
- array.removeAt(0)
- array += 6
+ array.removeAt(0)
+ array += 6
- val excepted = arrayOf(2, 3, 4, 5, 6)
- for (i in 0 until array.size) {
- assertEquals(excepted[i], array[i])
- }
+ val excepted = arrayOf(2, 3, 4, 5, 6)
+ for (i in 0 until array.size) {
+ assertEquals(excepted[i], array[i])
}
+ }
}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 2394aa65..54b6904a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,18 +1,14 @@
# 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.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2560M
-
# 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
diff --git a/logcat/build.gradle b/logcat/build.gradle
index 2360c79b..22db67de 100644
--- a/logcat/build.gradle
+++ b/logcat/build.gradle
@@ -4,40 +4,40 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
- compileSdkVersion 29
+ compileSdkVersion 29
- defaultConfig {
- minSdkVersion 16
- versionCode 1
- versionName "1.0"
+ defaultConfig {
+ minSdkVersion 16
+ versionCode 1
+ versionName "1.0"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
+ }
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation project(':logger')
- implementation project(':collections')
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
- implementation "androidx.appcompat:appcompat:$appcompat_version"
- implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_common_java8_version"
- implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_extensions_version"
- implementation "androidx.documentfile:documentfile:$documentfile_version"
- testImplementation "junit:junit:$junit_version"
- androidTestImplementation "androidx.test:runner:$runner_version"
- androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_core_version"
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':logger')
+ implementation project(':collections')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ implementation "androidx.appcompat:appcompat:$appcompat_version"
+ implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_common_java8_version"
+ implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_extensions_version"
+ implementation "androidx.documentfile:documentfile:$documentfile_version"
+ testImplementation "junit:junit:$junit_version"
+ androidTestImplementation "androidx.test:runner:$runner_version"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_core_version"
}
diff --git a/logcat/src/androidTest/java/com/dp/logcat/ExampleInstrumentedTest.java b/logcat/src/androidTest/java/com/dp/logcat/ExampleInstrumentedTest.java
index b7f7462f..30510b57 100644
--- a/logcat/src/androidTest/java/com/dp/logcat/ExampleInstrumentedTest.java
+++ b/logcat/src/androidTest/java/com/dp/logcat/ExampleInstrumentedTest.java
@@ -3,11 +3,10 @@
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
/**
* Instrumented test, which will execute on an Android device.
@@ -16,11 +15,11 @@
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("com.dp.logcatlibrary.test", appContext.getPackageName());
- }
+ assertEquals("com.dp.logcatlibrary.test", appContext.getPackageName());
+ }
}
diff --git a/logcat/src/main/AndroidManifest.xml b/logcat/src/main/AndroidManifest.xml
index 9e1f724f..2ff65f4b 100644
--- a/logcat/src/main/AndroidManifest.xml
+++ b/logcat/src/main/AndroidManifest.xml
@@ -1,2 +1 @@
-
+
diff --git a/logcat/src/main/java/com/dp/logcat/CommandUtils.kt b/logcat/src/main/java/com/dp/logcat/CommandUtils.kt
index 40b455d3..8ca29952 100644
--- a/logcat/src/main/java/com/dp/logcat/CommandUtils.kt
+++ b/logcat/src/main/java/com/dp/logcat/CommandUtils.kt
@@ -6,57 +6,60 @@ import kotlin.concurrent.thread
object CommandUtils {
- fun runCmd(vararg cmd: String, stdoutList: MutableList? = null,
- stderrList: MutableList? = null, redirectStderr: Boolean = false): Int {
- val processBuilder = ProcessBuilder(*cmd)
- .redirectErrorStream(redirectStderr)
- var process: Process? = null
+ fun runCmd(
+ vararg cmd: String,
+ stdoutList: MutableList? = null,
+ stderrList: MutableList? = null,
+ redirectStderr: Boolean = false
+ ): Int {
+ val processBuilder = ProcessBuilder(*cmd)
+ .redirectErrorStream(redirectStderr)
+ var process: Process? = null
+ try {
+ process = processBuilder.start()
+ val stderrStream = process.errorStream
+ val stdoutStream = process.inputStream
+
+ val stdoutThread = thread {
try {
- process = processBuilder.start()
- val stderrStream = process.errorStream
- val stdoutStream = process.inputStream
-
- val stdoutThread = thread {
- try {
- BufferedReader(InputStreamReader(stdoutStream)).use {
- while (true) {
- val line = it.readLine() ?: break
- stdoutList?.add(line)
- }
- }
- } catch (e: Exception) {
- }
+ BufferedReader(InputStreamReader(stdoutStream)).use {
+ while (true) {
+ val line = it.readLine() ?: break
+ stdoutList?.add(line)
}
- val stderrThread = thread {
- try {
- BufferedReader(InputStreamReader(stderrStream)).use {
- while (true) {
- val line = it.readLine() ?: break
- stderrList?.add(line)
- }
- }
- } catch (e: Exception) {
- }
+ }
+ } catch (e: Exception) {
+ }
+ }
+ val stderrThread = thread {
+ try {
+ BufferedReader(InputStreamReader(stderrStream)).use {
+ while (true) {
+ val line = it.readLine() ?: break
+ stderrList?.add(line)
}
+ }
+ } catch (e: Exception) {
+ }
+ }
- val exitCode = process.waitFor()
+ val exitCode = process.waitFor()
- try {
- stderrThread.join(1000)
- } catch (e: InterruptedException) {
- }
+ try {
+ stderrThread.join(1000)
+ } catch (e: InterruptedException) {
+ }
- try {
- stdoutThread.join(1000)
- } catch (e: InterruptedException) {
- }
+ try {
+ stdoutThread.join(1000)
+ } catch (e: InterruptedException) {
+ }
- return exitCode
- } catch (e: Exception) {
- return -1
- } finally {
- process?.destroy()
- }
+ return exitCode
+ } catch (e: Exception) {
+ return -1
+ } finally {
+ process?.destroy()
}
-
+ }
}
\ No newline at end of file
diff --git a/logcat/src/main/java/com/dp/logcat/Filter.kt b/logcat/src/main/java/com/dp/logcat/Filter.kt
index 610cee23..9a1c2086 100644
--- a/logcat/src/main/java/com/dp/logcat/Filter.kt
+++ b/logcat/src/main/java/com/dp/logcat/Filter.kt
@@ -2,5 +2,5 @@ package com.dp.logcat
interface Filter {
- fun apply(log: Log): Boolean
+ fun apply(log: Log): Boolean
}
\ No newline at end of file
diff --git a/logcat/src/main/java/com/dp/logcat/Log.kt b/logcat/src/main/java/com/dp/logcat/Log.kt
index b38c7c2f..91aa5704 100644
--- a/logcat/src/main/java/com/dp/logcat/Log.kt
+++ b/logcat/src/main/java/com/dp/logcat/Log.kt
@@ -6,74 +6,79 @@ import kotlinx.android.parcel.Parcelize
private var logCounter = 0
@Parcelize
-data class Log(val id: Int,
- val date: String,
- val time: String,
- val pid: String,
- val tid: String,
- val priority: String,
- val tag: String,
- val msg: String) : Parcelable {
-
- fun metadataToString() = "[$date $time $pid:$tid $priority/$tag]"
-
- override fun toString(): String = "${metadataToString()}\n$msg\n\n"
-
- companion object {
- fun parse(metadata: String, msg: String): Log {
- val date: String
- val time: String
- val pid: String
- val tid: String
- val priority: String
- val tag: String
-
- val trimmed = metadata.substring(1, metadata.length - 1).trim()
- var startIndex = 0
-
- var index = trimmed.indexOf(' ', startIndex)
- date = trimmed.substring(startIndex, index)
- startIndex = index + 1
-
- index = trimmed.indexOf(' ', startIndex)
- time = trimmed.substring(startIndex, index)
- startIndex = index + 1
-
- // NOTE(dparajuli): skip spaces
- while (trimmed[startIndex] == ' ') {
- startIndex++
- }
-
- index = trimmed.indexOf(':', startIndex)
- pid = trimmed.substring(startIndex, index)
- startIndex = index + 1
-
- // NOTE(dparajuli): skip spaces
- while (trimmed[startIndex] == ' ') {
- startIndex++
- }
-
- index = trimmed.indexOf(' ', startIndex)
- tid = trimmed.substring(startIndex, index)
- startIndex = index + 1
-
- index = trimmed.indexOf('/', startIndex)
- priority = trimmed.substring(startIndex, index)
- startIndex = index + 1
-
- tag = trimmed.substring(startIndex, trimmed.length).trim()
-
- return Log(logCounter++, date, time, pid, tid, priority, tag, msg)
- }
+data class Log(
+ val id: Int,
+ val date: String,
+ val time: String,
+ val pid: String,
+ val tid: String,
+ val priority: String,
+ val tag: String,
+ val msg: String
+) : Parcelable {
+
+ fun metadataToString() = "[$date $time $pid:$tid $priority/$tag]"
+
+ override fun toString(): String = "${metadataToString()}\n$msg\n\n"
+
+ companion object {
+ fun parse(
+ metadata: String,
+ msg: String
+ ): Log {
+ val date: String
+ val time: String
+ val pid: String
+ val tid: String
+ val priority: String
+ val tag: String
+
+ val trimmed = metadata.substring(1, metadata.length - 1).trim()
+ var startIndex = 0
+
+ var index = trimmed.indexOf(' ', startIndex)
+ date = trimmed.substring(startIndex, index)
+ startIndex = index + 1
+
+ index = trimmed.indexOf(' ', startIndex)
+ time = trimmed.substring(startIndex, index)
+ startIndex = index + 1
+
+ // NOTE(dparajuli): skip spaces
+ while (trimmed[startIndex] == ' ') {
+ startIndex++
+ }
+
+ index = trimmed.indexOf(':', startIndex)
+ pid = trimmed.substring(startIndex, index)
+ startIndex = index + 1
+
+ // NOTE(dparajuli): skip spaces
+ while (trimmed[startIndex] == ' ') {
+ startIndex++
+ }
+
+ index = trimmed.indexOf(' ', startIndex)
+ tid = trimmed.substring(startIndex, index)
+ startIndex = index + 1
+
+ index = trimmed.indexOf('/', startIndex)
+ priority = trimmed.substring(startIndex, index)
+ startIndex = index + 1
+
+ tag = trimmed.substring(startIndex, trimmed.length).trim()
+
+ return Log(logCounter++, date, time, pid, tid, priority, tag, msg)
}
+ }
}
object LogPriority {
- const val ASSERT = "A"
- const val DEBUG = "D"
- const val ERROR = "E"
- const val FATAL = "F"
- const val INFO = "I"
- const val VERBOSE = "V"
- const val WARNING = "W"
+ const val ASSERT = "A"
+ const val DEBUG = "D"
+ const val ERROR = "E"
+ const val FATAL = "F"
+ const val INFO = "I"
+ const val VERBOSE = "V"
+ const val WARNING = "W"
}
diff --git a/logcat/src/main/java/com/dp/logcat/Logcat.kt b/logcat/src/main/java/com/dp/logcat/Logcat.kt
index 8a18afd1..e6697053 100644
--- a/logcat/src/main/java/com/dp/logcat/Logcat.kt
+++ b/logcat/src/main/java/com/dp/logcat/Logcat.kt
@@ -11,589 +11,624 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.dp.logger.Logger
import com.logcat.collections.FixedCircularArray
-import java.io.*
-import java.util.*
+import java.io.BufferedReader
+import java.io.BufferedWriter
+import java.io.Closeable
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileWriter
+import java.io.IOException
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.io.OutputStreamWriter
+import java.util.Collections
+import java.util.WeakHashMap
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.thread
import kotlin.concurrent.withLock
class Logcat(initialCapacity: Int = INITIAL_LOG_CAPACITY) : Closeable {
- var logcatBuffers = DEFAULT_BUFFERS
- private val logcatCmd = arrayOf("logcat", "-v", "long")
- private var pollInterval: Long = 250L // in ms
- private var threadLogcat: Thread? = null
- private var logcatProcess: Process? = null
- private val handler: Handler = Handler(Looper.getMainLooper())
+ var logcatBuffers = DEFAULT_BUFFERS
+ private val logcatCmd = arrayOf("logcat", "-v", "long")
+ private var pollInterval: Long = 250L // in ms
+ private var threadLogcat: Thread? = null
+ private var logcatProcess: Process? = null
+ private val handler: Handler = Handler(Looper.getMainLooper())
- private var recordStartIndex = -1
+ private var recordStartIndex = -1
- private val listeners = Collections.newSetFromMap(WeakHashMap())
+ private val listeners = Collections.newSetFromMap(WeakHashMap())
- private var pollCondition = ConditionVariable()
+ private var pollCondition = ConditionVariable()
- private var exitCode: Int = -1
+ private var exitCode: Int = -1
- private var _pausedLock = Any()
- private var paused: Boolean = false
- get() = synchronized(_pausedLock) {
- field
- }
- set(value) = synchronized(_pausedLock) {
- field = value
- }
- private val pausePostLogsCondition = ConditionVariable()
-
- // must be synchronized
- private val logsLock = ReentrantLock()
- private val pendingLogsFullCondition = logsLock.newCondition()
- private var logs = FixedCircularArray(initialCapacity, INITIAL_LOG_SIZE)
- private var pendingLogs = FixedCircularArray(initialCapacity, INITIAL_LOG_SIZE)
- private val filters = mutableMapOf()
- private val exclusions = mutableMapOf()
-
- private var _activityInBackgroundLock = Any()
- private var activityInBackground: Boolean = true
- get() = synchronized(_activityInBackgroundLock) {
- field
- }
- set(value) = synchronized(_activityInBackgroundLock) {
- field = value
- }
+ private var _pausedLock = Any()
+ private var paused: Boolean = false
+ get() = synchronized(_pausedLock) {
+ field
+ }
+ set(value) = synchronized(_pausedLock) {
+ field = value
+ }
+ private val pausePostLogsCondition = ConditionVariable()
- private val activityInBackgroundCondition = ConditionVariable()
+ // must be synchronized
+ private val logsLock = ReentrantLock()
+ private val pendingLogsFullCondition = logsLock.newCondition()
+ private var logs = FixedCircularArray(initialCapacity, INITIAL_LOG_SIZE)
+ private var pendingLogs = FixedCircularArray(initialCapacity, INITIAL_LOG_SIZE)
+ private val filters = mutableMapOf()
+ private val exclusions = mutableMapOf()
- private val lifeCycleObserver = object : DefaultLifecycleObserver {
- override fun onResume(owner: LifecycleOwner) {
- Logger.debug(Logcat::class, "onActivityInForeground")
+ private var _activityInBackgroundLock = Any()
+ private var activityInBackground: Boolean = true
+ get() = synchronized(_activityInBackgroundLock) {
+ field
+ }
+ set(value) = synchronized(_activityInBackgroundLock) {
+ field = value
+ }
- if (!paused) {
- Logger.debug(Logcat::class, "Posting pending logs")
- postPendingLogs()
- }
+ private val activityInBackgroundCondition = ConditionVariable()
- logsLock.withLock {
- activityInBackground = false
- }
- activityInBackgroundCondition.open()
- }
+ private val lifeCycleObserver = object : DefaultLifecycleObserver {
+ override fun onResume(owner: LifecycleOwner) {
+ Logger.debug(Logcat::class, "onActivityInForeground")
- override fun onPause(owner: LifecycleOwner) {
- Logger.debug(Logcat::class, "onActivityInBackground")
- logsLock.withLock {
- activityInBackground = true
- }
- }
+ if (!paused) {
+ Logger.debug(Logcat::class, "Posting pending logs")
+ postPendingLogs()
+ }
+
+ logsLock.withLock {
+ activityInBackground = false
+ }
+ activityInBackgroundCondition.open()
}
- @Volatile
- private var isProcessAlive = false
+ override fun onPause(owner: LifecycleOwner) {
+ Logger.debug(Logcat::class, "onActivityInBackground")
+ logsLock.withLock {
+ activityInBackground = true
+ }
+ }
+ }
- private fun postPendingLogs() {
- logsLock.withLock {
- if (pendingLogs.isNotEmpty()) {
- logs.add(pendingLogs)
+ @Volatile
+ private var isProcessAlive = false
- val filteredLogs = pendingLogs.filter { e ->
- !exclusions.values.any { it.apply(e) } && filters.values.all { it.apply(e) }
- }
+ private fun postPendingLogs() {
+ logsLock.withLock {
+ if (pendingLogs.isNotEmpty()) {
+ logs.add(pendingLogs)
- if (filteredLogs.isNotEmpty()) {
- handler.post { listeners.forEach { it.onReceivedLogs(filteredLogs) } }
- }
+ val filteredLogs = pendingLogs.filter { e ->
+ !exclusions.values.any { it.apply(e) } && filters.values.all { it.apply(e) }
+ }
- pendingLogs.clear()
- pendingLogsFullCondition.signal()
- }
+ if (filteredLogs.isNotEmpty()) {
+ handler.post { listeners.forEach { it.onReceivedLogs(filteredLogs) } }
}
+
+ pendingLogs.clear()
+ pendingLogsFullCondition.signal()
+ }
}
+ }
- fun start() {
- if (logcatProcess == null) {
- paused = false
- exitCode = -1
- threadLogcat = thread(block = { runLogcat() }, name = "logcat")
- } else {
- Logger.info(Logcat::class, "Logcat is already running!")
- }
+ fun start() {
+ if (logcatProcess == null) {
+ paused = false
+ exitCode = -1
+ threadLogcat = thread(block = { runLogcat() }, name = "logcat")
+ } else {
+ Logger.info(Logcat::class, "Logcat is already running!")
}
+ }
- fun stop() {
- logcatProcess?.destroy()
+ fun stop() {
+ logcatProcess?.destroy()
- try {
- threadLogcat?.join(5000)
- } catch (e: InterruptedException) {
- }
+ try {
+ threadLogcat?.join(5000)
+ } catch (e: InterruptedException) {
+ }
- threadLogcat = null
- logcatProcess = null
+ threadLogcat = null
+ logcatProcess = null
- logsLock.withLock {
- logs.clear()
- pendingLogs.clear()
- }
+ logsLock.withLock {
+ logs.clear()
+ pendingLogs.clear()
}
+ }
- fun clearLogs(onClear: (() -> Unit)? = null) = withPaused {
- logsLock.withLock {
- logs.clear()
- pendingLogs.clear()
- }
-
- onClear?.invoke()
+ fun clearLogs(onClear: (() -> Unit)? = null) = withPaused {
+ logsLock.withLock {
+ logs.clear()
+ pendingLogs.clear()
}
- fun restart() {
- stop()
- if (!exitSuccess()) {
- Logger.error(Logcat::class, "Exit failure: $exitCode")
- }
- start()
+ onClear?.invoke()
+ }
+
+ fun restart() {
+ stop()
+ if (!exitSuccess()) {
+ Logger.error(Logcat::class, "Exit failure: $exitCode")
}
+ start()
+ }
- fun exitSuccess() = exitCode == 0
+ fun exitSuccess() = exitCode == 0
- fun isRunning() = isProcessAlive
+ fun isRunning() = isProcessAlive
- private inline fun withPaused(block: () -> T): T {
- val wasPaused = paused
- pause()
- val result = block()
- if (!wasPaused) {
- resume()
- }
- return result
+ private inline fun withPaused(block: () -> T): T {
+ val wasPaused = paused
+ pause()
+ val result = block()
+ if (!wasPaused) {
+ resume()
}
+ return result
+ }
- fun addEventListener(listener: LogsReceivedListener) = withPaused {
- logsLock.withLock {
- listeners += listener
- }
+ fun addEventListener(listener: LogsReceivedListener) = withPaused {
+ logsLock.withLock {
+ listeners += listener
}
+ }
- fun removeEventListener(listener: LogsReceivedListener) = withPaused {
- logsLock.withLock {
- listeners -= listener
- }
+ fun removeEventListener(listener: LogsReceivedListener) = withPaused {
+ logsLock.withLock {
+ listeners -= listener
}
+ }
- fun clearEventListeners() = withPaused {
- logsLock.withLock {
- listeners.clear()
- }
+ fun clearEventListeners() = withPaused {
+ logsLock.withLock {
+ listeners.clear()
}
+ }
- fun getLogsFiltered(): List {
- logsLock.withLock {
- return if (exclusions.isEmpty() && filters.isEmpty()) {
- logs.toList()
- } else {
- logs.filter { log ->
- !exclusions.values.any { it.apply(log) } && filters.values.all { it.apply(log) }
- }
- }
+ fun getLogsFiltered(): List {
+ logsLock.withLock {
+ return if (exclusions.isEmpty() && filters.isEmpty()) {
+ logs.toList()
+ } else {
+ logs.filter { log ->
+ !exclusions.values.any { it.apply(log) } && filters.values.all { it.apply(log) }
}
+ }
}
+ }
- fun addExclusion(name: String, filter: Filter) {
- logsLock.withLock {
- exclusions[name] = filter
- }
+ fun addExclusion(
+ name: String,
+ filter: Filter
+ ) {
+ logsLock.withLock {
+ exclusions[name] = filter
}
+ }
- fun removeExclusion(name: String) {
- logsLock.withLock {
- exclusions.remove(name)
- }
+ fun removeExclusion(name: String) {
+ logsLock.withLock {
+ exclusions.remove(name)
}
+ }
- fun clearExclusions() {
- logsLock.withLock {
- exclusions.clear()
- }
+ fun clearExclusions() {
+ logsLock.withLock {
+ exclusions.clear()
}
+ }
- fun addFilter(name: String, filter: Filter) {
- logsLock.withLock {
- filters[name] = filter
- }
+ fun addFilter(
+ name: String,
+ filter: Filter
+ ) {
+ logsLock.withLock {
+ filters[name] = filter
}
+ }
- fun removeFilter(name: String) {
- logsLock.withLock {
- filters.remove(name)
- }
+ fun removeFilter(name: String) {
+ logsLock.withLock {
+ filters.remove(name)
}
+ }
- fun clearFilters(exclude: String? = null) {
- logsLock.withLock {
- val it = filters.iterator()
- while (it.hasNext()) {
- if (it.next().key != exclude) {
- it.remove()
- }
- }
+ fun clearFilters(exclude: String? = null) {
+ logsLock.withLock {
+ val it = filters.iterator()
+ while (it.hasNext()) {
+ if (it.next().key != exclude) {
+ it.remove()
}
+ }
}
+ }
- fun pause() {
- paused = true
- }
+ fun pause() {
+ paused = true
+ }
- fun resume() {
- if (paused) {
- paused = false
- pausePostLogsCondition.open()
- pollCondition.open()
- }
+ fun resume() {
+ if (paused) {
+ paused = false
+ pausePostLogsCondition.open()
+ pollCondition.open()
}
+ }
- fun startRecording() {
- logsLock.withLock {
- recordStartIndex = logs.size - 1
- }
+ fun startRecording() {
+ logsLock.withLock {
+ recordStartIndex = logs.size - 1
}
+ }
- fun stopRecording(): List {
- logsLock.withLock {
- val result = mutableListOf()
- if (recordStartIndex >= 0) {
- for (i in recordStartIndex until logs.size) {
- result += logs[i]
- }
- }
- recordStartIndex = -1
- return result.filter { log -> filters.values.all { it.apply(log) } }
+ fun stopRecording(): List {
+ logsLock.withLock {
+ val result = mutableListOf()
+ if (recordStartIndex >= 0) {
+ for (i in recordStartIndex until logs.size) {
+ result += logs[i]
}
+ }
+ recordStartIndex = -1
+ return result.filter { log -> filters.values.all { it.apply(log) } }
}
+ }
- fun bind(activity: AppCompatActivity?) {
- activity?.lifecycle?.addObserver(lifeCycleObserver)
- }
+ fun bind(activity: AppCompatActivity?) {
+ activity?.lifecycle?.addObserver(lifeCycleObserver)
+ }
- fun unbind(activity: AppCompatActivity?) {
- activity?.lifecycle?.removeObserver(lifeCycleObserver)
- }
+ fun unbind(activity: AppCompatActivity?) {
+ activity?.lifecycle?.removeObserver(lifeCycleObserver)
+ }
- override fun close() {
- stop()
- logsLock.withLock {
- listeners.clear()
- }
+ override fun close() {
+ stop()
+ logsLock.withLock {
+ listeners.clear()
}
+ }
- fun setPollInterval(interval: Long) {
- this.pollInterval = interval
- pollCondition.open()
- }
+ fun setPollInterval(interval: Long) {
+ this.pollInterval = interval
+ pollCondition.open()
+ }
- private fun runLogcat() {
- val buffers = mutableListOf