mirror of
https://github.com/square/leakcanary.git
synced 2026-03-13 08:24:22 +08:00
Update minimum SDK to API 21 (Android 5.0 Lollipop)
This commit migrates the entire LeakCanary project from minimum SDK API 14 to API 21, enabling modern Android features and simplifying the codebase. API 21 corresponds to Android 5.0 (Lollipop), released in 2014, providing over a decade of Android compatibility while enabling Material Design and modern development practices. Changes included: ## Build Configuration - Update androidMinSdk from "14" to "21" in version catalog - Standardize all modules to use libs.versions.androidMinSdk reference - Update hardcoded minSdk values in sample and library modules to use TOML ## GitHub Actions / CI - Remove API 16 and API 19 from test matrix - Streamline CI to test only API 21+ (21, 23, 26, 31, 33, 34) - Improve CI efficiency with fewer test combinations ## Test Infrastructure - Upgrade androidx.test:orchestrator from 1.4.2 to 1.5.0 - Modernize test assertions to use direct assertThat(Throwable) calls - Remove API 16 workaround comments and code ## Code Modernization - Remove obsolete SDK version checks (9 lint issues resolved) - Simplify file provider methods for API 21+ only - Update notification builder usage for modern APIs - Remove legacy view tree observer workarounds - Modernize layout and rendering code ## Resources & UI - Migrate drawable resources from selectors to Material ripple effects - Update themes from Holo to Material Design (API 21+ requirement) - Merge and remove obsolete -v21 resource folders - Modernize button and touch feedback using ripples ## Benefits - Access to Material Design components and APIs - Simplified codebase with removed legacy compatibility code - Enhanced test infrastructure with latest orchestrator - Improved user experience with modern UI patterns - Faster CI builds with fewer test matrix combinations ## Compatibility All modules now consistently require Android 5.0 (API 21, 2014) or higher. This affects minSdk but does not change the public API surface. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
9
.github/workflows/main.yml
vendored
9
.github/workflows/main.yml
vendored
@@ -42,15 +42,6 @@ jobs:
|
||||
- 31
|
||||
- 33
|
||||
- 34
|
||||
include:
|
||||
- arch: x86
|
||||
api-level: 16
|
||||
target: google_apis
|
||||
channel: stable
|
||||
- arch: x86
|
||||
api-level: 19
|
||||
target: google_apis
|
||||
channel: stable
|
||||
steps:
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
|
||||
@@ -23,7 +23,7 @@ androidXJunit = "1.1.5"
|
||||
workManager = "2.7.0"
|
||||
detekt = "1.23.8"
|
||||
hilt = "2.53"
|
||||
androidMinSdk = "14"
|
||||
androidMinSdk = "21"
|
||||
androidCompileSdk = "35"
|
||||
|
||||
[libraries]
|
||||
@@ -60,7 +60,7 @@ androidX-test-monitor = { module = "androidx.test:monitor", version = "1.6.1" }
|
||||
androidX-test-rules = { module = "androidx.test:rules", version.ref = "androidXTest" }
|
||||
# Exposed transitively, avoid increasing
|
||||
androidX-test-runner = { module = "androidx.test:runner", version = "1.5.2" }
|
||||
androidX-test-orchestrator = { module = "androidx.test:orchestrator", version = "1.4.2" } # 1.5.0+ requires API 21+, keeping at 1.4.2 for API 16 compatibility
|
||||
androidX-test-orchestrator = { module = "androidx.test:orchestrator", version = "1.5.0" }
|
||||
androidX-test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.5.1" }
|
||||
androidX-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidXJunit" }
|
||||
androidX-test-junitKtx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidXJunit" }
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.StrictMode
|
||||
@@ -499,7 +498,7 @@ internal class LeakCanaryFileProvider : ContentProvider() {
|
||||
if (externalCacheDirs.isNotEmpty()) {
|
||||
target = externalCacheDirs[0]
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && TAG_EXTERNAL_MEDIA == tag) {
|
||||
} else if (TAG_EXTERNAL_MEDIA == tag) {
|
||||
val externalMediaDirs = context.externalMediaDirs
|
||||
if (externalMediaDirs.isNotEmpty()) {
|
||||
target = externalMediaDirs[0]
|
||||
@@ -519,19 +518,11 @@ internal class LeakCanaryFileProvider : ContentProvider() {
|
||||
context: Context,
|
||||
type: String?
|
||||
): Array<File> {
|
||||
return if (Build.VERSION.SDK_INT >= 19) {
|
||||
context.getExternalFilesDirs(type)
|
||||
} else {
|
||||
arrayOf(context.getExternalFilesDir(type)!!)
|
||||
}
|
||||
return context.getExternalFilesDirs(type)
|
||||
}
|
||||
|
||||
private fun getExternalCacheDirs(context: Context): Array<File> {
|
||||
return if (Build.VERSION.SDK_INT >= 19) {
|
||||
context.externalCacheDirs
|
||||
} else {
|
||||
arrayOf(context.externalCacheDir!!)
|
||||
}
|
||||
return context.externalCacheDirs
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Build.VERSION_CODES.JELLY_BEAN
|
||||
import android.os.Build.VERSION_CODES.O
|
||||
import com.squareup.leakcanary.core.R
|
||||
import leakcanary.LeakCanary
|
||||
@@ -130,11 +129,6 @@ internal object Notifications {
|
||||
builder.setGroup(type.name)
|
||||
}
|
||||
|
||||
return if (SDK_INT < JELLY_BEAN) {
|
||||
@Suppress("DEPRECATION")
|
||||
builder.notification
|
||||
} else {
|
||||
builder.build()
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.os.Build
|
||||
import android.text.Layout
|
||||
import com.squareup.leakcanary.core.R
|
||||
import kotlin.math.max
|
||||
@@ -107,7 +106,7 @@ internal abstract class SquigglySpanRenderer(context: Context) {
|
||||
|
||||
private fun Layout.getLineBottomWithoutSpacing(line: Int): Int {
|
||||
val lineBottom = getLineBottom(line)
|
||||
val lastLineSpacingNotAdded = Build.VERSION.SDK_INT >= 19
|
||||
val lastLineSpacingNotAdded = true
|
||||
val isLastLine = line == lineCount - 1
|
||||
|
||||
val lineBottomWithoutSpacing: Int
|
||||
|
||||
@@ -6,7 +6,7 @@ import kotlin.concurrent.getOrSet
|
||||
|
||||
/**
|
||||
* Similar to the more generic use() for Closable.
|
||||
* Cursor started implementing Closable in API 16.
|
||||
* Cursor implements Closable on all supported API levels (21+).
|
||||
*/
|
||||
internal inline fun <R> Cursor.use(block: (Cursor) -> R): R {
|
||||
var exception: Throwable? = null
|
||||
|
||||
@@ -2,8 +2,6 @@ package leakcanary.internal.activity.screen
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.os.Environment
|
||||
import android.os.Environment.DIRECTORY_DOWNLOADS
|
||||
import android.view.View
|
||||
@@ -60,11 +58,7 @@ internal class RenderHeapDumpScreen(
|
||||
imageView.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
||||
viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
} else {
|
||||
viewTreeObserver.removeGlobalOnLayoutListener(this)
|
||||
}
|
||||
viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package leakcanary.internal.navigation
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.Menu
|
||||
@@ -155,12 +154,10 @@ internal abstract class NavigatingActivity : Activity() {
|
||||
|
||||
private fun screenUpdated() {
|
||||
invalidateOptionsMenu()
|
||||
if (SDK_INT >= 18) {
|
||||
actionBar?.run {
|
||||
val goBack = backstack.size > 0
|
||||
val indicator = if (goBack) 0 else android.R.drawable.ic_menu_close_clear_cancel
|
||||
setHomeAsUpIndicator(indicator)
|
||||
}
|
||||
actionBar?.run {
|
||||
val goBack = backstack.size > 0
|
||||
val indicator = if (goBack) 0 else android.R.drawable.ic_menu_close_clear_cancel
|
||||
setHomeAsUpIndicator(indicator)
|
||||
}
|
||||
onNewScreen(currentScreen)
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_gray" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_gray" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true" android:state_pressed="false">
|
||||
<color android:color="?android:attr/colorControlHighlight" />
|
||||
</item>
|
||||
<item>
|
||||
<color android:color="@android:color/transparent" />
|
||||
</item>
|
||||
</selector>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/leak_canary_yellow_button_pressed">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_yellow_button" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_yellow_button" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/leak_canary_gray_6f">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_gray_3f" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_gray_3f" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/leak_canary_tab_selector_ripple" />
|
||||
</selector>
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_gray_3f" />
|
||||
<solid android:color="@color/leak_canary_gray" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
@@ -12,4 +13,4 @@
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</ripple>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true" android:state_pressed="false">
|
||||
<color android:color="@color/leak_canary_gray_3f" />
|
||||
<color android:color="?android:attr/colorControlHighlight" />
|
||||
</item>
|
||||
<item>
|
||||
<color android:color="@android:color/transparent" />
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_yellow_button_pressed" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/leak_canary_yellow_button_pressed">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_yellow_button" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</ripple>
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_gray_6f" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/leak_canary_gray_6f">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/leak_canary_gray_3f" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</ripple>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="false" android:drawable="@android:color/transparent" />
|
||||
<item android:drawable="@color/leak_canary_gray_darkest_25p" />
|
||||
</selector>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/leak_canary_gray_darkest_25p">
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</ripple>
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
<ripple
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/leak_canary_gray_darkest_25p"
|
||||
/>
|
||||
/>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 Square, Inc.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<style name="leak_canary_LeakCanary.Base" parent="android:Theme.Material">
|
||||
<item name="android:windowBackground">@color/leak_canary_background_color</item>
|
||||
<item name="android:actionBarStyle">@style/leak_canary_Widget.ActionBar</item>
|
||||
</style>
|
||||
|
||||
<style name="leak_canary_Widget.ActionBar" parent="android:Widget.Material.ActionBar">
|
||||
<item name="android:background">@color/leak_canary_background_color</item>
|
||||
<item name="android:elevation">0dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -15,14 +15,14 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<style name="leak_canary_LeakCanary.Base" parent="android:Theme.Holo">
|
||||
<style name="leak_canary_LeakCanary.Base" parent="android:Theme.Material">
|
||||
<item name="android:windowBackground">@color/leak_canary_background_color</item>
|
||||
<item name="android:actionBarStyle">@style/leak_canary_Widget.ActionBar</item>
|
||||
</style>
|
||||
|
||||
<style name="leak_canary_Widget.ActionBar" parent="android:Widget.Holo.ActionBar">
|
||||
<style name="leak_canary_Widget.ActionBar" parent="android:Widget.Material.ActionBar">
|
||||
<item name="android:background">@color/leak_canary_background_color</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:elevation">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="leak_canary_Theme.Transparent" parent="android:Theme">
|
||||
|
||||
@@ -64,19 +64,16 @@ class TestDescriptionHolderTest {
|
||||
|
||||
@Test
|
||||
fun testDescriptionThrowsBeforeClass() {
|
||||
// On API 16, assertThat(Throwable) causes a TypeComparators class init failure.
|
||||
assertThat(beforeClassThrowable == null).isFalse()
|
||||
assertThat(beforeClassThrowable).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDescriptionThrowsBeforeOuterEvaluate() {
|
||||
// On API 16, assertThat(Throwable) causes a TypeComparators class init failure.
|
||||
assertThat(outerRule.beforeEvaluateThrowable == null).isFalse()
|
||||
assertThat(outerRule.beforeEvaluateThrowable).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDescriptionDoesNotThrowBeforeInnerEvaluate() {
|
||||
// On API 16, assertThat(Throwable) causes a TypeComparators class init failure.
|
||||
assertThat(innerRule.beforeEvaluateThrowable == null).isTrue()
|
||||
assertThat(innerRule.beforeEvaluateThrowable).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ android {
|
||||
resourcePrefix = "leak_canary_"
|
||||
compileSdk = libs.versions.androidCompileSdk.get().toInt()
|
||||
defaultConfig {
|
||||
minSdk = 16
|
||||
minSdk = libs.versions.androidMinSdk.get().toInt()
|
||||
buildConfigField("String", "LIBRARY_VERSION", "\"${rootProject.property("VERSION_NAME")}\"")
|
||||
buildConfigField("String", "GIT_SHA", "\"${gitSha()}\"")
|
||||
consumerProguardFiles("consumer-proguard-rules.pro")
|
||||
|
||||
@@ -64,7 +64,7 @@ interface ProcessInfo {
|
||||
override fun availableRam(context: Context): AvailableRam {
|
||||
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
|
||||
if (SDK_INT >= 19 && activityManager.isLowRamDevice) {
|
||||
if (activityManager.isLowRamDevice) {
|
||||
return LowRamDevice
|
||||
} else {
|
||||
activityManager.getMemoryInfo(memoryInfo)
|
||||
@@ -91,17 +91,7 @@ interface ProcessInfo {
|
||||
val myPid = Process.myPid()
|
||||
val ticksAtProcessStart = readProcessStartTicks(myPid)
|
||||
|
||||
val ticksPerSecond = if (SDK_INT >= 21) {
|
||||
Os.sysconf(OsConstants._SC_CLK_TCK)
|
||||
} else {
|
||||
val tckConstant = try {
|
||||
Class.forName("android.system.OsConstants").getField("_SC_CLK_TCK").getInt(null)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
Class.forName("libcore.io.OsConstants").getField("_SC_CLK_TCK").getInt(null)
|
||||
}
|
||||
val os = Class.forName("libcore.io.Libcore").getField("os").get(null)!!
|
||||
os::class.java.getMethod("sysconf", Integer.TYPE).invoke(os, tckConstant) as Long
|
||||
}
|
||||
val ticksPerSecond = Os.sysconf(OsConstants._SC_CLK_TCK)
|
||||
return ticksAtProcessStart * 1000 / ticksPerSecond
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ android {
|
||||
compileSdk = libs.versions.androidCompileSdk.get().toInt()
|
||||
defaultConfig {
|
||||
targetSdk = libs.versions.androidCompileSdk.get().toInt()
|
||||
minSdk = 18
|
||||
minSdk = libs.versions.androidMinSdk.get().toInt()
|
||||
}
|
||||
buildFeatures.buildConfig = false
|
||||
namespace = "com.squareup.leakcanary.android.uiautomator"
|
||||
|
||||
@@ -49,7 +49,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.leakcanary"
|
||||
minSdk = 16
|
||||
minSdk = libs.versions.androidMinSdk.get().toInt()
|
||||
targetSdk = libs.versions.androidCompileSdk.get().toInt()
|
||||
|
||||
versionCode = 1
|
||||
|
||||
Reference in New Issue
Block a user