diff --git a/app/build.gradle b/app/build.gradle index 203b12f4f..a12c3b4aa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -145,6 +145,8 @@ dependencies { implementation Deps.mozilla_ui_icons implementation Deps.mozilla_lib_crash + debugImplementation Deps.leakcanary + releaseImplementation Deps.leakcanary_noop testImplementation Deps.junit androidTestImplementation Deps.tools_test_runner diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..f3c91aa4e --- /dev/null +++ b/app/src/debug/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt new file mode 100644 index 000000000..b34ab9ae1 --- /dev/null +++ b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import com.squareup.leakcanary.AndroidHeapDumper +import com.squareup.leakcanary.HeapDumper +import com.squareup.leakcanary.LeakCanary +import com.squareup.leakcanary.internal.LeakCanaryInternals +import org.mozilla.fenix.R.string.pref_key_leakcanary +import org.mozilla.fenix.ext.getPreferenceKey +import java.io.File + +class DebugFenixApplication : FenixApplication() { + + private var heapDumper: ToggleableHeapDumper? = null + + override fun setupLeakCanary() { + val leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this) + val defaultDumper = AndroidHeapDumper(this, leakDirectoryProvider) + heapDumper = ToggleableHeapDumper(this, defaultDumper) + LeakCanary.refWatcher(this) + .heapDumper(heapDumper) + .buildAndInstall() + } + + override fun toggleLeakCanary(newValue: Boolean) { + heapDumper?.enabled = newValue + } + + internal class ToggleableHeapDumper( + private val context: Context, + private val defaultDumper: HeapDumper + ) : HeapDumper { + var prefs: SharedPreferences? = PreferenceManager.getDefaultSharedPreferences(context) + var enabled = prefs?.getBoolean(context.getPreferenceKey(pref_key_leakcanary), true) ?: true + override fun dumpHeap(): File = if (enabled) defaultDumper.dumpHeap() else HeapDumper.RETRY_LATER + } +} diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index d3908dafd..068399fce 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -4,8 +4,10 @@ package org.mozilla.fenix +import android.annotation.SuppressLint import android.app.Application import android.content.Context +import com.squareup.leakcanary.LeakCanary import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -19,7 +21,8 @@ import mozilla.components.support.base.log.sink.AndroidLogSink import org.mozilla.fenix.components.Components import java.io.File -class FenixApplication : Application() { +@SuppressLint("Registered") +open class FenixApplication : Application() { lateinit var fretboard: Fretboard val components by lazy { Components(this) } @@ -28,11 +31,23 @@ class FenixApplication : Application() { super.onCreate() Log.addSink(AndroidLogSink()) + if (LeakCanary.isInAnalyzerProcess(this)) { + return // don't perform extra init in analyzer + } + setupLeakCanary() setupCrashReporting() setupGlean(this) loadExperiments() } + protected open fun setupLeakCanary() { + // no-op, LeakCanary is disabled by default + } + + open fun toggleLeakCanary(newValue: Boolean) { + // no-op, LeakCanary is disabled by default + } + private fun setupGlean(context: Context) { Glean.initialize(context) Glean.setUploadEnabled(BuildConfig.TELEMETRY) diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 220c3cd35..2cc085f38 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -13,8 +13,11 @@ import androidx.navigation.Navigation import androidx.preference.Preference import androidx.preference.Preference.OnPreferenceClickListener import androidx.preference.PreferenceFragmentCompat +import org.mozilla.fenix.BuildConfig +import org.mozilla.fenix.FenixApplication import org.mozilla.fenix.R import org.mozilla.fenix.R.string.pref_key_about +import org.mozilla.fenix.R.string.pref_key_leakcanary import org.mozilla.fenix.R.string.pref_key_make_default_browser import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.requireComponents @@ -39,13 +42,24 @@ class SettingsFragment : PreferenceFragmentCompat() { private fun setupPreferences() { val makeDefaultBrowserKey = context?.getPreferenceKey(pref_key_make_default_browser) val aboutKey = context?.getPreferenceKey(pref_key_about) + val leakKey = context?.getPreferenceKey(pref_key_leakcanary) val preferenceMakeDefaultBrowser = findPreference(makeDefaultBrowserKey) val preferenceAbout = findPreference(aboutKey) + val preferenceLeakCanary = findPreference(leakKey) preferenceMakeDefaultBrowser.onPreferenceClickListener = getClickListenerForMakeDefaultBrowser() preferenceAbout.onPreferenceClickListener = getAboutPageListener() + + preferenceLeakCanary.isVisible = BuildConfig.DEBUG + if (BuildConfig.DEBUG) { + preferenceLeakCanary.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _, newValue -> + (context?.applicationContext as FenixApplication).toggleLeakCanary(newValue as Boolean) + true + } + } } private val defaultClickListener = OnPreferenceClickListener { preference -> diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 0b90d11c2..6f4a5b053 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -18,4 +18,5 @@ pref_key_sign_in pref_key_private_mode pref_key_theme + pref_key_leakcanary diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f2802a0f6..34e7445f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,6 +105,8 @@ Language Data choices + + Leak Canary diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8e96bfc89..90f96bb70 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -64,6 +64,10 @@ android:icon="@drawable/ic_data_collection" android:key="@string/pref_key_data_choices" android:title="@string/preferences_data_choices" /> + entry.value.onComplete() } buses.remove(owner) } @@ -113,8 +113,7 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) { } /** - * getDestroyObservable observes to Lifecycle owner and fires when - * lifecycle.currentState == Lifecycle.State.DESTROYED + * getDestroyObservable observes to Lifecycle owner and fires during ON_STOP */ fun getDestroyObservable(): Observable { return owner.createDestroyObservable() @@ -141,8 +140,6 @@ inline fun LifecycleOwner.getManagedEmitter(): Observer /** * This method returns a destroy observable that can be passed to [org.mozilla.fenix.mvi.UIView]s as needed. - * This is deliberately scoped to the attached [LifecycleOwner]'s [Lifecycle.Event.ON_DESTROY] - * because a viewholder can be reused across adapter destroys. */ inline fun LifecycleOwner?.createDestroyObservable(): Observable { return Observable.create { emitter -> @@ -152,7 +149,7 @@ inline fun LifecycleOwner?.createDestroyObservable(): Observable { return@create } this.lifecycle.addObserver(object : LifecycleObserver { - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun emitDestroy() { if (emitter.isDisposed) { emitter.onNext(kotlin.Unit) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 2378c34e7..841102b19 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -10,6 +10,7 @@ private object Versions { const val rxKotlin = "2.3.0" const val anko = "0.10.8" const val sentry = "1.7.10" + const val leakcanary = "1.6.3" const val androidx_appcompat = "1.1.0-alpha02" const val androidx_constraint_layout = "2.0.0-alpha2" @@ -90,6 +91,8 @@ object Deps { const val mozilla_support_ktx = "org.mozilla.components:support-ktx:${Versions.mozilla_android_components}" const val sentry = "io.sentry:sentry-android:${Versions.sentry}" + const val leakcanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}" + const val leakcanary_noop = "com.squareup.leakcanary:leakcanary-android-no-op:${Versions.leakcanary}" const val junit = "junit:junit:${Versions.junit}" const val tools_test_runner = "com.android.support.test:runner:${Versions.test_tools}"