diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index e164d73..14e551f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,16 +1,22 @@
---
name: Bug report
-about: Create a report to help us improve
+about: Create a bug report to help us improve
title: ''
labels: bug
assignees: adriangl
---
-**Device and SW details (please complete the following information):**
- - Device: [e.g. Google Pixel 3]
- - OS: [e.g. Android 9]
- - Library Version [e.g. 1.0.0]
+**Device and SW details**
+- Device: [e.g. Google Pixel 3]
+- OS: [e.g. Android 9]
+- Library Version [e.g. 1.0.0]
+
+**Conditions for the library to work**
+- [ ] I have set the package name of the app to **exactly** the one I'd like to test in-app updates with.
+- [ ] I have signed the app with the same key that I used to sign the app I want to test in-app updates with.
+- [ ] I've ensured that any of my Google accounts in my test device has access to said app in Google Play Store.
+- [ ] The Google Play Store displays updates for the app I want to use to test in-app updates with.
**Summary and background of the bug**
A clear and concise description of what the bug is.
@@ -18,8 +24,8 @@ A clear and concise description of what the bug is.
**Steps to reproduce**
Steps to reproduce the behavior:
1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
+2. Click on '...'
+3. Scroll down to '...'
4. See error
Also attach notes or stack traces if applicable.
diff --git a/.gitignore b/.gitignore
index 6a02cb8..6a19dcf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,10 +40,10 @@ captures/
.idea/dictionaries
.idea/libraries
.idea/caches
-.idea/codeStyles
.idea/modules.xml
.idea/misc.xml
.idea/jarRepositories.xml
+.idea/vcs.xml
# Keystore files
*.jks
@@ -57,4 +57,7 @@ google-services.json
# Freeline
freeline.py
freeline/
-freeline_project_description.json
\ No newline at end of file
+freeline_project_description.json
+
+# App configuration properties
+app_config.properties
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..0d15693
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..61a9130
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 367fdaf..de753af 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,13 @@
This utility library aims to help Android developers to use the [Google Play In-App Updates API](https://developer.android.com/guide/app-bundle/in-app-updates) in an easy way.
-**It's highly encouraged that you first read the [Google Play In-App Updates API](https://developer.android.com/guide/app-bundle/in-app-updates) documentation before using this library in order to understand the core concepts of the library.**
+> It's highly encouraged that you first read the [Google Play In-App Updates API](https://developer.android.com/guide/app-bundle/in-app-updates) documentation before using this library in order to understand the core concepts of the library.
+
+## Setting Up
+In your main `build.gradle`, add [jitpack.io](https://jitpack.io/) repository in the `allprojects` block:
+
+Groovy
-## Installation
-Add the following dependencies to your main `build.gradle`:
```groovy
allprojects {
repositories {
@@ -14,45 +17,113 @@ allprojects {
}
}
```
+
-Add the following dependencies to your app's `build.gradle`:
+Kotlin
-* For Gradle < 4.0
- ```groovy
- dependencies {
- compile "com.github.bq:android-app-updates-helper:1.0.2"
+```kotlin
+allprojects {
+ repositories {
+ maven(url = "https://jitpack.io")
}
- ```
+}
+```
+
+
+
+Add the following dependencies to your app or library's `build.gradle`:
+
+Groovy
+
+```groovy
+dependencies {
+ implementation "com.github.bq:android-app-updates-helper:1.0.2"
+}
+```
+
+
+
+Kotlin
+
+```kotlin
+dependencies {
+ implementation("com.github.bq:android-app-updates-helper:1.0.2")
+}
+```
+
-* For Gradle 4.0+
- ```groovy
- dependencies {
- implementation "com.github.bq:android-app-updates-helper:1.0.2"
+You'll also need to add support for Java 8 in your project. To do so:
+Groovy
+
+```groovy
+android {
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
}
- ```
+}
+```
+
-## Example usage
+Kotlin
+
+```kotlin
+android {
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+```
+
+
+## How to use
* Create a new _AppUpdatesHelper_.
-* Start listening for app update changes with _AppUpdatesHelper.startListening()_, for example in _onCreate()_.
-* Stop listening for app update changes with _AppUpdatesHelper.stopListening()_ in _onDestroy()_.
+* Start listening for app update changes with _AppUpdatesHelper.startListening()_, for example in _Activity.onCreate()_ or in _Fragment.onViewCreated()_.
+* Stop listening for app update changes with _AppUpdatesHelper.stopListening()_ in _Activity.onDestroy()_ or in _Fragment.onDestroyView()_.
* Request app update information with _AppUpdatesHelper.getAppUpdateInfo()_.
* Request a flexible or immediate update with _AppUpdatesHelper.startFlexibleUpdate()_ or _AppUpdatesHelper.startImmediateUpdate()_
Check the [example app](app) for more implementation details about [flexible](app/src/main/kotlin/com/bq/appupdateshelper/flexible/FlexibleUpdateActivity.kt)
and [immediate](app/src/main/kotlin/com/bq/appupdateshelper/immediate/ImmediateUpdateActivity.kt) updates.
-If you use the example app, don't forget the following things when testing:
-* Change the package name of the example app to the one you'd like to test in-app updates with.
-* Sign the example app with the same keys that you used to sign the app you want to test in-app updates with.
-* If the app is not published yet, or you want to test with internal app sharing or closed tracks, ensure that any of your Google accounts in your device has access to said app in Google Play Store.
-
You can also use a [fake implementation](app/src/main/kotlin/com/bq/appupdateshelper/fake/FakeUpdateActivity.kt) to test in-app updates.
+Keep in mind that you may not see in-app updates if these conditions don't match:
+* The package name of the app is **exactly** the one you'd like to test in-app updates with.
+* The app must be signed with the same keys that you used to sign the app you want to test in-app updates with.
+* If the app is not published yet or you want to test with internal app sharing or closed tracks,
+ensure that any of your Google accounts in your device has access to said app in Google Play Store.
+* Check if the Google Play Store displays updates for the app you want to use to test in-app updates.
+
+Please ensure that all conditions apply when using this library in order to avoid unnecessary headaches.
+
+### Using the example app
+In order to ease using the example app with the sample data of your own app,
+you can create an `app_config.properties` file in the root of the project with the following content:
+```properties
+applicationId=your.application.id
+keystorePath=/full/path/to/your/keystore/file
+keystorePwd=your_keystore_password
+keystoreAlias=your_keystore_alias
+keystoreAliasPwd=your_keystore_alias_password
+```
+
+These values will be picked up by the compilation process of the example app
+and will set the application ID and signing configurations for you.
+
## Authors & Collaborators
-* [Adrián García](https://github.com/adriangl) - Author and maintainer
-* [Daniel Sánchez Ceinos](https://github.com/danielceinos) - Contributor
+* **[Adrián García](https://github.com/adriangl)** - *Author and maintainer*
+* **[Daniel Sánchez Ceinos](https://github.com/danielceinos)** - *Contributor*
## License
+This project is licensed under the Apache Software License, Version 2.0.
```
Copyright (C) 2019 BQ
diff --git a/app/build.gradle b/app/build.gradle
index 9620f0c..2517a6f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -21,42 +21,99 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.gladed.androidgitversion'
+apply from: "$rootDir/gradle/properties_utils.gradle"
+
+ext {
+ /*
+ ********************
+ * Android variables
+ ********************
+ */
+ compile_sdk_version = 30
+ min_sdk_version = 21
+ target_sdk_version = 30
+ build_tools_version = "30.0.2"
+}
+
android {
- compileSdkVersion project.ext.compileSdkVersion
+ compileSdkVersion compile_sdk_version
+ buildToolsVersion build_tools_version
defaultConfig {
- applicationId "com.bq.appupdateshelper" // Fake, replace with your own
- minSdkVersion project.ext.minSdkVersion
- targetSdkVersion project.ext.targetSdkVersion
+ /*
+ * Change your application ID to the app you want to test with
+ *
+ * Requires configuration via app_config.properties file or injected via environment variables.
+ * The loadEnvOrProperty will try to find the environment variable (in snake_case) or the
+ * property in signing.properties (converted to camelCase)
+ */
+ applicationId loadEnvOrProperty("APPLICATION_ID", "/", "app_config.properties")
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
versionCode 1
versionName androidGitVersion.name()
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
+
+ buildFeatures {
+ viewBinding true
+ }
+
+ signingConfigs {
+ /*
+ * Local release signing configuration
+ *
+ * Requires configuration via app_config.properties file or injected via environment variables.
+ * The loadEnvOrProperty will try to find the environment variable (in snake_case) or the
+ * property in signing.properties (converted to camelCase)
+ */
+ localRelease {
+ storeFile file(loadEnvOrProperty("KEYSTORE_PATH", "/", "app_config.properties"))
+ storePassword loadEnvOrProperty("KEYSTORE_PWD", "", "app_config.properties")
+ keyAlias loadEnvOrProperty("KEYSTORE_ALIAS", "", "app_config.properties")
+ keyPassword loadEnvOrProperty("KEYSTORE_ALIAS_PWD", "", "app_config.properties")
+ }
+ }
+
buildTypes {
+ debug {
+ signingConfig signingConfigs.localRelease
+ }
+
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.localRelease
}
}
sourceSets.all {
java.srcDirs += "src/${name}/kotlin"
}
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
}
dependencies {
implementation project(":lib")
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.0.2'
- implementation 'androidx.core:core-ktx:1.0.2'
- implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.core:core-ktx:1.3.2'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
- testImplementation 'junit:junit:4.12'
+ testImplementation 'junit:junit:4.13'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
- implementation 'com.google.android.material:material:1.0.0'
+ androidTestImplementation 'androidx.test:runner:1.3.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ implementation 'com.google.android.material:material:1.2.1'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 46626ac..6867204 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
+ package="com.bq.appupdateshelper_app">
-
+
-
+
-
+
-
+
+
+
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper/MainActivity.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/MainActivity.kt
similarity index 59%
rename from app/src/main/kotlin/com/bq/appupdateshelper/MainActivity.kt
rename to app/src/main/kotlin/com/bq/appupdateshelper_app/MainActivity.kt
index 7ad5ed2..d43897d 100644
--- a/app/src/main/kotlin/com/bq/appupdateshelper/MainActivity.kt
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/MainActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 BQ
+ * Copyright (C) 2020 BQ
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,28 +14,37 @@
* limitations under the License.
*/
-package com.bq.appupdateshelper
+package com.bq.appupdateshelper_app
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
-import com.bq.appupdateshelper.fake.FakeUpdateActivity
-import com.bq.appupdateshelper.flexible.FlexibleUpdateActivity
-import com.bq.appupdateshelper.immediate.ImmediateUpdateActivity
+import com.bq.appupdateshelper_app.databinding.MainActivityBinding
+import com.bq.appupdateshelper_app.fake.FakeUpdateActivity
+import com.bq.appupdateshelper_app.flexible.FlexibleUpdateActivity
+import com.bq.appupdateshelper_app.fragment.FragmentUpdateActivity
+import com.bq.appupdateshelper_app.immediate.ImmediateUpdateActivity
class MainActivity : AppCompatActivity() {
+ private lateinit var binding: MainActivityBinding
+
private val immediateButton: Button
- get() = findViewById(R.id.immediate_button)
+ get() = binding.immediateButton
private val flexibleButton: Button
- get() = findViewById(R.id.flexible_button)
+ get() = binding.flexibleButton
+
+ private val fragmentButton: Button
+ get() = binding.fragmentButton
private val fakeButton: Button
- get() = findViewById(R.id.fake_button)
+ get() = binding.fakeButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
+ binding = MainActivityBinding.inflate(layoutInflater).apply {
+ setContentView(root)
+ }
immediateButton.setOnClickListener {
startActivity(ImmediateUpdateActivity.newIntent(this))
@@ -45,6 +54,10 @@ class MainActivity : AppCompatActivity() {
startActivity(FlexibleUpdateActivity.newIntent(this))
}
+ fragmentButton.setOnClickListener {
+ startActivity(FragmentUpdateActivity.newIntent(this))
+ }
+
fakeButton.setOnClickListener {
startActivity(FakeUpdateActivity.newIntent(this))
}
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper/fake/FakeUpdateActivity.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/fake/FakeUpdateActivity.kt
similarity index 82%
rename from app/src/main/kotlin/com/bq/appupdateshelper/fake/FakeUpdateActivity.kt
rename to app/src/main/kotlin/com/bq/appupdateshelper_app/fake/FakeUpdateActivity.kt
index 3ccbfc5..02457eb 100644
--- a/app/src/main/kotlin/com/bq/appupdateshelper/fake/FakeUpdateActivity.kt
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/fake/FakeUpdateActivity.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.bq.appupdateshelper.fake
+package com.bq.appupdateshelper_app.fake
import android.content.Context
import android.content.Intent
@@ -26,8 +26,9 @@ import androidx.appcompat.app.AppCompatActivity
import com.bq.appupdateshelper.AppUpdateInfoResult
import com.bq.appupdateshelper.AppUpdateInstallState.Status.*
import com.bq.appupdateshelper.FakeAppUpdatesHelper
-import com.bq.appupdateshelper.R
-import com.bq.appupdateshelper.misc.showToast
+import com.bq.appupdateshelper_app.databinding.FakeUpdateActivityBinding
+import com.bq.appupdateshelper_app.R
+import com.bq.appupdateshelper_app.misc.showToast
import com.google.android.material.snackbar.Snackbar
/**
@@ -48,12 +49,17 @@ class FakeUpdateActivity : AppCompatActivity() {
private lateinit var fakeAppUpdatesHelper: FakeAppUpdatesHelper
+ private lateinit var binding: FakeUpdateActivityBinding
+
private val startUpdateButton: Button
- get() = findViewById(R.id.start_update_button)
+ get() = binding.startUpdateButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_fake_update)
+
+ binding = FakeUpdateActivityBinding.inflate(layoutInflater).apply {
+ setContentView(root)
+ }
setTitle(R.string.activity_fake_update_title)
@@ -84,18 +90,18 @@ class FakeUpdateActivity : AppCompatActivity() {
showToast("The update is downloading!")
}
DOWNLOADED -> {
- showToast("The update has been downloaded!")
+ showToast("The update is downloading! Progress: ${installState.downloadProgress}")
- Snackbar.make(findViewById(android.R.id.content), "Install the update?", Snackbar.LENGTH_INDEFINITE)
- .apply {
- setAction("Install") {
- fakeAppUpdatesHelper.completeUpdate()
+ Snackbar.make(binding.root, "Install the update?", Snackbar.LENGTH_INDEFINITE)
+ .apply {
+ setAction("Install") {
+ fakeAppUpdatesHelper.completeUpdate()
- // Fake installation process
- fakeAppUpdatesHelper.completeFakeUpdate()
+ // Fake installation process
+ fakeAppUpdatesHelper.completeFakeUpdate()
+ }
}
- }
- .show()
+ .show()
}
INSTALLING -> {
showToast("The update is being installed!")
@@ -106,13 +112,13 @@ class FakeUpdateActivity : AppCompatActivity() {
FAILED -> {
showToast("The update failed! Reason: ${installState.errorCode}")
- Snackbar.make(findViewById(android.R.id.content), "Retry?", Snackbar.LENGTH_LONG)
- .apply {
- setAction("Retry") {
- fakeAppUpdatesHelper.startImmediateUpdate(this@FakeUpdateActivity)
+ Snackbar.make(binding.root, "Retry?", Snackbar.LENGTH_LONG)
+ .apply {
+ setAction("Retry") {
+ fakeAppUpdatesHelper.startImmediateUpdate(this@FakeUpdateActivity)
+ }
}
- }
- .show()
+ .show()
}
CANCELED -> {
showToast("The user canceled the flexible update!")
@@ -161,7 +167,7 @@ class FakeUpdateActivity : AppCompatActivity() {
}
} else {
showToast("The update info could not be retrieved, " +
- "cause: ${appUpdateInfoResult.exception!!.message}")
+ "cause: ${appUpdateInfoResult.exception!!.message}")
}
}
}
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper/flexible/FlexibleUpdateActivity.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/flexible/FlexibleUpdateActivity.kt
similarity index 84%
rename from app/src/main/kotlin/com/bq/appupdateshelper/flexible/FlexibleUpdateActivity.kt
rename to app/src/main/kotlin/com/bq/appupdateshelper_app/flexible/FlexibleUpdateActivity.kt
index d85d41d..059283e 100644
--- a/app/src/main/kotlin/com/bq/appupdateshelper/flexible/FlexibleUpdateActivity.kt
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/flexible/FlexibleUpdateActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 BQ
+ * Copyright (C) 2020 BQ
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.bq.appupdateshelper.flexible
+package com.bq.appupdateshelper_app.flexible
import android.content.Context
import android.content.Intent
@@ -25,8 +25,9 @@ import androidx.appcompat.app.AppCompatActivity
import com.bq.appupdateshelper.AppUpdateInfoResult
import com.bq.appupdateshelper.AppUpdateInstallState.Status.*
import com.bq.appupdateshelper.AppUpdatesHelper
-import com.bq.appupdateshelper.R
-import com.bq.appupdateshelper.misc.showToast
+import com.bq.appupdateshelper_app.databinding.FlexibleUpdateActivityBinding
+import com.bq.appupdateshelper_app.R
+import com.bq.appupdateshelper_app.misc.showToast
import com.google.android.material.snackbar.Snackbar
/**
@@ -44,12 +45,16 @@ class FlexibleUpdateActivity : AppCompatActivity() {
private lateinit var appUpdatesHelper: AppUpdatesHelper
+ private lateinit var binding: FlexibleUpdateActivityBinding
+
private val startUpdateButton: Button
- get() = findViewById(R.id.start_update_button)
+ get() = binding.startUpdateButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_flexible_update)
+ binding = FlexibleUpdateActivityBinding.inflate(layoutInflater).apply {
+ setContentView(root)
+ }
setTitle(R.string.activity_flexible_update_title)
@@ -83,19 +88,19 @@ class FlexibleUpdateActivity : AppCompatActivity() {
showToast("Waiting for update to start!")
}
DOWNLOADING -> {
- showToast("The update is downloading!")
+ showToast("The update is downloading! Progress: ${installState.downloadProgress}")
}
DOWNLOADED -> {
showToast("The update has been downloaded!")
// Prompt the user to install the update when we know that the update has been
// downloaded successfully
- Snackbar.make(findViewById(android.R.id.content), "Install the update?", Snackbar.LENGTH_INDEFINITE)
- .apply {
- setAction("Install") {
- appUpdatesHelper.completeUpdate()
+ Snackbar.make(binding.root, "Install the update?", Snackbar.LENGTH_INDEFINITE)
+ .apply {
+ setAction("Install") {
+ appUpdatesHelper.completeUpdate()
+ }
}
- }
- .show()
+ .show()
}
INSTALLING -> {
showToast("The update is being installed!")
@@ -111,13 +116,13 @@ class FlexibleUpdateActivity : AppCompatActivity() {
// process if needed
showToast("The update failed! Reason: ${installState.errorCode}")
- Snackbar.make(findViewById(android.R.id.content), "Retry?", Snackbar.LENGTH_LONG)
- .apply {
- setAction("Retry") {
- appUpdatesHelper.startFlexibleUpdate(this@FlexibleUpdateActivity)
+ Snackbar.make(binding.root, "Retry?", Snackbar.LENGTH_LONG)
+ .apply {
+ setAction("Retry") {
+ appUpdatesHelper.startFlexibleUpdate(this@FlexibleUpdateActivity)
+ }
}
- }
- .show()
+ .show()
}
CANCELED -> {
// This state is only reachable in flexible updates and it happens when the
@@ -167,7 +172,7 @@ class FlexibleUpdateActivity : AppCompatActivity() {
}
} else {
showToast("The update info could not be retrieved, " +
- "cause: ${appUpdateInfoResult.exception!!.message}")
+ "cause: ${appUpdateInfoResult.exception!!.message}")
}
}
}
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper_app/fragment/FragmentUpdateActivity.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/fragment/FragmentUpdateActivity.kt
new file mode 100644
index 0000000..b51f914
--- /dev/null
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/fragment/FragmentUpdateActivity.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 BQ
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bq.appupdateshelper_app.fragment
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.bq.appupdateshelper.AppUpdatesHelper
+import com.bq.appupdateshelper_app.R
+import com.bq.appupdateshelper_app.databinding.FragmentUpdateActivityBinding
+
+/**
+ * Activity that illustrates the use of the [AppUpdatesHelper] class of the lib to start flexible
+ * updates in a fragment.
+ */
+class FragmentUpdateActivity : AppCompatActivity() {
+ companion object {
+ fun newIntent(context: Context): Intent {
+ return Intent(context, FragmentUpdateActivity::class.java)
+ }
+ }
+
+ private lateinit var binding: FragmentUpdateActivityBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = FragmentUpdateActivityBinding.inflate(layoutInflater).apply {
+ setContentView(root)
+ }
+
+ setTitle(R.string.activity_fragment_update_title)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper_app/fragment/FragmentUpdateFragment.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/fragment/FragmentUpdateFragment.kt
new file mode 100644
index 0000000..c8db2eb
--- /dev/null
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/fragment/FragmentUpdateFragment.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020 BQ
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bq.appupdateshelper_app.fragment
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import com.bq.appupdateshelper.AppUpdateInfoResult
+import com.bq.appupdateshelper.AppUpdateInstallState
+import com.bq.appupdateshelper.AppUpdatesHelper
+import com.bq.appupdateshelper_app.databinding.FragmentUpdateFragmentBinding
+import com.bq.appupdateshelper_app.misc.showToast
+import com.google.android.material.snackbar.Snackbar
+
+/**
+ * Fragment that illustrates the use of the [AppUpdatesHelper] class of the lib to start flexible
+ * updates in a fragment.
+ */
+class FragmentUpdateFragment : Fragment() {
+ companion object {
+ private const val TAG = "FragmentUpdateFragment"
+
+ fun newInstance(): FragmentUpdateFragment {
+ val args = Bundle()
+
+ val fragment = FragmentUpdateFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+
+ private lateinit var appUpdatesHelper: AppUpdatesHelper
+
+ private lateinit var binding: FragmentUpdateFragmentBinding
+
+ private val startUpdateButton: Button
+ get() = binding.startUpdateButton
+
+ override fun onCreateView(inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?): View? =
+ FragmentUpdateFragmentBinding.inflate(inflater, container, false)
+ .also { binding = it }.root
+
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ // Instantiate the app updates helper and start using it with startListening()
+ appUpdatesHelper = AppUpdatesHelper(requireContext())
+
+ appUpdatesHelper.startListening { installState ->
+ // The update process is tracked here from the moment the user clicks "Update" until the
+ // app is fully installed
+ Log.d(TAG, "Update install state: $installState")
+
+ when (installState.status) {
+ AppUpdateInstallState.Status.UNKNOWN -> {
+ showToast("Unknown install state")
+ }
+ AppUpdateInstallState.Status.DENIED -> {
+ // This should only be reached in immediate updates
+ showToast("The user denied the flexible update!")
+ requireActivity().finish()
+ }
+ AppUpdateInstallState.Status.REQUIRES_UI_INTENT -> {
+ // The docs don't really say anything about this state or when it's triggered
+ // so...
+ showToast("The update needs an UI intent!")
+ }
+ AppUpdateInstallState.Status.UPDATE_ACCEPTED -> {
+ // The user has accepted the update, so you can notify the user
+ showToast("The user accepted the update!")
+ }
+ AppUpdateInstallState.Status.PENDING -> {
+ showToast("Waiting for update to start!")
+ }
+ AppUpdateInstallState.Status.DOWNLOADING -> {
+ showToast("The update is downloading! Progress: ${installState.downloadProgress}")
+ }
+ AppUpdateInstallState.Status.DOWNLOADED -> {
+ showToast("The update has been downloaded!")
+ // Prompt the user to install the update when we know that the update has been
+ // downloaded successfully
+ Snackbar.make(binding.root, "Install the update?", Snackbar.LENGTH_INDEFINITE)
+ .apply {
+ setAction("Install") {
+ appUpdatesHelper.completeUpdate()
+ }
+ }
+ .show()
+ }
+ AppUpdateInstallState.Status.INSTALLING -> {
+ showToast("The update is being installed!")
+ }
+ AppUpdateInstallState.Status.INSTALLED -> {
+ // Usually you won't get up to this state, since the app closes automatically
+ // in the installation process. Anyway, it's not a bad practice to consider this
+ // case
+ showToast("The update has been installed!")
+ }
+ AppUpdateInstallState.Status.FAILED -> {
+ // The installation failed for some reason, here you can retry the update
+ // process if needed
+ showToast("The update failed! Reason: ${installState.errorCode}")
+
+ Snackbar.make(binding.root, "Retry?", Snackbar.LENGTH_LONG)
+ .apply {
+ setAction("Retry") {
+ appUpdatesHelper.startFlexibleUpdate(this@FragmentUpdateFragment)
+ }
+ }
+ .show()
+ }
+ AppUpdateInstallState.Status.CANCELED -> {
+ // This state is only reachable in flexible updates and it happens when the
+ // user cancels a flexible update. There's no need to do anything in this case.
+ showToast("The user canceled the flexible update!")
+ }
+ }
+ }
+
+ startUpdateButton.setOnClickListener {
+ // Start the update flow by first checking if there's any update available for the app
+ // in the Play Store using getAppUpdateInfo()
+ appUpdatesHelper.getAppUpdateInfo { appUpdateInfoResult ->
+ Log.d(TAG, "App update info: $appUpdateInfoResult")
+
+ if (appUpdateInfoResult.isSuccessful) {
+ // If the request went well, you can check the state of the app update
+ when (appUpdateInfoResult.updateAvailability!!) {
+ AppUpdateInfoResult.Availability.UNKNOWN -> {
+ showToast("The state of the update is unknown!")
+ }
+ AppUpdateInfoResult.Availability.UPDATE_NOT_AVAILABLE -> {
+ showToast("No update available!")
+ }
+ AppUpdateInfoResult.Availability.UPDATE_AVAILABLE -> {
+ showToast("Update available!")
+ // When we know that the update is available, we must check if we can
+ // perform the desired type of update
+ if (appUpdateInfoResult.canInstallFlexibleUpdate()) {
+ // Start the update flow
+ showToast("Can install flexible update!")
+ appUpdatesHelper.startFlexibleUpdate(this)
+ } else {
+ showToast("Can not install flexible update!")
+ }
+ }
+ AppUpdateInfoResult.Availability.UPDATE_IN_PROGRESS -> {
+ // If the update is in progress, we don't need to jump to the flexible flow again
+ showToast("Update in progress!")
+ }
+ AppUpdateInfoResult.Availability.UPDATE_DOWNLOADED -> {
+ // The app update is downloaded, but the install flow has not started
+ // yet, so complete the update
+ showToast("Update downloaded!")
+ appUpdatesHelper.completeUpdate()
+ }
+ }
+ } else {
+ showToast("The update info could not be retrieved, " +
+ "cause: ${appUpdateInfoResult.exception!!.message}")
+ }
+ }
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ // We have to bind the helper to the activity results so it can properly dispatch some
+ // update events
+ appUpdatesHelper.onUpdateStatusResult(requestCode, resultCode)
+ }
+
+ override fun onDestroyView() {
+ // Stop listening for updates with stopListening()
+ super.onDestroyView()
+ appUpdatesHelper.stopListening()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper/immediate/ImmediateUpdateActivity.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/immediate/ImmediateUpdateActivity.kt
similarity index 88%
rename from app/src/main/kotlin/com/bq/appupdateshelper/immediate/ImmediateUpdateActivity.kt
rename to app/src/main/kotlin/com/bq/appupdateshelper_app/immediate/ImmediateUpdateActivity.kt
index 1badca6..c1ecf41 100644
--- a/app/src/main/kotlin/com/bq/appupdateshelper/immediate/ImmediateUpdateActivity.kt
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/immediate/ImmediateUpdateActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 BQ
+ * Copyright (C) 2020 BQ
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.bq.appupdateshelper.immediate
+package com.bq.appupdateshelper_app.immediate
import android.content.Context
import android.content.Intent
@@ -25,8 +25,9 @@ import androidx.appcompat.app.AppCompatActivity
import com.bq.appupdateshelper.AppUpdateInfoResult
import com.bq.appupdateshelper.AppUpdateInstallState.Status.*
import com.bq.appupdateshelper.AppUpdatesHelper
-import com.bq.appupdateshelper.R
-import com.bq.appupdateshelper.misc.showToast
+import com.bq.appupdateshelper_app.databinding.ImmediateUpdateActivityBinding
+import com.bq.appupdateshelper_app.R
+import com.bq.appupdateshelper_app.misc.showToast
import com.google.android.material.snackbar.Snackbar
/**
@@ -44,12 +45,16 @@ class ImmediateUpdateActivity : AppCompatActivity() {
private lateinit var appUpdatesHelper: AppUpdatesHelper
+ private lateinit var binding: ImmediateUpdateActivityBinding
+
private val startUpdateButton: Button
- get() = findViewById(R.id.start_update_button)
+ get() = binding.startUpdateButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_immediate_update)
+ binding = ImmediateUpdateActivityBinding.inflate(layoutInflater).apply {
+ setContentView(root)
+ }
setTitle(R.string.activity_immediate_update_title)
@@ -85,7 +90,7 @@ class ImmediateUpdateActivity : AppCompatActivity() {
showToast("Waiting for update to start!")
}
DOWNLOADING -> {
- showToast("The update is downloading!")
+ showToast("The update is downloading! Progress: ${installState.downloadProgress}")
}
DOWNLOADED -> {
showToast("The update has been downloaded!")
@@ -104,13 +109,13 @@ class ImmediateUpdateActivity : AppCompatActivity() {
// process if needed
showToast("The update failed! Reason: ${installState.errorCode}")
- Snackbar.make(findViewById(android.R.id.content), "Retry?", Snackbar.LENGTH_LONG)
- .apply {
- setAction("Retry") {
- appUpdatesHelper.startImmediateUpdate(this@ImmediateUpdateActivity)
+ Snackbar.make(binding.root, "Retry?", Snackbar.LENGTH_LONG)
+ .apply {
+ setAction("Retry") {
+ appUpdatesHelper.startImmediateUpdate(this@ImmediateUpdateActivity)
+ }
}
- }
- .show()
+ .show()
}
CANCELED -> {
// This state is only reachable in flexible updates.
@@ -160,7 +165,7 @@ class ImmediateUpdateActivity : AppCompatActivity() {
}
} else {
showToast("The update info could not be retrieved, " +
- "cause: ${appUpdateInfoResult.exception!!.message}")
+ "cause: ${appUpdateInfoResult.exception!!.message}")
}
}
}
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper/misc/ActivityExtensions.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/misc/ActivityExtensions.kt
similarity index 97%
rename from app/src/main/kotlin/com/bq/appupdateshelper/misc/ActivityExtensions.kt
rename to app/src/main/kotlin/com/bq/appupdateshelper_app/misc/ActivityExtensions.kt
index 6ffac4e..5923ba5 100644
--- a/app/src/main/kotlin/com/bq/appupdateshelper/misc/ActivityExtensions.kt
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/misc/ActivityExtensions.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.bq.appupdateshelper.misc
+package com.bq.appupdateshelper_app.misc
import android.app.Activity
import android.widget.Toast
diff --git a/app/src/main/kotlin/com/bq/appupdateshelper_app/misc/FragmentExtensions.kt b/app/src/main/kotlin/com/bq/appupdateshelper_app/misc/FragmentExtensions.kt
new file mode 100644
index 0000000..c1f30d2
--- /dev/null
+++ b/app/src/main/kotlin/com/bq/appupdateshelper_app/misc/FragmentExtensions.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 BQ
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bq.appupdateshelper_app.misc
+
+import android.widget.Toast
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+
+/**
+ * Displays a text as a toast in the current fragment.
+ *
+ * @param text Text to display in the toast
+ * @param duration Duration, one of [Toast.LENGTH_SHORT] or [Toast.LENGTH_LONG]
+ */
+fun Fragment.showToast(text: String, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, text, duration).show()
+}
+
+/**
+ * Displays a text as a toast in the current fragment.
+ *
+ * @param stringResId Text to display in the toast as a string resource ID
+ * @param duration Duration, one of [Toast.LENGTH_SHORT] or [Toast.LENGTH_LONG]
+ */
+fun Fragment.showToast(@StringRes stringResId: Int, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, stringResId, duration).show()
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_fake_update.xml b/app/src/main/res/layout/fake_update_activity.xml
similarity index 96%
rename from app/src/main/res/layout/activity_fake_update.xml
rename to app/src/main/res/layout/fake_update_activity.xml
index eb85412..a1eda58 100644
--- a/app/src/main/res/layout/activity_fake_update.xml
+++ b/app/src/main/res/layout/fake_update_activity.xml
@@ -21,7 +21,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
- tools:context="com.bq.appupdateshelper.fake.FakeUpdateActivity"
+ tools:context="com.bq.appupdateshelper_app.fake.FakeUpdateActivity"
tools:ignore="HardcodedText">
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_update_fragment.xml b/app/src/main/res/layout/fragment_update_fragment.xml
new file mode 100644
index 0000000..3853a98
--- /dev/null
+++ b/app/src/main/res/layout/fragment_update_fragment.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_immediate_update.xml b/app/src/main/res/layout/immediate_update_activity.xml
similarity index 96%
rename from app/src/main/res/layout/activity_immediate_update.xml
rename to app/src/main/res/layout/immediate_update_activity.xml
index 234b09e..4cfdf44 100644
--- a/app/src/main/res/layout/activity_immediate_update.xml
+++ b/app/src/main/res/layout/immediate_update_activity.xml
@@ -22,7 +22,7 @@
android:layout_height="match_parent"
android:layout_margin="8dp"
android:layout_width="match_parent"
- tools:context="com.bq.appupdateshelper.immediate.ImmediateUpdateActivity"
+ tools:context="com.bq.appupdateshelper_app.immediate.ImmediateUpdateActivity"
tools:ignore="HardcodedText">
-
-
+ app:layout_constraintVertical_chainStyle="packed" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/flexible_button" />
+ app:layout_constraintTop_toBottomOf="@+id/fragment_button" />
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 34bc5c6..e2255f4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -20,5 +20,6 @@
Flexible updates exampleImmediate updates exampleFake updates example
+ In-app updates in Fragments
diff --git a/build.gradle b/build.gradle
index 9b6895f..fd5710b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,17 +17,17 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlinVersion = '1.4.10'
+ ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
mavenCentral()
- maven {url "https://plugins.gradle.org/m2/"}
+ maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.0.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
+ classpath "com.android.tools.build:gradle:4.1.0"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.github.dcendents:android-maven-gradle-plugin:2.1"
classpath "com.gladed.androidgitversion:gradle-android-git-version:0.4.13"
}
@@ -40,9 +40,7 @@ allprojects {
}
ext {
- compileSdkVersion = 29
- minSdkVersion = 21
- targetSdkVersion = 29
+ kotlin_version = "1.4.10"
}
}
diff --git a/gradle/properties_utils.gradle b/gradle/properties_utils.gradle
new file mode 100644
index 0000000..6fe07ae
--- /dev/null
+++ b/gradle/properties_utils.gradle
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 BQ
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Utility method that loads a Properties object from a given file name.
+ *
+ * @param fileName name of the properties file
+ * @return a Properties object, empty if the file could not be loaded
+ */
+def loadPropertiesFile(fileName) {
+ try {
+ Properties properties = new Properties()
+ properties.load(new FileInputStream(rootProject.file(fileName)))
+
+ return properties
+ }
+ catch (ignored) {
+ logger.info("File \"$fileName\" not found, returning empty properties object")
+ return new Properties()
+ }
+}
+
+/**
+ * Utility method that tries to find a variable in the following places:
+ * - System environment variable
+ * - Property in a properties file (the property will try to find the name in camel case)
+ *
+ * System environment property takes precedence over the property in a properties file.
+ *
+ * @param key Key to find in snake case
+ * @param defaultValue Default value in case the key is not found, null by default
+ * @param propertiesFileName file in the root of the project to check for properties. If not defined,
+ * it will default to the project's gradle.properties
+ */
+def loadEnvOrProperty(String key, defaultValue = null, String propertiesFileName = "gradle.properties") {
+ def envVarKey = key
+ def propertyKey = toCamelCase(key)
+
+ logger.info("Trying to find environment variable \"$envVarKey\" (or $propertiesFileName property \"$propertyKey\")")
+
+ // First try to get the value from the environment variable
+ def result = System.getenv(envVarKey)
+
+ if (result == null) {
+ logger.info("Environment variable \"$envVarKey\" not found, trying to find it in $propertiesFileName property \"$propertyKey\"")
+
+ // Then try to get it from the properties file
+ def properties = loadPropertiesFile(propertiesFileName)
+
+ if (properties == null) {
+ // If none could be found, return the default value
+ logger.error("Requested file $propertiesFileName not found, returning default value")
+ return defaultValue
+ }
+
+ result = properties[propertyKey]
+ }
+
+ if (result == null) {
+ logger.error("Environment variable \"$envVarKey\" (or $propertiesFileName property \"$propertyKey\") not found, returning default value")
+ return defaultValue
+ }
+ else {
+ return result
+ }
+}
+
+static String toCamelCase(String text, boolean capitalized = false) {
+ def newText = text.toLowerCase().replaceAll("(_)([A-Za-z0-9])", { Object[] it -> it[2].toUpperCase() })
+ return capitalized ? capitalize(newText) : newText
+}
+
+ext {
+ loadPropertiesFile = this.&loadPropertiesFile
+ loadEnvOrProperty = this.&loadEnvOrProperty
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 8d5ac84..09a5b0b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Sep 24 17:32:09 CEST 2020
+#Tue Oct 13 10:26:28 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
diff --git a/lib/build.gradle b/lib/build.gradle
index f713937..025093a 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2019 BQ
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
apply plugin: 'com.android.library'
apply plugin: "com.gladed.androidgitversion"
@@ -7,12 +23,25 @@ androidGitVersion {
tagPattern(/^[0-9]+.*/) // Tag names should follow the pattern MM.NN.PP
}
+ext {
+ /*
+ ********************
+ * Android variables
+ ********************
+ */
+ compile_sdk_version = 30
+ min_sdk_version = 21
+ target_sdk_version = 30
+ build_tools_version = "30.0.2"
+}
+
android {
- compileSdkVersion 30
+ compileSdkVersion compile_sdk_version
+ buildToolsVersion = build_tools_version
defaultConfig {
- minSdkVersion 21
- targetSdkVersion 30
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
versionName androidGitVersion.name()
versionCode androidGitVersion.code()
@@ -26,6 +55,10 @@ android {
}
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
dependencies {
@@ -35,7 +68,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
// Needed for the in-app updates API
- api "com.google.android.play:core:1.8.0"
+ api "com.google.android.play:core:1.8.2"
testImplementation 'junit:junit:4.13'
@@ -43,22 +76,6 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
-/*
- * Copyright (C) 2019 BQ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
// Build a jar with source files
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
diff --git a/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInfoResult.java b/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInfoResult.java
index 0adc932..d3236a3 100644
--- a/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInfoResult.java
+++ b/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInfoResult.java
@@ -33,36 +33,56 @@
* {@link AppUpdatesHelper#getAppUpdateInfo(GetUpdateInfoListener)}.
*/
public class AppUpdateInfoResult {
+ public static final int VERSION_UNKNOWN = -1;
+ public static final int VERSION_STALENESS_UNKNOWN = -1;
+ public static final int UPDATE_PRIORITY_UNKNOWN = -1;
+
+
private final boolean isSuccessful;
private final int versionCode;
private final Availability updateAvailability;
+ private final int updatePriority;
private final boolean canInstallFlexibleUpdate;
private final boolean canInstallImmediateUpdate;
+ private final int clientVersionStalenessDays;
private final Exception exception;
AppUpdateInfoResult(@Nullable AppUpdateInfo info,
@Nullable Exception exception) {
this.isSuccessful = info != null && exception == null;
- this.versionCode = (info != null) ? info.availableVersionCode() : -1;
+ this.versionCode = (info != null) ? info.availableVersionCode() : VERSION_UNKNOWN;
this.updateAvailability = Availability.from(
- (info != null) ? info.updateAvailability() : -1,
- (info != null) ? info.installStatus() : -1);
+ (info != null) ? info.updateAvailability() : UpdateAvailability.UNKNOWN,
+ (info != null) ? info.installStatus() : InstallStatus.UNKNOWN);
+ this.updatePriority = info != null ? info.updatePriority() : UPDATE_PRIORITY_UNKNOWN;
this.canInstallFlexibleUpdate = info != null && info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE);
this.canInstallImmediateUpdate = info != null && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE);
+ Integer infoVersionStalenessDays =
+ info != null ? info.clientVersionStalenessDays() : Integer.valueOf(VERSION_STALENESS_UNKNOWN);
+ //noinspection ConstantConditions
+ this.clientVersionStalenessDays = infoVersionStalenessDays != null ? infoVersionStalenessDays : VERSION_STALENESS_UNKNOWN;
+
this.exception = exception;
}
- @VisibleForTesting AppUpdateInfoResult(boolean isSuccessful,
- int versionCode,
- Availability updateAvailability,
- boolean canInstallFlexibleUpdate,
- boolean canInstallImmediateUpdate, Exception exception) {
+ @VisibleForTesting
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ AppUpdateInfoResult(boolean isSuccessful,
+ int versionCode,
+ Availability updateAvailability,
+ int updatePriority,
+ boolean canInstallFlexibleUpdate,
+ boolean canInstallImmediateUpdate,
+ int clientVersionStalenessDays,
+ Exception exception) {
this.isSuccessful = isSuccessful;
this.versionCode = versionCode;
this.updateAvailability = updateAvailability;
+ this.updatePriority = updatePriority;
this.canInstallFlexibleUpdate = canInstallFlexibleUpdate;
this.canInstallImmediateUpdate = canInstallImmediateUpdate;
+ this.clientVersionStalenessDays = clientVersionStalenessDays;
this.exception = exception;
}
@@ -78,6 +98,14 @@ public Availability getUpdateAvailability() {
return updateAvailability;
}
+ public int getUpdatePriority() {
+ return updatePriority;
+ }
+
+ public int getClientVersionStalenessDays() {
+ return clientVersionStalenessDays;
+ }
+
/**
* @return if the update can be flexible
*/
@@ -103,27 +131,34 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
AppUpdateInfoResult that = (AppUpdateInfoResult) o;
return isSuccessful == that.isSuccessful &&
- versionCode == that.versionCode &&
- canInstallFlexibleUpdate == that.canInstallFlexibleUpdate &&
- canInstallImmediateUpdate == that.canInstallImmediateUpdate &&
- updateAvailability == that.updateAvailability &&
- Objects.equals(exception, that.exception);
+ versionCode == that.versionCode &&
+ updatePriority == that.updatePriority &&
+ canInstallFlexibleUpdate == that.canInstallFlexibleUpdate &&
+ canInstallImmediateUpdate == that.canInstallImmediateUpdate &&
+ clientVersionStalenessDays == that.clientVersionStalenessDays &&
+ updateAvailability == that.updateAvailability &&
+ exception.equals(that.exception);
}
@Override
public int hashCode() {
- return Objects.hash(isSuccessful, versionCode, updateAvailability, canInstallFlexibleUpdate, canInstallImmediateUpdate, exception);
+ return Objects.hash(isSuccessful, versionCode, updateAvailability,
+ updatePriority, canInstallFlexibleUpdate, canInstallImmediateUpdate,
+ clientVersionStalenessDays, exception);
}
- @Override public String toString() {
+ @Override
+ public String toString() {
return "AppUpdateInfoResult{" +
- "isSuccessful=" + isSuccessful +
- ", versionCode=" + versionCode +
- ", updateAvailability=" + updateAvailability +
- ", canInstallFlexibleUpdate=" + canInstallFlexibleUpdate +
- ", canInstallImmediateUpdate=" + canInstallImmediateUpdate +
- ", exception=" + exception +
- '}';
+ "isSuccessful=" + isSuccessful +
+ ", versionCode=" + versionCode +
+ ", updateAvailability=" + updateAvailability +
+ ", updatePriority=" + updatePriority +
+ ", canInstallFlexibleUpdate=" + canInstallFlexibleUpdate +
+ ", canInstallImmediateUpdate=" + canInstallImmediateUpdate +
+ ", clientVersionStalenessDays=" + clientVersionStalenessDays +
+ ", exception=" + exception +
+ '}';
}
/**
diff --git a/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInstallState.java b/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInstallState.java
index 0cba398..78cc027 100644
--- a/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInstallState.java
+++ b/lib/src/main/java/com/bq/appupdateshelper/AppUpdateInstallState.java
@@ -28,43 +28,89 @@
* Class that contains information about the app update installation state.
*/
public class AppUpdateInstallState {
- @NonNull private final Status status;
- @NonNull private final ErrorCode errorCode;
+ static final long BYTES_UNKNOWN = 0;
+ static final long PROGRESS_UNKNOWN = 0;
- AppUpdateInstallState(@NonNull Status status, @NonNull ErrorCode errorCode) {
+ @NonNull
+ private final Status status;
+ @NonNull
+ private final ErrorCode errorCode;
+ private final long bytesDownloaded;
+ private final long totalBytesToDownload;
+ private final float downloadProgress;
+
+ AppUpdateInstallState(@NonNull Status status,
+ @NonNull ErrorCode errorCode,
+ long bytesDownloaded,
+ long totalBytesToDownload) {
this.status = status;
this.errorCode = errorCode;
+ this.bytesDownloaded = bytesDownloaded;
+ this.totalBytesToDownload = totalBytesToDownload;
+ if (status == Status.DOWNLOADED) {
+ this.downloadProgress = 100f;
+ } else if (totalBytesToDownload <= BYTES_UNKNOWN) {
+ this.downloadProgress = PROGRESS_UNKNOWN;
+ } else {
+ this.downloadProgress = bytesDownloaded * 100f / totalBytesToDownload;
+ }
}
AppUpdateInstallState(@NonNull InstallState state) {
- this(Status.from(state), ErrorCode.from(state));
+ this(Status.from(state),
+ ErrorCode.from(state),
+ state.bytesDownloaded(),
+ state.totalBytesToDownload());
}
- @NonNull public Status getStatus() {
+ @NonNull
+ public Status getStatus() {
return status;
}
- @NonNull public ErrorCode getErrorCode() {
+ @NonNull
+ public ErrorCode getErrorCode() {
return errorCode;
}
- @Override public boolean equals(Object o) {
+ public long getBytesDownloaded() {
+ return bytesDownloaded;
+ }
+
+ public long getTotalBytesToDownload() {
+ return totalBytesToDownload;
+ }
+
+ public float getDownloadProgress() {
+ return downloadProgress;
+ }
+
+ @Override
+ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AppUpdateInstallState that = (AppUpdateInstallState) o;
- return status == that.status &&
- errorCode == that.errorCode;
+ return bytesDownloaded == that.bytesDownloaded &&
+ totalBytesToDownload == that.totalBytesToDownload &&
+ Float.compare(that.downloadProgress, downloadProgress) == 0 &&
+ status == that.status &&
+ errorCode == that.errorCode;
}
- @Override public int hashCode() {
- return Objects.hash(status, errorCode);
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, errorCode, bytesDownloaded, totalBytesToDownload, downloadProgress);
}
- @NonNull @Override public String toString() {
+ @Override
+ public String toString() {
return "AppUpdateInstallState{" +
- "status=" + status +
- ", errorCode=" + errorCode +
- '}';
+ "status=" + status +
+ ", errorCode=" + errorCode +
+ ", bytesDownloaded=" + bytesDownloaded +
+ ", totalBytesToDownload=" + totalBytesToDownload +
+ ", downloadProgress=" + downloadProgress +
+ '}';
}
/**
@@ -127,37 +173,37 @@ static Status from(@NonNull InstallState state) {
/**
* Specific enum related to an app install error code. It will always be present in {@link AppUpdateInstallState}
- * (even in non-error cases, then the value of this field will be NO_ERROR or NO_ERROR_PARTIALLY_ALLOWED)
+ * (even in non-error cases, then the value of this field will be NO_ERROR)
*
* The enums represent the following states:
*
* - NO_ERROR: No error occurred; all types of update flow are allowed.
- * - NO_ERROR_PARTIALLY_ALLOWED: No error occurred; only some types of update flow are allowed, while others are forbidden.
* - ERROR_UNKNOWN: An unknown error occurred.
- * - ERROR_API_NOT_AVAILABLE: The API is not available on this device.
+ * - ERROR_API_NOT_AVAILABLE: The API is not available on this device (such as when the device is not supported).
* - ERROR_INVALID_REQUEST: The request that was sent by the app is malformed.
* - ERROR_INSTALL_UNAVAILABLE: The install is unavailable to this user or device.
* - ERROR_INSTALL_NOT_ALLOWED: The download/install is not allowed, due to the current device state (e.g. low battery, low disk space…)
* - ERROR_DOWNLOAD_NOT_PRESENT: The install/update has not been (fully) downloaded yet.
+ * - ERROR_APP_NOT_OWNED: The user hasn't acquired the app via Play
+ * - ERROR_PLAY_STORE_NOT_FOUND: The Play Store app is either not installed or not the official version.
* - ERROR_INTERNAL_ERROR: An internal error happened in the Play Store.
*/
public enum ErrorCode {
NO_ERROR,
- NO_ERROR_PARTIALLY_ALLOWED,
ERROR_UNKNOWN,
ERROR_API_NOT_AVAILABLE,
ERROR_INVALID_REQUEST,
ERROR_INSTALL_UNAVAILABLE,
ERROR_INSTALL_NOT_ALLOWED,
ERROR_DOWNLOAD_NOT_PRESENT,
+ ERROR_APP_NOT_OWNED,
+ ERROR_PLAY_STORE_NOT_FOUND,
ERROR_INTERNAL_ERROR;
static ErrorCode from(InstallState state) {
switch (state.installErrorCode()) {
case InstallErrorCode.NO_ERROR:
return ErrorCode.NO_ERROR;
- case InstallErrorCode.NO_ERROR_PARTIALLY_ALLOWED:
- return ErrorCode.NO_ERROR_PARTIALLY_ALLOWED;
case InstallErrorCode.ERROR_API_NOT_AVAILABLE:
return ErrorCode.ERROR_API_NOT_AVAILABLE;
case InstallErrorCode.ERROR_INVALID_REQUEST:
@@ -168,20 +214,25 @@ static ErrorCode from(InstallState state) {
return ErrorCode.ERROR_INSTALL_NOT_ALLOWED;
case InstallErrorCode.ERROR_DOWNLOAD_NOT_PRESENT:
return ErrorCode.ERROR_DOWNLOAD_NOT_PRESENT;
+ case InstallErrorCode.ERROR_APP_NOT_OWNED:
+ return ErrorCode.ERROR_APP_NOT_OWNED;
+ case InstallErrorCode.ERROR_PLAY_STORE_NOT_FOUND:
+ return ErrorCode.ERROR_PLAY_STORE_NOT_FOUND;
case InstallErrorCode.ERROR_INTERNAL_ERROR:
return ErrorCode.ERROR_INTERNAL_ERROR;
case InstallErrorCode.ERROR_UNKNOWN:
+ //noinspection deprecation
+ case InstallErrorCode.NO_ERROR_PARTIALLY_ALLOWED:
default:
return ErrorCode.ERROR_UNKNOWN;
}
}
- @InstallErrorCode int getValue() {
+ @InstallErrorCode
+ int getValue() {
switch (this) {
case NO_ERROR:
return InstallErrorCode.NO_ERROR;
- case NO_ERROR_PARTIALLY_ALLOWED:
- return InstallErrorCode.NO_ERROR_PARTIALLY_ALLOWED;
case ERROR_API_NOT_AVAILABLE:
return InstallErrorCode.ERROR_API_NOT_AVAILABLE;
case ERROR_INVALID_REQUEST:
@@ -192,6 +243,10 @@ static ErrorCode from(InstallState state) {
return InstallErrorCode.ERROR_INSTALL_NOT_ALLOWED;
case ERROR_DOWNLOAD_NOT_PRESENT:
return InstallErrorCode.ERROR_DOWNLOAD_NOT_PRESENT;
+ case ERROR_APP_NOT_OWNED:
+ return InstallErrorCode.ERROR_APP_NOT_OWNED;
+ case ERROR_PLAY_STORE_NOT_FOUND:
+ return InstallErrorCode.ERROR_PLAY_STORE_NOT_FOUND;
case ERROR_INTERNAL_ERROR:
return InstallErrorCode.ERROR_INTERNAL_ERROR;
case ERROR_UNKNOWN:
diff --git a/lib/src/main/java/com/bq/appupdateshelper/AppUpdatesHelper.java b/lib/src/main/java/com/bq/appupdateshelper/AppUpdatesHelper.java
index f8c5bcd..84e7f6e 100644
--- a/lib/src/main/java/com/bq/appupdateshelper/AppUpdatesHelper.java
+++ b/lib/src/main/java/com/bq/appupdateshelper/AppUpdatesHelper.java
@@ -22,6 +22,7 @@
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
+import android.view.View;
import com.google.android.play.core.appupdate.AppUpdateInfo;
import com.google.android.play.core.appupdate.AppUpdateManager;
@@ -33,6 +34,7 @@
import com.google.android.play.core.tasks.Task;
import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
import static android.app.Activity.RESULT_CANCELED;
import static com.google.android.play.core.install.model.ActivityResult.RESULT_IN_APP_UPDATE_FAILED;
@@ -41,14 +43,14 @@
* Helper class used to simplify the use of the In-App Updates library from Google.
*
* Its use is as follows:
- * - Register it using {@link #startListening(InstallStateListener)}, for example in {@link Activity#onCreate(Bundle)}.
- * - Unregister it using {@link #stopListening()}, for example in {@link Activity#onDestroy()}.
+ * - Register it using {@link #startListening(InstallStateListener)}, for example in {@link Activity#onCreate(Bundle)} or in {@link Fragment#onViewCreated(View, Bundle)}.
+ * - Unregister it using {@link #stopListening()}, for example in {@link Activity#onDestroy()} or in {@link Fragment#onDestroyView()}.
* - Call {@link #onUpdateStatusResult(int, int)} in your Activity's
- * {@link Activity#onActivityResult(int, int, Intent)} so the helper can properly report status
+ * {@link Activity#onActivityResult(int, int, Intent)} or your Fragment's {@link Fragment#onActivityResult(int, int, Intent)} so the helper can properly report status
* updates.
* - When you want to check if there are any updates, call {@link #getAppUpdateInfo(GetUpdateInfoListener)}.
- * - When you want to perform an update, call {@link #startImmediateUpdate(Activity)}
- * or {@link #startFlexibleUpdate(Activity)} after successfully receiving an update via
+ * - When you want to perform an update, call {@link #startImmediateUpdate(Activity)}/{@link #startImmediateUpdate(Fragment)}
+ * or {@link #startFlexibleUpdate(Activity)}/{@link #startFlexibleUpdate(Fragment)} after successfully receiving an update via
* {@link #getAppUpdateInfo(GetUpdateInfoListener)}.
*/
@SuppressWarnings("JavadocReference")
@@ -90,7 +92,8 @@ public void startListening(@NonNull final InstallStateListener installStateListe
this.isListening = true;
this.installStateListener = installStateListener;
this.installStateUpdatedListener = new InstallStateUpdatedListener() {
- @Override public void onStateUpdate(InstallState installState) {
+ @Override
+ public void onStateUpdate(InstallState installState) {
AppUpdateInstallState state = new AppUpdateInstallState(installState);
Log.d(TAG, "Update status result: " + state.toString());
@@ -124,7 +127,8 @@ public void getAppUpdateInfo(@NonNull final GetUpdateInfoListener getUpdateInfoL
final Task appUpdateInfoTask = manager.getAppUpdateInfo();
appUpdateInfoTask.addOnCompleteListener(new OnCompleteListener() {
- @Override public void onComplete(Task task) {
+ @Override
+ public void onComplete(Task task) {
Exception exception = null;
if (task.isSuccessful()) {
appUpdateInfo = task.getResult();
@@ -151,17 +155,44 @@ public void getAppUpdateInfo(@NonNull final GetUpdateInfoListener getUpdateInfoL
public void startImmediateUpdate(@NonNull Activity activity) {
if (!isListening)
throw new IllegalStateException("You must call startListening() " +
- "before requesting an immediate update");
+ "before requesting an immediate update");
if (appUpdateInfo == null)
throw new IllegalStateException("You must call getAppUpdateInfo() " +
- "with a successful response before requesting an immediate update");
+ "with a successful response before requesting an immediate update");
try {
manager.startUpdateFlowForResult(
- appUpdateInfo,
- AppUpdateType.IMMEDIATE,
- activity,
- IMMEDIATE_UPDATE_REQUEST_CODE);
+ appUpdateInfo,
+ AppUpdateType.IMMEDIATE,
+ activity,
+ IMMEDIATE_UPDATE_REQUEST_CODE);
+ } catch (IntentSender.SendIntentException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Starts an immediate update.
+ * It will receive callbacks in {@link InstallStateListener}.
+ *
+ * The method must only be called after calling {@link #startListening(InstallStateListener)}.
+ *
+ * @param activity The {@link Fragment} to link to the update.
+ */
+ public void startImmediateUpdate(@NonNull Fragment fragment) {
+ if (!isListening)
+ throw new IllegalStateException("You must call startListening() " +
+ "before requesting an immediate update");
+ if (appUpdateInfo == null)
+ throw new IllegalStateException("You must call getAppUpdateInfo() " +
+ "with a successful response before requesting an immediate update");
+
+ try {
+ manager.startUpdateFlowForResult(
+ appUpdateInfo,
+ AppUpdateType.IMMEDIATE,
+ fragment::startIntentSenderForResult,
+ IMMEDIATE_UPDATE_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
@@ -179,17 +210,45 @@ public void startImmediateUpdate(@NonNull Activity activity) {
public void startFlexibleUpdate(@NonNull Activity activity) {
if (!isListening)
throw new IllegalStateException("You must call startListening() " +
- "before requesting a flexible update");
+ "before requesting a flexible update");
+ if (appUpdateInfo == null)
+ throw new IllegalStateException("You must call getAppUpdateInfo() " +
+ "with a successful response before requesting a flexible update");
+
+ try {
+ manager.startUpdateFlowForResult(
+ appUpdateInfo,
+ AppUpdateType.FLEXIBLE,
+ activity,
+ FLEXIBLE_UPDATE_REQUEST_CODE);
+ } catch (IntentSender.SendIntentException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Starts a flexible update.
+ * It will receive callbacks in {@link InstallStateListener}.
+ *
+ * The method must only be called after calling {@link #startListening(InstallStateListener)}
+ * and {@link #getAppUpdateInfo(GetUpdateInfoListener)}.
+ *
+ * @param activity The {@link Fragment} to link to the update.
+ */
+ public void startFlexibleUpdate(@NonNull Fragment fragment) {
+ if (!isListening)
+ throw new IllegalStateException("You must call startListening() " +
+ "before requesting a flexible update");
if (appUpdateInfo == null)
throw new IllegalStateException("You must call getAppUpdateInfo() " +
- "with a successful response before requesting a flexible update");
+ "with a successful response before requesting a flexible update");
try {
manager.startUpdateFlowForResult(
- appUpdateInfo,
- AppUpdateType.FLEXIBLE,
- activity,
- FLEXIBLE_UPDATE_REQUEST_CODE);
+ appUpdateInfo,
+ AppUpdateType.FLEXIBLE,
+ fragment::startIntentSenderForResult,
+ FLEXIBLE_UPDATE_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
@@ -209,31 +268,34 @@ public void onUpdateStatusResult(int requestCode, int resultCode) {
case RESULT_CANCELED:
if (requestCode == IMMEDIATE_UPDATE_REQUEST_CODE)
state = new AppUpdateInstallState(
- AppUpdateInstallState.Status.DENIED,
- AppUpdateInstallState.ErrorCode.ERROR_INSTALL_NOT_ALLOWED
+ AppUpdateInstallState.Status.DENIED,
+ AppUpdateInstallState.ErrorCode.ERROR_INSTALL_NOT_ALLOWED,
+ AppUpdateInstallState.BYTES_UNKNOWN,
+ AppUpdateInstallState.BYTES_UNKNOWN
);
else
state = new AppUpdateInstallState(
- AppUpdateInstallState.Status.CANCELED,
- AppUpdateInstallState.ErrorCode.ERROR_INSTALL_NOT_ALLOWED);
+ AppUpdateInstallState.Status.CANCELED,
+ AppUpdateInstallState.ErrorCode.ERROR_INSTALL_NOT_ALLOWED,
+ AppUpdateInstallState.BYTES_UNKNOWN,
+ AppUpdateInstallState.BYTES_UNKNOWN);
break;
case RESULT_IN_APP_UPDATE_FAILED:
// We don't know why the update failed, so return an unknown error
state = new AppUpdateInstallState(
- AppUpdateInstallState.Status.FAILED,
- AppUpdateInstallState.ErrorCode.ERROR_UNKNOWN
+ AppUpdateInstallState.Status.FAILED,
+ AppUpdateInstallState.ErrorCode.ERROR_UNKNOWN,
+ AppUpdateInstallState.BYTES_UNKNOWN,
+ AppUpdateInstallState.BYTES_UNKNOWN
);
break;
default:
- // If everything goes well, check which updates are allowed and use the proper (no) error code
- boolean areAllUpdateFlowsAllowed = appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
- && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE);
-
+ // If everything goes well, do stuff
state = new AppUpdateInstallState(
- AppUpdateInstallState.Status.UPDATE_ACCEPTED,
- areAllUpdateFlowsAllowed
- ? AppUpdateInstallState.ErrorCode.NO_ERROR
- : AppUpdateInstallState.ErrorCode.NO_ERROR_PARTIALLY_ALLOWED
+ AppUpdateInstallState.Status.UPDATE_ACCEPTED,
+ AppUpdateInstallState.ErrorCode.NO_ERROR,
+ AppUpdateInstallState.BYTES_UNKNOWN,
+ AppUpdateInstallState.BYTES_UNKNOWN
);
break;
}
@@ -256,10 +318,10 @@ public void onUpdateStatusResult(int requestCode, int resultCode) {
public void completeUpdate() {
if (!isListening)
throw new IllegalStateException("You must call startListening() " +
- "before completing an update");
+ "before completing an update");
if (appUpdateInfo == null)
throw new IllegalStateException("You must call getAppUpdateInfo() " +
- "before completing an update");
+ "before completing an update");
manager.completeUpdate();
}