diff --git a/app/build.gradle b/app/build.gradle index d34de50..2f42134 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { minSdkVersion 21 targetSdkVersion 33 - versionCode 29 - versionName "1.15.0" + versionCode 31 + versionName "1.15.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -44,10 +44,12 @@ android { play { buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true' buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true' + buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'false' } fdroid { buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false' buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'false' + buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'true' } } @@ -64,12 +66,29 @@ android { } } +// Disables GoogleServices tasks for F-Droid variant android.applicationVariants.all { variant -> def shouldProcessGoogleServices = variant.flavorName == "play" def googleTask = tasks.findByName("process${variant.name.capitalize()}GoogleServices") googleTask.enabled = shouldProcessGoogleServices } +// Strips out REQUEST_INSTALL_PACKAGES permission for Google Play variant +android.applicationVariants.all { variant -> + def shouldStripInstallPermission = variant.flavorName == "play" + if (shouldStripInstallPermission) { + variant.outputs.each { output -> + def processManifest = output.getProcessManifestProvider().get() + processManifest.doLast { task -> + def outputDir = task.getMultiApkManifestOutputDirectory().get().asFile + def manifestOutFile = file("$outputDir/AndroidManifest.xml") + def newFileContents = manifestOutFile.collect { s -> s.contains("android.permission.REQUEST_INSTALL_PACKAGES") ? "" : s }.join("\n") + manifestOutFile.write(newFileContents, 'UTF-8') + } + } + } +} + dependencies { // AndroidX, The Basics implementation "androidx.appcompat:appcompat:1.5.1" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4cbdf60..0580856 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + @@ -8,10 +9,17 @@ - + + + Unit, private val onLongClick: (Notification) -> Unit) : ListAdapter(TopicDiffCallback) { val selected = mutableSetOf() // Notification IDs @@ -371,6 +371,12 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: } private fun openFile(context: Context, attachment: Attachment): Boolean { + if (!canOpenAttachment(attachment)) { + Toast + .makeText(context, context.getString(R.string.detail_item_cannot_open_apk), Toast.LENGTH_LONG) + .show() + return true + } Log.d(TAG, "Opening file ${attachment.contentUri}") try { val contentUri = Uri.parse(attachment.contentUri) diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index d03fe92..078bf27 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -25,6 +25,7 @@ import android.view.Window import android.widget.ImageView import android.widget.Toast import androidx.appcompat.app.AppCompatDelegate +import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R import io.heckel.ntfy.db.* import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64 @@ -321,6 +322,8 @@ fun formatBytes(bytes: Long, decimals: Int = 1): String { return java.lang.String.format("%.${decimals}f %cB", value / 1024.0, ci.current()) } +const val androidAppMimeType = "application/vnd.android.package-archive" + fun mimeTypeToIconResource(mimeType: String?): Int { return if (mimeType?.startsWith("image/") == true) { R.drawable.ic_file_image_red_24dp @@ -328,7 +331,7 @@ fun mimeTypeToIconResource(mimeType: String?): Int { R.drawable.ic_file_video_orange_24dp } else if (mimeType?.startsWith("audio/") == true) { R.drawable.ic_file_audio_purple_24dp - } else if (mimeType == "application/vnd.android.package-archive") { + } else if (mimeType == androidAppMimeType) { R.drawable.ic_file_app_gray_24dp } else { R.drawable.ic_file_document_blue_24dp @@ -339,6 +342,15 @@ fun supportedImage(mimeType: String?): Boolean { return listOf("image/jpeg", "image/png").contains(mimeType) } +// Google Play doesn't allow us to install received .apk files anymore. +// See https://github.com/binwiederhier/ntfy/issues/531 +fun canOpenAttachment(attachment: Attachment?): Boolean { + if (attachment?.type == androidAppMimeType && !BuildConfig.INSTALL_PACKAGES_AVAILABLE) { + return false + } + return true +} + // Check if battery optimization is enabled, see https://stackoverflow.com/a/49098293/1440785 fun isIgnoringBatteryOptimizations(context: Context): Boolean { val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 59c9817..d26c9d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -153,6 +153,7 @@ Cannot open attachment: %1$s Cannot open attachment: The file may have been deleted, or no installed app can open the file. Cannot open URL: %1$s + Apps cannot be installed anymore. Download via browser instead. See issue #531 for details. Cannot save attachment: %1$s Cannot delete attachment: %1$s Could not download attachment: %1$s diff --git a/fastlane/metadata/android/en-US/changelog/29.txt b/fastlane/metadata/android/en-US/changelog/31.txt similarity index 93% rename from fastlane/metadata/android/en-US/changelog/29.txt rename to fastlane/metadata/android/en-US/changelog/31.txt index 84dd455..c807459 100644 --- a/fastlane/metadata/android/en-US/changelog/29.txt +++ b/fastlane/metadata/android/en-US/changelog/31.txt @@ -13,6 +13,7 @@ Bug fixes + maintenance: * Fix crashes from large images (#474, thanks to @daedric7 for reporting) * Fix notification click opens wrong subscription (#261, thanks to @SMAW for reporting) * Fix Firebase-only "link expired" issue (#529) +* Remove "Install .apk" feature in Google Play variant due to policy change (#531) * Add donate button (no ticket) Additional translations: