1+ package com.nmc.android.appupdate
2+
3+ import android.app.Activity
4+ import androidx.activity.result.ActivityResult
5+ import androidx.activity.result.ActivityResultLauncher
6+ import androidx.activity.result.IntentSenderRequest
7+ import androidx.activity.result.contract.ActivityResultContracts
8+ import androidx.appcompat.app.AppCompatActivity
9+ import com.google.android.material.snackbar.Snackbar
10+ import com.google.android.play.core.appupdate.AppUpdateInfo
11+ import com.google.android.play.core.appupdate.AppUpdateManagerFactory
12+ import com.google.android.play.core.appupdate.AppUpdateOptions
13+ import com.google.android.play.core.install.InstallState
14+ import com.google.android.play.core.install.InstallStateUpdatedListener
15+ import com.google.android.play.core.install.model.ActivityResult.RESULT_IN_APP_UPDATE_FAILED
16+ import com.google.android.play.core.install.model.AppUpdateType
17+ import com.google.android.play.core.install.model.InstallStatus
18+ import com.google.android.play.core.install.model.UpdateAvailability
19+ import com.google.firebase.ktx.Firebase
20+ import com.google.firebase.remoteconfig.ktx.get
21+ import com.google.firebase.remoteconfig.ktx.remoteConfig
22+ import com.nmc.android.remoteconfig.RemoteConfigInit.Companion.APP_VERSION_KEY
23+ import com.nmc.android.remoteconfig.RemoteConfigInit.Companion.FORCE_UPDATE_KEY
24+ import com.owncloud.android.R
25+ import com.owncloud.android.lib.common.utils.Log_OC
26+ import com.owncloud.android.utils.DisplayUtils
27+
28+ class InAppUpdateHelperImpl (private val activity : AppCompatActivity ) : InAppUpdateHelper, InstallStateUpdatedListener {
29+
30+ companion object {
31+ private val TAG = InAppUpdateHelperImpl ::class .java.simpleName
32+ }
33+
34+ private val remoteConfig = Firebase .remoteConfig
35+ private val isForceUpdate = remoteConfig[FORCE_UPDATE_KEY ].asBoolean()
36+ private val appVersionCode = remoteConfig[APP_VERSION_KEY ].asLong()
37+
38+ private val appUpdateManager = AppUpdateManagerFactory .create(activity)
39+
40+ @AppUpdateType
41+ private var updateType = if (isForceUpdate) AppUpdateType .IMMEDIATE else AppUpdateType .FLEXIBLE
42+
43+ init {
44+ Log_OC .d(TAG , " App Update Remote Config Values : Force Update- $isForceUpdate -- Version Code- $appVersionCode " )
45+
46+ val appUpdateInfoTask = appUpdateManager.appUpdateInfo
47+
48+ appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
49+ if (appUpdateInfo.updateAvailability() == UpdateAvailability .UPDATE_AVAILABLE ) {
50+ Log_OC .d(TAG , " App update is available." )
51+
52+ // if app version in remote config is not equal to the latest app version code in play store
53+ // then do the flexible update instead of reading the value from remote config
54+ if (appUpdateInfo.availableVersionCode() != appVersionCode.toInt()) {
55+ Log_OC .d(
56+ TAG ,
57+ " Available app version code mismatch with remote config. Setting update type to optional."
58+ )
59+ updateType = AppUpdateType .FLEXIBLE
60+ }
61+
62+ if (appUpdateInfo.isUpdateTypeAllowed(updateType)) {
63+ // Request the update.
64+ startAppUpdate(
65+ appUpdateInfo,
66+ updateType
67+ )
68+ }
69+ } else {
70+ Log_OC .d(TAG , " No app update available." )
71+ }
72+ }
73+ }
74+
75+ private fun startAppUpdate (
76+ appUpdateInfo : AppUpdateInfo ,
77+ @AppUpdateType updateType : Int
78+ ) {
79+
80+ if (updateType == AppUpdateType .FLEXIBLE ) {
81+ // Before starting an update, register a listener for updates.
82+ appUpdateManager.registerListener(this )
83+ }
84+
85+ Log_OC .d(TAG , " App update dialog showing to the user." )
86+
87+ appUpdateManager.startUpdateFlowForResult(
88+ appUpdateInfo,
89+ appUpdateResultLauncher,
90+ AppUpdateOptions .newBuilder(updateType).build()
91+ )
92+ }
93+
94+ private val appUpdateResultLauncher: ActivityResultLauncher <IntentSenderRequest > =
95+ activity.registerForActivityResult(ActivityResultContracts .StartIntentSenderForResult ()) { result: ActivityResult ->
96+ when (result.resultCode) {
97+ Activity .RESULT_OK -> {
98+ Log_OC .d(TAG , " The user has accepted to download the update or the download finished." )
99+ }
100+
101+ Activity .RESULT_CANCELED -> {
102+ Log_OC .e(TAG , " Update flow failed: The user has denied or canceled the update." )
103+ }
104+
105+ RESULT_IN_APP_UPDATE_FAILED -> {
106+ Log_OC .e(
107+ TAG ,
108+ " Update flow failed: Some other error prevented either the user from providing consent or the update from proceeding."
109+ )
110+ }
111+ }
112+
113+ }
114+
115+ private fun flexibleUpdateDownloadCompleted () {
116+ DisplayUtils .createSnackbar(
117+ activity.findViewById(android.R .id.content),
118+ R .string.app_update_downloaded,
119+ Snackbar .LENGTH_INDEFINITE
120+ ).apply {
121+ setAction(R .string.common_restart) { appUpdateManager.completeUpdate() }
122+ show()
123+ }
124+ }
125+
126+ override fun onResume () {
127+ appUpdateManager
128+ .appUpdateInfo
129+ .addOnSuccessListener { appUpdateInfo: AppUpdateInfo ->
130+ // for AppUpdateType.IMMEDIATE only, already executing updater
131+ if (updateType == AppUpdateType .IMMEDIATE ) {
132+ if (appUpdateInfo.updateAvailability()
133+ == UpdateAvailability .DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
134+ ) {
135+ Log_OC .d(TAG , " Resume the Immediate update if in-app update is already running." )
136+ // If an in-app update is already running, resume the update.
137+ startAppUpdate(
138+ appUpdateInfo,
139+ AppUpdateType .IMMEDIATE
140+ )
141+ }
142+ } else if (updateType == AppUpdateType .FLEXIBLE ) {
143+ // If the update is downloaded but not installed,
144+ // notify the user to complete the update.
145+ if (appUpdateInfo.installStatus() == InstallStatus .DOWNLOADED ) {
146+ Log_OC .d(TAG , " Resume: Flexible update is downloaded but not installed. User is notified." )
147+ flexibleUpdateDownloadCompleted()
148+ }
149+ }
150+ }
151+ }
152+
153+ override fun onDestroy () {
154+ appUpdateManager.unregisterListener(this )
155+ }
156+
157+ override fun onStateUpdate (state : InstallState ) {
158+ if (state.installStatus() == InstallStatus .DOWNLOADED ) {
159+ Log_OC .d(TAG , " Flexible update is downloaded. User is notified to restart the app." )
160+
161+ // After the update is downloaded, notifying user via snackbar
162+ // and request user confirmation to restart the app.
163+ flexibleUpdateDownloadCompleted()
164+ }
165+ }
166+ }
0 commit comments