From 01cb551cbc1709bc7a20a6b6797eae4e844f7a09 Mon Sep 17 00:00:00 2001 From: johan12345 Date: Wed, 7 May 2025 22:57:16 +0200 Subject: [PATCH] Use CarAppService for startActivity instead of CarContext fixes #375 for startActivity and openUrl https://issuetracker.google.com/issues/372055514 Warning: You must update to androidx.car.app:1.7.0-alpha01 or later for the permissions dialog to show up on the phone screen when your app is used on a device running Android 14 or higher. --- .../net/vonforst/evmap/auto/CarAppService.kt | 2 +- .../vonforst/evmap/auto/ChargepriceScreen.kt | 27 +++++++-- .../evmap/auto/ChargerDetailScreen.kt | 22 ++++++-- .../java/net/vonforst/evmap/auto/MapScreen.kt | 2 +- .../vonforst/evmap/auto/SettingsScreens.kt | 55 +++++++++++++------ .../java/net/vonforst/evmap/auto/Utils.kt | 8 ++- 6 files changed, 86 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/net/vonforst/evmap/auto/CarAppService.kt b/app/src/main/java/net/vonforst/evmap/auto/CarAppService.kt index 129ce3ee..d18aafb1 100644 --- a/app/src/main/java/net/vonforst/evmap/auto/CarAppService.kt +++ b/app/src/main/java/net/vonforst/evmap/auto/CarAppService.kt @@ -162,7 +162,7 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver } if (!prefs.privacyAccepted) { screens.add( - AcceptPrivacyScreen(carContext) + AcceptPrivacyScreen(carContext, this) ) } handleACRAIntent(intent)?.let { diff --git a/app/src/main/java/net/vonforst/evmap/auto/ChargepriceScreen.kt b/app/src/main/java/net/vonforst/evmap/auto/ChargepriceScreen.kt index 7cdc8808..d1db6140 100644 --- a/app/src/main/java/net/vonforst/evmap/auto/ChargepriceScreen.kt +++ b/app/src/main/java/net/vonforst/evmap/auto/ChargepriceScreen.kt @@ -3,10 +3,18 @@ package net.vonforst.evmap.auto import androidx.car.app.CarContext import androidx.car.app.CarToast import androidx.car.app.Screen +import androidx.car.app.annotations.ExperimentalCarApi import androidx.car.app.constraints.ConstraintManager import androidx.car.app.hardware.CarHardwareManager import androidx.car.app.hardware.info.Model -import androidx.car.app.model.* +import androidx.car.app.model.Action +import androidx.car.app.model.ActionStrip +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.SectionedItemList +import androidx.car.app.model.Template import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.IconCompat import androidx.lifecycle.lifecycleScope @@ -18,7 +26,16 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import net.vonforst.evmap.R -import net.vonforst.evmap.api.chargeprice.* +import net.vonforst.evmap.api.chargeprice.ChargePrice +import net.vonforst.evmap.api.chargeprice.ChargepriceApi +import net.vonforst.evmap.api.chargeprice.ChargepriceCar +import net.vonforst.evmap.api.chargeprice.ChargepriceChargepointMeta +import net.vonforst.evmap.api.chargeprice.ChargepriceInclude +import net.vonforst.evmap.api.chargeprice.ChargepriceMeta +import net.vonforst.evmap.api.chargeprice.ChargepriceOptions +import net.vonforst.evmap.api.chargeprice.ChargepriceRequest +import net.vonforst.evmap.api.chargeprice.ChargepriceRequestTariffMeta +import net.vonforst.evmap.api.chargeprice.ChargepriceStation import net.vonforst.evmap.api.equivalentPlugTypes import net.vonforst.evmap.api.nameForPlugType import net.vonforst.evmap.api.stringProvider @@ -32,7 +49,9 @@ import retrofit2.HttpException import java.io.IOException import kotlin.math.roundToInt -class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(ctx) { +@ExperimentalCarApi +class ChargepriceScreen(ctx: CarContext, val session: EVMapSession, val charger: ChargeLocation) : + Screen(ctx) { private val prefs = PreferenceDataSource(ctx) private val db = AppDatabase.getInstance(carContext) private val api by lazy { @@ -130,7 +149,7 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c ) ).build() ).setOnClickListener { - openUrl(carContext, ChargepriceApi.getPoiUrl(charger)) + openUrl(carContext, session.cas, ChargepriceApi.getPoiUrl(charger)) }.build() ).build() ) diff --git a/app/src/main/java/net/vonforst/evmap/auto/ChargerDetailScreen.kt b/app/src/main/java/net/vonforst/evmap/auto/ChargerDetailScreen.kt index 2c46eeaa..4fe73d57 100644 --- a/app/src/main/java/net/vonforst/evmap/auto/ChargerDetailScreen.kt +++ b/app/src/main/java/net/vonforst/evmap/auto/ChargerDetailScreen.kt @@ -16,6 +16,7 @@ import androidx.car.app.CarContext import androidx.car.app.CarToast import androidx.car.app.HostException import androidx.car.app.Screen +import androidx.car.app.annotations.ExperimentalCarApi import androidx.car.app.constraints.ConstraintManager import androidx.car.app.model.Action import androidx.car.app.model.ActionStrip @@ -76,7 +77,12 @@ import kotlin.math.roundToInt private const val TAG = "ChargerDetailScreen" -class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Screen(ctx) { +@ExperimentalCarApi +class ChargerDetailScreen( + ctx: CarContext, + val chargerSparse: ChargeLocation, + val session: EVMapSession +) : Screen(ctx) { var charger: ChargeLocation? = null var photo: Bitmap? = null private var availability: ChargeLocationStatus? = null @@ -153,14 +159,20 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : .setTitle(carContext.getString(R.string.auto_prices)) .setOnClickListener { if (prefs.chargepriceNativeIntegration) { - screenManager.push(ChargepriceScreen(carContext, charger)) + screenManager.push( + ChargepriceScreen( + carContext, + session, + charger + ) + ) } else { val intent = Intent( Intent.ACTION_VIEW, Uri.parse(ChargepriceApi.getPoiUrl(charger)) ) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - carContext.startActivity(intent) + session.cas.startActivity(intent) } } .build()) @@ -179,12 +191,12 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Action.Builder() .setTitle(carContext.getString(R.string.open_in_app)) .setOnClickListener(ParkedOnlyOnClickListener.create { - val intent = Intent(carContext, MapsActivity::class.java) + val intent = Intent(session.cas, MapsActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(EXTRA_CHARGER_ID, chargerSparse.id) .putExtra(EXTRA_LAT, chargerSparse.coordinates.lat) .putExtra(EXTRA_LON, chargerSparse.coordinates.lng) - carContext.startActivity(intent) + session.cas.startActivity(intent) CarToast.makeText( carContext, R.string.opened_on_phone, diff --git a/app/src/main/java/net/vonforst/evmap/auto/MapScreen.kt b/app/src/main/java/net/vonforst/evmap/auto/MapScreen.kt index c27dd27e..2d3a8744 100644 --- a/app/src/main/java/net/vonforst/evmap/auto/MapScreen.kt +++ b/app/src/main/java/net/vonforst/evmap/auto/MapScreen.kt @@ -386,7 +386,7 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) : ) setOnClickListener { - screenManager.push(ChargerDetailScreen(carContext, charger)) + screenManager.push(ChargerDetailScreen(carContext, charger, session)) session.mapScreen = null } }.build() diff --git a/app/src/main/java/net/vonforst/evmap/auto/SettingsScreens.kt b/app/src/main/java/net/vonforst/evmap/auto/SettingsScreens.kt index d1a31249..e8388a60 100644 --- a/app/src/main/java/net/vonforst/evmap/auto/SettingsScreens.kt +++ b/app/src/main/java/net/vonforst/evmap/auto/SettingsScreens.kt @@ -79,7 +79,7 @@ class SettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) { ) setBrowsable(true) setOnClickListener { - screenManager.push(DataSettingsScreen(carContext)) + screenManager.push(DataSettingsScreen(carContext, session)) } }.build()) addItem(Row.Builder().apply { @@ -144,7 +144,7 @@ class SettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) { ) .setBrowsable(true) .setOnClickListener { - screenManager.push(AboutScreen(carContext)) + screenManager.push(AboutScreen(carContext, session)) } .build() ) @@ -153,7 +153,8 @@ class SettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) { } } -class DataSettingsScreen(ctx: CarContext) : Screen(ctx) { +@ExperimentalCarApi +class DataSettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) { val prefs = PreferenceDataSource(ctx) val encryptedPrefs = EncryptedPreferenceDataStore(ctx) val db = AppDatabase.getInstance(ctx) @@ -279,7 +280,7 @@ class DataSettingsScreen(ctx: CarContext) : Screen(ctx) { } }, IntentFilter(OAuthLoginFragment.ACTION_OAUTH_RESULT)) - carContext.startActivity(intent) + session.cas.startActivity(intent) if (BuildConfig.FLAVOR_automotive != "automotive") { CarToast.makeText( @@ -752,7 +753,8 @@ class SelectChargingRangeScreen(ctx: CarContext) : Screen(ctx) { } } -class AboutScreen(ctx: CarContext) : Screen(ctx) { +@ExperimentalCarApi +class AboutScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) { val prefs = PreferenceDataSource(ctx) var developerOptionsCounter = 0 private val maxRows = ctx.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST) @@ -797,7 +799,11 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { .setTitle(carContext.getString(R.string.faq)) .setBrowsable(true) .setOnClickListener(ParkedOnlyOnClickListener.create { - openUrl(carContext, carContext.getString(R.string.faq_link)) + openUrl( + carContext, + session.cas, + carContext.getString(R.string.faq_link) + ) }).build() ) addItem( @@ -808,12 +814,16 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { .setOnClickListener(ParkedOnlyOnClickListener.create { if (BuildConfig.FLAVOR_automotive == "automotive") { // we can't open the donation page on the phone in this case - openUrl(carContext, carContext.getString(R.string.donate_link)) + openUrl( + carContext, + session.cas, + carContext.getString(R.string.donate_link) + ) } else { val intent = Intent(carContext, MapsActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(EXTRA_DONATE, true) - carContext.startActivity(intent) + session.cas.startActivity(intent) CarToast.makeText( carContext, R.string.opened_on_phone, @@ -829,7 +839,11 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { .addText(carContext.getString(R.string.mastodon_handle)) .setBrowsable(true) .setOnClickListener(ParkedOnlyOnClickListener.create { - openUrl(carContext, carContext.getString(R.string.mastodon_url)) + openUrl( + carContext, + session.cas, + carContext.getString(R.string.mastodon_url) + ) }).build() ) if (maxRows > 8) { @@ -839,7 +853,11 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { .addText(carContext.getString(R.string.twitter_handle)) .setBrowsable(true) .setOnClickListener(ParkedOnlyOnClickListener.create { - openUrl(carContext, carContext.getString(R.string.twitter_url)) + openUrl( + carContext, + session.cas, + carContext.getString(R.string.twitter_url) + ) }).build() ) } @@ -849,7 +867,7 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { .setBrowsable(true) .setOnClickListener(ParkedOnlyOnClickListener.create { openUrl( - carContext, + carContext, session.cas, carContext.getString(R.string.goingelectric_forum_url) ) }).build() @@ -862,7 +880,7 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { .setBrowsable(true) .setOnClickListener(ParkedOnlyOnClickListener.create { openUrl( - carContext, + carContext, session.cas, carContext.getString(R.string.tff_forum_url) ) }).build() @@ -874,14 +892,18 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { .setTitle(carContext.getString(R.string.github_link_title)) .setBrowsable(true) .setOnClickListener(ParkedOnlyOnClickListener.create { - openUrl(carContext, carContext.getString(R.string.github_link)) + openUrl(carContext, session.cas, carContext.getString(R.string.github_link)) }).build() ) addItem(Row.Builder() .setTitle(carContext.getString(R.string.privacy)) .setBrowsable(true) .setOnClickListener(ParkedOnlyOnClickListener.create { - openUrl(carContext, carContext.getString(R.string.privacy_link)) + openUrl( + carContext, + session.cas, + carContext.getString(R.string.privacy_link) + ) }).build() ) }.build(), carContext.getString(R.string.other))) @@ -889,7 +911,8 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) { } } -class AcceptPrivacyScreen(ctx: CarContext) : Screen(ctx) { +@ExperimentalCarApi +class AcceptPrivacyScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) { val prefs = PreferenceDataSource(ctx) override fun onGetTemplate(): Template { val textWithoutLink = HtmlCompat.fromHtml( @@ -910,7 +933,7 @@ class AcceptPrivacyScreen(ctx: CarContext) : Screen(ctx) { addAction(Action.Builder() .setTitle(carContext.getString(R.string.privacy)) .setOnClickListener(ParkedOnlyOnClickListener.create { - openUrl(carContext, carContext.getString(R.string.privacy_link)) + openUrl(carContext, session.cas, carContext.getString(R.string.privacy_link)) }).build() ) }.build() diff --git a/app/src/main/java/net/vonforst/evmap/auto/Utils.kt b/app/src/main/java/net/vonforst/evmap/auto/Utils.kt index f309cac7..dc1b4ac7 100644 --- a/app/src/main/java/net/vonforst/evmap/auto/Utils.kt +++ b/app/src/main/java/net/vonforst/evmap/auto/Utils.kt @@ -12,6 +12,7 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.car.app.CarContext import androidx.car.app.CarToast import androidx.car.app.Screen +import androidx.car.app.annotations.ExperimentalCarApi import androidx.car.app.constraints.ConstraintManager import androidx.car.app.hardware.common.CarUnit import androidx.car.app.model.CarColor @@ -221,13 +222,14 @@ fun supportsCarApiLevel3(ctx: CarContext): Boolean { return true } -fun openUrl(carContext: CarContext, url: String) { +@ExperimentalCarApi +fun openUrl(carContext: CarContext, cas: CarAppService, url: String) { val intent = CustomTabsIntent.Builder() .setDefaultColorSchemeParams( CustomTabColorSchemeParams.Builder() .setToolbarColor( ContextCompat.getColor( - carContext, + cas, R.color.colorPrimary ) ) @@ -237,7 +239,7 @@ fun openUrl(carContext: CarContext, url: String) { intent.data = Uri.parse(url) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) try { - carContext.startActivity(intent) + cas.startActivity(intent) if (BuildConfig.FLAVOR_automotive != "automotive") { // only show the toast "opened on phone" if we're running on a phone CarToast.makeText(