瀏覽代碼

feat: official website url (#286)

Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/286
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
septs 1 月之前
父節點
當前提交
639c1d0ea6

+ 7 - 1
app-common/src/main/java/im/angry/openeuicc/di/CustomizableTextProvider.kt

@@ -1,5 +1,6 @@
 package im.angry.openeuicc.di
 
+import android.net.Uri
 import im.angry.openeuicc.core.EuiccChannel
 
 interface CustomizableTextProvider {
@@ -14,6 +15,11 @@ interface CustomizableTextProvider {
      */
     val profileSwitchingTimeoutMessage: String
 
+    /**
+     * Display the website link in settings; null if not available.
+     */
+    val websiteUri: Uri?
+
     /**
      * Format the name of a logical slot -- not for USB channels
      */
@@ -24,4 +30,4 @@ interface CustomizableTextProvider {
      * this is used in the download flow to distinguish between them on the same chip.
      */
     fun formatNonUsbChannelNameWithSeId(logicalSlotId: Int, seId: EuiccChannel.SecureElementId): String
-}
+}

+ 7 - 7
app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt

@@ -1,23 +1,23 @@
 package im.angry.openeuicc.di
 
 import android.content.Context
+import android.net.Uri
 import im.angry.openeuicc.common.R
 import im.angry.openeuicc.core.EuiccChannel
 
-open class DefaultCustomizableTextProvider(private val context: Context) :
-    CustomizableTextProvider {
+open class DefaultCustomizableTextProvider(private val context: Context) : CustomizableTextProvider {
     override val noEuiccExplanation: String
         get() = context.getString(R.string.no_euicc)
 
     override val profileSwitchingTimeoutMessage: String
         get() = context.getString(R.string.profile_switch_timeout)
 
+    override val websiteUri: Uri?
+        get() = null
+
     override fun formatNonUsbChannelName(logicalSlotId: Int): String =
         context.getString(R.string.channel_name_format, logicalSlotId)
 
-    override fun formatNonUsbChannelNameWithSeId(
-        logicalSlotId: Int,
-        seId: EuiccChannel.SecureElementId
-    ): String =
+    override fun formatNonUsbChannelNameWithSeId(logicalSlotId: Int, seId: EuiccChannel.SecureElementId): String =
         context.getString(R.string.channel_name_format_se, logicalSlotId, seId.id)
-}
+}

+ 9 - 2
app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt

@@ -13,16 +13,16 @@ import androidx.preference.Preference
 import androidx.preference.PreferenceCategory
 import androidx.preference.PreferenceFragmentCompat
 import im.angry.openeuicc.common.R
+import im.angry.openeuicc.util.OpenEuiccContextMarker
 import im.angry.openeuicc.util.PreferenceFlowWrapper
 import im.angry.openeuicc.util.mainViewPaddingInsetHandler
-import im.angry.openeuicc.util.preferenceRepository
 import im.angry.openeuicc.util.selfAppVersion
 import im.angry.openeuicc.util.setupRootViewSystemBarInsets
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
-open class SettingsFragment : PreferenceFragmentCompat() {
+open class SettingsFragment : PreferenceFragmentCompat(), OpenEuiccContextMarker {
     private lateinit var developerPref: PreferenceCategory
 
     // Hidden developer options switch
@@ -91,6 +91,13 @@ open class SettingsFragment : PreferenceFragmentCompat() {
         requirePreference<Preference>("pref_developer_isdr_aid_list").apply {
             intent = Intent(requireContext(), IsdrAidListActivity::class.java)
         }
+
+        requirePreference<Preference>("pref_info_website").apply {
+            val uri = appContainer.customizableTextProvider.websiteUri ?: return@apply
+            isVisible = true
+            summary = uri.buildUpon().clearQuery().build().toString()
+            intent = Intent(/* action = */ Intent.ACTION_VIEW, uri)
+        }
     }
 
     protected fun <T : Preference> requirePreference(key: CharSequence) =

+ 8 - 7
app-common/src/main/java/im/angry/openeuicc/util/Utils.kt

@@ -1,6 +1,7 @@
 package im.angry.openeuicc.util
 
 import android.content.Context
+import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.se.omapi.SEService
@@ -21,14 +22,14 @@ import kotlin.coroutines.resume
 import kotlin.coroutines.resumeWithException
 import kotlin.coroutines.suspendCoroutine
 
+val Context.packageInfo: PackageInfo
+    get() = packageManager.getPackageInfo(packageName, /* flags = */ 0)!!
+
 val Context.selfAppVersion: String
-    get() =
-        try {
-            val pInfo = packageManager.getPackageInfo(packageName, 0)
-            pInfo.versionName!!
-        } catch (e: PackageManager.NameNotFoundException) {
-            throw RuntimeException(e)
-        }
+    get() = packageInfo.versionName!!
+
+val Context.selfAppVersionCode: Long
+    get() = packageInfo.longVersionCode
 
 suspend fun readSelfLog(lines: Int = 2048): String = withContext(Dispatchers.IO) {
     try {

+ 1 - 0
app-common/src/main/res/values-ja/strings.xml

@@ -192,6 +192,7 @@
     <string name="pref_developer_isdr_aid_list_desc">一部ブランドのリムーバブル eSIM は独自の非標準な ISD-R AID を使用しているため、サードパーティー製アプリからアクセスできない場合があります。このリストに追加された非標準な AID の使用を試みますが、動作の保証はできません。</string>
     <string name="pref_info">情報</string>
     <string name="pref_info_app_version">アプリバージョン</string>
+    <string name="pref_info_website">公式サイト</string>
     <string name="pref_info_source_code">ソースコード</string>
     <string name="channel_name_format_se">論理スロット %1$d, SE %2$d</string>
 </resources>

+ 1 - 0
app-common/src/main/res/values-zh-rCN/strings.xml

@@ -90,6 +90,7 @@
     </string-array>
     <string name="pref_info">信息</string>
     <string name="pref_info_app_version">App 版本</string>
+    <string name="pref_info_website">官方网站</string>
     <string name="pref_info_source_code">源码</string>
     <string name="profile_class_testing">测试</string>
     <string name="profile_class_provisioning">准备中</string>

+ 1 - 0
app-common/src/main/res/values-zh-rTW/strings.xml

@@ -86,6 +86,7 @@
     <string name="pref_developer_es10x_mss_desc">全域 ES10x MSS</string>
     <string name="pref_info">資訊</string>
     <string name="pref_info_app_version">App 版本</string>
+    <string name="pref_info_website">官方網站</string>
     <string name="pref_info_source_code">原始碼</string>
     <string name="profile_class_testing">測試</string>
     <string name="profile_class_provisioning">準備中</string>

+ 1 - 0
app-common/src/main/res/values/strings.xml

@@ -231,6 +231,7 @@
     <string name="pref_developer_isdr_aid_list_desc">Some brands of removable eSIMs may use their own non-standard ISD-R AID, rendering them inaccessible to third-party apps. We can attempt to use non-standard AIDs added in this list, but there is no guarantee that they will work.</string>
     <string name="pref_info">Info</string>
     <string name="pref_info_app_version">App Version</string>
+    <string name="pref_info_website">Official Website</string>
     <string name="pref_info_source_code">Source Code</string>
     <string name="pref_info_source_code_url" translatable="false">https://gitea.angry.im/PeterCxy/OpenEUICC</string>
 </resources>

+ 7 - 0
app-common/src/main/res/xml/pref_settings.xml

@@ -101,6 +101,13 @@
             app:key="pref_info_app_version"
             app:title="@string/pref_info_app_version" />
 
+        <Preference
+            app:enableCopying="true"
+            app:iconSpaceReserved="false"
+            app:isPreferenceVisible="false"
+            app:key="pref_info_website"
+            app:title="@string/pref_info_website" />
+
         <Preference
             app:enableCopying="true"
             app:iconSpaceReserved="false"

+ 38 - 3
app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedCustomizableTextProvider.kt

@@ -1,11 +1,46 @@
 package im.angry.openeuicc.di
 
 import android.content.Context
+import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
+import android.net.Uri
+import android.util.Base64
+import androidx.core.net.toUri
 import im.angry.easyeuicc.R
+import im.angry.openeuicc.common.BuildConfig
 import im.angry.openeuicc.core.EuiccChannel
+import im.angry.openeuicc.util.selfAppVersion
+import im.angry.openeuicc.util.selfAppVersionCode
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+
+class UnprivilegedCustomizableTextProvider(private val context: Context) : DefaultCustomizableTextProvider(context) {
+    override val websiteUri: Uri
+        get() {
+            val parts = arrayOf(
+                context.selfAppVersion, // show user current version
+                context.selfAppVersionCode.toString(36), // check is upgradable
+                BuildConfig.BUILD_TYPE, // update channels
+            )
+            val message = parts.joinToString("\u0000")
+            val signed = Base64.encodeToString(
+                // HMAC-SHA256 over the message with app signing certs as key
+                with(Mac.getInstance("HmacSHA256")) {
+                    // Concatenate all signing certs bytes to form the key
+                    val signingInfo = with(context) {
+                        packageManager.getPackageInfo(packageName, /* flags = */ GET_SIGNING_CERTIFICATES).signingInfo!!
+                    }
+                    val key = signingInfo.apkContentsSigners.map { it.toByteArray() }.reduce { a, b -> a + b }
+                    init(SecretKeySpec(key, algorithm))
+                    doFinal(message.encodeToByteArray())
+                },
+                Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING,
+            )
+            return context.getString(R.string.pref_info_website_url).toUri().buildUpon()
+                .appendQueryParameter("v", message)
+                .appendQueryParameter("v", signed)
+                .build()
+        }
 
-class UnprivilegedCustomizableTextProvider(private val context: Context) :
-    DefaultCustomizableTextProvider(context) {
     override fun formatNonUsbChannelName(logicalSlotId: Int): String =
         context.getString(R.string.channel_name_format_unpriv, logicalSlotId)
 
@@ -14,4 +49,4 @@ class UnprivilegedCustomizableTextProvider(private val context: Context) :
         seId: EuiccChannel.SecureElementId
     ): String =
         context.getString(R.string.channel_name_format_unpriv_se, logicalSlotId, seId.id)
-}
+}

+ 6 - 12
app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt

@@ -2,7 +2,7 @@ package im.angry.openeuicc.ui
 
 import android.content.ClipData
 import android.content.ClipboardManager
-import android.content.pm.PackageManager
+import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
 import android.os.Build
 import android.os.Bundle
 import android.widget.Toast
@@ -13,17 +13,11 @@ import java.security.MessageDigest
 
 class UnprivilegedSettingsFragment : SettingsFragment() {
     private val firstSigner by lazy {
-        val packageInfo = requireContext().let {
-            it.packageManager.getPackageInfo(
-                it.packageName,
-                PackageManager.GET_SIGNING_CERTIFICATES,
-            )
-        }
-        packageInfo.signingInfo!!.apkContentsSigners.first().let {
-            MessageDigest.getInstance("SHA-1")
-                .apply { update(it.toByteArray()) }
-                .digest()
+        val packageInfo = with(requireContext()) {
+            packageManager.getPackageInfo(packageName, /* flags = */ GET_SIGNING_CERTIFICATES)
         }
+        packageInfo.signingInfo!!.apkContentsSigners.first().toByteArray()
+            .let(MessageDigest.getInstance("SHA-1")::digest)
     }
 
     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -43,4 +37,4 @@ class UnprivilegedSettingsFragment : SettingsFragment() {
             }
         }
     }
-}
+}

+ 2 - 1
app-unpriv/src/main/res/values/strings.xml

@@ -9,6 +9,7 @@
 
     <!-- Settings -->
     <string name="pref_developer_ara_m" translatable="false">ARA-M SHA-1</string>
+    <string name="pref_info_website_url" translatable="false">https://easyeuicc.org</string>
 
     <!-- Toast -->
     <string name="toast_ara_m_copied">ARA-M SHA-1 copied to clipboard</string>
@@ -26,4 +27,4 @@
     <string name="quick_compatibility_button_continue">Continue</string>
     <string name="quick_compatibility_skip">Don\'t show this message again</string>
     <string name="quick_compatibility_unknown">Unknown</string>
-</resources>
+</resources>