chore: prepare Android app for release
This commit is contained in:
@@ -1,207 +1,90 @@
|
||||
# 📺 RelayTV Android Remote
|
||||
# RelayTV Android Remote
|
||||
|
||||
**RelayTV Android** is the official companion remote app for
|
||||
**RelayTV**, a powerful local-first media relay and playback system.
|
||||
RelayTV Android is the Android companion app for the self-hosted RelayTV server. It gives you a lightweight remote, embedded `/ui` access, multi-server management, and Android share targets for queueing or immediately playing links on a RelayTV box.
|
||||
|
||||
Control, launch, and share content to your RelayTV server instantly from
|
||||
Android --- with multi-server support and seamless share integration.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
## 🚀 What is RelayTV?
|
||||
|
||||
RelayTV is a local media relay and playback server designed for:
|
||||
|
||||
- YouTube & streaming links
|
||||
- Direct media URLs
|
||||
- Queue management
|
||||
- HDMI playback (mpv-based)
|
||||
- LAN-first operation
|
||||
- Smart `/smart` auto-routing endpoint
|
||||
|
||||
🔗 RelayTV Server:\
|
||||
RelayTV server:
|
||||
https://github.com/mcgeezy/relaytv
|
||||
|
||||
🔗 Home Assistant Integration:\
|
||||
Home Assistant integration:
|
||||
https://github.com/mcgeezy/relaytv-ha
|
||||
|
||||
------------------------------------------------------------------------
|
||||
## Features
|
||||
|
||||
# ✨ Features
|
||||
- Multi-server support with health verification before save
|
||||
- LAN auto-discovery of `_relaytv._tcp` servers with Android NSD / mDNS
|
||||
- Embedded RelayTV `/ui` WebView with reconnect and heartbeat recovery
|
||||
- Dedicated Android share targets:
|
||||
- `RelayTV Queue` -> `POST /smart`
|
||||
- `RelayTV Play` -> `POST /play_now`
|
||||
- HTTP and HTTPS support for self-hosted RelayTV servers
|
||||
|
||||
## 📡 Multi-Server Support
|
||||
## Requirements
|
||||
|
||||
- Add unlimited RelayTV servers
|
||||
- Name each server (Living Room, Office, Bedroom, etc.)
|
||||
- Instantly switch active server
|
||||
- Remove or edit servers anytime
|
||||
- Built-in `/health` verification before saving
|
||||
- Android 8.0+ (`minSdk 26`)
|
||||
- RelayTV server reachable on your LAN or via a trusted remote URL
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
## 🔗 Share to RelayTV
|
||||
|
||||
- Share any link from Android to RelayTV
|
||||
|
||||
- Automatically sends to:
|
||||
|
||||
POST /smart
|
||||
|
||||
- Smart endpoint handles play vs queue logic
|
||||
|
||||
- Instant feedback toast:
|
||||
|
||||
Sent to "Living Room"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
## 🎨 Clean RelayTV UI
|
||||
|
||||
- Material 3 design
|
||||
- Dark-mode optimized
|
||||
- RelayTV branding (electric blue + charcoal theme)
|
||||
- Quick server switch from toolbar
|
||||
- Pull-to-refresh WebView
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
## 🧠 Smart URL Handling
|
||||
|
||||
- Auto-normalizes URLs:
|
||||
- 10.0.55.2:8787 → http://10.0.55.2:8787
|
||||
- Validates server before saving
|
||||
- Prevents crash loops from invalid input
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
## 📱 WebView Relay Mode
|
||||
|
||||
Loads:
|
||||
|
||||
/ui
|
||||
|
||||
from the active server for full RelayTV interface access.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# 🏗 Requirements
|
||||
|
||||
- Android 8.0+
|
||||
- RelayTV server running locally or remotely
|
||||
- HTTP or HTTPS supported
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# 🔐 HTTPS Notes
|
||||
|
||||
RelayTV supports both:
|
||||
|
||||
http://
|
||||
https://
|
||||
|
||||
For best compatibility:
|
||||
|
||||
- Prefer HTTPS
|
||||
- Use a valid certificate (reverse proxy, Nginx Proxy Manager, etc.)
|
||||
- Or use HTTP on trusted LAN environments
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# 🛠 Build Instructions
|
||||
|
||||
Clone:
|
||||
|
||||
git clone https://github.com/mcgeezy/relaytv-android.git
|
||||
cd relaytv-android
|
||||
|
||||
Debug build:
|
||||
|
||||
./gradlew assembleDebug
|
||||
|
||||
Release bundle (for Play):
|
||||
|
||||
./gradlew bundleRelease
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
## 🔧 Local Build/Test Scripts
|
||||
## Build And Test
|
||||
|
||||
Validate host environment:
|
||||
|
||||
./scripts/check-android-env.sh all
|
||||
```bash
|
||||
./scripts/check-android-env.sh all
|
||||
```
|
||||
|
||||
Install SDK components (requires sdkmanager):
|
||||
Install or refresh Android SDK components:
|
||||
|
||||
./scripts/bootstrap-android.sh
|
||||
```bash
|
||||
./scripts/bootstrap-android.sh
|
||||
```
|
||||
|
||||
Build + lint + unit tests:
|
||||
Debug build, lint, and unit tests:
|
||||
|
||||
./scripts/build-debug.sh
|
||||
```bash
|
||||
./scripts/build-debug.sh
|
||||
```
|
||||
|
||||
Connected/device tests:
|
||||
Release bundle and release lint:
|
||||
|
||||
./scripts/test-connected.sh
|
||||
```bash
|
||||
./scripts/build-release.sh
|
||||
```
|
||||
|
||||
------------------------------------------------------------------------
|
||||
Connected tests on a physical device or emulator:
|
||||
|
||||
# 📦 Play Store Ready
|
||||
```bash
|
||||
./scripts/test-connected.sh
|
||||
```
|
||||
|
||||
Release build includes:
|
||||
## Release Notes
|
||||
|
||||
- Resource shrinking
|
||||
- Code minification
|
||||
- Material 3 theming
|
||||
- Clean package namespace
|
||||
- Current Play target: Android 15 / API 35
|
||||
- Release bundle output: `app/build/outputs/bundle/release/app-release.aab`
|
||||
- Release checklist: [docs/RELEASE_CHECKLIST.md](/opt/dev/relaytv-android/docs/RELEASE_CHECKLIST.md)
|
||||
- Privacy policy draft: [docs/PRIVACY_POLICY.md](/opt/dev/relaytv-android/docs/PRIVACY_POLICY.md)
|
||||
|
||||
Before Play upload:
|
||||
## Network Notes
|
||||
|
||||
- Configure signing keystore
|
||||
- Increment versionCode
|
||||
- Generate .aab
|
||||
RelayTV Android intentionally supports both:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
- `http://`
|
||||
- `https://`
|
||||
|
||||
# 🏠 Home Assistant Integration
|
||||
HTTP support is retained because RelayTV is commonly deployed as a trusted LAN-first service on local IPs and hostnames. If your server is reachable outside your private network, use HTTPS.
|
||||
|
||||
RelayTV integrates directly with Home Assistant via:
|
||||
## API Surface Used By The App
|
||||
|
||||
https://github.com/mcgeezy/relaytv-ha
|
||||
- `GET /health`
|
||||
- `GET /ui`
|
||||
- `POST /smart`
|
||||
- `POST /play_now`
|
||||
|
||||
Features include:
|
||||
## Contributing
|
||||
|
||||
- Media player entity
|
||||
- Queue control
|
||||
- Playback control
|
||||
- Side panel iframe UI
|
||||
- Multi-server support
|
||||
- Keep Android UX aligned with RelayTV’s self-hosted, local-first model
|
||||
- Preserve the queue vs play-now share behavior
|
||||
- Avoid regressions in reconnect and server discovery flows
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# 🔄 API Reference
|
||||
|
||||
RelayTV Android primarily uses:
|
||||
|
||||
POST /smart
|
||||
GET /health
|
||||
GET /ui
|
||||
|
||||
Full API reference available in:
|
||||
## License
|
||||
|
||||
Same license as the RelayTV core project:
|
||||
https://github.com/mcgeezy/relaytv
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# 🤝 Contributing
|
||||
|
||||
Contributions welcome!
|
||||
|
||||
- Keep UI consistent with RelayTV branding
|
||||
- Maintain `/smart` routing behavior
|
||||
- Preserve multi-server architecture
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# 📄 License
|
||||
|
||||
Same license as RelayTV core project.\
|
||||
See: https://github.com/mcgeezy/relaytv
|
||||
|
||||
+19
-15
@@ -4,33 +4,38 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "pro.relaytv"
|
||||
compileSdk 34
|
||||
namespace = "pro.relaytv"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId "pro.relaytv"
|
||||
minSdk 26
|
||||
targetSdk 34
|
||||
versionCode 2
|
||||
versionName "1.1.0"
|
||||
applicationId = "pro.relaytv"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 3
|
||||
versionName = "1.2.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
minifyEnabled = true
|
||||
shrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
minifyEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
kotlinOptions { jvmTarget = "17" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -38,7 +43,6 @@ dependencies {
|
||||
implementation "androidx.appcompat:appcompat:1.7.0"
|
||||
implementation "androidx.activity:activity-ktx:1.9.2"
|
||||
implementation "com.google.android.material:material:1.12.0"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.9.1"
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
@@ -26,18 +28,60 @@
|
||||
|
||||
<activity
|
||||
android:name=".ShareActivity"
|
||||
android:exported="true"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:taskAffinity=""
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay"/>
|
||||
|
||||
<activity-alias
|
||||
android:name=".ShareQueueAlias"
|
||||
android:targetActivity=".ShareActivity"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:taskAffinity=""
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/share_queue_label"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<meta-data
|
||||
android:name="pro.relaytv.SHARE_ENDPOINT_PATH"
|
||||
android:value="/smart" />
|
||||
<meta-data
|
||||
android:name="pro.relaytv.SHARE_SUCCESS_TEMPLATE"
|
||||
android:value="@string/share_queue_toast" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".SharePlayNowAlias"
|
||||
android:targetActivity=".ShareActivity"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:taskAffinity=""
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/share_play_now_label"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<meta-data
|
||||
android:name="pro.relaytv.SHARE_ENDPOINT_PATH"
|
||||
android:value="/play_now" />
|
||||
<meta-data
|
||||
android:name="pro.relaytv.SHARE_SUCCESS_TEMPLATE"
|
||||
android:value="@string/share_play_now_toast" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package pro.relaytv
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
@@ -25,7 +27,6 @@ import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import okhttp3.Call
|
||||
@@ -44,7 +45,6 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var web: WebView
|
||||
private lateinit var toolbar: MaterialToolbar
|
||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||
|
||||
private var activeBaseUrl: String? = null
|
||||
private var consecutiveHealthFailures = 0
|
||||
@@ -83,7 +83,6 @@ class MainActivity : AppCompatActivity() {
|
||||
val openServers = intent.getBooleanExtra("open_servers", false)
|
||||
toolbar = findViewById(R.id.toolbar)
|
||||
web = findViewById(R.id.web)
|
||||
swipeRefresh = findViewById(R.id.swipeRefresh)
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (this@MainActivity::web.isInitialized && web.canGoBack()) {
|
||||
@@ -106,29 +105,25 @@ class MainActivity : AppCompatActivity() {
|
||||
loadActiveServer(forcePickerOnFailure = false, manualRefresh = true)
|
||||
true
|
||||
}
|
||||
R.id.action_privacy -> {
|
||||
openPrivacyPolicy()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
web.settings.javaScriptEnabled = true
|
||||
web.settings.domStorageEnabled = true
|
||||
web.settings.mediaPlaybackRequiresUserGesture = false
|
||||
web.settings.userAgentString = web.settings.userAgentString + " RelayTV/1.1.0"
|
||||
swipeRefresh.setOnRefreshListener {
|
||||
loadActiveServer(forcePickerOnFailure = false, manualRefresh = true)
|
||||
}
|
||||
configureWebView()
|
||||
|
||||
web.webChromeClient = WebChromeClient()
|
||||
web.webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
mainFrameFailed = false
|
||||
swipeRefresh.isRefreshing = false
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
|
||||
if (request.isForMainFrame) {
|
||||
mainFrameFailed = true
|
||||
swipeRefresh.isRefreshing = false
|
||||
Toast.makeText(this@MainActivity, "Connection lost. Retrying…", Toast.LENGTH_SHORT).show()
|
||||
scheduleHeartbeat(1_500)
|
||||
}
|
||||
@@ -147,6 +142,26 @@ class MainActivity : AppCompatActivity() {
|
||||
loadServerBase(base.trimEnd('/'), forcePickerOnFailure = true, manualRefresh = false)
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun configureWebView() {
|
||||
val versionName = runCatching {
|
||||
packageManager.getPackageInfo(packageName, 0).versionName
|
||||
}.getOrNull() ?: "dev"
|
||||
web.settings.javaScriptEnabled = true
|
||||
web.settings.domStorageEnabled = true
|
||||
web.settings.mediaPlaybackRequiresUserGesture = false
|
||||
web.settings.userAgentString = web.settings.userAgentString + " RelayTV/$versionName"
|
||||
}
|
||||
|
||||
private fun openPrivacyPolicy() {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.privacy_policy_url)))
|
||||
runCatching {
|
||||
startActivity(browserIntent)
|
||||
}.onFailure {
|
||||
Toast.makeText(this, getString(R.string.privacy_open_failed), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
isInForeground = true
|
||||
@@ -173,13 +188,9 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun loadServerBase(base: String, forcePickerOnFailure: Boolean, manualRefresh: Boolean) {
|
||||
val normalized = base.trimEnd('/')
|
||||
if (normalized.isBlank()) {
|
||||
swipeRefresh.isRefreshing = false
|
||||
return
|
||||
}
|
||||
activeBaseUrl = normalized
|
||||
if (manualRefresh || !initialLoadComplete) {
|
||||
swipeRefresh.isRefreshing = true
|
||||
}
|
||||
probeServerHealth(
|
||||
base = normalized,
|
||||
forcePickerOnFailure = forcePickerOnFailure,
|
||||
@@ -191,7 +202,6 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun loadActiveServer(forcePickerOnFailure: Boolean, manualRefresh: Boolean) {
|
||||
val base = activeBaseUrl ?: HostStore.getActiveBaseUrl(this)?.trimEnd('/')
|
||||
if (base.isNullOrBlank()) {
|
||||
swipeRefresh.isRefreshing = false
|
||||
showServerPicker(force = true)
|
||||
return
|
||||
}
|
||||
@@ -248,7 +258,6 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
mainFrameFailed = false
|
||||
initialLoadComplete = true
|
||||
swipeRefresh.isRefreshing = false
|
||||
scheduleHeartbeat(HEARTBEAT_OK_MS)
|
||||
}
|
||||
}
|
||||
@@ -257,7 +266,6 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private fun onServerHealthFailed(forcePickerOnFailure: Boolean, manualRefresh: Boolean) {
|
||||
consecutiveHealthFailures += 1
|
||||
swipeRefresh.isRefreshing = false
|
||||
|
||||
if (forcePickerOnFailure && !initialLoadComplete) {
|
||||
Toast.makeText(this, "Server not reachable. Switch servers.", Toast.LENGTH_LONG).show()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package pro.relaytv
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
@@ -10,6 +11,11 @@ import androidx.work.WorkManager
|
||||
|
||||
class ShareActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val META_ENDPOINT_PATH = "pro.relaytv.SHARE_ENDPOINT_PATH"
|
||||
private const val META_SUCCESS_TEMPLATE = "pro.relaytv.SHARE_SUCCESS_TEMPLATE"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -26,10 +32,13 @@ class ShareActivity : AppCompatActivity() {
|
||||
|
||||
val shared = intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
|
||||
val url = extractUrl(shared) ?: run { finish(); return }
|
||||
val endpointPath = resolveMetaString(META_ENDPOINT_PATH).ifBlank { "/smart" }
|
||||
val successTemplate = resolveMetaString(META_SUCCESS_TEMPLATE).ifBlank { "Sent to \"%s\"" }
|
||||
|
||||
val input = Data.Builder()
|
||||
.putString(ShareWorker.KEY_BASE, base)
|
||||
.putString(ShareWorker.KEY_URL, url)
|
||||
.putString(ShareWorker.KEY_ENDPOINT_PATH, endpointPath)
|
||||
.build()
|
||||
|
||||
val req = OneTimeWorkRequestBuilder<ShareWorker>()
|
||||
@@ -38,10 +47,20 @@ class ShareActivity : AppCompatActivity() {
|
||||
|
||||
WorkManager.getInstance(this).enqueue(req)
|
||||
val serverName = HostStore.getActiveHost(this)?.name?.ifBlank { "Server" } ?: "Server"
|
||||
Toast.makeText(this, "Sent to \"$serverName\"", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this, successTemplate.format(serverName), Toast.LENGTH_SHORT).show()
|
||||
finishAndRemoveTask()
|
||||
}
|
||||
|
||||
private fun resolveMetaString(key: String): String {
|
||||
return try {
|
||||
val flags = PackageManager.GET_META_DATA
|
||||
val info = packageManager.getActivityInfo(componentName, flags)
|
||||
info.metaData?.getString(key).orEmpty()
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractUrl(text: String): String? {
|
||||
val m = Regex("""https?://\S+""").find(text)
|
||||
return m?.value?.trim()?.trimEnd(')', ']', '>', '"', '\'')
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
@@ -16,6 +15,7 @@ class ShareWorker(appContext: Context, params: WorkerParameters) : Worker(appCon
|
||||
companion object {
|
||||
const val KEY_BASE = "base"
|
||||
const val KEY_URL = "url"
|
||||
const val KEY_ENDPOINT_PATH = "endpoint_path"
|
||||
private const val CHANNEL_ID = "relaytv_silent"
|
||||
private const val NOTIF_ID = 4242
|
||||
}
|
||||
@@ -23,9 +23,10 @@ class ShareWorker(appContext: Context, params: WorkerParameters) : Worker(appCon
|
||||
override fun doWork(): Result {
|
||||
val base = inputData.getString(KEY_BASE)?.trim()?.trimEnd('/') ?: return Result.failure()
|
||||
val url = inputData.getString(KEY_URL) ?: return Result.failure()
|
||||
val endpointPath = inputData.getString(KEY_ENDPOINT_PATH)?.trim()?.ifBlank { "/smart" } ?: "/smart"
|
||||
return try {
|
||||
val payload = JSONObject().put("url", url).toString()
|
||||
val req = Net.postJson(base + "/smart", payload)
|
||||
val req = Net.postJson(base + endpointPath, payload)
|
||||
|
||||
Net.client.newCall(req).execute().use { resp ->
|
||||
val body = resp.body?.string().orEmpty()
|
||||
@@ -57,10 +58,8 @@ class ShareWorker(appContext: Context, params: WorkerParameters) : Worker(appCon
|
||||
private fun postNotification(title: String, text: String, tapToOpen: Boolean) {
|
||||
val nm = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val ch = NotificationChannel(CHANNEL_ID, "RelayTV", NotificationManager.IMPORTANCE_LOW)
|
||||
nm.createNotificationChannel(ch)
|
||||
}
|
||||
val ch = NotificationChannel(CHANNEL_ID, "RelayTV", NotificationManager.IMPORTANCE_LOW)
|
||||
nm.createNotificationChannel(ch)
|
||||
|
||||
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_media_play)
|
||||
@@ -78,7 +77,7 @@ class ShareWorker(appContext: Context, params: WorkerParameters) : Worker(appCon
|
||||
applicationContext,
|
||||
0,
|
||||
openIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
builder.setContentIntent(pending)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="120"
|
||||
android:viewportHeight="120">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12.98,22.42v74.54c0,5.48 2.19,9.87 6.58,12.06 4.38,2.19 8.77,2.19 13.15,0l70.15,-37.27c4.38,-2.19 6.58,-6.58 6.58,-12.06 0,-5.48 -2.19,-9.87 -6.58,-12.06L32.71,10.36C23.47,5.74 12.98,12.99 12.98,22.42z" />
|
||||
<path
|
||||
android:fillColor="#1B2439"
|
||||
android:pathData="M16.98,25.69v68c0,5 2,9 6,11 4,2 8,2 12,0l64,-34c4,-2 6,-6 6,-11 0,-5 -2,-9 -6,-11l-64,-34c-8.43,-4.22 -18,2.41 -18,11z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M64.89,39.13C61.32,36.63 38.94,24.09 34.01,21.83c-5.76,-2.64 -10.27,1.85 -9.88,5.88 0.17,1.77 0.18,57.55 0.21,63.09 0.04,8.03 5.25,8.1 8.46,7.16 5.14,-1.41 28.58,-15.18 29.63,-15.78"
|
||||
android:strokeColor="#36D9F4"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="5" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M66.28,72.15C65.1,71.98 52.37,69.37 50.4,69.24M67.69,50.59C65.1,51.84 49.2,61.51 49.2,61.51"
|
||||
android:strokeColor="#2D89FF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeWidth="4" />
|
||||
<path
|
||||
android:fillColor="#2D89FF"
|
||||
android:pathData="M73.01,66.99a6.97,6.97 0 1,1 0,13.93a6.97,6.97 0 1,1 0,-13.93z" />
|
||||
<path
|
||||
android:fillColor="#36D9F4"
|
||||
android:pathData="M44.86,55.91a9.96,9.96 0 1,1 0,19.92a9.96,9.96 0 1,1 0,-19.92z" />
|
||||
<path
|
||||
android:fillColor="#2D89FF"
|
||||
android:pathData="M74.24,41.12a7,7 0 1,1 0,14a7,7 0 1,1 0,-14z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="120"
|
||||
android:viewportHeight="120">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M16.98,25.69v68c0,5 2,9 6,11 4,2 8,2 12,0l64,-34c4,-2 6,-6 6,-11 0,-5 -2,-9 -6,-11l-64,-34c-8.43,-4.22 -18,2.41 -18,11z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="6" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M64.89,39.13C61.32,36.63 38.94,24.09 34.01,21.83c-5.76,-2.64 -10.27,1.85 -9.88,5.88 0.17,1.77 0.18,57.55 0.21,63.09 0.04,8.03 5.25,8.1 8.46,7.16 5.14,-1.41 28.58,-15.18 29.63,-15.78"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="5" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M66.28,72.15C65.1,71.98 52.37,69.37 50.4,69.24M67.69,50.59C65.1,51.84 49.2,61.51 49.2,61.51"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeWidth="4" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M73.01,66.99a6.97,6.97 0 1,1 0,13.93a6.97,6.97 0 1,1 0,-13.93z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M44.86,55.91a9.96,9.96 0 1,1 0,19.92a9.96,9.96 0 1,1 0,-19.92z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M74.24,41.12a7,7 0 1,1 0,14a7,7 0 1,1 0,-14z" />
|
||||
</vector>
|
||||
@@ -2,8 +2,7 @@
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/relaytv_surface">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -18,16 +17,10 @@
|
||||
app:menu="@menu/main_menu" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefresh"
|
||||
<WebView
|
||||
android:id="@+id/web"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/web"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RelayTV Server"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingBottom="10dp"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Base URL (HTTP or HTTPS). Example: http://nuc.lan:8787"
|
||||
android:textSize="14sp"
|
||||
android:paddingBottom="8dp"/>
|
||||
<EditText
|
||||
android:id="@+id/editBaseUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="http://nuc.lan:8787"
|
||||
android:inputType="textUri"
|
||||
android:singleLine="true"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="14dp">
|
||||
<Button
|
||||
android:id="@+id/btnSave"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Save"/>
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
<Button
|
||||
android:id="@+id/btnDetect"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Auto-detect"/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/txtStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="13sp"
|
||||
android:paddingTop="10dp"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tip: Share a link from BravePipe → RelayTV. It sends to /smart in the background and shows a notification (tap it to open UI)."
|
||||
android:textSize="13sp"
|
||||
android:paddingTop="16dp"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -11,4 +11,8 @@
|
||||
android:title="@string/reload"
|
||||
android:icon="@android:drawable/ic_popup_sync"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_privacy"
|
||||
android:title="@string/privacy_policy"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/relaytv_surface" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/relaytv_surface" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
@@ -21,4 +21,11 @@
|
||||
<string name="status_offline">Offline</string>
|
||||
<string name="status_checking">Checking…</string>
|
||||
<string name="status_active_prefix">Active</string>
|
||||
<string name="share_queue_label">RelayTV Queue</string>
|
||||
<string name="share_play_now_label">RelayTV Play</string>
|
||||
<string name="share_queue_toast">Queued on "%s"</string>
|
||||
<string name="share_play_now_toast">Playing now on "%s"</string>
|
||||
<string name="privacy_policy">Privacy policy</string>
|
||||
<string name="privacy_policy_url">https://github.com/mcgeezy/relaytv-android/blob/main/docs/PRIVACY_POLICY.md</string>
|
||||
<string name="privacy_open_failed">No browser available to open the privacy policy.</string>
|
||||
</resources>
|
||||
|
||||
@@ -10,12 +10,4 @@
|
||||
|
||||
</style>
|
||||
<style name="Theme.RelayTV.NoActionBar" parent="Theme.Material3.DayNight.NoActionBar" />
|
||||
|
||||
<style name="Theme.RelayTV.Share" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackground">@android:color/transparent</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<exclude domain="root" path="." />
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="root" path="." />
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="root" path="." />
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
<network-security-config xmlns:tools="http://schemas.android.com/tools">
|
||||
<base-config
|
||||
cleartextTrafficPermitted="true"
|
||||
tools:ignore="InsecureBaseConfiguration" />
|
||||
</network-security-config>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# RelayTV Android Privacy Policy
|
||||
|
||||
Last updated: April 3, 2026
|
||||
|
||||
## Summary
|
||||
|
||||
RelayTV Android is a companion remote for self-hosted RelayTV servers. The app does not require an account, does not include advertising, and does not use third-party analytics SDKs.
|
||||
|
||||
## Information The App Processes
|
||||
|
||||
RelayTV Android may process the following categories of data in order to operate:
|
||||
|
||||
1. RelayTV server information
|
||||
- Server names and base URLs you add in the app
|
||||
- The currently selected active server
|
||||
2. Shared content
|
||||
- Links you share into the app using Android share targets such as `RelayTV Queue` and `RelayTV Play`
|
||||
3. Local network discovery data
|
||||
- RelayTV service advertisements discovered on your local network via mDNS / NSD
|
||||
4. Device-local notification data
|
||||
- Notification text used to confirm a queue or play action
|
||||
|
||||
## How The App Uses Data
|
||||
|
||||
RelayTV Android uses the data above only to:
|
||||
|
||||
1. Connect to the RelayTV server you selected
|
||||
2. Load the RelayTV web UI in an embedded WebView
|
||||
3. Send shared links to RelayTV endpoints such as `/smart` or `/play_now`
|
||||
4. Discover compatible RelayTV servers on your local network
|
||||
5. Show local notifications about share results and reconnect state
|
||||
|
||||
## Data Storage
|
||||
|
||||
The app stores configured server names and base URLs locally on your device using Android shared preferences.
|
||||
|
||||
The app does not operate its own cloud backend and does not sync your app data to a RelayTV-operated online service.
|
||||
|
||||
## Data Sharing
|
||||
|
||||
RelayTV Android sends requests only to the RelayTV server you configured or discovered and chose to use.
|
||||
|
||||
The app does not sell personal information and does not share data with advertising networks or analytics providers.
|
||||
|
||||
## Network Security
|
||||
|
||||
RelayTV is designed for local-first and self-hosted use. Because many self-hosted RelayTV installations run on trusted LAN HTTP endpoints, the app allows cleartext HTTP connections in addition to HTTPS. If your RelayTV server is exposed outside a trusted private network, you should use HTTPS.
|
||||
|
||||
## Permissions
|
||||
|
||||
RelayTV Android may request or use these permissions:
|
||||
|
||||
1. `INTERNET`
|
||||
- Required to contact your RelayTV server
|
||||
2. `ACCESS_NETWORK_STATE`
|
||||
- Required to detect connectivity changes and recover the UI after network interruptions
|
||||
3. `CHANGE_WIFI_MULTICAST_STATE`
|
||||
- Required for local network RelayTV discovery on some devices
|
||||
4. `POST_NOTIFICATIONS`
|
||||
- Required on newer Android versions to show queue/play result notifications
|
||||
|
||||
## Children
|
||||
|
||||
RelayTV Android is not directed to children and does not knowingly collect personal information from children.
|
||||
|
||||
## Changes
|
||||
|
||||
This privacy policy may be updated when app behavior changes. Update the "Last updated" date whenever this document changes materially.
|
||||
|
||||
## Contact
|
||||
|
||||
For project updates or issue reporting, use the project repository:
|
||||
|
||||
https://github.com/mcgeezy/relaytv-android
|
||||
@@ -0,0 +1,60 @@
|
||||
# RelayTV Android Release Checklist
|
||||
|
||||
Use this checklist before cutting a public release or uploading a new Play Console build.
|
||||
|
||||
## Build Readiness
|
||||
|
||||
1. Verify Android toolchain prerequisites:
|
||||
- `./scripts/check-android-env.sh all`
|
||||
2. Install or refresh required SDK components:
|
||||
- `./scripts/bootstrap-android.sh`
|
||||
3. Validate debug build, lint, and unit tests:
|
||||
- `./scripts/build-debug.sh`
|
||||
4. Validate release bundle and release lint:
|
||||
- `./scripts/build-release.sh`
|
||||
5. If you have a device connected, run instrumentation tests:
|
||||
- `./scripts/test-connected.sh`
|
||||
|
||||
## Versioning
|
||||
|
||||
1. Increment `versionCode` for every public upload.
|
||||
2. Set `versionName` to the user-facing release number.
|
||||
3. Verify the user agent string and release notes match the version being shipped.
|
||||
|
||||
## Play Console Requirements
|
||||
|
||||
1. Keep `targetSdk` at the current Google Play minimum.
|
||||
- As of April 3, 2026, Play requires new apps and updates to target Android 15 / API 35.
|
||||
2. Upload an Android App Bundle:
|
||||
- `app/build/outputs/bundle/release/app-release.aab`
|
||||
3. Use Play App Signing in Play Console.
|
||||
4. Complete the Data safety form for this release.
|
||||
5. Provide a valid support email in Play Console.
|
||||
6. Publish a public privacy policy URL.
|
||||
- This repo includes a draft policy in `docs/PRIVACY_POLICY.md`.
|
||||
- The in-app privacy menu points to the GitHub-hosted copy on `main`.
|
||||
|
||||
## App-Specific Verification
|
||||
|
||||
1. Confirm manual server entry still works for both HTTP and HTTPS RelayTV servers.
|
||||
2. Confirm LAN discovery finds `_relaytv._tcp` services on the local network.
|
||||
3. Confirm both Android share targets work:
|
||||
- `RelayTV Queue` -> `/smart`
|
||||
- `RelayTV Play` -> `/play_now`
|
||||
4. Confirm the WebView reconnects after the RelayTV server restarts or the network drops.
|
||||
5. Confirm the privacy menu opens the published policy URL.
|
||||
|
||||
## Signing And Distribution
|
||||
|
||||
1. Configure your release signing key or Play App Signing flow.
|
||||
2. Archive the signed `.aab`, release notes, and the exact commit SHA used for the build.
|
||||
3. Tag the release in git after the Play upload is verified.
|
||||
|
||||
## Official References
|
||||
|
||||
1. Target API requirement:
|
||||
- https://developer.android.com/google/play/requirements/target-sdk
|
||||
2. App setup, versioning, and Play App Signing:
|
||||
- https://support.google.com/googleplay/android-developer/answer/9859152
|
||||
3. Developer support contact requirements:
|
||||
- https://support.google.com/googleplay/android-developer/answer/113477
|
||||
@@ -1,2 +1,3 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.suppressUnsupportedCompileSdk=35
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<lint>
|
||||
<issue id="GradleDependency" severity="ignore" />
|
||||
<issue id="IconDuplicates" severity="ignore" />
|
||||
<issue id="IconLauncherShape" severity="ignore" />
|
||||
</lint>
|
||||
@@ -5,9 +5,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
INSTALL_EMULATOR="${INSTALL_EMULATOR:-0}"
|
||||
ANDROID_PLATFORM="${ANDROID_PLATFORM:-android-34}"
|
||||
ANDROID_BUILD_TOOLS="${ANDROID_BUILD_TOOLS:-34.0.0}"
|
||||
ANDROID_SYSTEM_IMAGE="${ANDROID_SYSTEM_IMAGE:-system-images;android-34;google_apis;x86_64}"
|
||||
ANDROID_PLATFORM="${ANDROID_PLATFORM:-android-35}"
|
||||
ANDROID_BUILD_TOOLS="${ANDROID_BUILD_TOOLS:-35.0.0}"
|
||||
ANDROID_SYSTEM_IMAGE="${ANDROID_SYSTEM_IMAGE:-system-images;android-35;google_apis;x86_64}"
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
|
||||
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
if [[ ! -x "./scripts/check-android-env.sh" ]]; then
|
||||
chmod +x "./scripts/check-android-env.sh"
|
||||
fi
|
||||
./scripts/check-android-env.sh build
|
||||
|
||||
if [[ ! -x "./gradlew" ]]; then
|
||||
chmod +x ./gradlew
|
||||
fi
|
||||
|
||||
if [[ "$#" -gt 0 ]]; then
|
||||
TASKS=("$@")
|
||||
else
|
||||
TASKS=("clean" "bundleRelease" "lintRelease")
|
||||
fi
|
||||
|
||||
echo "Running Gradle tasks: ${TASKS[*]}"
|
||||
./gradlew --no-daemon "${TASKS[@]}"
|
||||
@@ -7,6 +7,8 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
failures=0
|
||||
warnings=0
|
||||
ANDROID_PLATFORM="${ANDROID_PLATFORM:-android-35}"
|
||||
ANDROID_BUILD_TOOLS="${ANDROID_BUILD_TOOLS:-35.0.0}"
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
ok() { say "[ok] $*"; }
|
||||
@@ -90,6 +92,20 @@ check_sdk() {
|
||||
else
|
||||
warn "sdkmanager not found in PATH or ${sdk_root}/cmdline-tools/latest/bin."
|
||||
fi
|
||||
|
||||
if [[ -n "${sdk_root}" ]]; then
|
||||
if [[ -d "${sdk_root}/platforms/${ANDROID_PLATFORM}" ]]; then
|
||||
ok "Found Android platform: ${ANDROID_PLATFORM}"
|
||||
else
|
||||
fail "Missing Android platform: ${ANDROID_PLATFORM}"
|
||||
fi
|
||||
|
||||
if [[ -d "${sdk_root}/build-tools/${ANDROID_BUILD_TOOLS}" ]]; then
|
||||
ok "Found Android build-tools: ${ANDROID_BUILD_TOOLS}"
|
||||
else
|
||||
fail "Missing Android build-tools: ${ANDROID_BUILD_TOOLS}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_gradle_wrapper() {
|
||||
|
||||
Reference in New Issue
Block a user