From 271124dc9c6fe6bb10b20f75e5e90132c2648b46 Mon Sep 17 00:00:00 2001 From: Andre K Date: Tue, 30 May 2023 18:30:46 -0300 Subject: [PATCH] refactor: implement CompatExtensions (#641) updates deprecated methods, classes, and flags introduced in Android SDK 33, while ensuring compatibility with previous Android versions --- .../java/com/geeksville/mesh/DataPacket.kt | 5 +- .../java/com/geeksville/mesh/MainActivity.kt | 13 ++--- .../com/geeksville/mesh/model/BTScanModel.kt | 7 ++- .../repository/usb/UsbBroadcastReceiver.kt | 5 +- .../mesh/service/MeshServiceNotifications.kt | 38 +++++++-------- .../geeksville/mesh/ui/SettingsFragment.kt | 29 +++++------ .../geeksville/mesh/util/CompatExtensions.kt | 48 +++++++++++++++++++ 7 files changed, 100 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt diff --git a/app/src/main/java/com/geeksville/mesh/DataPacket.kt b/app/src/main/java/com/geeksville/mesh/DataPacket.kt index 8be25e317..6e991aab4 100644 --- a/app/src/main/java/com/geeksville/mesh/DataPacket.kt +++ b/app/src/main/java/com/geeksville/mesh/DataPacket.kt @@ -2,6 +2,7 @@ package com.geeksville.mesh import android.os.Parcel import android.os.Parcelable +import com.geeksville.mesh.util.readParcelableCompat import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable @@ -77,7 +78,7 @@ data class DataPacket( parcel.readString(), parcel.readLong(), parcel.readInt(), - parcel.readParcelable(MessageStatus::class.java.classLoader), + parcel.readParcelableCompat(MessageStatus::class.java.classLoader), parcel.readInt(), parcel.readInt(), ) @@ -138,7 +139,7 @@ data class DataPacket( from = parcel.readString() time = parcel.readLong() id = parcel.readInt() - status = parcel.readParcelable(MessageStatus::class.java.classLoader) + status = parcel.readParcelableCompat(MessageStatus::class.java.classLoader) hopLimit = parcel.readInt() channel = parcel.readInt() } diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 0d52760f4..99781da58 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -41,8 +41,10 @@ import com.geeksville.mesh.repository.radio.SerialInterface import com.geeksville.mesh.service.* import com.geeksville.mesh.ui.* import com.geeksville.mesh.util.Exceptions +import com.geeksville.mesh.util.getParcelableExtraCompat import com.geeksville.mesh.util.LanguageUtils import com.geeksville.mesh.util.exceptionReporter +import com.geeksville.mesh.util.getPackageInfoCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator @@ -264,7 +266,7 @@ class MainActivity : AppCompatActivity(), Logging { } UsbManager.ACTION_USB_DEVICE_ATTACHED -> { - val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) + val device: UsbDevice? = intent.getParcelableExtraCompat(UsbManager.EXTRA_DEVICE) if (device != null) { debug("Handle USB device attached! $device") usbDevice = device @@ -475,12 +477,11 @@ class MainActivity : AppCompatActivity(), Logging { when (intent.action) { MeshService.ACTION_NODE_CHANGE -> { - val info: NodeInfo = - intent.getParcelableExtra(EXTRA_NODEINFO)!! + val info: NodeInfo? = intent.getParcelableExtraCompat(EXTRA_NODEINFO) debug("UI nodechange $info") // We only care about nodes that have user info - info.user?.id?.let { + info?.user?.id?.let { val nodes = model.nodeDB.nodes.value!! + Pair(it, info) model.nodeDB.setNodes(nodes) } @@ -698,7 +699,7 @@ class MainActivity : AppCompatActivity(), Logging { return true } - val handler: Handler by lazy { + private val handler: Handler by lazy { Handler(Looper.getMainLooper()) } @@ -791,7 +792,7 @@ class MainActivity : AppCompatActivity(), Logging { private fun getVersionInfo() { try { - val packageInfo: PackageInfo = packageManager.getPackageInfo(packageName, 0) + val packageInfo: PackageInfo = packageManager.getPackageInfoCompat(packageName, 0) val versionName = packageInfo.versionName Toast.makeText(this, versionName, Toast.LENGTH_LONG).show() } catch (e: PackageManager.NameNotFoundException) { diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt index 5c0208c8e..d784ecbda 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -320,7 +320,12 @@ class BTScanModel @Inject constructor( associationRequest(), @SuppressLint("NewApi") object : CompanionDeviceManager.Callback() { - override fun onDeviceFound(chooserLauncher: IntentSender) { + @Deprecated("Deprecated in Java", ReplaceWith("onAssociationPending(intentSender)")) + override fun onDeviceFound(intentSender: IntentSender) { + onAssociationPending(intentSender) + } + + override fun onAssociationPending(chooserLauncher: IntentSender) { debug("CompanionDeviceManager - device found") _spinner.value = false chooserLauncher.let { diff --git a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt index 47f7a2b29..fafeed7c2 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt @@ -8,6 +8,7 @@ import android.hardware.usb.UsbDevice import android.hardware.usb.UsbManager import com.geeksville.mesh.android.Logging import com.geeksville.mesh.util.exceptionReporter +import com.geeksville.mesh.util.getParcelableExtraCompat import javax.inject.Inject /** @@ -24,7 +25,9 @@ class UsbBroadcastReceiver @Inject constructor( } override fun onReceive(context: Context, intent: Intent) = exceptionReporter { - val deviceName: String = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)?.deviceName ?: "unknown" + val device: UsbDevice? = intent.getParcelableExtraCompat(UsbManager.EXTRA_DEVICE) + val deviceName: String = device?.deviceName ?: "unknown" + when (intent.action) { UsbManager.ACTION_USB_DEVICE_DETACHED -> { debug("USB device '$deviceName' was detached") diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt index c9b6ac6b8..e473c1e1a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt @@ -7,17 +7,18 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.Bitmap -import android.graphics.Canvas import android.graphics.Color import android.media.AudioAttributes import android.media.RingtoneManager import android.os.Build import androidx.annotation.RequiresApi +import androidx.appcompat.content.res.AppCompatResources import androidx.core.app.NotificationCompat -import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap import com.geeksville.mesh.MainActivity import com.geeksville.mesh.R import com.geeksville.mesh.android.notificationManager +import com.geeksville.mesh.util.PendingIntentCompat import java.io.Closeable @@ -105,28 +106,20 @@ class MeshServiceNotifications( ) private val openAppIntent: PendingIntent by lazy { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0) - } else { - PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE) - } + PendingIntent.getActivity( + context, + 0, + Intent(context, MainActivity::class.java), + PendingIntentCompat.FLAG_IMMUTABLE + ) } /** * Generate a bitmap from a vector drawable (even on old builds) * https://stackoverflow.com/questions/33696488/getting-bitmap-from-vector-drawable */ - private fun getBitmapFromVectorDrawable(drawableId: Int): Bitmap { - val drawable = ContextCompat.getDrawable(context, drawableId)!! - val bitmap = Bitmap.createBitmap( - drawable.intrinsicWidth, - drawable.intrinsicHeight, Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(bitmap) - drawable.setBounds(0, 0, canvas.width, canvas.height) - drawable.draw(canvas) - return bitmap - } + private fun getBitmapFromVectorDrawable(drawableId: Int): Bitmap? = + AppCompatResources.getDrawable(context, drawableId)?.toBitmap() private fun commonBuilder(channel: String): NotificationCompat.Builder { val builder = NotificationCompat.Builder(context, channel) @@ -142,10 +135,13 @@ class MeshServiceNotifications( // Newer androids also support a 'large' icon // We delay making this bitmap until we know we need it - if (largeIcon == null) - largeIcon = getBitmapFromVectorDrawable(R.mipmap.ic_launcher2) + largeIcon = largeIcon ?: getBitmapFromVectorDrawable(R.mipmap.ic_launcher2) - builder.setSmallIcon(if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) R.drawable.app_icon_novect else R.drawable.app_icon) // vector form icons don't work reliably on older androids + builder.setSmallIcon( + // vector form icons don't work reliably on older androids + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) R.drawable.app_icon_novect + else R.drawable.app_icon + ) .setLargeIcon(largeIcon) } return builder diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 3dacfcb4a..9d438cf8b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -3,7 +3,6 @@ package com.geeksville.mesh.ui import android.annotation.SuppressLint import android.app.PendingIntent import android.bluetooth.BluetoothDevice -import android.companion.CompanionDeviceManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -48,9 +47,12 @@ import com.geeksville.mesh.repository.radio.MockInterface import com.geeksville.mesh.repository.usb.UsbRepository import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.SoftwareUpdateService +import com.geeksville.mesh.util.CompanionDeviceManagerCompat +import com.geeksville.mesh.util.PendingIntentCompat import com.geeksville.mesh.util.anonymize import com.geeksville.mesh.util.exceptionReporter import com.geeksville.mesh.util.exceptionToSnackbar +import com.geeksville.mesh.util.getParcelableExtraCompat import com.geeksville.mesh.util.onEditorAction import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar @@ -245,10 +247,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { ActivityResultContracts.StartIntentSenderForResult() ) { it.data - ?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE) - ?.let { device -> - onSelected(BTScanModel.BLEDeviceListEntry(device)) - } + ?.getParcelableExtraCompat(CompanionDeviceManagerCompat.EXTRA_DEVICE) + ?.let { device -> onSelected(BTScanModel.BLEDeviceListEntry(device)) } } val requestBackgroundAndCheckLauncher = @@ -558,8 +558,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { override fun onReceive(context: Context, intent: Intent) { if (BTScanModel.ACTION_USB_PERMISSION == intent.action) { - val device: UsbDevice = - intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)!! + val device: UsbDevice? = + intent.getParcelableExtraCompat(UsbManager.EXTRA_DEVICE) + val deviceName: String = device?.deviceName ?: "unknown" if (intent.getBooleanExtra( UsbManager.EXTRA_PERMISSION_GRANTED, @@ -569,7 +570,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { info("User approved USB access") changeDeviceAddress(it.fullAddress) } else { - errormsg("USB permission denied for device $device") + errormsg("USB permission denied for device $deviceName") } } // We don't need to stay registered @@ -577,12 +578,12 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - val permissionIntent = - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) { - PendingIntent.getBroadcast(activity, 0, Intent(BTScanModel.ACTION_USB_PERMISSION), 0) - } else { - PendingIntent.getBroadcast(activity, 0, Intent(BTScanModel.ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE) - } + val permissionIntent = PendingIntent.getBroadcast( + activity, + 0, + Intent(BTScanModel.ACTION_USB_PERMISSION), + PendingIntentCompat.FLAG_IMMUTABLE + ) val filter = IntentFilter(BTScanModel.ACTION_USB_PERMISSION) requireActivity().registerReceiver(usbReceiver, filter) requireContext().usbManager.requestPermission(it.usb.device, permissionIntent) diff --git a/app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt b/app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt new file mode 100644 index 000000000..00d1f0f8b --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt @@ -0,0 +1,48 @@ +package com.geeksville.mesh.util + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.companion.CompanionDeviceManager +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Parcel +import android.os.Parcelable +import androidx.core.content.IntentCompat +import androidx.core.os.ParcelCompat + +object PendingIntentCompat { + val FLAG_MUTABLE = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE + } else { + 0 + } + + val FLAG_IMMUTABLE = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + PendingIntent.FLAG_IMMUTABLE + } else { + 0 + } +} + +object CompanionDeviceManagerCompat { + @SuppressLint("InlinedApi") + val EXTRA_DEVICE = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + CompanionDeviceManager.EXTRA_ASSOCIATION + } else { + @Suppress("DEPRECATION") CompanionDeviceManager.EXTRA_DEVICE + } +} + +inline fun Parcel.readParcelableCompat(loader: ClassLoader?): T? = + ParcelCompat.readParcelable(this, loader, T::class.java) + +inline fun Intent.getParcelableExtraCompat(key: String?): T? = + IntentCompat.getParcelableExtra(this, key, T::class.java) + +fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int = 0): PackageInfo = + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags.toLong())) + } else { + @Suppress("DEPRECATION") getPackageInfo(packageName, flags) + }