mirror of
https://github.com/whyorean/AuroraStore.git
synced 2026-06-12 01:38:35 -04:00
Merge branch 'dev' into 'master'
Allow huawei variants to install microG & support silent installs. See merge request AuroraOSS/AuroraStore!500
This commit is contained in:
@@ -264,6 +264,8 @@ dependencies {
|
||||
|
||||
implementation(libs.process.phoenix)
|
||||
|
||||
"huaweiImplementation"(libs.ag.coreservice)
|
||||
|
||||
// LeakCanary
|
||||
debugImplementation(libs.squareup.leakcanary.android)
|
||||
}
|
||||
|
||||
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@@ -124,3 +124,9 @@
|
||||
-keep class * extends androidx.viewbinding.ViewBinding {
|
||||
*;
|
||||
}
|
||||
|
||||
# Keep Huawei specific classes and methods
|
||||
-keep class com.huawei.** { *; }
|
||||
-dontwarn com.huawei.**
|
||||
-keep class com.hihonor.** { *; }
|
||||
-dontwarn com.hihonor.**
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.aurora.store.data.receiver
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import com.aurora.Constants.PACKAGE_NAME_APP_GALLERY
|
||||
import com.huawei.appgallery.coreservice.api.ApiClient
|
||||
import com.huawei.appgallery.coreservice.api.ApiCode
|
||||
import com.huawei.appgallery.coreservice.api.IConnectionResult
|
||||
import com.huawei.appgallery.coreservice.api.PendingCall
|
||||
import com.huawei.appgallery.coreservice.internal.framework.ipc.transport.data.BaseIPCRequest
|
||||
import com.huawei.appgallery.coreservice.internal.framework.ipc.transport.data.BaseIPCResponse
|
||||
import com.huawei.appmarket.framework.coreservice.Status
|
||||
import com.huawei.appmarket.service.externalservice.distribution.thirdsilentinstall.SilentInstallRequest
|
||||
import com.huawei.appmarket.service.externalservice.distribution.thirdsilentinstall.SilentInstallResponse
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class InstallerStatusReceiver : BaseInstallerStatusReceiver() {
|
||||
|
||||
private val TAG = InstallerStatusReceiver::class.java.simpleName
|
||||
|
||||
private lateinit var apiClient: ApiClient
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
super.onReceive(context, intent)
|
||||
}
|
||||
|
||||
override fun doAppropriatePrompt(context: Context, intent: Intent, sessionId: Int) {
|
||||
if (isHuaweiSilentInstallSupported(context)) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"Huawei silent install supported, proceeding with ApiClient connection"
|
||||
)
|
||||
connectApiClient(context, intent, sessionId)
|
||||
} else {
|
||||
promptUser(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postStatus(status: Int, packageName: String?, extra: String?, context: Context) {
|
||||
super.postStatus(status, packageName, extra, context)
|
||||
|
||||
if (::apiClient.isInitialized && apiClient.isConnected) {
|
||||
apiClient.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectApiClient(context: Context, intent: Intent, sessionId: Int) {
|
||||
// Check if the ApiClient is already initialized and connected
|
||||
if (::apiClient.isInitialized && apiClient.isConnected) {
|
||||
Log.i(TAG, "ApiClient already connected, requesting silent install")
|
||||
requestSilentInstall(context, intent, sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
apiClient = ApiClient.Builder(context.applicationContext)
|
||||
.setHomeCountry("CN")
|
||||
.addConnectionCallbacks(object : ApiClient.ConnectionCallback {
|
||||
override fun onConnected() {
|
||||
Log.i(TAG, "ApiClient connected")
|
||||
requestSilentInstall(context, intent, sessionId)
|
||||
}
|
||||
|
||||
override fun onConnectionSuspended(cause: Int) {
|
||||
Log.w(TAG, "ApiClient connection suspended: $cause")
|
||||
}
|
||||
|
||||
override fun onConnectionFailed(result: IConnectionResult?) {
|
||||
Log.e(TAG, "ApiClient failed to connect with result: $result, prompting user")
|
||||
promptUser(context, intent)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
||||
apiClient.connect()
|
||||
}
|
||||
|
||||
private fun requestSilentInstall(context: Context, intent: Intent, sessionId: Int) {
|
||||
val request = SilentInstallRequest().apply {
|
||||
setSessionId(sessionId)
|
||||
}
|
||||
|
||||
val pendingResult = PendingCall<BaseIPCRequest, BaseIPCResponse>(
|
||||
apiClient,
|
||||
request
|
||||
)
|
||||
|
||||
if (::apiClient.isInitialized && apiClient.isConnected) {
|
||||
pendingResult.setCallback { handleIPCResponse(context, intent, it) }
|
||||
} else {
|
||||
Log.e(TAG, "ApiClient null or not connected")
|
||||
promptUser(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleIPCResponse(
|
||||
context: Context,
|
||||
intent: Intent,
|
||||
ipcResponse: Status<BaseIPCResponse>
|
||||
) {
|
||||
with(ipcResponse) {
|
||||
if (response is SilentInstallResponse || response is BaseIPCResponse) {
|
||||
Log.i(TAG, "IPC Response: ${ApiCode.getStatusCodeString(statusCode)}")
|
||||
|
||||
if (statusCode != ApiCode.SUCCESS) {
|
||||
Log.e(TAG, "Silent install failed with status code: $statusCode")
|
||||
promptUser(context, intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isHuaweiSilentInstallSupported(context: Context): Boolean {
|
||||
return try {
|
||||
val applicationInfo: ApplicationInfo = context.packageManager.getApplicationInfo(
|
||||
PACKAGE_NAME_APP_GALLERY,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
|
||||
val supportFunction = applicationInfo.metaData.getInt("appgallery_support_function")
|
||||
Log.i(TAG, "Huawei silent install support function: $supportFunction")
|
||||
|
||||
(supportFunction and (1 shl 5)) != 0
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.aurora.store.view.ui.onboarding
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.Event
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.Dash
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.databinding.FragmentOnboardingMicrogBinding
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.view.epoxy.views.EpoxyTextViewModel_
|
||||
import com.aurora.store.view.epoxy.views.preference.DashViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.onboarding.MicroGViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MicroGFragment : BaseFragment<FragmentOnboardingMicrogBinding>() {
|
||||
// Shared ViewModel
|
||||
val microGViewModel: MicroGViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
// RecyclerView
|
||||
epoxyRecycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
|
||||
add(
|
||||
EpoxyTextViewModel_()
|
||||
.id("microg_desc")
|
||||
.title(getString(R.string.onboarding_gms_missing))
|
||||
.size(14)
|
||||
.style(R.style.AuroraTextStyle)
|
||||
)
|
||||
|
||||
add(
|
||||
EpoxyTextViewModel_()
|
||||
.id("microg_gms")
|
||||
.title(getString(R.string.onboarding_gms_microg))
|
||||
.size(14)
|
||||
.style(R.style.AuroraTextStyle)
|
||||
)
|
||||
|
||||
|
||||
dashItems().forEach {
|
||||
add(
|
||||
DashViewModel_()
|
||||
.id(it.id)
|
||||
.dash(it)
|
||||
.click { _ ->
|
||||
requireContext().browse(it.url)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
checkboxAgreement.setOnCheckedChangeListener { _, value ->
|
||||
microGViewModel.markAgreement(value)
|
||||
btnMicroG.isEnabled = value
|
||||
}
|
||||
|
||||
btnMicroG.setOnClickListener { microGViewModel.downloadMicroG() }
|
||||
}
|
||||
|
||||
microGViewModel.download.filterNotNull().onEach {
|
||||
when (it.downloadStatus) {
|
||||
DownloadStatus.DOWNLOADING -> updateProgressBar(visible = true, it.progress)
|
||||
DownloadStatus.FAILED -> updateProgressBar(visible = false, 0)
|
||||
DownloadStatus.QUEUED -> updateProgressBar(visible = true, -1)
|
||||
DownloadStatus.COMPLETED -> updateProgressBar(visible = true, -1)
|
||||
else -> {}
|
||||
}
|
||||
}.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.events.installerEvent.collect { onEvent(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is InstallerEvent.Installed -> {
|
||||
if (PackageUtil.isMicroGBundleInstalled(requireContext())) {
|
||||
markInstallationComplete()
|
||||
}
|
||||
}
|
||||
|
||||
is InstallerEvent.Failed -> markInstallationFailed()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgressBar(visible: Boolean, downloadProgress: Int) {
|
||||
with(binding.progressBar) {
|
||||
if (visible) show() else hide()
|
||||
isIndeterminate = downloadProgress == -1
|
||||
progress = downloadProgress
|
||||
}
|
||||
}
|
||||
|
||||
private fun markInstallationComplete() {
|
||||
with(binding) {
|
||||
with(btnMicroG) {
|
||||
isEnabled = false
|
||||
text = getString(R.string.title_installed)
|
||||
}
|
||||
checkboxAgreement.isEnabled = false
|
||||
progressBar.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun markInstallationFailed() {
|
||||
with(binding) {
|
||||
with(btnMicroG) {
|
||||
isEnabled = false
|
||||
text = getString(R.string.action_install)
|
||||
}
|
||||
checkboxAgreement.isChecked = false
|
||||
progressBar.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun dashItems(): List<Dash> {
|
||||
return listOf(
|
||||
Dash(
|
||||
id = 2,
|
||||
title = requireContext().getString(R.string.details_dev_website),
|
||||
subtitle = requireContext().getString(R.string.microg_website),
|
||||
icon = R.drawable.ic_network,
|
||||
url = "https://microG.org"
|
||||
),
|
||||
Dash(
|
||||
id = 4,
|
||||
title = requireContext().getString(R.string.privacy_policy_title),
|
||||
subtitle = requireContext().getString(R.string.microg_privacy_policy),
|
||||
icon = R.drawable.ic_privacy,
|
||||
url = "https://microg.org/privacy.html"
|
||||
),
|
||||
Dash(
|
||||
id = 5,
|
||||
title = requireContext().getString(R.string.menu_disclaimer),
|
||||
subtitle = requireContext().getString(R.string.microg_license_agreement),
|
||||
icon = R.drawable.ic_disclaimer,
|
||||
url = "https://raw.githubusercontent.com/microg/GmsCore/refs/heads/master/LICENSE"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@
|
||||
package com.aurora.store.view.ui.onboarding
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.aurora.extensions.isHuawei
|
||||
import com.aurora.store.data.providers.BlacklistProvider
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
|
||||
@@ -34,9 +37,12 @@ import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_VENDING_VERSION
|
||||
import com.aurora.store.util.save
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
@Inject
|
||||
lateinit var blacklistProvider: BlacklistProvider
|
||||
|
||||
override fun loadDefaultPreferences() {
|
||||
/*Filters*/
|
||||
@@ -44,7 +50,7 @@ class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
save(PREFERENCE_FILTER_FDROID, true)
|
||||
|
||||
/*Network*/
|
||||
save(PREFERENCE_DISPENSER_URLS, setOf())
|
||||
save(PREFERENCE_DISPENSER_URLS, emptySet())
|
||||
save(PREFERENCE_VENDING_VERSION, 0)
|
||||
|
||||
/*Customization*/
|
||||
@@ -63,10 +69,26 @@ class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
}
|
||||
|
||||
override fun onboardingPages(): List<Fragment> {
|
||||
return listOf(
|
||||
var pages = mutableListOf(
|
||||
WelcomeFragment(),
|
||||
PermissionsFragment.newInstance()
|
||||
)
|
||||
|
||||
/**
|
||||
* MicroG Fragment Preconditions:
|
||||
* 1. It should be a Huawei device
|
||||
* 2. Supported App Gallery should be available, i.e. v15.1.x or above
|
||||
* 3. MicroG bundle should not be already installed
|
||||
*/
|
||||
if (
|
||||
isHuawei &&
|
||||
PackageUtil.hasSupportedAppGallery(requireContext()) &&
|
||||
!PackageUtil.isMicroGBundleInstalled(requireContext())
|
||||
) {
|
||||
pages.add(MicroGFragment())
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun setupAutoUpdates() {
|
||||
@@ -76,8 +98,9 @@ class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
}
|
||||
|
||||
override fun finishOnboarding() {
|
||||
super.finishOnboarding()
|
||||
blacklistProvider.blacklist("com.android.vending")
|
||||
blacklistProvider.blacklist("com.google.android.gms")
|
||||
|
||||
// Remove super & implement variant logic here
|
||||
super.finishOnboarding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.huawei.appmarket.service.externalservice.distribution.thirdsilentinstall;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.huawei.appgallery.coreservice.internal.framework.ipc.transport.data.BaseIPCRequest;
|
||||
import com.huawei.appgallery.coreservice.internal.support.parcelable.AutoParcelable;
|
||||
import com.huawei.appgallery.coreservice.internal.support.parcelable.EnableAutoParcel;
|
||||
|
||||
@Keep
|
||||
public class SilentInstallRequest extends BaseIPCRequest {
|
||||
public static final Parcelable.Creator<SilentInstallRequest> CREATOR = new AutoParcelable.AutoCreator<>(SilentInstallRequest.class);
|
||||
|
||||
public static final String METHOD = "method.requestSilentInstall";
|
||||
@EnableAutoParcel(1)
|
||||
private int sessionId;
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return METHOD;
|
||||
}
|
||||
|
||||
public int getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(int sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
super.writeToParcel(parcel, i);
|
||||
parcel.writeInt(sessionId);
|
||||
}
|
||||
|
||||
public void readFromParcel(Parcel source) {
|
||||
this.sessionId = source.readInt();
|
||||
}
|
||||
|
||||
public SilentInstallRequest() {
|
||||
}
|
||||
|
||||
protected SilentInstallRequest(Parcel in) {
|
||||
this.sessionId = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.huawei.appmarket.service.externalservice.distribution.thirdsilentinstall;
|
||||
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.huawei.appgallery.coreservice.internal.framework.ipc.transport.data.BaseIPCResponse;
|
||||
import com.huawei.appgallery.coreservice.internal.support.parcelable.AutoParcelable;
|
||||
import com.huawei.appgallery.coreservice.internal.support.parcelable.EnableAutoParcel;
|
||||
|
||||
public class SilentInstallResponse extends BaseIPCResponse {
|
||||
|
||||
public static final Parcelable.Creator<SilentInstallResponse> CREATOR = new AutoParcelable.AutoCreator<>(SilentInstallResponse.class);
|
||||
|
||||
@EnableAutoParcel(1)
|
||||
private int result;
|
||||
|
||||
public int getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
public void setResult(int result) {
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
@@ -62,4 +62,9 @@ object Constants {
|
||||
const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY"
|
||||
|
||||
const val JSON_MIME_TYPE = "application/json"
|
||||
|
||||
// PACKAGE NAMES
|
||||
const val PACKAGE_NAME_GMS = "com.google.android.gms"
|
||||
const val PACKAGE_NAME_PLAY_STORE = "com.android.vending"
|
||||
const val PACKAGE_NAME_APP_GALLERY = "com.huawei.appmarket"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
@@ -114,7 +115,7 @@ private fun buildExtras(app: App): List<String> {
|
||||
add(stringResource(R.string.details_no_ads))
|
||||
}
|
||||
|
||||
if (app.dependencies.dependentPackages.contains(PackageUtil.PACKAGE_NAME_GMS)) {
|
||||
if (app.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS)) {
|
||||
add(stringResource(R.string.details_gsf_dependent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.data.room.download.DownloadDao
|
||||
import com.aurora.store.data.room.suite.ExternalApk
|
||||
import com.aurora.store.data.room.update.Update
|
||||
import com.aurora.store.data.work.DownloadWorker
|
||||
import com.aurora.store.util.PathUtil
|
||||
@@ -90,6 +91,14 @@ class DownloadHelper @Inject constructor(
|
||||
downloadDao.insert(Download.fromUpdate(update))
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues ExternalApk for download & install
|
||||
* @param externalApk [ExternalApk] to download
|
||||
*/
|
||||
suspend fun enqueueStandalone(externalApk: ExternalApk) {
|
||||
downloadDao.insert(Download.fromExternalApk(externalApk))
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the download for the given package
|
||||
* @param packageName Name of the package to cancel download
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageInstaller.EXTRA_SESSION_ID
|
||||
import android.content.pm.PackageInstaller.PACKAGE_SOURCE_STORE
|
||||
import android.content.pm.PackageInstaller.SessionParams
|
||||
import android.content.pm.PackageManager
|
||||
@@ -240,24 +241,31 @@ class SessionInstaller @Inject constructor(
|
||||
}
|
||||
|
||||
private fun commitInstall(sessionInfo: SessionInfo) {
|
||||
Log.i(TAG, "Starting install session for ${sessionInfo.packageName}")
|
||||
|
||||
val sessionId = sessionInfo.sessionId
|
||||
packageInstaller.getSessionInfo(sessionId) ?: run {
|
||||
Log.e(TAG, "Session $sessionId is no longer valid, skipping commit.")
|
||||
removeFromInstallQueue(sessionInfo.packageName)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
Log.i(TAG, "Starting install session for ${sessionInfo.packageName}")
|
||||
|
||||
val existingSessionInfo = packageInstaller.getSessionInfo(sessionInfo.sessionId)
|
||||
if (existingSessionInfo == null) {
|
||||
Log.e(TAG, "Session ${sessionInfo.sessionId} is no longer valid.")
|
||||
return removeFromInstallQueue(sessionInfo.packageName)
|
||||
}
|
||||
|
||||
commitSession(sessionInfo)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error committing session: ${e.message}")
|
||||
removeFromInstallQueue(sessionInfo.packageName)
|
||||
postError(sessionInfo.packageName, e.localizedMessage, e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun commitSession(sessionInfo: SessionInfo) {
|
||||
try {
|
||||
val session = packageInstaller.openSession(sessionInfo.sessionId)
|
||||
session.commit(getCallBackIntent(sessionInfo)!!.intentSender)
|
||||
session.close()
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to commit session $sessionId: ${e.message}")
|
||||
removeFromInstallQueue(sessionInfo.packageName)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unexpected error in commitInstall for session $sessionId", e)
|
||||
Log.e(TAG, "Error committing session: ${e.message}")
|
||||
} finally {
|
||||
removeFromInstallQueue(sessionInfo.packageName)
|
||||
}
|
||||
}
|
||||
@@ -266,6 +274,7 @@ class SessionInstaller @Inject constructor(
|
||||
val callBackIntent = Intent(context, InstallerStatusReceiver::class.java).apply {
|
||||
action = ACTION_INSTALL_STATUS
|
||||
setPackage(context.packageName)
|
||||
putExtra(EXTRA_SESSION_ID, sessionInfo.sessionId)
|
||||
putExtra(EXTRA_PACKAGE_NAME, sessionInfo.packageName)
|
||||
putExtra(EXTRA_VERSION_CODE, sessionInfo.versionCode)
|
||||
putExtra(EXTRA_DISPLAY_NAME, sessionInfo.displayName)
|
||||
@@ -280,4 +289,22 @@ class SessionInstaller @Inject constructor(
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
enum class ServiceResultCode(val code: Int, val reason: String) {
|
||||
SUCCESS(0, "Request successful"),
|
||||
SERVICE_VERSION_UPDATE_REQUIRED(2, "Interface depends on a higher version"),
|
||||
SERVICE_INVALID(4, "Service is invalid"),
|
||||
METHOD_UNSUPPORTED(5, "Interface is not supported"),
|
||||
RESOLUTION_REQUIRED(6, "Needs to be resolved by opening PendingIntent"),
|
||||
NETWORK_ERROR(7, "Network exception, unable to complete interface request"),
|
||||
INTERNAL_ERROR(8, "Internal code error, incorrect parameter transmission in scenario"),
|
||||
TIMEOUT(10, "Interface access timeout return"),
|
||||
DEAD_CLIENT(12, "Current client is unavailable"),
|
||||
RESPONSE_ERROR(13, "Server returns abnormal response"),
|
||||
PROTOCOL_ERROR(15, "Not signed Huawei App Market agreement");
|
||||
|
||||
companion object {
|
||||
fun fromCode(code: Int): ServiceResultCode? = entries.find { it.code == code }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,10 @@ class ShizukuInstaller @Inject constructor(
|
||||
sharedLibPkgName: String = "",
|
||||
displayName: String = ""
|
||||
) {
|
||||
Log.i(TAG, "Received session install request for ${sharedLibPkgName.ifBlank { packageName }}")
|
||||
Log.i(
|
||||
TAG,
|
||||
"Received session install request for ${sharedLibPkgName.ifBlank { packageName }}"
|
||||
)
|
||||
|
||||
val (sessionId, session) = kotlin.runCatching {
|
||||
val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
|
||||
@@ -168,7 +171,11 @@ class ShizukuInstaller @Inject constructor(
|
||||
Log.i(TAG, "Writing splits to session for ${sharedLibPkgName.ifBlank { packageName }}")
|
||||
getFiles(packageName, versionCode, sharedLibPkgName).forEach {
|
||||
it.inputStream().use { input ->
|
||||
session.openWrite("${sharedLibPkgName.ifBlank { packageName }}_${System.currentTimeMillis()}", 0, -1).use { output ->
|
||||
session.openWrite(
|
||||
"${sharedLibPkgName.ifBlank { packageName }}_${System.currentTimeMillis()}",
|
||||
0,
|
||||
-1
|
||||
).use { output ->
|
||||
input.copyTo(output)
|
||||
session.fsync(output)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class AuthProvider @Inject constructor(
|
||||
return if (rawAuth.isNotBlank()) {
|
||||
json.decodeFromString<AuthData>(rawAuth)
|
||||
} else {
|
||||
null
|
||||
AuthData("BOGUS")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ package com.aurora.store.data.providers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.aurora.extensions.isNAndAbove
|
||||
import com.aurora.store.util.Preferences
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -39,7 +38,11 @@ class BlacklistProvider @Inject constructor(
|
||||
private val PREFERENCE_BLACKLIST = "PREFERENCE_BLACKLIST"
|
||||
|
||||
var blacklist: MutableSet<String>
|
||||
set(value) = Preferences.putString(context, PREFERENCE_BLACKLIST, json.encodeToString(value))
|
||||
set(value) = Preferences.putString(
|
||||
context,
|
||||
PREFERENCE_BLACKLIST,
|
||||
json.encodeToString(value)
|
||||
)
|
||||
get() {
|
||||
return try {
|
||||
val rawBlacklist = if (isNAndAbove) {
|
||||
@@ -54,7 +57,7 @@ class BlacklistProvider @Inject constructor(
|
||||
Context.MODE_PRIVATE
|
||||
) as SharedPreferences
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
Preferences.getPrefs(context)
|
||||
.getString(
|
||||
PREFERENCE_BLACKLIST,
|
||||
refSharedPreferences.getString(PREFERENCE_BLACKLIST, "")
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageInstaller.EXTRA_SESSION_ID
|
||||
import android.util.Log
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.content.getSystemService
|
||||
@@ -35,28 +36,31 @@ import com.aurora.store.data.installer.AppInstaller.Companion.EXTRA_DISPLAY_NAME
|
||||
import com.aurora.store.data.installer.AppInstaller.Companion.EXTRA_PACKAGE_NAME
|
||||
import com.aurora.store.data.installer.AppInstaller.Companion.EXTRA_VERSION_CODE
|
||||
import com.aurora.store.data.installer.base.InstallerBase
|
||||
import com.aurora.store.util.CommonUtil.inForeground
|
||||
import com.aurora.store.util.NotificationUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.PathUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class InstallerStatusReceiver : BroadcastReceiver() {
|
||||
abstract class BaseInstallerStatusReceiver : BroadcastReceiver() {
|
||||
|
||||
private val TAG = InstallerStatusReceiver::class.java.simpleName
|
||||
private val TAG = BaseInstallerStatusReceiver::class.java.simpleName
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (context != null && intent?.action == ACTION_INSTALL_STATUS) {
|
||||
val packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME)!!
|
||||
val displayName = intent.getStringExtra(EXTRA_DISPLAY_NAME)!!
|
||||
val versionCode = intent.getLongExtra(EXTRA_VERSION_CODE, -1)
|
||||
val packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return
|
||||
val displayName = intent.getStringExtra(EXTRA_DISPLAY_NAME) ?: packageName
|
||||
|
||||
val versionCode = intent.getLongExtra(EXTRA_VERSION_CODE, -1)
|
||||
val sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1)
|
||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||
val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||
|
||||
Log.i(
|
||||
TAG,
|
||||
"$packageName ($versionCode) sessionId=$sessionId, status=$status, extra=$extra"
|
||||
)
|
||||
|
||||
// If package was successfully installed, exit after notifying user and doing cleanup
|
||||
if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||
// No post-install steps for shared libraries
|
||||
@@ -64,19 +68,22 @@ class InstallerStatusReceiver : BroadcastReceiver() {
|
||||
|
||||
AuroraApp.enqueuedInstalls.remove(packageName)
|
||||
InstallerBase.notifyInstallation(context, displayName, packageName)
|
||||
|
||||
if (Preferences.getBoolean(context, PREFERENCE_AUTO_DELETE)) {
|
||||
PathUtil.getAppDownloadDir(context, packageName, versionCode)
|
||||
.deleteRecursively()
|
||||
}
|
||||
return
|
||||
|
||||
return postStatus(status, packageName, extra, context)
|
||||
}
|
||||
|
||||
if (inForeground() && status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||
promptUser(intent, context)
|
||||
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||
doAppropriatePrompt(context, intent, sessionId)
|
||||
} else {
|
||||
AuroraApp.enqueuedInstalls.remove(packageName)
|
||||
postStatus(status, packageName, extra, context)
|
||||
notifyUser(context, packageName, displayName, status)
|
||||
|
||||
postStatus(status, packageName, extra, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,24 +101,38 @@ class InstallerStatusReceiver : BroadcastReceiver() {
|
||||
displayName,
|
||||
InstallerBase.getErrorString(context, status)
|
||||
)
|
||||
notificationManager!!.notify(packageName.hashCode(), notification)
|
||||
notificationManager?.notify(packageName.hashCode(), notification)
|
||||
}
|
||||
|
||||
private fun promptUser(intent: Intent, context: Context) {
|
||||
IntentCompat.getParcelableExtra(intent, Intent.EXTRA_INTENT, Intent::class.java)?.let {
|
||||
it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.packageName)
|
||||
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
internal fun promptUser(context: Context, intent: Intent) {
|
||||
runOnUiThread {
|
||||
val launchIntent = IntentCompat.getParcelableExtra(
|
||||
intent,
|
||||
Intent.EXTRA_INTENT,
|
||||
Intent::class.java
|
||||
)
|
||||
|
||||
try {
|
||||
runOnUiThread { context.startActivity(it) }
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to trigger installation!", exception)
|
||||
if (launchIntent != null) {
|
||||
launchIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
launchIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.packageName)
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
try {
|
||||
context.startActivity(launchIntent)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to launch intent!", exception)
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No launch intent found in the installation request.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun postStatus(status: Int, packageName: String?, extra: String?, context: Context) {
|
||||
open fun doAppropriatePrompt(context: Context, intent: Intent, sessionId: Int) {
|
||||
promptUser(context, intent)
|
||||
}
|
||||
|
||||
open fun postStatus(status: Int, packageName: String?, extra: String?, context: Context) {
|
||||
val event = when (status) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
InstallerEvent.Installed(packageName!!).apply {
|
||||
@@ -132,6 +153,7 @@ class InstallerStatusReceiver : BroadcastReceiver() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AuroraApp.events.send(event)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import androidx.room.PrimaryKey
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.PlayFile
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.data.room.suite.ExternalApk
|
||||
import com.aurora.store.data.room.update.Update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Date
|
||||
@@ -81,5 +82,26 @@ data class Download(
|
||||
Date().time
|
||||
)
|
||||
}
|
||||
|
||||
fun fromExternalApk(externalApk: ExternalApk): Download {
|
||||
return Download(
|
||||
packageName = externalApk.packageName,
|
||||
versionCode = externalApk.versionCode,
|
||||
offerType = 0,
|
||||
isInstalled = false,
|
||||
displayName = externalApk.displayName,
|
||||
iconURL = externalApk.iconURL,
|
||||
size = 0,
|
||||
id = 0,
|
||||
downloadStatus = DownloadStatus.QUEUED,
|
||||
progress = 0,
|
||||
speed = 0L,
|
||||
timeRemaining = 0L,
|
||||
totalFiles = 1,
|
||||
downloadedFiles = 0,
|
||||
fileList = externalApk.fileList,
|
||||
sharedLibs = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.aurora.store.data.room.suite
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.aurora.gplayapi.data.models.PlayFile
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "externalApk")
|
||||
data class ExternalApk(
|
||||
@PrimaryKey
|
||||
val packageName: String,
|
||||
val versionCode: Long,
|
||||
val versionName: String,
|
||||
val displayName: String,
|
||||
val iconURL: String,
|
||||
val developerName: String,
|
||||
var fileList: List<PlayFile>
|
||||
) : Parcelable {
|
||||
|
||||
fun isInstalled(context: Context): Boolean {
|
||||
return PackageUtil.isInstalled(context, packageName)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import androidx.core.os.bundleOf
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE
|
||||
import com.aurora.gplayapi.data.models.AuthData
|
||||
import com.aurora.gplayapi.helpers.AuthHelper
|
||||
import com.aurora.store.data.model.AccountType
|
||||
@@ -21,7 +22,6 @@ import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlin.coroutines.resume
|
||||
@@ -140,7 +140,7 @@ open class AuthWorker @AssistedInject constructor(
|
||||
Account(email, GOOGLE_ACCOUNT_TYPE),
|
||||
GOOGLE_PLAY_AUTH_TOKEN_TYPE,
|
||||
bundleOf(
|
||||
"overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
|
||||
"overridePackage" to PACKAGE_NAME_PLAY_STORE,
|
||||
"overrideCertificate" to Base64.decode(
|
||||
GOOGLE_PLAY_CERT,
|
||||
Base64.DEFAULT
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.aurora.Constants.PACKAGE_NAME_APP_GALLERY
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.extensions.generateX509Certificate
|
||||
import com.aurora.extensions.getUpdateOwnerPackageNameCompat
|
||||
import com.aurora.extensions.isPAndAbove
|
||||
@@ -40,7 +42,6 @@ object CertUtil {
|
||||
|
||||
const val GOOGLE_ACCOUNT_TYPE = "com.google"
|
||||
const val GOOGLE_PLAY_AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/googleplay"
|
||||
const val GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"
|
||||
const val GOOGLE_PLAY_CERT =
|
||||
"MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK"
|
||||
|
||||
@@ -49,7 +50,7 @@ object CertUtil {
|
||||
}
|
||||
|
||||
fun isAppGalleryApp(context: Context, packageName: String): Boolean {
|
||||
return context.packageManager.getUpdateOwnerPackageNameCompat(packageName) == "com.huawei.appmarket"
|
||||
return context.packageManager.getUpdateOwnerPackageNameCompat(packageName) == PACKAGE_NAME_APP_GALLERY
|
||||
}
|
||||
|
||||
fun isAuroraStoreApp(context: Context, packageName: String): Boolean {
|
||||
@@ -91,16 +92,17 @@ object CertUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun isMicroGGMS(context: Context, packageName: String): Boolean {
|
||||
fun isMicroGGms(context: Context): Boolean {
|
||||
return try {
|
||||
val packageInfo = getPackageInfo(context, packageName, PackageManager.GET_PERMISSIONS)
|
||||
val packageInfo =
|
||||
getPackageInfo(context, PACKAGE_NAME_GMS, PackageManager.GET_PERMISSIONS)
|
||||
val hasFakePackageSignature = packageInfo.requestedPermissions?.any { permission ->
|
||||
permission == "android.permission.FAKE_PACKAGE_SIGNATURE"
|
||||
} == true
|
||||
|
||||
return hasFakePackageSignature
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to check origin for $packageName")
|
||||
Log.e(TAG, "Failed to check origin for $PACKAGE_NAME_GMS")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.net.toUri
|
||||
import com.aurora.Constants.PACKAGE_NAME_APP_GALLERY
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE
|
||||
import com.aurora.extensions.isHuawei
|
||||
import com.aurora.extensions.isOAndAbove
|
||||
import com.aurora.extensions.isPAndAbove
|
||||
@@ -50,7 +53,6 @@ object PackageUtil {
|
||||
|
||||
private const val TAG = "PackageUtil"
|
||||
|
||||
const val PACKAGE_NAME_GMS = "com.google.android.gms"
|
||||
private const val VERSION_CODE_MICRO_G: Long = 240913402
|
||||
private const val VERSION_CODE_MICRO_G_HUAWEI: Long = 240913007
|
||||
|
||||
@@ -63,8 +65,39 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun hasSupportedMicroG(context: Context): Boolean {
|
||||
val isMicroG = CertUtil.isMicroGGMS(context, PACKAGE_NAME_GMS)
|
||||
fun hasSupportedAppGallery(context: Context): Boolean {
|
||||
return try {
|
||||
val result = context.packageManager.checkPermission(
|
||||
android.Manifest.permission.INSTALL_PACKAGES,
|
||||
PACKAGE_NAME_APP_GALLERY
|
||||
)
|
||||
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.w(TAG, "AppGallery does not have INSTALL_PACKAGES permission")
|
||||
return false
|
||||
}
|
||||
|
||||
val packageInfo = context.packageManager.getPackageInfo(
|
||||
PACKAGE_NAME_APP_GALLERY,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val versionCode = if (Build.VERSION.SDK_INT >= 28)
|
||||
packageInfo.longVersionCode
|
||||
else
|
||||
packageInfo.versionCode.toLong()
|
||||
|
||||
Log.i(TAG, "AppGallery - ${packageInfo.versionName} ($versionCode)")
|
||||
|
||||
versionCode >= 15010000L
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun hasSupportedMicroGVariant(context: Context): Boolean {
|
||||
val isMicroG = CertUtil.isMicroGGms(context)
|
||||
|
||||
// Do not proceed if MicroG variant is not installed
|
||||
if (!isMicroG) return false
|
||||
@@ -76,20 +109,15 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun isInstalled(context: Context, packageName: String): Boolean {
|
||||
fun isInstalled(context: Context, packageName: String, versionCode: Long? = null): Boolean {
|
||||
return try {
|
||||
getPackageInfo(context, packageName, PackageManager.GET_META_DATA)
|
||||
true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun isInstalled(context: Context, packageName: String, versionCode: Long): Boolean {
|
||||
return try {
|
||||
val packageInfo = getPackageInfo(context, packageName)
|
||||
return PackageInfoCompat.getLongVersionCode(packageInfo) >= versionCode.toLong()
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
val packageInfo = getPackageInfo(context, packageName, PackageManager.GET_META_DATA)
|
||||
if (versionCode != null) {
|
||||
PackageInfoCompat.getLongVersionCode(packageInfo) >= versionCode
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -110,7 +138,11 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun isSharedLibraryInstalled(context: Context, packageName: String, versionCode: Long): Boolean {
|
||||
fun isSharedLibraryInstalled(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
versionCode: Long
|
||||
): Boolean {
|
||||
return if (isOAndAbove) {
|
||||
val sharedLibraries = getAllSharedLibraries(context)
|
||||
if (isPAndAbove) {
|
||||
@@ -137,6 +169,16 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun isMicroGBundleInstalled(context: Context): Boolean {
|
||||
/**
|
||||
* Confirm if MicroG bundle is installed
|
||||
* Considering the following:
|
||||
* 1. GmsCore is installed and it is a microG huawei variant
|
||||
* 2. Play Store is installed - (microG Companion)
|
||||
*/
|
||||
return hasSupportedMicroGVariant(context) && isInstalled(context, PACKAGE_NAME_PLAY_STORE)
|
||||
}
|
||||
|
||||
fun getInstalledVersionName(context: Context, packageName: String): String {
|
||||
return try {
|
||||
getPackageInfo(context, packageName).versionName ?: ""
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewTextBinding
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class EpoxyTextView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewTextBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
@ModelProp
|
||||
fun title(title: String) {
|
||||
binding.txtView.text = title
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
@JvmOverloads
|
||||
fun size(int: Int = 16) {
|
||||
binding.txtView.setTextSize(TypedValue.COMPLEX_UNIT_SP, int.toFloat())
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
@JvmOverloads
|
||||
fun style(resId: Int = R.style.AuroraTextStyle) {
|
||||
binding.txtView.setTextAppearance(context, resId)
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.MobileNavigationDirections
|
||||
import com.aurora.store.data.model.MinimalApp
|
||||
import com.aurora.store.data.providers.PermissionProvider
|
||||
import com.aurora.store.view.ui.details.AppDetailsFragmentDirections
|
||||
import java.lang.reflect.ParameterizedType
|
||||
|
||||
abstract class BaseFragment<ViewBindingType : ViewBinding> : Fragment() {
|
||||
@@ -131,6 +132,12 @@ abstract class BaseFragment<ViewBindingType : ViewBinding> : Fragment() {
|
||||
findNavController().navigate(MobileNavigationDirections.actionGlobalAppMenuSheet(app))
|
||||
}
|
||||
|
||||
fun openGMSWarningFragment() {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections.actionAppDetailsFragmentToGmsWarnFragment()
|
||||
)
|
||||
}
|
||||
|
||||
private fun cleanupRecyclerViews(recyclerViews: List<EpoxyRecyclerView>) {
|
||||
recyclerViews.forEach { recyclerView ->
|
||||
runCatching {
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.isVisible
|
||||
@@ -46,8 +47,10 @@ import coil3.transform.CircleCropTransformation
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.aurora.Constants
|
||||
import com.aurora.Constants.EXODUS_SUBMIT_PAGE
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.isHuawei
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.extensions.requiresObbDir
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
@@ -397,6 +400,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.events.installerEvent.collect { onEvent(it) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -525,6 +529,24 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
|
||||
private fun purchase(app: App) {
|
||||
/**
|
||||
* MicroG Fragment Preconditions:
|
||||
* 1. App being installed must have GSF dependency
|
||||
* 2. It should be a Huawei device
|
||||
* 3. Supported App Gallery should be available, i.e. v15.1.x or above
|
||||
* 4. MicroG bundle should not be already installed
|
||||
*
|
||||
* TODO: Extract this trigger out of Vanilla & put in Huawei flavour
|
||||
*/
|
||||
if (
|
||||
app.dependencies.dependentPackages.fastAny { it == PACKAGE_NAME_GMS } &&
|
||||
isHuawei &&
|
||||
PackageUtil.hasSupportedAppGallery(requireContext()) &&
|
||||
!PackageUtil.isMicroGBundleInstalled(requireContext())
|
||||
) {
|
||||
return openGMSWarningFragment()
|
||||
}
|
||||
|
||||
if (app.fileList.requiresObbDir()) {
|
||||
if (permissionProvider.isGranted(PermissionType.STORAGE_MANAGER)) {
|
||||
viewModel.download(app)
|
||||
@@ -1036,7 +1058,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
|
||||
private fun updateCompatibilityInfo() {
|
||||
if (app.dependencies.dependentPackages.contains(PackageUtil.PACKAGE_NAME_GMS)) {
|
||||
if (app.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS)) {
|
||||
viewModel.fetchPlexusReport(app.packageName)
|
||||
|
||||
binding.layoutDetailsCompatibility.txtGmsDependency.apply {
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.Event
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.Dash
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.databinding.FragmentDetailsMicrogBinding
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.view.epoxy.views.EpoxyTextViewModel_
|
||||
import com.aurora.store.view.epoxy.views.preference.DashViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.onboarding.MicroGViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsMicroGFragment : BaseFragment<FragmentDetailsMicrogBinding>() {
|
||||
|
||||
val microGViewModel: MicroGViewModel by viewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.apply {
|
||||
title = ""
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
// RecyclerView
|
||||
epoxyRecycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
|
||||
add(
|
||||
EpoxyTextViewModel_()
|
||||
.id("microg_title")
|
||||
.title(getString(R.string.onboarding_title_gsf))
|
||||
.size(32)
|
||||
.style(R.style.AuroraTextStyle)
|
||||
)
|
||||
|
||||
add(
|
||||
EpoxyTextViewModel_()
|
||||
.id("microg_desc")
|
||||
.title(getString(R.string.onboarding_title_gsf_desc))
|
||||
.size(18)
|
||||
.style(R.style.AuroraTextStyle)
|
||||
)
|
||||
|
||||
add(
|
||||
EpoxyTextViewModel_()
|
||||
.id("microg_desc")
|
||||
.title(getString(R.string.onboarding_gms_missing))
|
||||
.size(14)
|
||||
.style(R.style.AuroraTextStyle)
|
||||
)
|
||||
|
||||
add(
|
||||
EpoxyTextViewModel_()
|
||||
.id("microg_gms")
|
||||
.title(getString(R.string.onboarding_gms_microg))
|
||||
.size(14)
|
||||
.style(R.style.AuroraTextStyle)
|
||||
)
|
||||
|
||||
|
||||
dashItems().forEach {
|
||||
add(
|
||||
DashViewModel_()
|
||||
.id(it.id)
|
||||
.dash(it)
|
||||
.click { _ ->
|
||||
requireContext().browse(it.url)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
checkboxAgreement.setOnCheckedChangeListener { _, value ->
|
||||
microGViewModel.markAgreement(value)
|
||||
btnMicroG.isEnabled = value
|
||||
}
|
||||
|
||||
btnMicroG.setOnClickListener { microGViewModel.downloadMicroG() }
|
||||
btnSkip.setOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
microGViewModel.download.filterNotNull().onEach {
|
||||
when (it.downloadStatus) {
|
||||
DownloadStatus.DOWNLOADING -> updateProgressBar(visible = true, it.progress)
|
||||
DownloadStatus.FAILED -> updateProgressBar(visible = false, 0)
|
||||
DownloadStatus.QUEUED -> updateProgressBar(visible = true, -1)
|
||||
DownloadStatus.COMPLETED -> updateProgressBar(visible = true, -1)
|
||||
else -> {}
|
||||
}
|
||||
}.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.events.installerEvent.collect { onEvent(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is InstallerEvent.Installed -> {
|
||||
if (PackageUtil.isMicroGBundleInstalled(requireContext())) {
|
||||
markInstallationComplete()
|
||||
}
|
||||
}
|
||||
|
||||
is InstallerEvent.Failed -> markInstallationFailed()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgressBar(visible: Boolean, downloadProgress: Int) {
|
||||
with(binding.progressBar) {
|
||||
if (visible) show() else hide()
|
||||
isIndeterminate = downloadProgress == -1
|
||||
progress = downloadProgress
|
||||
}
|
||||
}
|
||||
|
||||
private fun markInstallationComplete() {
|
||||
with(binding) {
|
||||
with(btnMicroG) {
|
||||
isEnabled = true
|
||||
text = getString(R.string.action_finish)
|
||||
setOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
checkboxAgreement.isEnabled = false
|
||||
progressBar.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun markInstallationFailed() {
|
||||
with(binding) {
|
||||
with(btnMicroG) {
|
||||
isEnabled = false
|
||||
text = getString(R.string.action_install)
|
||||
}
|
||||
checkboxAgreement.isChecked = false
|
||||
progressBar.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun dashItems(): List<Dash> {
|
||||
return listOf(
|
||||
Dash(
|
||||
id = 2,
|
||||
title = requireContext().getString(R.string.details_dev_website),
|
||||
subtitle = requireContext().getString(R.string.microg_website),
|
||||
icon = R.drawable.ic_network,
|
||||
url = "https://microG.org"
|
||||
),
|
||||
Dash(
|
||||
id = 4,
|
||||
title = requireContext().getString(R.string.privacy_policy_title),
|
||||
subtitle = requireContext().getString(R.string.microg_privacy_policy),
|
||||
icon = R.drawable.ic_privacy,
|
||||
url = "https://microg.org/privacy.html"
|
||||
),
|
||||
Dash(
|
||||
id = 5,
|
||||
title = requireContext().getString(R.string.menu_disclaimer),
|
||||
subtitle = requireContext().getString(R.string.microg_license_agreement),
|
||||
icon = R.drawable.ic_disclaimer,
|
||||
url = "https://raw.githubusercontent.com/microg/GmsCore/refs/heads/master/LICENSE"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,22 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.aurora.extensions.areNotificationsEnabled
|
||||
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.Event
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.UpdateMode
|
||||
import com.aurora.store.data.work.CacheWorker
|
||||
import com.aurora.store.databinding.FragmentOnboardingBinding
|
||||
@@ -25,15 +30,20 @@ import com.aurora.store.util.Preferences.PREFERENCE_INTRO
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO
|
||||
import com.aurora.store.util.save
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.onboarding.MicroGViewModel
|
||||
import com.aurora.store.viewmodel.onboarding.OnboardingPage
|
||||
import com.aurora.store.viewmodel.onboarding.OnboardingViewModel
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
abstract class BaseFlavouredOnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
|
||||
// Shared ViewModels
|
||||
val microGViewModel: MicroGViewModel by activityViewModels()
|
||||
val onboardingViewModel: OnboardingViewModel by activityViewModels()
|
||||
|
||||
val viewModel: OnboardingViewModel by viewModels()
|
||||
|
||||
var lastPosition = 0
|
||||
var currentPage = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -64,52 +74,141 @@ abstract class BaseFlavouredOnboardingFragment : BaseFragment<FragmentOnboarding
|
||||
if (PackageUtil.isTv(view.context)) finishOnboarding()
|
||||
}
|
||||
|
||||
// ViewPager2
|
||||
binding.viewpager2.apply {
|
||||
adapter = PagerAdapter(
|
||||
childFragmentManager,
|
||||
viewLifecycleOwner.lifecycle,
|
||||
onboardingPages()
|
||||
)
|
||||
isUserInputEnabled = false
|
||||
setCurrentItem(0, true)
|
||||
registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
activity?.runOnUiThread {
|
||||
lastPosition = position
|
||||
refreshButtonState()
|
||||
val pages = onboardingPages()
|
||||
|
||||
with(binding) {
|
||||
// ViewPager2
|
||||
with(viewpager2) {
|
||||
adapter = PagerAdapter(
|
||||
childFragmentManager,
|
||||
viewLifecycleOwner.lifecycle,
|
||||
pages
|
||||
)
|
||||
isUserInputEnabled = false
|
||||
setCurrentItem(0, true)
|
||||
registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
onboardingViewModel.setCurrentPage(
|
||||
when (position) {
|
||||
0 -> OnboardingPage.WELCOME
|
||||
1 -> OnboardingPage.PERMISSIONS
|
||||
2 -> OnboardingPage.GSF
|
||||
else -> OnboardingPage.WELCOME
|
||||
}
|
||||
)
|
||||
currentPage = position
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
TabLayoutMediator(tabLayout, viewpager2, true) { tab, position ->
|
||||
tab.text = (position + 1).toString()
|
||||
}.attach()
|
||||
}
|
||||
|
||||
updateBackwardButton(false)
|
||||
updateForwardButton(true)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
// Combine both relevant flows
|
||||
combine(
|
||||
microGViewModel.checked,
|
||||
onboardingViewModel.currentPage
|
||||
) { isChecked, page -> isChecked to page }.collect { (isChecked, page) ->
|
||||
when (page) {
|
||||
OnboardingPage.WELCOME -> {
|
||||
updateBackwardButton(enabled = false)
|
||||
updateForwardButton(enabled = true)
|
||||
}
|
||||
|
||||
OnboardingPage.PERMISSIONS -> {
|
||||
updateBackwardButton(enabled = true)
|
||||
val isLastPage = pages.size == 2
|
||||
|
||||
updateForwardButton(
|
||||
enabled = true,
|
||||
resId = if (isLastPage) R.string.action_finish else R.string.action_next,
|
||||
if (isLastPage) {
|
||||
{ finishOnboarding() }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingPage.GSF -> {
|
||||
updateBackwardButton(enabled = true)
|
||||
|
||||
if (isChecked) {
|
||||
val isInstalled = PackageUtil.isMicroGBundleInstalled(requireContext())
|
||||
updateForwardButton(
|
||||
enabled = isInstalled,
|
||||
resId = R.string.action_finish,
|
||||
action = if (isInstalled) {
|
||||
{ finishOnboarding() }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
} else {
|
||||
updateForwardButton(
|
||||
enabled = false,
|
||||
resId = R.string.action_finish,
|
||||
action = { finishOnboarding() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
TabLayoutMediator(binding.tabLayout, binding.viewpager2, true) { tab, position ->
|
||||
tab.text = (position + 1).toString()
|
||||
}.attach()
|
||||
|
||||
binding.btnForward.setOnClickListener {
|
||||
binding.viewpager2.setCurrentItem(binding.viewpager2.currentItem + 1, true)
|
||||
}
|
||||
|
||||
binding.btnBackward.setOnClickListener {
|
||||
binding.viewpager2.setCurrentItem(binding.viewpager2.currentItem - 1, true)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.events.installerEvent.collect { onEvent(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshButtonState() {
|
||||
binding.btnBackward.isEnabled = lastPosition != 0
|
||||
binding.btnForward.isEnabled = lastPosition != 1
|
||||
private fun updateBackwardButton(
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
with(binding.btnBackward) {
|
||||
isEnabled = enabled
|
||||
text = getString(R.string.action_back)
|
||||
setOnClickListener({
|
||||
binding.viewpager2.setCurrentItem(binding.viewpager2.currentItem - 1, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateForwardButton(
|
||||
enabled: Boolean = true,
|
||||
@StringRes resId: Int = R.string.action_next,
|
||||
action: ((View) -> Unit)? = null
|
||||
) {
|
||||
with(binding.btnForward) {
|
||||
isEnabled = enabled
|
||||
text = getString(resId)
|
||||
setOnClickListener(action ?: {
|
||||
binding.viewpager2.setCurrentItem(binding.viewpager2.currentItem + 1, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is InstallerEvent.Installed -> {
|
||||
if (PackageUtil.isMicroGBundleInstalled(requireContext())) {
|
||||
with(binding.btnForward) {
|
||||
isEnabled = true
|
||||
text = getString(R.string.action_finish)
|
||||
setOnClickListener {
|
||||
finishOnboarding()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
if (lastPosition == 1) {
|
||||
binding.btnForward.text = getString(R.string.action_finish)
|
||||
binding.btnForward.isEnabled = true
|
||||
binding.btnForward.setOnClickListener { finishOnboarding() }
|
||||
} else {
|
||||
binding.btnForward.text = getString(R.string.action_next)
|
||||
binding.btnForward.setOnClickListener {
|
||||
binding.viewpager2.setCurrentItem(
|
||||
binding.viewpager2.currentItem + 1, true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +235,7 @@ abstract class BaseFlavouredOnboardingFragment : BaseFragment<FragmentOnboarding
|
||||
|
||||
save(PREFERENCE_UPDATES_AUTO, updateMode.ordinal)
|
||||
|
||||
viewModel.updateHelper.scheduleAutomatedCheck()
|
||||
onboardingViewModel.updateHelper.scheduleAutomatedCheck()
|
||||
}
|
||||
|
||||
internal class PagerAdapter(
|
||||
|
||||
@@ -66,7 +66,7 @@ class NetworkPreference : BasePreferenceFragment() {
|
||||
}
|
||||
|
||||
findPreference<SwitchPreferenceCompat>(PREFERENCE_MICROG_AUTH)?.isEnabled =
|
||||
PackageUtil.hasSupportedMicroG(requireContext())
|
||||
PackageUtil.hasSupportedMicroGVariant(requireContext())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE
|
||||
import com.aurora.extensions.getPackageName
|
||||
import com.aurora.extensions.isMAndAbove
|
||||
import com.aurora.extensions.navigate
|
||||
@@ -27,7 +28,6 @@ import com.aurora.store.databinding.FragmentSplashBinding
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||
@@ -45,7 +45,7 @@ abstract class BaseFlavouredSplashFragment : BaseFragment<FragmentSplashBinding>
|
||||
val viewModel: AuthViewModel by activityViewModels()
|
||||
|
||||
val canLoginWithMicroG: Boolean
|
||||
get() = isMAndAbove && PackageUtil.hasSupportedMicroG(requireContext()) &&
|
||||
get() = isMAndAbove && PackageUtil.hasSupportedMicroGVariant(requireContext()) &&
|
||||
Preferences.getBoolean(requireContext(), PREFERENCE_MICROG_AUTH, true)
|
||||
|
||||
val startForAccount =
|
||||
@@ -215,7 +215,7 @@ abstract class BaseFlavouredSplashFragment : BaseFragment<FragmentSplashBinding>
|
||||
Account(accountName, GOOGLE_ACCOUNT_TYPE),
|
||||
GOOGLE_PLAY_AUTH_TOKEN_TYPE,
|
||||
bundleOf(
|
||||
"overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
|
||||
"overridePackage" to PACKAGE_NAME_PLAY_STORE,
|
||||
"overrideCertificate" to Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
|
||||
),
|
||||
requireActivity(),
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.onboarding
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE
|
||||
import com.aurora.gplayapi.data.models.PlayFile
|
||||
import com.aurora.store.data.helper.DownloadHelper
|
||||
import com.aurora.store.data.room.suite.ExternalApk
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MicroGViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val downloadHelper: DownloadHelper
|
||||
) : ViewModel() {
|
||||
|
||||
private val _packageNames = MutableSharedFlow<List<String>>()
|
||||
val packageNames = _packageNames.asSharedFlow()
|
||||
|
||||
private val _checked = MutableStateFlow(false)
|
||||
val checked: StateFlow<Boolean> = _checked
|
||||
|
||||
val download = combine(packageNames, downloadHelper.downloadsList) { apps, list ->
|
||||
list.find { d -> apps.any { it == d.packageName } }
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
|
||||
val externalApks = listOf<ExternalApk>(
|
||||
ExternalApk(
|
||||
packageName = "com.google.android.gms",
|
||||
versionCode = 244735012,
|
||||
versionName = "v0.3.6.244735",
|
||||
displayName = "microG Services",
|
||||
iconURL = "https://raw.githubusercontent.com/microg/GmsCore/refs/heads/master/play-services-core/src/main/res/mipmap-xxxhdpi/ic_app.png",
|
||||
developerName = "microG Team",
|
||||
fileList = listOf(
|
||||
PlayFile(
|
||||
url = "https://github.com/microg/GmsCore/releases/download/v0.3.6.244735/com.google.android.gms-244735012-hw.apk",
|
||||
name = "com.google.android.gms-244735012-hw.apk",
|
||||
size = 32509431,
|
||||
sha256 = "2f14df2974811b576bfafa6167a97e3b3032f2bd6e6ec3887a833fd2fa350dda"
|
||||
)
|
||||
)
|
||||
),
|
||||
ExternalApk(
|
||||
packageName = "com.android.vending",
|
||||
versionCode = 84022612,
|
||||
versionName = "v0.3.6.244735",
|
||||
displayName = "microG Companion",
|
||||
iconURL = "https://raw.githubusercontent.com/microg/FakeStore/refs/heads/main/fake-store/src/main/res/mipmap-xxxhdpi/ic_app.png",
|
||||
developerName = "microG Team",
|
||||
fileList = listOf(
|
||||
PlayFile(
|
||||
url = "https://github.com/microg/GmsCore/releases/download/v0.3.6.244735/com.android.vending-84022612-hw.apk",
|
||||
name = "com.android.vending-84022612-hw.apk",
|
||||
size = 3915551,
|
||||
sha256 = "6835b09016cef0fc3469b4a36b1720427ad3f81161cf20b188f0dadb5f8594e1"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
fun markAgreement(checked: Boolean) {
|
||||
viewModelScope.launch {
|
||||
_checked.emit(checked)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadMicroG() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_packageNames.emit(
|
||||
listOf(
|
||||
PACKAGE_NAME_GMS,
|
||||
PACKAGE_NAME_PLAY_STORE
|
||||
)
|
||||
)
|
||||
|
||||
externalApks.forEach {
|
||||
// Enqueue download only if not already installed
|
||||
if (!it.isInstalled(context)) {
|
||||
downloadHelper.enqueueStandalone(it)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aurora.store.viewmodel.onboarding
|
||||
|
||||
enum class OnboardingPage {
|
||||
WELCOME,
|
||||
PERMISSIONS,
|
||||
GSF,
|
||||
}
|
||||
@@ -6,9 +6,23 @@
|
||||
package com.aurora.store.viewmodel.onboarding
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.store.data.helper.UpdateHelper
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class OnboardingViewModel @Inject constructor(val updateHelper: UpdateHelper) : ViewModel()
|
||||
class OnboardingViewModel @Inject constructor(val updateHelper: UpdateHelper) : ViewModel() {
|
||||
|
||||
private val _page = MutableStateFlow<OnboardingPage>(OnboardingPage.WELCOME)
|
||||
val currentPage: StateFlow<OnboardingPage> = _page
|
||||
|
||||
fun setCurrentPage(page: OnboardingPage) {
|
||||
viewModelScope.launch {
|
||||
_page.emit(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
109
app/src/main/res/layout-land/fragment_onboarding_microg.xml
Normal file
109
app/src/main/res/layout-land/fragment_onboarding_microg.xml
Normal file
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~ Aurora Store
|
||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
~
|
||||
~ Aurora Store 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 2 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/top_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@id/bottom_layout"
|
||||
android:layout_alignParentTop="true"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="true"
|
||||
android:text="@string/onboarding_title_gsf"
|
||||
android:textAlignment="textStart"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="42sp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/AuroraTextStyle.Subtitle.Alt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_title_gsf_desc"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxy_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:overScrollMode="never"
|
||||
android:padding="@dimen/padding_medium"
|
||||
android:scrollbars="none"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_dash" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_large"
|
||||
android:showDividers="middle">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/checkbox_agreement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_xxsmall"
|
||||
android:text="@string/onboarding_gms_agreement" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_microG"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:maxWidth="@dimen/width_button"
|
||||
android:text="@string/action_install_microG" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:visibility="invisible" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
@@ -38,7 +38,8 @@
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
108
app/src/main/res/layout/fragment_details_microg.xml
Normal file
108
app/src/main/res/layout/fragment_details_microg.xml
Normal file
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Aurora Store
|
||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
~
|
||||
~ Aurora Store 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 2 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxy_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@id/bottom_layout"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:overScrollMode="never"
|
||||
android:padding="@dimen/padding_medium"
|
||||
android:scrollbars="none"
|
||||
app:itemSpacing="@dimen/margin_small"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_dash" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="?attr/colorSurface"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/checkbox_agreement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_xxsmall"
|
||||
android:text="@string/onboarding_gms_agreement" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/layout_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_microG"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_margin="@dimen/margin_xsmall"
|
||||
android:enabled="false"
|
||||
android:text="@string/action_install_microG"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_skip"
|
||||
app:layout_constraintTop_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_skip"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_margin="@dimen/margin_xsmall"
|
||||
android:text="@string/action_ignore"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_microG"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
106
app/src/main/res/layout/fragment_onboarding_microg.xml
Normal file
106
app/src/main/res/layout/fragment_onboarding_microg.xml
Normal file
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~ Aurora Store
|
||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
~
|
||||
~ Aurora Store 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 2 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/top_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="true"
|
||||
android:paddingStart="@dimen/padding_normal"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:text="@string/onboarding_title_gsf"
|
||||
android:textAlignment="textStart"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="32sp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/AuroraTextStyle.Subtitle.Alt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_normal"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:text="@string/onboarding_title_gsf_desc"
|
||||
android:textAlignment="textStart" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxy_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@id/bottom_layout"
|
||||
android:layout_below="@+id/top_layout"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:overScrollMode="never"
|
||||
android:padding="@dimen/padding_medium"
|
||||
android:scrollbars="none"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_dash" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="?attr/colorSurface"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_large"
|
||||
android:showDividers="middle">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/checkbox_agreement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_xxsmall"
|
||||
android:text="@string/onboarding_gms_agreement" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_microG"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:enabled="false"
|
||||
android:text="@string/action_install_microG" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:visibility="invisible" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
@@ -47,7 +47,7 @@
|
||||
android:text="@string/onboarding_title_permissions"
|
||||
android:textAlignment="textStart"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="42sp" />
|
||||
android:textSize="32sp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/subtitle"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
android:text="@string/onboarding_title_welcome"
|
||||
android:textAlignment="textStart"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="42sp" />
|
||||
android:textSize="32sp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/AuroraTextStyle.Subtitle.Alt"
|
||||
|
||||
25
app/src/main/res/layout/view_text.xml
Normal file
25
app/src/main/res/layout/view_text.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Aurora Store
|
||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
~
|
||||
~ Aurora Store 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 2 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/txt_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Some text" />
|
||||
@@ -91,7 +91,7 @@
|
||||
android:id="@+id/settingsFragment"
|
||||
android:name="com.aurora.store.view.ui.preferences.SettingsFragment"
|
||||
android:label="@string/title_settings"
|
||||
tools:layout="@layout/fragment_setting" >
|
||||
tools:layout="@layout/fragment_setting">
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_permissionsFragment"
|
||||
app:destination="@id/permissionsFragment" />
|
||||
@@ -157,6 +157,11 @@
|
||||
<argument
|
||||
android:name="packageName"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="app"
|
||||
android:defaultValue="@null"
|
||||
app:argType="com.aurora.gplayapi.data.models.App"
|
||||
app:nullable="true" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_devAppsFragment"
|
||||
app:destination="@id/devAppsFragment" />
|
||||
@@ -169,11 +174,6 @@
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_detailsExodusFragment"
|
||||
app:destination="@id/detailsExodusFragment" />
|
||||
<argument
|
||||
android:name="app"
|
||||
android:defaultValue="@null"
|
||||
app:argType="com.aurora.gplayapi.data.models.App"
|
||||
app:nullable="true" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_manualDownloadSheet"
|
||||
app:destination="@id/manualDownloadSheet" />
|
||||
@@ -183,6 +183,9 @@
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_installErrorDialogSheet"
|
||||
app:destination="@id/installErrorDialogSheet" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_gmsWarnFragment"
|
||||
app:destination="@id/detailsMicroGFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/categoryBrowseFragment"
|
||||
@@ -375,6 +378,11 @@
|
||||
android:defaultValue="true"
|
||||
app:argType="boolean" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/detailsMicroGFragment"
|
||||
android:name="com.aurora.store.view.ui.details.DetailsMicroGFragment"
|
||||
android:label="MicroGFragment"
|
||||
tools:layout="@layout/fragment_details_microg" />
|
||||
<dialog
|
||||
android:id="@+id/appMenuSheet"
|
||||
android:name="com.aurora.store.view.ui.sheets.AppMenuSheet"
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
<dimen name="height_bottomsheet_button">52dp</dimen>
|
||||
<dimen name="height_peek">64dp</dimen>
|
||||
<dimen name="height_nav_header">148dp</dimen>
|
||||
<dimen name="height_microg_action">96dp</dimen>
|
||||
|
||||
<dimen name="radius_small">8dp</dimen>
|
||||
<dimen name="radius_medium">10dp</dimen>
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<string name="action_granted">"Granted"</string>
|
||||
<string name="action_ignore">"Ignore"</string>
|
||||
<string name="action_install">"Install"</string>
|
||||
<string name="action_install_microG">"Install microG Bundle"</string>
|
||||
<string name="action_installations">"Installations"</string>
|
||||
<string name="action_installing">"Installing"</string>
|
||||
<string name="action_join">"Join"</string>
|
||||
@@ -193,21 +194,6 @@
|
||||
<string name="notification_channel_export">App export notification</string>
|
||||
<string name="notification_channel_install">Install notification</string>
|
||||
<string name="notification_channel_downloads">Downloads notification</string>
|
||||
<string name="onboarding_permission_select">"Aurora Store requires following permissions"</string>
|
||||
<string name="onboarding_title_installer">"Installer"</string>
|
||||
<string name="onboarding_title_permissions">"Permissions"</string>
|
||||
<string name="onboarding_title_welcome">"Welcome"</string>
|
||||
<string name="onboarding_welcome_select">"How you doing?"</string>
|
||||
<string name="onboarding_permission_esa">"External storage access"</string>
|
||||
<string name="onboarding_permission_esa_desc">To save APK expansion files (OBBs) for large apps & games.</string>
|
||||
<string name="onboarding_permission_esm">"External storage manager"</string>
|
||||
<string name="onboarding_permission_installer">"Installer permission"</string>
|
||||
<string name="onboarding_permission_installer_desc">"Allow installing apps from Aurora Store"</string>
|
||||
<string name="onboarding_permission_installer_legacy_desc">"Allow installation of apps from unknown sources"</string>
|
||||
<string name="onboarding_permission_notifications">"Notifications"</string>
|
||||
<string name="onboarding_permission_notifications_desc">"Send notifications regarding installations status"</string>
|
||||
<string name="onboarding_permission_doze">"Background downloads"</string>
|
||||
<string name="onboarding_permission_doze_desc">"Allow Aurora Store to download and update apps in background"</string>
|
||||
<string name="pref_common_extra">"Extras"</string>
|
||||
<string name="pref_filter_fdroid_summary">"Don't check updates for apps installed from F-Droid"</string>
|
||||
<string name="pref_filter_fdroid_title">"Filter F-Droid apps"</string>
|
||||
@@ -362,6 +348,31 @@
|
||||
<string name="app_info_min_android">"Minimum Android Version"</string>
|
||||
<string name="app_info_target_android">"Target API Level"</string>
|
||||
|
||||
<!-- OnBoarding Fragment -->
|
||||
<string name="onboarding_title_installer">"Installer"</string>
|
||||
<string name="onboarding_title_gsf">"App Compatibility"</string>
|
||||
<string name="onboarding_title_gsf_desc">"Missing dependencies"</string>
|
||||
<string name="onboarding_title_permissions">"Permissions"</string>
|
||||
<string name="onboarding_title_welcome">"Welcome"</string>
|
||||
<string name="onboarding_welcome_select">"How you doing?"</string>
|
||||
<string name="onboarding_permission_esa">"External storage access"</string>
|
||||
<string name="onboarding_permission_esa_desc">To save APK expansion files (OBBs) for large apps & games.</string>
|
||||
<string name="onboarding_permission_esm">"External storage manager"</string>
|
||||
<string name="onboarding_permission_installer">"Installer permission"</string>
|
||||
<string name="onboarding_permission_installer_desc">"Allow installing apps from Aurora Store"</string>
|
||||
<string name="onboarding_permission_installer_legacy_desc">"Allow installation of apps from unknown sources"</string>
|
||||
<string name="onboarding_permission_notifications">"Notifications"</string>
|
||||
<string name="onboarding_permission_notifications_desc">"Send notifications regarding installations status"</string>
|
||||
<string name="onboarding_permission_doze">"Background downloads"</string>
|
||||
<string name="onboarding_permission_doze_desc">"Allow Aurora Store to download and update apps in background"</string>
|
||||
<string name="onboarding_permission_select">"Aurora Store requires following permissions"</string>
|
||||
<string name="onboarding_gms_agreement">"I have read and agree to the microG Terms of Service and Privacy Policy"</string>
|
||||
<string name="onboarding_gms_missing">"We couldn't find Google Play Services on your device, several popular apps now require it to function properly!"</string>
|
||||
<string name="onboarding_gms_microg">"microG is a free and open-source implementation that provides similar functionality to run apps dependent on Google Play Services for Android devices through re-implementation.\n\nmicroG enables apps to access those Google APIs, enhancing compatibility for users while offering privacy benefits. \n\nmicroG will run automatically in the background when you open applications dependent on Google Mobile Services."</string>
|
||||
<string name="microg_privacy_policy">Read microG Privacy Policy</string>
|
||||
<string name="microg_license_agreement">Read microG License and Agreement</string>
|
||||
<string name="microg_website">Visit microG Project Website</string>
|
||||
|
||||
<!-- InstallerFragment -->
|
||||
<string name="session_installer_subtitle">Session based installer for bundled/split APKs</string>
|
||||
<string name="session_installer_desc">Recommended, in-built and supports all Android versions</string>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.aurora.store.data.receiver
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class InstallerStatusReceiver : BaseInstallerStatusReceiver()
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.aurora.store.data.receiver
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class InstallerStatusReceiver : BaseInstallerStatusReceiver()
|
||||
@@ -1,5 +1,6 @@
|
||||
[versions]
|
||||
activity = "1.10.1"
|
||||
agCoreservice = "13.3.1.300"
|
||||
androidGradlePlugin = "8.11.0"
|
||||
androidx-hilt = "1.2.0"
|
||||
androidx-junit = "1.2.1"
|
||||
@@ -38,6 +39,7 @@ viewpager2 = "1.1.0"
|
||||
work = "2.10.2"
|
||||
|
||||
[libraries]
|
||||
ag-coreservice = { module = "com.huawei.hms:ag-coreservice", version.ref = "agCoreservice" }
|
||||
airbnb-epoxy-android = { module = "com.airbnb.android:epoxy", version.ref = "epoxy" }
|
||||
airbnb-epoxy-processor = { module = "com.airbnb.android:epoxy-processor", version.ref = "epoxy" }
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" }
|
||||
|
||||
@@ -36,6 +36,7 @@ dependencyResolutionManagement {
|
||||
includeModule("com.github.topjohnwu.libsu", "core")
|
||||
}
|
||||
}
|
||||
maven { url = uri("https://developer.huawei.com/repo/") }
|
||||
}
|
||||
}
|
||||
include(":app")
|
||||
|
||||
Reference in New Issue
Block a user