Dynamically remove install permission via gradle
This commit is contained in:
parent
b91778ad7d
commit
5409c84c66
7 changed files with 55 additions and 5 deletions
|
@ -14,8 +14,8 @@ android {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
|
|
||||||
versionCode 29
|
versionCode 31
|
||||||
versionName "1.15.0"
|
versionName "1.15.2"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
@ -44,10 +44,12 @@ android {
|
||||||
play {
|
play {
|
||||||
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true'
|
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true'
|
||||||
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true'
|
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true'
|
||||||
|
buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'false'
|
||||||
}
|
}
|
||||||
fdroid {
|
fdroid {
|
||||||
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false'
|
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false'
|
||||||
buildConfigField 'boolean', 'RATE_APP_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 ->
|
android.applicationVariants.all { variant ->
|
||||||
def shouldProcessGoogleServices = variant.flavorName == "play"
|
def shouldProcessGoogleServices = variant.flavorName == "play"
|
||||||
def googleTask = tasks.findByName("process${variant.name.capitalize()}GoogleServices")
|
def googleTask = tasks.findByName("process${variant.name.capitalize()}GoogleServices")
|
||||||
googleTask.enabled = shouldProcessGoogleServices
|
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 {
|
dependencies {
|
||||||
// AndroidX, The Basics
|
// AndroidX, The Basics
|
||||||
implementation "androidx.appcompat:appcompat:1.5.1"
|
implementation "androidx.appcompat:appcompat:1.5.1"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="io.heckel.ntfy">
|
package="io.heckel.ntfy">
|
||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <!-- For instant delivery foregrounds service -->
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <!-- For instant delivery foregrounds service -->
|
||||||
|
@ -8,10 +9,17 @@
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <!-- To restart service on reboot -->
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <!-- To restart service on reboot -->
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/> <!-- Incoming notifications should be able to vibrate the phone -->
|
<uses-permission android:name="android.permission.VIBRATE"/> <!-- Incoming notifications should be able to vibrate the phone -->
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 -->
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 -->
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <!-- To install packages downloaded through ntfy; craazyy! -->
|
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry -->
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- As of Android 13, we need to ask for permission to post notifications -->
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- As of Android 13, we need to ask for permission to post notifications -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Permission REQUEST_INSTALL_PACKAGES (F-Droid only!):
|
||||||
|
- Permission is used to install .apk files that were received as attachments
|
||||||
|
- Google rejected the permission for ntfy, so this permission is STRIPPED OUT by the build process
|
||||||
|
for the Google Play variant of the app.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".app.Application"
|
android:name=".app.Application"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
|
@ -166,6 +166,9 @@ class NotificationService(val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeAddOpenAction(builder: NotificationCompat.Builder, notification: Notification) {
|
private fun maybeAddOpenAction(builder: NotificationCompat.Builder, notification: Notification) {
|
||||||
|
if (!canOpenAttachment(notification.attachment)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (notification.attachment?.contentUri != null) {
|
if (notification.attachment?.contentUri != null) {
|
||||||
val contentUri = Uri.parse(notification.attachment.contentUri)
|
val contentUri = Uri.parse(notification.attachment.contentUri)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, contentUri).apply {
|
val intent = Intent(Intent.ACTION_VIEW, contentUri).apply {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.stfalcon.imageviewer.StfalconImageViewer
|
import com.stfalcon.imageviewer.StfalconImageViewer
|
||||||
|
import io.heckel.ntfy.BuildConfig
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.*
|
import io.heckel.ntfy.db.*
|
||||||
import io.heckel.ntfy.msg.DownloadManager
|
import io.heckel.ntfy.msg.DownloadManager
|
||||||
|
@ -35,7 +36,6 @@ import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
|
||||||
import io.heckel.ntfy.util.*
|
import io.heckel.ntfy.util.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
|
||||||
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
||||||
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
||||||
val selected = mutableSetOf<String>() // Notification IDs
|
val selected = mutableSetOf<String>() // Notification IDs
|
||||||
|
@ -371,6 +371,12 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openFile(context: Context, attachment: Attachment): Boolean {
|
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}")
|
Log.d(TAG, "Opening file ${attachment.contentUri}")
|
||||||
try {
|
try {
|
||||||
val contentUri = Uri.parse(attachment.contentUri)
|
val contentUri = Uri.parse(attachment.contentUri)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.view.Window
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import io.heckel.ntfy.BuildConfig
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.*
|
import io.heckel.ntfy.db.*
|
||||||
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
|
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())
|
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 {
|
fun mimeTypeToIconResource(mimeType: String?): Int {
|
||||||
return if (mimeType?.startsWith("image/") == true) {
|
return if (mimeType?.startsWith("image/") == true) {
|
||||||
R.drawable.ic_file_image_red_24dp
|
R.drawable.ic_file_image_red_24dp
|
||||||
|
@ -328,7 +331,7 @@ fun mimeTypeToIconResource(mimeType: String?): Int {
|
||||||
R.drawable.ic_file_video_orange_24dp
|
R.drawable.ic_file_video_orange_24dp
|
||||||
} else if (mimeType?.startsWith("audio/") == true) {
|
} else if (mimeType?.startsWith("audio/") == true) {
|
||||||
R.drawable.ic_file_audio_purple_24dp
|
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
|
R.drawable.ic_file_app_gray_24dp
|
||||||
} else {
|
} else {
|
||||||
R.drawable.ic_file_document_blue_24dp
|
R.drawable.ic_file_document_blue_24dp
|
||||||
|
@ -339,6 +342,15 @@ fun supportedImage(mimeType: String?): Boolean {
|
||||||
return listOf("image/jpeg", "image/png").contains(mimeType)
|
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
|
// Check if battery optimization is enabled, see https://stackoverflow.com/a/49098293/1440785
|
||||||
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
|
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
|
||||||
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
|
|
@ -153,6 +153,7 @@
|
||||||
<string name="detail_item_cannot_open">Cannot open attachment: %1$s</string>
|
<string name="detail_item_cannot_open">Cannot open attachment: %1$s</string>
|
||||||
<string name="detail_item_cannot_open_not_found">Cannot open attachment: The file may have been deleted, or no installed app can open the file.</string>
|
<string name="detail_item_cannot_open_not_found">Cannot open attachment: The file may have been deleted, or no installed app can open the file.</string>
|
||||||
<string name="detail_item_cannot_open_url">Cannot open URL: %1$s</string>
|
<string name="detail_item_cannot_open_url">Cannot open URL: %1$s</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Apps cannot be installed anymore. Download via browser instead. See issue #531 for details.</string>
|
||||||
<string name="detail_item_cannot_save">Cannot save attachment: %1$s</string>
|
<string name="detail_item_cannot_save">Cannot save attachment: %1$s</string>
|
||||||
<string name="detail_item_cannot_delete">Cannot delete attachment: %1$s</string>
|
<string name="detail_item_cannot_delete">Cannot delete attachment: %1$s</string>
|
||||||
<string name="detail_item_download_failed">Could not download attachment: %1$s</string>
|
<string name="detail_item_download_failed">Could not download attachment: %1$s</string>
|
||||||
|
|
|
@ -13,6 +13,7 @@ Bug fixes + maintenance:
|
||||||
* Fix crashes from large images (#474, thanks to @daedric7 for reporting)
|
* Fix crashes from large images (#474, thanks to @daedric7 for reporting)
|
||||||
* Fix notification click opens wrong subscription (#261, thanks to @SMAW for reporting)
|
* Fix notification click opens wrong subscription (#261, thanks to @SMAW for reporting)
|
||||||
* Fix Firebase-only "link expired" issue (#529)
|
* 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)
|
* Add donate button (no ticket)
|
||||||
|
|
||||||
Additional translations:
|
Additional translations:
|
Loading…
Reference in a new issue