diff --git a/receivers/android/.gitignore b/receivers/android/.gitignore index aa724b7..c1d077d 100644 --- a/receivers/android/.gitignore +++ b/receivers/android/.gitignore @@ -7,9 +7,14 @@ /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml +/.idea/* .DS_Store /build /captures .externalNativeBuild .cxx local.properties + +/app/defaultFlavor +/app/playstore + diff --git a/receivers/android/.gitlab-ci.yml b/receivers/android/.gitlab-ci.yml index 1a6bef5..40ab25e 100644 --- a/receivers/android/.gitlab-ci.yml +++ b/receivers/android/.gitlab-ci.yml @@ -1,7 +1,49 @@ -buildAndDeployAndroid: - stage: buildAndDeployAndroid + +buildAndroidDockerContainer: + stage: buildDockerContainers + image: docker:20.10.16 + services: + - docker:20.10.16-dind + tags: + - fcast-instance-runner before_script: - cd receivers/android script: - - sh deploy.sh - when: manual \ No newline at end of file + - echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin + - docker build -t $CI_REGISTRY/videostreaming/fcast/receiver-android-dev:latest . + - docker push $CI_REGISTRY/videostreaming/fcast/receiver-android-dev:latest + when: manual + +buildAndroid: + stage: buildAndDeployAndroid + image: gitlab.futo.org:5050/videostreaming/fcast/receiver-android-dev:latest + tags: + - fcast-instance-runner + variables: + ANDROID_VERSION_NAME: "1" + ANDROID_VERSION_CODE: "1" + before_script: + - cd receivers/android + script: + - echo "Building content..." + - ./gradlew --stacktrace assembleRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE + - ./gradlew --stacktrace bundlePlaystoreRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE + - echo $ANDROID_VERSION_CODE > ./fcast-version.txt + - mkdir -p /artifacts/$ANDROID_VERSION_CODE + - cp -rf ./app/build/outputs/apk/defaultFlavor/release/app-defaultFlavor-release.apk /artifacts/$ANDROID_VERSION_CODE/fcast-release.apk + - cp -rf ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab /artifacts/$ANDROID_VERSION_CODE/fcast-playstore-release.aab + - cp -rf ./fcast-version.txt /artifacts/fcast-version.txt + + # Artifact uploads require artifacts to be in project directory + - mkdir -p ./$ANDROID_VERSION_CODE + - mv ./app/build/outputs/apk/defaultFlavor/release/app-defaultFlavor-release.apk ./$ANDROID_VERSION_CODE/fcast-release.apk + - mv ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab ./$ANDROID_VERSION_CODE/fcast-playstore-release.aab + artifacts: + untracked: false + when: on_success + access: all + expire_in: "30 days" + paths: + - receivers/android/$ANDROID_VERSION_CODE/fcast-release.apk + - receivers/android/$ANDROID_VERSION_CODE/fcast-playstore-release.aab + when: manual diff --git a/receivers/android/Dockerfile b/receivers/android/Dockerfile new file mode 100644 index 0000000..c0143c5 --- /dev/null +++ b/receivers/android/Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:24.04 + +# TZ +ARG DEBIAN_FRONTEND=noninteractive + +ENV ANDROID_HOME=/Android/Sdk +ENV TZ=Etc/UTC + +RUN apt update +RUN apt install -y zip wget tzdata + +RUN apt install -y openjdk-21-jdk +RUN wget https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip +RUN unzip commandlinetools-linux-13114758_latest.zip +RUN mkdir -p $ANDROID_HOME +RUN mv /cmdline-tools $ANDROID_HOME + +RUN yes | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=$ANDROID_HOME --licenses +RUN $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=$ANDROID_HOME --install "platforms;android-36" diff --git a/receivers/android/app/build.gradle b/receivers/android/app/build.gradle index 89665eb..c648835 100644 --- a/receivers/android/app/build.gradle +++ b/receivers/android/app/build.gradle @@ -1,7 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.10' + id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0' } ext { @@ -13,19 +13,19 @@ println("Version Name: $currentVersionName") println("Version Code: $currentVersionCode") def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('/opt/key.properties') +def keystorePropertiesFile = rootProject.file('/certs/key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } android { namespace 'com.futo.fcast.receiver' - compileSdk 34 + compileSdk 36 defaultConfig { applicationId "com.futo.fcast.receiver" minSdk 24 - targetSdk 34 + targetSdk 36 versionCode currentVersionCode versionName currentVersionName @@ -69,11 +69,8 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } buildFeatures { buildConfig true @@ -84,18 +81,18 @@ android { } dependencies { - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.11.0' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" - implementation 'androidx.media3:media3-exoplayer:1.2.0' - implementation "com.squareup.okhttp3:okhttp:4.11.0" + implementation 'androidx.core:core-ktx:1.16.0' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'com.google.android.material:material:1.12.0' + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0" + implementation 'androidx.media3:media3-exoplayer:1.7.1' + implementation "com.squareup.okhttp3:okhttp:5.1.0" implementation 'com.journeyapps:zxing-android-embedded:4.3.0' - implementation 'org.java-websocket:Java-WebSocket:1.5.4' - implementation 'androidx.media3:media3-ui:1.2.0' - implementation 'androidx.media3:media3-exoplayer-dash:1.2.0' - implementation 'androidx.media3:media3-exoplayer-hls:1.2.0' + implementation 'org.java-websocket:Java-WebSocket:1.6.0' + implementation 'androidx.media3:media3-ui:1.7.1' + implementation 'androidx.media3:media3-exoplayer-dash:1.7.1' + implementation 'androidx.media3:media3-exoplayer-hls:1.7.1' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' } diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/BootReceiver.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/BootReceiver.kt index b77471f..50da31e 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/BootReceiver.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/BootReceiver.kt @@ -19,7 +19,7 @@ class BootReceiver : BroadcastReceiver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Show a notification with an action to start the service - showStartServiceNotification(context); + showStartServiceNotification(context) } else { // Directly start the service for older versions val serviceIntent = Intent(context, NetworkService::class.java) diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/CustomStyledPlayerView.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/CustomStyledPlayerView.kt index ea4be8b..df0a380 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/CustomStyledPlayerView.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/CustomStyledPlayerView.kt @@ -4,4 +4,4 @@ import android.content.Context import android.util.AttributeSet import androidx.media3.ui.PlayerView -class CustomPlayerView(context: Context, attrs: AttributeSet? = null) : PlayerView(context, attrs) { } \ No newline at end of file +class CustomPlayerView(context: Context, attrs: AttributeSet? = null) : PlayerView(context, attrs) \ No newline at end of file diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/DiscoveryService.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/DiscoveryService.kt index 89c940b..368b3d1 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/DiscoveryService.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/DiscoveryService.kt @@ -1,6 +1,5 @@ package com.futo.fcast.receiver -import WebSocketListenerService import android.content.Context import android.net.nsd.NsdManager import android.net.nsd.NsdServiceInfo @@ -40,14 +39,14 @@ class DiscoveryService(private val _context: Context) { try { _nsdManager?.unregisterService(_registrationListenerTcp) - } catch (e: Throwable) { - Log.e(TAG, "Failed to unregister TCP Listener."); + } catch (_: Throwable) { + Log.e(TAG, "Failed to unregister TCP Listener.") } try { _nsdManager?.unregisterService(_registrationListenerWs) - } catch (e: Throwable) { - Log.e(TAG, "Failed to unregister TCP Listener."); + } catch (_: Throwable) { + Log.e(TAG, "Failed to unregister TCP Listener.") } _nsdManager = null diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/FCastSession.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/FCastSession.kt index dbe2b46..73b12f0 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/FCastSession.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/FCastSession.kt @@ -1,27 +1,13 @@ package com.futo.fcast.receiver -import android.util.Base64 import android.util.Log -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.DataOutputStream import java.io.OutputStream -import java.math.BigInteger import java.net.SocketAddress import java.nio.ByteBuffer -import java.security.KeyFactory -import java.security.KeyPair -import java.security.KeyPairGenerator -import java.security.MessageDigest -import java.security.PrivateKey -import java.security.spec.X509EncodedKeySpec import java.util.UUID -import javax.crypto.Cipher -import javax.crypto.KeyAgreement -import javax.crypto.spec.DHParameterSpec -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec + enum class SessionState { @@ -48,8 +34,8 @@ enum class Opcode(val value: Byte) { Pong(13); companion object { - private val _map = values().associateBy { it.value } - fun find(value: Byte): Opcode = _map[value] ?: Opcode.None + private val _map = entries.associateBy { it.value } + fun find(value: Byte): Opcode = _map[value] ?: None } } @@ -111,7 +97,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress: } fun processBytes(data: ByteBuffer) { - Log.i(TAG, "${data.remaining()} bytes received from ${_remoteSocketAddress}") + Log.i(TAG, "${data.remaining()} bytes received from $_remoteSocketAddress") if (!data.hasArray()) { throw IllegalArgumentException("ByteBuffer does not have a backing array") } @@ -132,7 +118,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress: return } - Log.i(TAG, "$count bytes received from ${_remoteSocketAddress}") + Log.i(TAG, "$count bytes received from $_remoteSocketAddress") when (_state) { SessionState.WaitingForLength -> handleLengthBytes(data, 0, count) @@ -166,7 +152,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress: } if (bytesRemaining > 0) { - Log.i(TAG, "$bytesRemaining remaining bytes ${_remoteSocketAddress} pushed to handlePacketBytes") + Log.i(TAG, "$bytesRemaining remaining bytes $_remoteSocketAddress pushed to handlePacketBytes") handlePacketBytes(data, offset + bytesToRead, bytesRemaining) } } @@ -181,7 +167,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress: Log.i(TAG, "Read $bytesToRead bytes from packet") if (_bytesRead >= _packetLength) { - Log.i(TAG, "Packet finished receiving from ${_remoteSocketAddress} of $_packetLength bytes.") + Log.i(TAG, "Packet finished receiving from $_remoteSocketAddress of $_packetLength bytes.") handleNextPacket() _state = SessionState.WaitingForLength @@ -189,14 +175,14 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress: _bytesRead = 0 if (bytesRemaining > 0) { - Log.i(TAG, "$bytesRemaining remaining bytes ${_remoteSocketAddress} pushed to handleLengthBytes") + Log.i(TAG, "$bytesRemaining remaining bytes $_remoteSocketAddress pushed to handleLengthBytes") handleLengthBytes(data, offset + bytesToRead, bytesRemaining) } } } private fun handleNextPacket() { - Log.i(TAG, "Processing packet of $_bytesRead bytes from ${_remoteSocketAddress}") + Log.i(TAG, "Processing packet of $_bytesRead bytes from $_remoteSocketAddress") val opcode = Opcode.find(_buffer[0]) val body = if (_packetLength > 1) _buffer.copyOfRange(1, _packetLength) diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt index 2339f06..2500c27 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt @@ -1,16 +1,12 @@ package com.futo.fcast.receiver -import WebSocketListenerService import android.Manifest -import android.app.Activity import android.app.AlertDialog import android.app.PendingIntent -import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller import android.content.pm.PackageManager import android.graphics.drawable.Animatable -import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings @@ -34,12 +30,12 @@ import androidx.media3.ui.PlayerView import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.BarcodeEncoder import kotlinx.coroutines.* -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import java.io.InputStream import java.io.OutputStream import java.net.NetworkInterface +import androidx.core.net.toUri class MainActivity : AppCompatActivity() { @@ -153,7 +149,7 @@ class MainActivity : AppCompatActivity() { Log.i(TAG, "connection url: $url") val bitmap = barcodeEncoder.encodeBitmap(url, BarcodeFormat.QR_CODE, px, px) _imageQr.setImageBitmap(bitmap) - } catch (e: java.lang.Exception) { + } catch (_: java.lang.Exception) { _textScanToConnect.visibility = View.GONE _imageQr.visibility = View.GONE } @@ -201,7 +197,7 @@ class MainActivity : AppCompatActivity() { _player = ExoPlayer.Builder(this).build() _videoBackground.player = _player - val mediaItem = MediaItem.fromUri(Uri.parse("android.resource://" + packageName + "/" + R.raw.c)) + val mediaItem = MediaItem.fromUri(("android.resource://" + packageName + "/" + R.raw.c).toUri()) _player.setMediaItem(mediaItem) _player.prepare() _player.repeatMode = Player.REPEAT_MODE_ALL @@ -224,7 +220,7 @@ class MainActivity : AppCompatActivity() { if (listPermissionsNeeded.isNotEmpty()) { val permissionRequestedKey = "NOTIFICATIONS_PERMISSION_REQUESTED" - val sharedPref = this.getSharedPreferences(_preferenceFileKey, Context.MODE_PRIVATE) + val sharedPref = this.getSharedPreferences(_preferenceFileKey, MODE_PRIVATE) val hasRequestedPermission = sharedPref.getBoolean(permissionRequestedKey, false) if (!hasRequestedPermission) { ActivityCompat.requestPermissions(this, listPermissionsNeeded.toTypedArray(), REQUEST_ID_MULTIPLE_PERMISSIONS) @@ -244,7 +240,7 @@ class MainActivity : AppCompatActivity() { private fun requestSystemAlertWindowPermission() { try { val permissionRequestedKey = "SYSTEM_ALERT_WINDOW_PERMISSION_REQUESTED" - val sharedPref = this.getSharedPreferences(_preferenceFileKey, Context.MODE_PRIVATE) + val sharedPref = this.getSharedPreferences(_preferenceFileKey, MODE_PRIVATE) val hasRequestedPermission = sharedPref.getBoolean(permissionRequestedKey, false) if (!Settings.canDrawOverlays(this)) { @@ -254,7 +250,8 @@ class MainActivity : AppCompatActivity() { .setMessage(R.string.permission_dialog_message) .setPositiveButton(R.string.permission_dialog_positive_button) { _, _ -> try { - val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")) + val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + "package:$packageName".toUri()) _systemAlertWindowPermissionLauncher.launch(intent) } catch (e: Throwable) { Log.e("OverlayPermission", "Error requesting overlay permission", e) @@ -276,7 +273,7 @@ class MainActivity : AppCompatActivity() { Toast.makeText(this, "Optional system alert window permission missing", Toast.LENGTH_SHORT).show() } } - } catch (e: Throwable) { + } catch (_: Throwable) { Log.e(TAG, "Failed to request system alert window permissions") } } @@ -367,7 +364,7 @@ class MainActivity : AppCompatActivity() { _updateSpinner.visibility = View.INVISIBLE setText(resources.getText(R.string.there_is_an_update_available_do_you_wish_to_update)) _buttonUpdate.visibility = View.VISIBLE - } catch (e: Throwable) { + } catch (_: Throwable) { Toast.makeText(this@MainActivity, "Failed to show update dialog", Toast.LENGTH_LONG).show() Log.w(TAG, "Error occurred in update dialog.") } @@ -388,11 +385,11 @@ class MainActivity : AppCompatActivity() { .build() val response = client.newCall(request).execute() - if (!response.isSuccessful || response.body == null) { + if (!response.isSuccessful) { return null } - return response.body?.string()?.trim()?.toInt() + return response.body.string().trim().toInt() } private fun update() { @@ -414,7 +411,7 @@ class MainActivity : AppCompatActivity() { val response = client.newCall(request).execute() val body = response.body - if (response.isSuccessful && body != null) { + if (response.isSuccessful) { inputStream = body.byteStream() val dataLength = body.contentLength() install(inputStream, dataLength) @@ -540,8 +537,8 @@ class MainActivity : AppCompatActivity() { companion object { const val TAG = "MainActivity" - const val VERSION_URL = "https://releases.grayjay.app/fcast-version.txt" - const val APK_URL = "https://releases.grayjay.app/fcast-release.apk" + const val VERSION_URL = "https://dl.fcast.org/android/fcast-version.txt" + const val APK_URL = "https://dl.fcast.org/android/fcast-release.apk" const val REQUEST_ID_MULTIPLE_PERMISSIONS = 1 const val REQUEST_CODE = 2 } diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/NetworkService.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/NetworkService.kt index 0820057..9580827 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/NetworkService.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/NetworkService.kt @@ -1,8 +1,6 @@ package com.futo.fcast.receiver -import WebSocketListenerService import android.app.* -import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build @@ -10,10 +8,8 @@ import android.os.IBinder import android.provider.Settings import android.util.Log import android.widget.Toast -import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import kotlinx.coroutines.* -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class NetworkService : Service() { @@ -48,7 +44,7 @@ class NetworkService : Service() { description = descriptionText } - val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager: NotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } @@ -72,7 +68,7 @@ class NetworkService : Service() { try { Log.i(TAG, "Sending version ${session.id}") session.send(Opcode.Version, VersionMessage(2)) - } catch (e: Throwable) { + } catch (_: Throwable) { Log.e(TAG, "Failed to send version ${session.id}") } } @@ -122,7 +118,7 @@ class NetworkService : Service() { try { _webSocketListenerService?.stop() - } catch (e: Throwable) { + } catch (_: Throwable) { //Ignored } finally { _webSocketListenerService = null @@ -193,7 +189,7 @@ class NetworkService : Service() { .setAutoCancel(true) .build() - val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(PLAY_NOTIFICATION_ID, playNotification) } } else { diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/PlayerActivity.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/PlayerActivity.kt index f08a35d..35440c8 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/PlayerActivity.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/PlayerActivity.kt @@ -1,6 +1,5 @@ package com.futo.fcast.receiver -import android.content.Context import android.graphics.drawable.Animatable import android.net.ConnectivityManager import android.net.Network @@ -44,6 +43,7 @@ import java.io.File import java.io.FileOutputStream import kotlin.math.abs import kotlin.math.max +import androidx.core.net.toUri class PlayerActivity : AppCompatActivity() { @@ -245,7 +245,7 @@ class PlayerActivity : AppCompatActivity() { _playerControlView.controllerAutoShow = false Log.i(TAG, "Attached onConnectionAvailable listener.") - _connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + _connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager val netReq = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) @@ -398,7 +398,7 @@ class PlayerActivity : AppCompatActivity() { } if (!playMessage.url.isNullOrEmpty()) { - mediaItemBuilder.setUri(Uri.parse(playMessage.url)) + mediaItemBuilder.setUri(playMessage.url.toUri()) } else if (!playMessage.content.isNullOrEmpty()) { val tempFile = File.createTempFile("content_", ".tmp", cacheDir) tempFile.deleteOnExit() diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/TcpListenerService.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/TcpListenerService.kt index 3ae4ddf..79d4b44 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/TcpListenerService.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/TcpListenerService.kt @@ -1,7 +1,6 @@ package com.futo.fcast.receiver import android.util.Log -import java.io.BufferedInputStream import java.net.InetSocketAddress import java.net.ServerSocket import java.net.Socket diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketListenerService.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketListenerService.kt index 13729d8..69d087d 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketListenerService.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketListenerService.kt @@ -1,6 +1,6 @@ +package com.futo.fcast.receiver + import android.util.Log -import com.futo.fcast.receiver.FCastSession -import com.futo.fcast.receiver.NetworkService import org.java_websocket.WebSocket import org.java_websocket.handshake.ClientHandshake import org.java_websocket.server.WebSocketServer diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketOutputStream.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketOutputStream.kt index 3381463..2c86b87 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketOutputStream.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/WebSocketOutputStream.kt @@ -1,3 +1,5 @@ +package com.futo.fcast.receiver + import org.java_websocket.WebSocket import java.io.IOException import java.io.OutputStream diff --git a/receivers/android/app/src/main/res/values-ar/strings.xml b/receivers/android/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..74dae8b --- /dev/null +++ b/receivers/android/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,25 @@ + + جهاز استقبال FCast + فشلت العملية بشكل عام + فشلت العملية بسبب إلغائها بشكل نشط + فشلت العملية بسبب حظرها + فشلت العملية لأنها تتعارض (أو غير متوافقة مع) حزمة أخرى مثبتة بالفعل على الجهاز + فشلت العملية لأنها غير متوافقة بشكل أساسي مع هذا الجهاز + فشلت العملية لأن أحد ملفات APK أو أكثر كان غير صالح + فشلت العملية بسبب مشاكل التخزين + جاري تنزيل التحديث… + جاري تثبيت التحديث… + نجاح + فشل تحديث الحزمة بسبب الخطأ + هناك تحديث متاح لجهاز الاستقبال FCast، هل ترغب في التحديث؟ + أبداً + يغلق + تحديث + التحقق من التحديثات… + لا توجد تحديثات متاحة + إذن نافذة تنبيه النظام + يتطلب هذا التطبيق إذن "نافذة تنبيه النظام" لعرض المحتوى فوق التطبيقات الأخرى. يُرجى منح الإذن. + يسمح + يلغي + في انتظار وسائل الإعلام + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values-de/strings.xml b/receivers/android/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..88a3f10 --- /dev/null +++ b/receivers/android/app/src/main/res/values-de/strings.xml @@ -0,0 +1,25 @@ + + FCast-Empfänger + Der Vorgang ist auf allgemeine Weise fehlgeschlagen + Der Vorgang ist fehlgeschlagen, da er aktiv abgebrochen wurde + Der Vorgang ist fehlgeschlagen, weil er blockiert wurde + Der Vorgang ist fehlgeschlagen, da er mit einem anderen Paket, das bereits auf dem Gerät installiert ist, in Konflikt steht (oder inkonsistent ist). + Der Vorgang ist fehlgeschlagen, da er grundsätzlich nicht mit diesem Gerät kompatibel ist + Der Vorgang ist fehlgeschlagen, da eine oder mehrere APKs ungültig waren + Der Vorgang ist aufgrund von Speicherproblemen fehlgeschlagen + Update wird heruntergeladen… + Update wird installiert… + Erfolg + Paket konnte nicht aktualisiert werden. Fehler + Für den FCast-Receiver ist ein Update verfügbar. Möchten Sie ein Update durchführen? + Niemals + Schließen + Aktualisieren + Suche nach Updates… + Keine Updates verfügbar + Berechtigung für das Systemwarnungsfenster + Diese App benötigt die Berechtigung „Systemwarnfenster“, um Inhalte über anderen Apps anzuzeigen. Bitte erteilen Sie die Berechtigung. + Erlauben + Stornieren + Warten auf Medien + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values-es/strings.xml b/receivers/android/app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..f80cd97 --- /dev/null +++ b/receivers/android/app/src/main/res/values-es/strings.xml @@ -0,0 +1,25 @@ + + Receptor FCast + La operación falló de forma genérica + La operación falló porque fue abortada activamente. + La operación falló porque fue bloqueada + La operación falló porque entra en conflicto (o es inconsistente) con otro paquete ya instalado en el dispositivo + La operación falló porque es fundamentalmente incompatible con este dispositivo. + La operación falló porque uno o más de los APK no eran válidos + La operación falló debido a problemas de almacenamiento. + Descargando actualización… + Instalando actualización… + Éxito + No se pudo actualizar el paquete con error + Hay una actualización disponible para el receptor FCast, ¿desea actualizar? + Nunca + Cerca + Actualizar + Buscando actualizaciones… + No hay actualizaciones disponibles + Permiso de la ventana de alerta del sistema + Esta aplicación requiere el permiso de la ventana de alertas del sistema para mostrar contenido sobre otras aplicaciones. Por favor, concédelo. + Permitir + Cancelar + Esperando a los medios + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values-fr/strings.xml b/receivers/android/app/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..5a7beff --- /dev/null +++ b/receivers/android/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,25 @@ + + Récepteur FCast + L\'opération a échoué de manière générique + L\'opération a échoué car elle a été activement interrompue + L\'opération a échoué car elle a été bloquée + L\'opération a échoué car elle est en conflit (ou est incohérente avec) avec un autre package déjà installé sur l\'appareil + L\'opération a échoué car elle est fondamentalement incompatible avec cet appareil + L\'opération a échoué car un ou plusieurs APK n\'étaient pas valides + L\'opération a échoué en raison de problèmes de stockage + Téléchargement de la mise à jour… + Installation de la mise à jour… + Succès + Échec de la mise à jour du package avec erreur + Une mise à jour est disponible pour le récepteur FCast, souhaitez-vous effectuer la mise à jour ? + Jamais + Fermer + Mise à jour + Vérification des mises à jour… + Aucune mise à jour disponible + Autorisation de la fenêtre d\'alerte système + Cette application nécessite l\'autorisation de la fenêtre d\'alerte système pour afficher du contenu par-dessus d\'autres applications. Veuillez accorder cette autorisation. + Permettre + Annuler + En attente des médias + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values-ja/strings.xml b/receivers/android/app/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..92a96d1 --- /dev/null +++ b/receivers/android/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,25 @@ + + FCastレシーバー + 操作は一般的な方法で失敗しました + 操作は強制的に中止されたため失敗しました + ブロックされたため操作に失敗しました + デバイスにすでにインストールされている別のパッケージと競合(または矛盾)しているため、操作に失敗しました + このデバイスと根本的に互換性がないため、操作は失敗しました + 1 つ以上の APK が無効であったため、操作に失敗しました + ストレージの問題により操作が失敗しました + アップデートをダウンロードしています… + アップデートをインストールしています… + 成功 + エラーのためパッケージの更新に失敗しました + FCast レシーバーのアップデートが利用可能です。アップデートしますか? + 一度もない + 近い + アップデート + アップデートを確認しています… + 更新情報はありません + システム警告ウィンドウの権限 + このアプリは、他のアプリの上にコンテンツを表示するためにシステムアラートウィンドウの権限が必要です。権限を付与してください。 + 許可する + キャンセル + メディアを待っています + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values-ko/strings.xml b/receivers/android/app/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..8852a8e --- /dev/null +++ b/receivers/android/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,25 @@ + + FCast 수신기 + 작업이 일반적인 방식으로 실패했습니다. + 작업이 적극적으로 중단되어 실패했습니다. + 작업이 차단되어 실패했습니다. + 해당 작업은 장치에 이미 설치된 다른 패키지와 충돌하거나 일관성이 없기 때문에 실패했습니다. + 이 작업은 이 장치와 근본적으로 호환되지 않기 때문에 실패했습니다. + APK 중 하나 이상이 유효하지 않아 작업이 실패했습니다. + 저장소 문제로 인해 작업이 실패했습니다. + 업데이트를 다운로드하는 중… + 업데이트를 설치하는 중… + 성공 + 오류로 인해 패키지를 업데이트하지 못했습니다. + FCast 수신기에 대한 업데이트가 있습니다. 업데이트하시겠습니까? + 절대 + 닫다 + 업데이트 + 업데이트를 확인하는 중… + 업데이트가 없습니다 + 시스템 알림 창 권한 + 이 앱은 다른 앱 위에 콘텐츠를 표시하기 위해 시스템 알림 창 권한이 필요합니다. 권한을 부여해 주세요. + 허용하다 + 취소 + 미디어를 기다리는 중 + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values-pt-rBR/strings.xml b/receivers/android/app/src/main/res/values-pt-rBR/strings.xml index 2ed5d4b..9a8f4b0 100644 --- a/receivers/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/receivers/android/app/src/main/res/values-pt-rBR/strings.xml @@ -7,15 +7,15 @@ A operação falhou pois é fundamentalmente incompatível com este dispositivo A operação falhou pois um ou mais dos APKs eram inválidos A operação falhou por causa de problemas de armazenamento - Baixando a atualização... - Instalando a atualização... + Baixando a atualização… + Instalando a atualização… Sucesso Falhou em atualizar pacote com erro Há uma atualização nova para o receptor do FCast, você quer atualizar? Nunca Fechar Atualizar - Buscando atualizações... + Buscando atualizações… Não há atualizações disponíveis Sobrepor outros apps Este app precisa da permissão para sobrepor outros apps para que possa exibir conteúdo em cima de outros apps. Por favor conceda a permissão. diff --git a/receivers/android/app/src/main/res/values-pt/strings.xml b/receivers/android/app/src/main/res/values-pt/strings.xml new file mode 100644 index 0000000..9a8f4b0 --- /dev/null +++ b/receivers/android/app/src/main/res/values-pt/strings.xml @@ -0,0 +1,25 @@ + + Receptor FCast + A operação falhou de um modo genérico + A operação falhou pois foi abortada ativamente + A operação falhou pois foi bloqueada + A operação falhou pois conflita (ou é inconsistente) com outro pacote que já está instalado no dispositivo + A operação falhou pois é fundamentalmente incompatível com este dispositivo + A operação falhou pois um ou mais dos APKs eram inválidos + A operação falhou por causa de problemas de armazenamento + Baixando a atualização… + Instalando a atualização… + Sucesso + Falhou em atualizar pacote com erro + Há uma atualização nova para o receptor do FCast, você quer atualizar? + Nunca + Fechar + Atualizar + Buscando atualizações… + Não há atualizações disponíveis + Sobrepor outros apps + Este app precisa da permissão para sobrepor outros apps para que possa exibir conteúdo em cima de outros apps. Por favor conceda a permissão. + Permitir + Cancelar + Aguardando mídia + diff --git a/receivers/android/app/src/main/res/values-ru/strings.xml b/receivers/android/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..cc8bbb4 --- /dev/null +++ b/receivers/android/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,25 @@ + + FCast-приемник + Операция провалилась в общем виде + Операция провалилась, потому что ее активно отменили. + Операция не удалась, так как она была заблокирована + Операция не выполнена, поскольку она конфликтует (или несовместима) с другим пакетом, уже установленным на устройстве. + Операция не удалась, так как она принципиально несовместима с этим устройством. + Операция не удалась, так как один или несколько APK-файлов оказались недействительными. + Операция не удалась из-за проблем с хранением + Загрузка обновления… + Установка обновления… + Успех + Не удалось обновить пакет из-за ошибки + Доступно обновление для приемника FCast. Хотите обновиться? + Никогда + Закрывать + Обновлять + Проверка обновлений… + Нет доступных обновлений + Разрешение окна системного оповещения + Этому приложению требуется разрешение System Alert Window для отображения контента поверх других приложений. Пожалуйста, предоставьте разрешение. + Позволять + Отмена + Ожидание СМИ + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values-zh/strings.xml b/receivers/android/app/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..c28dfed --- /dev/null +++ b/receivers/android/app/src/main/res/values-zh/strings.xml @@ -0,0 +1,25 @@ + + FCast接收器 + 操作以一般方式失败 + 操作失败,因为它被主动中止 + 操作失败,因为被阻止 + 操作失败,因为它与设备上已安装的另一个软件包冲突(或不一致) + 操作失败,因为它与该设备根本不兼容 + 操作失败,因为一个或多个 APK 无效 + 由于存储问题,操作失败 + 正在下载更新… + 正在安装更新… + 成功 + 更新包失败并出现错误 + FCast 接收器有可用的更新,您是否要更新? + 绝不 + 关闭 + 更新 + 正在检查更新… + 没有可用更新 + 系统警报窗口权限 + 此应用需要“系统警报窗口”权限才能在其他应用上方显示内容。请授予此权限。 + 允许 + 取消 + 等待媒体 + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/values/strings.xml b/receivers/android/app/src/main/res/values/strings.xml index 8f6ed41..c1c7cc7 100644 --- a/receivers/android/app/src/main/res/values/strings.xml +++ b/receivers/android/app/src/main/res/values/strings.xml @@ -7,15 +7,15 @@ The operation failed because it is fundamentally incompatible with this device The operation failed because one or more of the APKs was invalid The operation failed because of storage issues - Downloading update... - Installing update... + Downloading update… + Installing update… Success Failed to update package with error There is an update available for FCast receiver, do you wish to update? Never Close Update - Checking for updates... + Checking for updates… No updates available System Alert Window Permission This app requires the System Alert Window permission to display content on top of other apps. Please grant the permission. diff --git a/receivers/android/build.gradle b/receivers/android/build.gradle index a7ea160..853cdbc 100644 --- a/receivers/android/build.gradle +++ b/receivers/android/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.2.0' apply false - id 'com.android.library' version '8.2.0' apply false - id 'org.jetbrains.kotlin.android' version '1.8.0' apply false + id 'com.android.application' version '8.11.0' apply false + id 'com.android.library' version '8.11.0' apply false + id 'org.jetbrains.kotlin.android' version '2.2.0' apply false } \ No newline at end of file diff --git a/receivers/android/deploy.sh b/receivers/android/deploy.sh deleted file mode 100644 index c6f08ae..0000000 --- a/receivers/android/deploy.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -if [ -z "$ANDROID_VERSION_NAME" ] || [ -z "$ANDROID_VERSION_CODE" ]; then echo "Version name or code not specified. Skipping build."; exit 0; fi - -DOCUMENT_ROOT=/var/www/html - -# Build content -echo "Building content..." -./gradlew --stacktrace assembleRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE -./gradlew --stacktrace bundlePlaystoreRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE - -# Take site offline -echo "Taking site offline..." -touch $DOCUMENT_ROOT/maintenance.file - -# Swap over the content -echo "Deploying content..." -echo $ANDROID_VERSION_CODE > /var/www/html/fcast-version.txt -cp ./app/build/outputs/apk/defaultFlavor/release/app-defaultFlavor-release.apk /var/www/html/fcast-release.apk -cp ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab /var/www/html/fcast-playstore-release.aab - -# Notify Cloudflare to wipe the CDN cache -echo "Purging Cloudflare cache..." -curl -X POST "https://api.cloudflare.com/client/v4/zones/ff904f7348b9513064b23e852e328abb/purge_cache" \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"purge_everything":true}' - -sleep 30 - -# Take site back online -echo "Bringing site back online..." -rm $DOCUMENT_ROOT/maintenance.file diff --git a/receivers/android/gradle/wrapper/gradle-wrapper.properties b/receivers/android/gradle/wrapper/gradle-wrapper.properties index 14ad7bf..0f65ab0 100644 --- a/receivers/android/gradle/wrapper/gradle-wrapper.properties +++ b/receivers/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon May 01 06:24:43 CDT 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME