Skip to content

Commit

Permalink
Merge pull request #27 from chenxiaolong/local
Browse files Browse the repository at this point in the history
Add support for installing from a local directory
  • Loading branch information
chenxiaolong authored Dec 22, 2023
2 parents b17e861 + a46ee8c commit 5b10c0e
Show file tree
Hide file tree
Showing 19 changed files with 666 additions and 229 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Custota is installed via a Magisk/KernelSU module so that it can run as a system

1. Follow the instructions in the [OTA server](#ota-server) section to set up a webserver and generate the metadata files for the OTA zips.

Alternatively, Custota supports installing OTAs from a local directory instead of downloading them from an OTA server. When using a local directory, the expected directory structure is exactly the same as how it would be with a server.

2. If you're installing OTA updates signed with a custom key, follow the instructions in the [Custom Verification Key](#custom-verification-key) section.

3. Download the latest version from the [releases page](https://github.com/chenxiaolong/Custota/releases). To verify the digital signature, see the [verifying digital signatures](#verifying-digital-signatures) section.
Expand Down
6 changes: 6 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
# only goal of minification.
-dontobfuscate

# We construct TreeDocumentFile via reflection in DocumentFileExtensions
# to speed up SAF performance when doing path lookups.
-keepclassmembers class androidx.documentfile.provider.TreeDocumentFile {
<init>(androidx.documentfile.provider.DocumentFile, android.content.Context, android.net.Uri);
}

# Keep classes generated from AIDL
-keep class android.os.IUpdateEngine* {
*;
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/cpp/custota_selinux/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,18 @@ static bool apply_patches(
// allow custota_app oem_lock_service:service_manager find;
ff(add_rule(pdb, target_type, "oem_lock_service", "service_manager", "find", errors));

// Now, allow update_engine to access the file descriptor we pass to it via
// binder for a file opened from local storage.

// allow update_engine mediaprovider_app:fd use;
ff(add_rule(pdb, "update_engine", "mediaprovider_app", "fd", "use", errors));

// allow update_engine fuse:file getattr;
// allow update_engine fuse:file read;
for (auto const &perm : {"getattr", "read"}) {
ff(add_rule(pdb, "update_engine", "fuse", "file", perm, errors));
}

if (strip_no_audit) {
ff(raw_strip_no_audit(pdb) != SELinuxResult::Error);
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/chiller3/custota/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class MainApplication : Application() {
// Enable Material You colors
DynamicColors.applyToActivitiesIfAvailable(this)

Preferences(this).migrate()

Notifications(this).updateChannels()

UpdaterJob.schedulePeriodic(this, false)
Expand Down
65 changes: 54 additions & 11 deletions app/src/main/java/com/chiller3/custota/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@

package com.chiller3.custota

import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import java.net.URL

class Preferences(context: Context) {
class Preferences(private val context: Context) {
companion object {
private val TAG = Preferences::class.java.simpleName

const val CATEGORY_CERTIFICATES = "certificates"
const val CATEGORY_DEBUG = "debug"

const val PREF_CHECK_FOR_UPDATES = "check_for_updates"
const val PREF_OTA_SERVER_URL = "ota_server_url"
const val PREF_OTA_SOURCE = "ota_source"
const val PREF_AUTOMATIC_CHECK = "automatic_check"
const val PREF_AUTOMATIC_INSTALL = "automatic_install"
const val PREF_UNMETERED_ONLY = "unmetered_only"
Expand All @@ -34,6 +39,9 @@ class Preferences(context: Context) {

// Not associated with a UI preference
private const val PREF_DEBUG_MODE = "debug_mode"

// Legacy preferences
private const val PREF_OTA_SERVER_URL = "ota_server_url"
}

private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
Expand All @@ -42,14 +50,41 @@ class Preferences(context: Context) {
get() = prefs.getBoolean(PREF_DEBUG_MODE, false)
set(enabled) = prefs.edit { putBoolean(PREF_DEBUG_MODE, enabled) }

/** Base URL to fetch OTA updates. */
var otaServerUrl: URL?
get() = prefs.getString(PREF_OTA_SERVER_URL, null)?.let { URL(it) }
set(url) = prefs.edit {
if (url == null) {
remove(PREF_OTA_SERVER_URL)
} else {
putString(PREF_OTA_SERVER_URL, url.toString())
/** Base URI to fetch OTA updates. This is either an HTTP/HTTPS URL or a SAF URI. */
var otaSource: Uri?
get() = prefs.getString(PREF_OTA_SOURCE, null)?.let { Uri.parse(it) }
set(uri) {
val oldUri = otaSource
if (oldUri == uri) {
// URI is the same as before or both are null
return
}

prefs.edit {
if (uri != null) {
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
// Persist permissions for the new URI first
context.contentResolver.takePersistableUriPermission(
uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

putString(PREF_OTA_SOURCE, uri.toString())
} else {
remove(PREF_OTA_SOURCE)
}
}

// Release persisted permissions on the old directory only after the new URI is set to
// guarantee atomicity
if (oldUri != null && oldUri.scheme == ContentResolver.SCHEME_CONTENT) {
// It's not documented, but this can throw an exception when trying to release a
// previously persisted URI that's associated with an app that's no longer installed
try {
context.contentResolver.releasePersistableUriPermission(
oldUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
} catch (e: Exception) {
Log.w(TAG, "Error when releasing persisted URI permission for: $oldUri", e)
}
}
}

Expand Down Expand Up @@ -82,4 +117,12 @@ class Preferences(context: Context) {
var allowReinstall: Boolean
get() = prefs.getBoolean(PREF_ALLOW_REINSTALL, false)
set(enabled) = prefs.edit { putBoolean(PREF_ALLOW_REINSTALL, enabled) }

/** Migrate legacy preferences to current preferences. */
fun migrate() {
if (prefs.contains(PREF_OTA_SERVER_URL)) {
otaSource = prefs.getString(PREF_OTA_SERVER_URL, null)?.let { Uri.parse(it) }
prefs.edit { remove(PREF_OTA_SERVER_URL) }
}
}
}

This file was deleted.

Loading

0 comments on commit 5b10c0e

Please sign in to comment.