Allow huawei variants to install microG & support silent installs.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user