diff --git a/app/libs/splitinstall-lib.jar b/app/libs/splitinstall-lib.jar index a18b464e1ce836e121e24e1be57a9fbdbd5a9c43..bcfa5e5c9a1fae760affdddfbfe7051e16dea0ce 100644 Binary files a/app/libs/splitinstall-lib.jar and b/app/libs/splitinstall-lib.jar differ diff --git a/app/src/main/aidl/com/android/vending/splitinstall/ISplitInstallServiceCallback.aidl b/app/src/main/aidl/com/android/vending/splitinstall/ISplitInstallServiceCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..40356869808174b158937125ab9feece0d177ed0 --- /dev/null +++ b/app/src/main/aidl/com/android/vending/splitinstall/ISplitInstallServiceCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright Murena SAS 2023 + * Copyright 2013-2022 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.vending.splitinstall; + +import android.os.Bundle; + +interface ISplitInstallServiceCallback { + + void onStartInstall(int i); + + void onInstalled(int i); + + void onError(int errorCode); +} \ No newline at end of file diff --git a/app/src/main/aidl/com/google/android/play/core/splitinstall/protocol/ISplitInstallServiceCallback.aidl b/app/src/main/aidl/com/google/android/play/core/splitinstall/protocol/ISplitInstallServiceCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..cabc980fd7f164bab6dd4698b7a78fef5ed0057d --- /dev/null +++ b/app/src/main/aidl/com/google/android/play/core/splitinstall/protocol/ISplitInstallServiceCallback.aidl @@ -0,0 +1,48 @@ +/* + * Copyright Murena SAS 2023 + * Copyright 2013-2022 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.play.core.splitinstall.protocol; + +import android.os.Bundle; + +interface ISplitInstallServiceCallback { + + // Method not identified yet + void a(); + + void onStartInstall(int i); + + void onCompleteInstall(int i); + + void onCancelInstall(int i); + + void onGetSession(int i); + + void onError(in Bundle bundle); + + void onGetSessionStates(in List list); + + void onDeferredUninstall(in Bundle bundle); + + void onDeferredInstall(in Bundle bundle); + + void onGetSplitsForAppUpdate(int i, in Bundle bundle); + + void onCompleteInstallForAppUpdate(); + + void onDeferredLanguageInstall(int i); +} diff --git a/app/src/main/aidl/foundation/e/apps/ISplitInstallService.aidl b/app/src/main/aidl/foundation/e/apps/ISplitInstallService.aidl index bf327d45daffb03ea6941486f1b453c5c3ca5722..6e759db983d9dd8afb890c999aea2e3f066ba7e5 100644 --- a/app/src/main/aidl/foundation/e/apps/ISplitInstallService.aidl +++ b/app/src/main/aidl/foundation/e/apps/ISplitInstallService.aidl @@ -1,6 +1,25 @@ +/* + * Copyright Murena SAS 2023 + * Copyright 2013-2022 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package foundation.e.apps; +import com.android.vending.splitinstall.ISplitInstallServiceCallback; + interface ISplitInstallService { - void installSplitModule(String packageName, String moduleName); + void installSplitModule(String packageName, String moduleName, ISplitInstallServiceCallback callback); } \ No newline at end of file diff --git a/app/src/main/aidl/foundation/e/apps/ISplitInstallServiceCallback.aidl b/app/src/main/aidl/foundation/e/apps/ISplitInstallServiceCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..21f38abe32949536fa6eeea7070ff71c821fad82 --- /dev/null +++ b/app/src/main/aidl/foundation/e/apps/ISplitInstallServiceCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright Murena SAS 2023 + * Copyright 2013-2022 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package foundation.e.apps; + +interface ISplitInstallServiceCallback { + + void onStartInstall(int sessionId); + + void onInstalled(int sessionId); + + void onError(int errorCode); +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallBinder.kt b/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallBinder.kt index fcd7528b2fc942470e1e33910a30e7f5a01a53d2..ce9f8e32222b2b531af0ca858149d4e4045d4b31 100644 --- a/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallBinder.kt +++ b/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallBinder.kt @@ -18,12 +18,30 @@ package foundation.e.apps.install.splitinstall +import android.Manifest +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import androidx.core.content.pm.PackageInfoCompat -import com.aurora.gplayapi.data.models.AuthData -import foundation.e.apps.ISplitInstallService +import com.android.vending.splitinstall.ISplitInstallServiceCallback +import foundation.e.apps.MainActivity +import foundation.e.apps.R import foundation.e.apps.data.DownloadManager import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.login.AuthenticatorRepository +import foundation.e.apps.data.login.exceptions.GPlayLoginException +import foundation.e.apps.ISplitInstallService as ISplitInstallAppLoungeService +import foundation.e.splitinstall.ISplitInstallService as ISplitInstallSystemService +import foundation.e.splitinstall.ISplitInstallSystemServiceCallback import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -35,53 +53,149 @@ class SplitInstallBinder( private val coroutineScope: CoroutineScope, val applicationRepository: ApplicationRepository, val downloadManager: DownloadManager, - val authData: AuthData?, - private var splitInstallSystemService: foundation.e.splitinstall.ISplitInstallService? -) : ISplitInstallService.Stub() { + val authenticatorRepository: AuthenticatorRepository, + private var splitInstallSystemService: ISplitInstallSystemService? +) : ISplitInstallAppLoungeService.Stub() { private val modulesToInstall = HashMap() + private val callbacks = HashMap() companion object { const val TAG = "SplitInstallerBinder" + const val NOTIFICATION_ID = 667 + const val NOTIFICATION_CHANNEL = "AuthToken" + const val PLAY_STORE_NOT_FOUND_ERROR = -14 } - override fun installSplitModule(packageName: String, moduleName: String) { - if (authData == null) { - Timber.i("No authentication data. Could not install on demand module") + fun setService(service: foundation.e.splitinstall.ISplitInstallService) { + splitInstallSystemService = service + installPendingModules() + } + + override fun installSplitModule( + packageName: String, + moduleName: String, + callback: ISplitInstallServiceCallback + ) { + try { + if (authenticatorRepository.gplayAuth == null) { + handleError(packageName, callback) + return + } + + coroutineScope.launch { + downloadModule(packageName, moduleName, callback) + } + } catch (exception: GPlayLoginException) { + Timber.w(TAG, "Could not get auth data", exception) + handleError(packageName, callback) return } + } - coroutineScope.launch { - downloadModule(packageName, moduleName) + private fun handleError(packageName: String, callback: ISplitInstallServiceCallback) { + if (VERSION.SDK_INT < VERSION_CODES.O) { + return } + + createNotificationChannel(context) + showNotification(context, packageName) + callback.onError(PLAY_STORE_NOT_FOUND_ERROR) } - fun setService(service: foundation.e.splitinstall.ISplitInstallService) { - splitInstallSystemService = service - installPendingModules() + @RequiresApi(VERSION_CODES.O) + private fun createNotificationChannel(context: Context) { + val descriptionText = context.getString(R.string.notification_channel_desc) + val notificationChannel = NotificationChannel( + NOTIFICATION_CHANNEL, + NOTIFICATION_CHANNEL, + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + description = descriptionText + } + + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + notificationManager.createNotificationChannel(notificationChannel) } - private suspend fun downloadModule(packageName: String, moduleName: String) { + private fun showNotification(context: Context, packageName: String) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED + ) { + return + } + + val intent = Intent(context, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK + val pendingIntent = PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE + ) + + val notificationBuilder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL) + .setSmallIcon(R.drawable.app_lounge_notification_icon) + .setContentTitle(context.getString(R.string.split_install_warning_title, packageName)) + .setContentText(context.getString(R.string.split_install_warning_text)) + .setPriority(NotificationManager.IMPORTANCE_DEFAULT) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + + with(NotificationManagerCompat.from(context)) { + notify(NOTIFICATION_ID, notificationBuilder.build()) + } + } + + private suspend fun downloadModule( + packageName: String, + moduleName: String, + callback: ISplitInstallServiceCallback + ) { withContext(Dispatchers.IO) { val versionCode = getPackageVersionCode(packageName) val url = fetchModuleUrl(packageName, moduleName, versionCode) if (url == null) { Timber.e("Could not find split module named $moduleName for $packageName package") + callback.onError(PLAY_STORE_NOT_FOUND_ERROR) return@withContext } downloadManager.downloadFileInExternalStorage( url, packageName, "$packageName.split.$moduleName.apk" ) { success, path -> - if (success) { - Timber.i("Split module has been downloaded: $path") - if (splitInstallSystemService == null) { - Timber.i("Not connected to system service now. Adding $path to the list.") - modulesToInstall[path] = packageName + if (!success) { + return@downloadFileInExternalStorage + } + + Timber.i("Split module has been downloaded: $path") + if (splitInstallSystemService == null) { + Timber.i("Not connected to system service now. Adding $path to the list.") + modulesToInstall[path] = packageName + } + + callbacks[moduleName] = object : ISplitInstallSystemServiceCallback.Stub() { + override fun onStartInstall(sessionId: Int) { + callback.onStartInstall(sessionId) + } + + override fun onInstalled(sessionId: Int) { + callback.onInstalled(sessionId) + } + + override fun onError(errorCode: Int) { + callback.onError(errorCode) } - splitInstallSystemService?.installSplitModule(packageName, path) } + + splitInstallSystemService?.installSplitModule( + packageName, + path, + callbacks[moduleName] + ) } } } @@ -114,7 +228,7 @@ class SplitInstallBinder( private fun installPendingModules() { for (module in modulesToInstall.keys) { val packageName = modulesToInstall[module] - splitInstallSystemService?.installSplitModule(packageName, module) + splitInstallSystemService?.installSplitModule(packageName, module, callbacks[module]) } } } diff --git a/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallService.kt b/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallService.kt index 1335092429afdaec35a670babee19586cb03fc57..c56448a0f0f7fb047f369f62af6e7059f94fed0f 100644 --- a/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallService.kt +++ b/app/src/main/java/foundation/e/apps/install/splitinstall/SplitInstallService.kt @@ -29,6 +29,7 @@ import com.google.gson.Gson import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.data.DownloadManager import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.login.AuthenticatorRepository import foundation.e.apps.data.preference.DataStoreModule import foundation.e.splitinstall.ISplitInstallService import foundation.e.splitinstall.SplitInstall @@ -46,6 +47,7 @@ class SplitInstallService : LifecycleService() { @Inject lateinit var applicationRepository: ApplicationRepository @Inject lateinit var downloadManager: DownloadManager @Inject lateinit var gson: Gson + @Inject lateinit var authenticatorRepository: AuthenticatorRepository private lateinit var binder: SplitInstallBinder private var authData: AuthData? = null private var splitInstallSystemService: ISplitInstallService? = null @@ -67,6 +69,7 @@ class SplitInstallService : LifecycleService() { val intent = Intent().apply { component = SplitInstall.SPLIT_INSTALL_SYSTEM_SERVICE } + bindService(intent, serviceConnection, BIND_AUTO_CREATE) lifecycleScope.launch { @@ -94,7 +97,7 @@ class SplitInstallService : LifecycleService() { lifecycleScope, applicationRepository, downloadManager, - authData, + authenticatorRepository, splitInstallSystemService ) return binder diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b2fd32fa11dc16e4192ba675cb597343560f15d..3888fc3a21c6133e4f85fcc265757d3ff627d73a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -241,4 +241,9 @@ Free up %1$s on your phone to get the latest updates. Please free up some space on your phone so App Lounge can work properly. + + + Split install warning: %s + Please reconnect in AppLounge (either Anonymously or with a Google account). + Authentication token channel