Loading app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -126,6 +126,7 @@ android { buildConfig = true viewBinding = true compose = true aidl = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_21 Loading app/src/main/AndroidManifest.xml +11 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,10 @@ android:name="${applicationId}.permission.AUTH_DATA_PROVIDER" android:protectionLevel="signature" /> <permission android:name="foundation.e.apps.permission.BIND_INSTALL_SERVICE" android:protectionLevel="signature" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_TASKS" /> Loading Loading @@ -128,6 +132,13 @@ android:foregroundServiceType="dataSync"> </service> <service android:name=".services.InstallAppService" android:exported="true" android:foregroundServiceType="dataSync" android:permission="foundation.e.apps.permission.BIND_INSTALL_SERVICE"> </service> <receiver android:name="foundation.e.apps.install.download.DownloadManagerBR" android:exported="true"> Loading app/src/main/aidl/foundation/e/apps/IInstallAppCallback.aidl 0 → 100644 +7 −0 Original line number Diff line number Diff line package foundation.e.apps; interface IInstallAppCallback { boolean onStatusUpdate(String status); void onError(String code, String message); void onProgress(int percent); } app/src/main/aidl/foundation/e/apps/IInstallAppService.aidl 0 → 100644 +7 −0 Original line number Diff line number Diff line package foundation.e.apps; import foundation.e.apps.IInstallAppCallback; interface IInstallAppService { void installAppId(String appId, @nullable String source, IInstallAppCallback callback); } app/src/main/java/foundation/e/apps/services/InstallAppService.kt 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 MURENA SAS * Apps Quickly and easily install Android apps onto your device! * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.apps.services import android.app.Service import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder import android.util.Log import androidx.core.app.NotificationCompat import foundation.e.apps.R import foundation.e.apps.IInstallAppCallback import foundation.e.apps.IInstallAppService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch // Should we filter blacklisted app ? <-- same behavior as manual install <- check it // Should we filter faulty apps ? <-- same behavior as manual install <- check it. class InstallAppService : Service() { companion object { private const val NOTIFICATION_ID = 2201 } private val binder = object : IInstallAppService.Stub() { override fun installAppId(appId: String, source: String?, callback: IInstallAppCallback) { Log.d("DebugGJ", "InstallAppService::installAppId") GlobalScope.launch(Dispatchers.IO) { callback.onStatusUpdate("START_INSTALL") var i = 10 while (i > 0) { Log.d("DebugGJ", "InstallAppService::installAppId will send progress") callback.onProgress(i) delay(2000) i -= 1 } } } } override fun onCreate() { super.onCreate() startAsForeground() } override fun onBind(intent: Intent): IBinder { return binder } override fun onDestroy() { stopForeground(STOP_FOREGROUND_REMOVE) super.onDestroy() } private fun startAsForeground() { val channelId = getString(R.string.basic_notification_channel_id) val notification = NotificationCompat.Builder(this, channelId) .setContentTitle(getString(R.string.app_name)) .setContentText(getString(R.string.installing)) .setSmallIcon(R.drawable.app_lounge_notification_icon) .setOngoing(true) .build() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { startForeground( NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC ) } else { startForeground(NOTIFICATION_ID, notification) } } } Loading
app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -126,6 +126,7 @@ android { buildConfig = true viewBinding = true compose = true aidl = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_21 Loading
app/src/main/AndroidManifest.xml +11 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,10 @@ android:name="${applicationId}.permission.AUTH_DATA_PROVIDER" android:protectionLevel="signature" /> <permission android:name="foundation.e.apps.permission.BIND_INSTALL_SERVICE" android:protectionLevel="signature" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_TASKS" /> Loading Loading @@ -128,6 +132,13 @@ android:foregroundServiceType="dataSync"> </service> <service android:name=".services.InstallAppService" android:exported="true" android:foregroundServiceType="dataSync" android:permission="foundation.e.apps.permission.BIND_INSTALL_SERVICE"> </service> <receiver android:name="foundation.e.apps.install.download.DownloadManagerBR" android:exported="true"> Loading
app/src/main/aidl/foundation/e/apps/IInstallAppCallback.aidl 0 → 100644 +7 −0 Original line number Diff line number Diff line package foundation.e.apps; interface IInstallAppCallback { boolean onStatusUpdate(String status); void onError(String code, String message); void onProgress(int percent); }
app/src/main/aidl/foundation/e/apps/IInstallAppService.aidl 0 → 100644 +7 −0 Original line number Diff line number Diff line package foundation.e.apps; import foundation.e.apps.IInstallAppCallback; interface IInstallAppService { void installAppId(String appId, @nullable String source, IInstallAppCallback callback); }
app/src/main/java/foundation/e/apps/services/InstallAppService.kt 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 MURENA SAS * Apps Quickly and easily install Android apps onto your device! * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.apps.services import android.app.Service import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder import android.util.Log import androidx.core.app.NotificationCompat import foundation.e.apps.R import foundation.e.apps.IInstallAppCallback import foundation.e.apps.IInstallAppService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch // Should we filter blacklisted app ? <-- same behavior as manual install <- check it // Should we filter faulty apps ? <-- same behavior as manual install <- check it. class InstallAppService : Service() { companion object { private const val NOTIFICATION_ID = 2201 } private val binder = object : IInstallAppService.Stub() { override fun installAppId(appId: String, source: String?, callback: IInstallAppCallback) { Log.d("DebugGJ", "InstallAppService::installAppId") GlobalScope.launch(Dispatchers.IO) { callback.onStatusUpdate("START_INSTALL") var i = 10 while (i > 0) { Log.d("DebugGJ", "InstallAppService::installAppId will send progress") callback.onProgress(i) delay(2000) i -= 1 } } } } override fun onCreate() { super.onCreate() startAsForeground() } override fun onBind(intent: Intent): IBinder { return binder } override fun onDestroy() { stopForeground(STOP_FOREGROUND_REMOVE) super.onDestroy() } private fun startAsForeground() { val channelId = getString(R.string.basic_notification_channel_id) val notification = NotificationCompat.Builder(this, channelId) .setContentTitle(getString(R.string.app_name)) .setContentText(getString(R.string.installing)) .setSmallIcon(R.drawable.app_lounge_notification_icon) .setOngoing(true) .build() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { startForeground( NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC ) } else { startForeground(NOTIFICATION_ID, notification) } } }