Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5409c84c authored by Philipp Heckel's avatar Philipp Heckel
Browse files

Dynamically remove install permission via gradle

parent b91778ad
Loading
Loading
Loading
Loading
+21 −2
Original line number Diff line number Diff line
@@ -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"
+9 −1
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="io.heckel.ntfy">

    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <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.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.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.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
            android:name=".app.Application"
            android:allowBackup="true"
+3 −0
Original line number Diff line number Diff line
@@ -166,6 +166,9 @@ class NotificationService(val context: Context) {
    }

    private fun maybeAddOpenAction(builder: NotificationCompat.Builder, notification: Notification) {
        if (!canOpenAttachment(notification.attachment)) {
            return
        }
        if (notification.attachment?.contentUri != null) {
            val contentUri = Uri.parse(notification.attachment.contentUri)
            val intent = Intent(Intent.ACTION_VIEW, contentUri).apply {
+7 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.stfalcon.imageviewer.StfalconImageViewer
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
import io.heckel.ntfy.db.*
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 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) :
    ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
    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 {
            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)
+13 −1
Original line number Diff line number Diff line
@@ -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
Loading