COVIDSafe code from version 1.11.0 (#26)

This commit is contained in:
COVIDSafe Support 2020-09-23 08:46:31 +10:00 committed by GitHub
parent 12c06add12
commit ae18438d17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
139 changed files with 8039 additions and 6010 deletions

View file

@ -1,19 +1,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
buildscript { buildscript {
repositories { repositories {
google() google()
jcenter()
} }
} }
@ -27,26 +22,22 @@ def getGitHash = { ->
} }
android { android {
compileSdkVersion 29 compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion "29.0.3" buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig { defaultConfig {
applicationId "au.gov.health.covidsafe" applicationId "au.gov.health.covidsafe"
minSdkVersion 21 minSdkVersion rootProject.ext.minSdkVersion
/* /*
TargetSdk is currently set to 28 because we are using a greylisted api in SDK 29, in order to fix a BLE vulnerability TargetSdk is currently set to 28 because we are using a greylisted api in SDK 29, in order to fix a BLE vulnerability
Before you increase the targetSdkVersion make sure that all its usage are still working Before you increase the targetSdkVersion make sure that all its usage are still working
*/ */
targetSdkVersion 28 targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 54 versionCode 72
versionName "1.0.54" versionName "1.11.0"
buildConfigField "String", "GITHASH", "\"${getGitHash()}\"" buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
@ -105,9 +96,9 @@ android {
String ssid = STAGING_SERVICE_UUID String ssid = STAGING_SERVICE_UUID
versionNameSuffix "-debug-${getGitHash()}-${ssid.substring(ssid.length() - 5,ssid.length() - 1 )}" versionNameSuffix "-debug-${getGitHash()}-${ssid.substring(ssid.length() - 5, ssid.length() - 1)}"
resValue "string", "app_name", "COVIDSafe Debug" resValue "string", "app_name", "COVIDSafe Debug"
applicationIdSuffix "debug" applicationIdSuffix ".debug"
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
@ -119,15 +110,12 @@ android {
buildConfigField "String", "IOS_BACKGROUND_UUID", STAGING_BACKGROUND_IOS_SERVICE_UUID buildConfigField "String", "IOS_BACKGROUND_UUID", STAGING_BACKGROUND_IOS_SERVICE_UUID
buildConfigField "String", "ENCRYPTION_PUBLIC_KEY", STAGING_ENCRYPTION_PUBLIC_KEY buildConfigField "String", "ENCRYPTION_PUBLIC_KEY", STAGING_ENCRYPTION_PUBLIC_KEY
// Retrieve bluetooth ssid from staging's strings.xml // Retrieve bluetooth ssid from staging's strings.xml
String ssid = STAGING_SERVICE_UUID String ssid = STAGING_SERVICE_UUID
versionNameSuffix "-beta-${getGitHash()}-${ssid.substring(ssid.length() - 5,ssid.length() - 1 )}" versionNameSuffix "-beta-${getGitHash()}-${ssid.substring(ssid.length() - 5, ssid.length() - 1)}"
debuggable false debuggable false
applicationIdSuffix "beta" applicationIdSuffix ".beta"
resValue "string", "app_name", "COVIDSafe beta" resValue "string", "app_name", "COVIDSafe beta"
lintOptions { lintOptions {
@ -137,6 +125,7 @@ android {
matchingFallbacks = ['release'] matchingFallbacks = ['release']
signingConfig signingConfigs.staging signingConfig signingConfigs.staging
} }
release { release {
buildConfigField "String", "BLE_SSID", PRODUCTION_SERVICE_UUID buildConfigField "String", "BLE_SSID", PRODUCTION_SERVICE_UUID
@ -145,8 +134,6 @@ android {
buildConfigField "String", "IOS_BACKGROUND_UUID", PRODUCTION_BACKGROUND_IOS_SERVICE_UUID buildConfigField "String", "IOS_BACKGROUND_UUID", PRODUCTION_BACKGROUND_IOS_SERVICE_UUID
buildConfigField "String", "ENCRYPTION_PUBLIC_KEY", PRODUCTION_ENCRYPTION_PUBLIC_KEY buildConfigField "String", "ENCRYPTION_PUBLIC_KEY", PRODUCTION_ENCRYPTION_PUBLIC_KEY
debuggable false debuggable false
jniDebuggable false jniDebuggable false
renderscriptDebuggable false renderscriptDebuggable false
@ -161,10 +148,13 @@ android {
abortOnError false abortOnError false
} }
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
} }
buildFeatures {
dataBinding true
}
sourceSets { sourceSets {
staging { staging {
java.srcDirs = ['src/debug/java'] java.srcDirs = ['src/debug/java']
@ -185,45 +175,31 @@ android {
packagingOptions { packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module' exclude 'META-INF/atomicfu.kotlin_module'
} }
} }
repositories {
jcenter()
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation project(":feedback-android") implementation project(":feedback-android")
// kotlin // kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
def kotlin_coroutines_version = "1.3.5"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
//androidx //androidx
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0-alpha01' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'pub.devrel:easypermissions:3.0.0' implementation 'pub.devrel:easypermissions:3.0.0'
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.code.gson:gson:2.8.6'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// room // room
def room_version = "2.2.5"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
// http
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
// rx // rx
@ -233,11 +209,10 @@ dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
//cardview implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version"
implementation 'androidx.cardview:cardview:1.0.0'
//Lottie //Lottie
implementation 'com.airbnb.android:lottie:3.4.0' implementation 'com.airbnb.android:lottie:3.4.1'
implementation 'com.google.guava:guava:28.2-android' implementation 'com.google.guava:guava:28.2-android'
implementation "androidx.collection:collection:1.1.0" implementation "androidx.collection:collection:1.1.0"
@ -251,7 +226,9 @@ dependencies {
// Firebase Cloud Messaging // Firebase Cloud Messaging
implementation 'com.google.firebase:firebase-messaging:20.2.4' implementation 'com.google.firebase:firebase-messaging:20.2.4'
//Unit testing
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "androidx.room:room-testing:2.2.5" androidTestImplementation "androidx.room:room-testing:2.2.5"
} }

View file

@ -3,7 +3,7 @@
package="au.gov.health.covidsafe"> package="au.gov.health.covidsafe">
<application <application
android:name=".TracerApp" android:name=".app.TracerApp"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"

View file

@ -12,14 +12,18 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.extensions.fromHtml
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordStorage import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordStorage
import au.gov.health.covidsafe.streetpass.view.RecordViewModel import au.gov.health.covidsafe.streetpass.view.RecordViewModel
import au.gov.health.covidsafe.ui.utils.Utils
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.database_peek.* import kotlinx.android.synthetic.main.database_peek.*
import kotlinx.android.synthetic.main.database_peek.home_push_notification_token
import java.io.File import java.io.File
private const val TAG = "PeekActivity" private const val TAG = "PeekActivity"
@ -42,8 +46,8 @@ class PeekActivity : AppCompatActivity() {
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
val dividerItemDecoration = DividerItemDecoration( val dividerItemDecoration = DividerItemDecoration(
recyclerView.context, recyclerView.context,
layoutManager.orientation layoutManager.orientation
) )
recyclerView.addItemDecoration(dividerItemDecoration) recyclerView.addItemDecoration(dividerItemDecoration)
@ -69,28 +73,28 @@ class PeekActivity : AppCompatActivity() {
val builder = AlertDialog.Builder(this) val builder = AlertDialog.Builder(this)
builder builder
.setTitle("Are you sure?") .setTitle("Are you sure?")
.setCancelable(false) .setCancelable(false)
.setMessage("Deleting the DB records is irreversible") .setMessage("Deleting the DB records is irreversible")
.setPositiveButton("DELETE") { dialog, which -> .setPositiveButton("DELETE") { dialog, _ ->
Observable.create<Boolean> { Observable.create<Boolean> {
StreetPassRecordStorage(this).nukeDb() StreetPassRecordStorage(this).nukeDb()
it.onNext(true) it.onNext(true)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { result ->
Toast.makeText(this, "Database nuked: $result", Toast.LENGTH_SHORT)
.show()
view.isEnabled = true
dialog.cancel()
} }
} .observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { result ->
Toast.makeText(this, "Database nuked: $result", Toast.LENGTH_SHORT)
.show()
view.isEnabled = true
dialog.cancel()
}
}
.setNegativeButton("DON'T DELETE") { dialog, which -> .setNegativeButton("DON'T DELETE") { dialog, _ ->
view.isEnabled = true view.isEnabled = true
dialog.cancel() dialog.cancel()
} }
val dialog: AlertDialog = builder.create() val dialog: AlertDialog = builder.create()
dialog.show() dialog.show()
@ -100,12 +104,12 @@ class PeekActivity : AppCompatActivity() {
shareDatabase.setOnClickListener { shareDatabase.setOnClickListener {
val authority = "${BuildConfig.APPLICATION_ID}.fileprovider" val authority = "${BuildConfig.APPLICATION_ID}.fileprovider"
val databaseFilePath= getDatabasePath("record_database").absolutePath val databaseFilePath = getDatabasePath("record_database").absolutePath
val databaseFile = File(databaseFilePath) val databaseFile = File(databaseFilePath)
CentralLog.d(TAG, "authority = $authority, databaseFilePath = $databaseFilePath") CentralLog.d(TAG, "authority = $authority, databaseFilePath = $databaseFilePath")
if(databaseFile.exists()) { if (databaseFile.exists()) {
CentralLog.d(TAG, "databaseFile.length = ${databaseFile.length()}") CentralLog.d(TAG, "databaseFile.length = ${databaseFile.length()}")
FileProvider.getUriForFile( FileProvider.getUriForFile(
@ -123,7 +127,9 @@ class PeekActivity : AppCompatActivity() {
} }
} }
if(!BuildConfig.DEBUG) { showPushTokenOnDebugBuild()
if (!BuildConfig.DEBUG) {
start.visibility = View.GONE start.visibility = View.GONE
stop.visibility = View.GONE stop.visibility = View.GONE
delete.visibility = View.GONE delete.visibility = View.GONE
@ -131,6 +137,20 @@ class PeekActivity : AppCompatActivity() {
} }
private fun showPushTokenOnDebugBuild() {
if (BuildConfig.DEBUG) {
val token = Preference.getFirebaseInstanceID(this)
home_push_notification_token.run {
visibility = View.VISIBLE
val tokenText = "<b>Firebase Push Token:</b> $token"
text = fromHtml(tokenText)
}
} else {
home_push_notification_token.visibility = View.GONE
}
}
private var timePeriod: Int = 0 private var timePeriod: Int = 0
private fun nextTimePeriod(): Int { private fun nextTimePeriod(): Int {

View file

@ -8,6 +8,7 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecord import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecord
import au.gov.health.covidsafe.streetpass.view.StreetPassRecordViewModel import au.gov.health.covidsafe.streetpass.view.StreetPassRecordViewModel
import au.gov.health.covidsafe.ui.utils.Utils
class RecordListAdapter internal constructor(context: Context) : class RecordListAdapter internal constructor(context: Context) :

View file

@ -19,7 +19,7 @@
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application <application
android:name="au.gov.health.covidsafe.TracerApp" android:name="au.gov.health.covidsafe.app.TracerApp"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
@ -30,7 +30,7 @@
tools:replace="android:supportsRtl"> tools:replace="android:supportsRtl">
<activity <activity
android:name="au.gov.health.covidsafe.SplashActivity" android:name="au.gov.health.covidsafe.ui.splash.SplashActivity"
android:configChanges="keyboardHidden"> android:configChanges="keyboardHidden">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -47,17 +47,21 @@
android:windowSoftInputMode="adjustPan" /> android:windowSoftInputMode="adjustPan" />
<activity android:name="au.gov.health.covidsafe.WebViewActivity" /> <activity android:name="au.gov.health.covidsafe.ui.webview.WebViewActivity" />
<activity android:name="au.gov.health.covidsafe.DeviceNameChangePromptActivity" /> <activity android:name="au.gov.health.covidsafe.ui.devicename.DeviceNameChangePromptActivity" />
<activity android:name="au.gov.health.covidsafe.InternetConnectionIssuesActivity" /> <activity android:name="au.gov.health.covidsafe.ui.connection.InternetConnectionIssuesActivity" />
<activity <activity
android:name="au.gov.health.covidsafe.HomeActivity" android:name="au.gov.health.covidsafe.HomeActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan" > android:windowSoftInputMode="adjustPan">
<intent-filter> <intent-filter>
<action android:name="au.gov.health.covidsafe.UPGRADE_APP" /> <action android:name="au.gov.health.covidsafe.UPGRADE_APP" />
<category android:name="android.intent.category.DEFAULT"/> <action android:name="au.gov.health.covidsafe.POSSIBLE_ISSUE" />
<action android:name="au.gov.health.covidsafe.NO_CHECKIN" />
<action android:name="au.gov.health.covidsafe.POSSIBLE_ENCOUNTER_ERROR" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -72,8 +76,6 @@
android:name="au.gov.health.covidsafe.services.BluetoothMonitoringService" android:name="au.gov.health.covidsafe.services.BluetoothMonitoringService"
android:foregroundServiceType="location" /> android:foregroundServiceType="location" />
<service android:name="au.gov.health.covidsafe.services.SensorMonitoringService" />
<receiver android:name="au.gov.health.covidsafe.receivers.UpgradeReceiver"> <receiver android:name="au.gov.health.covidsafe.receivers.UpgradeReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
@ -83,7 +85,7 @@
<receiver android:name="au.gov.health.covidsafe.receivers.PrivacyCleanerReceiver" /> <receiver android:name="au.gov.health.covidsafe.receivers.PrivacyCleanerReceiver" />
<service <service
android:name="au.gov.health.covidsafe.services.CovidFirebaseMessagingService" android:name="au.gov.health.covidsafe.notifications.CovidFirebaseMessagingService"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" /> <action android:name="com.google.firebase.MESSAGING_EVENT" />
@ -92,8 +94,8 @@
<service <service
android:name="au.gov.health.covidsafe.scheduler.GetMessagesJobSchedulerService" android:name="au.gov.health.covidsafe.scheduler.GetMessagesJobSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"
android:exported="true"/> android:permission="android.permission.BIND_JOB_SERVICE" />
</application> </application>

View file

@ -3,24 +3,96 @@ package au.gov.health.covidsafe
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.Utils.checkInternetConnectionToGoogle import androidx.lifecycle.MutableLiveData
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.response.Message import au.gov.health.covidsafe.networking.response.Message
import au.gov.health.covidsafe.networking.response.MessagesResponse import au.gov.health.covidsafe.networking.response.MessagesResponse
import au.gov.health.covidsafe.notifications.NotificationBuilder
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.scheduler.GetMessagesScheduler import au.gov.health.covidsafe.scheduler.GetMessagesScheduler
import au.gov.health.covidsafe.ui.home.HomeFragment import au.gov.health.covidsafe.ui.devicename.DeviceNameChangePromptActivity
import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.utils.NetworkConnectionCheck
import com.google.android.gms.tasks.OnCompleteListener import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.FirebaseInstanceId
private const val TAG = "HomeActivity" private const val TAG = "HomeActivity"
class HomeActivity : FragmentActivity() { class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectionListener {
private fun checkInternetConnection() {
checkInternetConnectionToGoogle { var isAppUpdateAvailableLiveData = MutableLiveData<Boolean>()
HomeFragment.instanceWeakRef?.get()?.updateConnectionTile(it) var appUpdateAvailableMessageResponseLiveData = MutableLiveData<MessagesResponse>()
var isWindowFocusChangeLiveData = MutableLiveData<Boolean>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CentralLog.d(TAG, "onCreate() intent.action = ${intent.action}")
NotificationBuilder.clearPossibleIssueNotificationCheck()
NotificationBuilder.handlePushNotification(this, intent.action)
setContentView(R.layout.activity_home)
Utils.startBluetoothMonitoringService(this)
//Get Firebase Token
getInstanceID()
NetworkConnectionCheck.addNetworkChangedListener(this, this)
}
override fun onResume() {
super.onResume()
checkAndShowDeviceNameChangePrompt()
checkAndUpdateHealthStatus()
}
private fun checkAndShowDeviceNameChangePrompt() {
if (!Preference.isDeviceNameChangePromptDisplayed(this)) {
Intent(this, DeviceNameChangePromptActivity::class.java).also {
startActivity(it)
}
Preference.setDeviceNameChangePromptDisplayed(this)
} }
} }
private fun checkAndUpdateHealthStatus() {
GetMessagesScheduler.scheduleGetMessagesJob {
val isAppWithLatestVersion = it.messages.isNullOrEmpty()
isAppUpdateAvailableLiveData.postValue(isAppWithLatestVersion)
CentralLog.d(TAG, "isAppWithLatestVersion: $it")
if (!isAppWithLatestVersion) {
val title = getString(R.string.update_available_title)
val body = getString(R.string.update_available_message_android)
appUpdateAvailableMessageResponseLiveData.postValue(MessagesResponse(
listOf(
Message(
title,
body,
"https://play.google.com/store/apps/details?id=au.gov.health.covidsafe")
)
))
}
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
CentralLog.d(TAG, "onNewIntent = ${intent?.action})")
NotificationBuilder.handlePushNotification(this, intent?.action)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
CentralLog.d(TAG, "onWindowFocusChanged(hasFocus = $hasFocus)")
isWindowFocusChangeLiveData.postValue(hasFocus)
super.onWindowFocusChanged(hasFocus)
}
/** /**
* Provides notification support to inform users to update to the latest version of the app. * Provides notification support to inform users to update to the latest version of the app.
* This feature will also allow for troubleshooting of the app in the future and allow for * This feature will also allow for troubleshooting of the app in the future and allow for
@ -46,76 +118,12 @@ class HomeActivity : FragmentActivity() {
}) })
} }
override fun onCreate(savedInstanceState: Bundle?) { private var previousInternetConnection = true
super.onCreate(savedInstanceState) override fun onNetworkStatusChanged(isAvailable: Boolean) {
if (!previousInternetConnection && isAvailable) {
CentralLog.d(TAG, "onCreate() intent.action = ${intent.action}") checkAndUpdateHealthStatus()
}
setContentView(R.layout.activity_home) previousInternetConnection = isAvailable
Utils.startBluetoothMonitoringService(this)
// messages API related
getInstanceID()
} }
override fun onResume() {
super.onResume()
CentralLog.d(TAG, "onResume() intent.action = ${intent.action}")
if (intent.action == "au.gov.health.covidsafe.UPGRADE_APP") {
Utils.gotoPlayStore(this)
}
if (!Preference.isDeviceNameChangePromptDisplayed(this)) {
Intent(this, DeviceNameChangePromptActivity::class.java).also {
startActivity(it)
}
Preference.setDeviceNameChangePromptDisplayed(this)
}
checkInternetConnection()
GetMessagesScheduler.scheduleGetMessagesJob {
// HomeFragment.instanceWeakRef?.get()?.updateMessageTiles(it)
// HomeFragment.instanceWeakRef?.get()?.updateMessageTiles(MessagesResponse(
// listOf(
// Message(
// "Update available",
// "Weve been making improvements to COVIDSafe. Update via Google Play Store.",
// "market://details?id=au.gov.health.covidsafe"),
// Message(
// "Update available",
// "Weve been making improvements to COVIDSafe. Update via Google Play Store.",
// "https://play.google.com/store/apps/details?id=au.gov.health.covidsafe")
// )
// ))
if (!it.messages.isNullOrEmpty()) {
val title = getString(R.string.update_available_title)
val body = getString(R.string.update_available_message_android)
HomeFragment.instanceWeakRef?.get()?.updateMessageTiles(MessagesResponse(
listOf(
Message(
title,
body,
"https://play.google.com/store/apps/details?id=au.gov.health.covidsafe")
)
))
}
}
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
CentralLog.d(TAG, "onWindowFocusChanged(hasFocus = $hasFocus)")
HomeFragment.instanceWeakRef?.get()?.refreshSetupCompleteOrIncompleteUi()
checkInternetConnection()
super.onWindowFocusChanged(hasFocus)
}
} }

View file

@ -1,12 +1,12 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.app
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import au.gov.health.covidsafe.BuildConfig
import com.atlassian.mobilekit.module.feedback.FeedbackModule import com.atlassian.mobilekit.module.feedback.FeedbackModule
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.scheduler.GetMessagesScheduler
import au.gov.health.covidsafe.services.BluetoothMonitoringService import au.gov.health.covidsafe.services.BluetoothMonitoringService
import au.gov.health.covidsafe.streetpass.CentralDevice import au.gov.health.covidsafe.streetpass.CentralDevice
import au.gov.health.covidsafe.streetpass.PeripheralDevice import au.gov.health.covidsafe.streetpass.PeripheralDevice
@ -23,6 +23,7 @@ class TracerApp : Application() {
companion object { companion object {
private const val TAG = "TracerApp" private const val TAG = "TracerApp"
const val ORG = BuildConfig.ORG const val ORG = BuildConfig.ORG
const val protocolVersion = BuildConfig.PROTOCOL_VERSION const val protocolVersion = BuildConfig.PROTOCOL_VERSION

View file

@ -10,7 +10,7 @@ import android.os.ParcelUuid
import android.util.Base64 import android.util.Base64
import android.util.Base64.decode import android.util.Base64.decode
import au.gov.health.covidsafe.BuildConfig import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.bluetooth.BLEScanner.FilterConstant.APPLE_MANUFACTURER_ID import au.gov.health.covidsafe.bluetooth.BLEScanner.FilterConstant.APPLE_MANUFACTURER_ID
import au.gov.health.covidsafe.bluetooth.BLEScanner.FilterConstant.BACKGROUND_IOS_SERVICE_UUID import au.gov.health.covidsafe.bluetooth.BLEScanner.FilterConstant.BACKGROUND_IOS_SERVICE_UUID
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog

View file

@ -4,8 +4,8 @@ import android.bluetooth.*
import android.bluetooth.BluetoothGatt.GATT_FAILURE import android.bluetooth.BluetoothGatt.GATT_FAILURE
import android.bluetooth.BluetoothGatt.GATT_SUCCESS import android.bluetooth.BluetoothGatt.GATT_SUCCESS
import android.content.Context import android.content.Context
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.streetpass.CentralDevice import au.gov.health.covidsafe.streetpass.CentralDevice
import au.gov.health.covidsafe.streetpass.ConnectionRecord import au.gov.health.covidsafe.streetpass.ConnectionRecord
@ -41,8 +41,7 @@ class GattServer constructor(val context: Context, serviceUUIDString: String) {
BluetoothProfile.STATE_CONNECTED -> { BluetoothProfile.STATE_CONNECTED -> {
CentralLog.i(TAG, "${device?.address} Connected to local GATT server") CentralLog.i(TAG, "${device?.address} Connected to local GATT server")
device?.let { device?.let {
val b = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT) bluetoothManager.getConnectedDevices(BluetoothProfile.GATT).contains(device)
.contains(device)
} }
} }

View file

@ -3,23 +3,33 @@ package au.gov.health.covidsafe.boot
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.scheduler.GetMessagesScheduler
private const val TAG = "StartOnBootReceiver"
class StartOnBootReceiver : BroadcastReceiver() { class StartOnBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BOOT_COMPLETED == intent.action) { if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
CentralLog.d("StartOnBootReceiver", "boot completed received") CentralLog.d(TAG, "boot completed received")
try { try {
CentralLog.d("StartOnBootReceiver", "Attempting to start service") CentralLog.d("StartOnBootReceiver", "Attempting to start service")
Utils.scheduleStartMonitoringService(context, 500) Utils.scheduleStartMonitoringService(context, 500)
checkAndUpdateHealthStatus()
} catch (e: Throwable) { } catch (e: Throwable) {
CentralLog.e("StartOnBootReceiver", e.localizedMessage) CentralLog.e(TAG, e.localizedMessage ?: "Error message is empty:")
e.printStackTrace() e.printStackTrace()
} }
} }
} }
private fun checkAndUpdateHealthStatus() {
GetMessagesScheduler.scheduleGetMessagesJob {
CentralLog.d(TAG, "checkAndUpdateHealthStatus on app reboot completed ")
}
}
} }

View file

@ -11,10 +11,9 @@ import android.os.PowerManager
import android.provider.Settings import android.provider.Settings
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions
import pub.devrel.easypermissions.PermissionRequest import pub.devrel.easypermissions.PermissionRequest
@ -62,6 +61,7 @@ private fun Fragment.requestFineLocationAndCheckBleSupportThenNextPermission(onE
} }
} else { } else {
checkBLESupport() checkBLESupport()
onEndCallback.invoke()
} }
} }

View file

@ -1,10 +1,15 @@
package au.gov.health.covidsafe.extensions package au.gov.health.covidsafe.extensions
import android.content.Context
import android.content.Intent
import android.os.Build
import android.text.Html
import android.text.SpannableString import android.text.SpannableString
import android.text.Spanned import android.text.Spanned
import android.text.style.URLSpan import android.text.style.URLSpan
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
fun TextView.toHyperlink(textToHyperLink: String? = null, onClick: () -> Unit) { fun TextView.toHyperlink(textToHyperLink: String? = null, onClick: () -> Unit) {
@ -27,4 +32,35 @@ fun TextView.toHyperlink(textToHyperLink: String? = null, onClick: () -> Unit) {
onClick.invoke() onClick.invoke()
} }
}
fun Context.shareThisApp() {
val newIntent = Intent(Intent.ACTION_SEND)
newIntent.type = "text/plain"
newIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_this_app_content))
startActivity(Intent.createChooser(newIntent, null))
}
fun Context.getAppVersionNumberDetails(): String {
return getString(R.string.home_version_number, BuildConfig.VERSION_NAME)
}
fun fromHtml(stringData: String): Spanned {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(stringData, Html.FROM_HTML_MODE_COMPACT)
} else {
Html.fromHtml(stringData)
}
}
fun Context.allPermissionsEnabled(): Boolean {
val bluetoothEnabled = isBlueToothEnabled() ?: false
val nonBatteryOptimizationAllowed = isBatteryOptimizationDisabled() ?: true
val locationStatusAllowed = isLocationPermissionAllowed() ?: true
return bluetoothEnabled &&
nonBatteryOptimizationAllowed &&
locationStatusAllowed &&
isLocationEnabledOnDevice()
} }

View file

@ -19,6 +19,8 @@ interface NetworkFactory {
RetrofitServiceGenerator.createService(AwsClient::class.java) RetrofitServiceGenerator.createService(AwsClient::class.java)
} }
private const val USER_AGENT_TAG = "User-Agent"
val okHttpClient: OkHttpClient by lazy { val okHttpClient: OkHttpClient by lazy {
val okHttpClientBuilder = OkHttpClient.Builder() val okHttpClientBuilder = OkHttpClient.Builder()
@ -26,6 +28,15 @@ interface NetworkFactory {
okHttpClientBuilder.addInterceptor(logging) okHttpClientBuilder.addInterceptor(logging)
} }
okHttpClientBuilder.addInterceptor { chain ->
val request = chain.request()
val newRequest = request.newBuilder()
.removeHeader(USER_AGENT_TAG)
.addHeader(USER_AGENT_TAG, getCustomUserAgent())
.build()
chain.proceed(newRequest)
}
// This certificate pinning mechanism is only needed on Android 23 and lower. // This certificate pinning mechanism is only needed on Android 23 and lower.
// For Android 24 and above, the pinning is set up in AndroidManifest.xml // For Android 24 and above, the pinning is set up in AndroidManifest.xml
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
@ -52,6 +63,10 @@ interface NetworkFactory {
okHttpClientBuilder.build() okHttpClientBuilder.build()
} }
private fun getCustomUserAgent(): String = "COVIDSafe/${BuildConfig.VERSION_NAME}" +
" (build:${BuildConfig.VERSION_CODE}; android-${Build.VERSION.SDK_INT}) " + okhttp3.internal.userAgent
} }
} }

View file

@ -0,0 +1,53 @@
package au.gov.health.covidsafe.interactor.usecase
import android.content.Context
import androidx.lifecycle.Lifecycle
import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success
import au.gov.health.covidsafe.interactor.UseCase
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.response.CaseStatisticResponse
import au.gov.health.covidsafe.networking.service.AwsClient
import au.gov.health.covidsafe.preference.Preference
private const val TAG = "GetCaseStatisticsUseCase"
class GetCaseStatisticsUseCase(private val awsClient: AwsClient, lifecycle: Lifecycle, private val context: Context?) : UseCase<CaseStatisticResponse, String>(lifecycle) {
override suspend fun run(params: String): Either<Exception, CaseStatisticResponse> {
val token = Preference.getEncrypterJWTToken(context)
return token?.let { jwtToken ->
try {
CentralLog.d(TAG, "GetCaseStatisticsUseCase run request")
val response = retryRetrofitCall { awsClient.getCaseStatistics("Bearer $jwtToken").execute() }
when {
response?.code() == 200 -> {
response.body()?.let { body ->
CentralLog.d(TAG, "GetCaseStatistics Success: ${body.national}")
Success(body)
} ?: run {
CentralLog.d(TAG, "GetCaseStatistics Invalid response")
Failure(GetCaseStatisticsException.GetGetCaseStatisticsServiceException(response.code()))
}
}
else -> {
CentralLog.d(TAG, "GetCaseStatistics AWSAuthServiceError")
Failure(GetCaseStatisticsException.GetGetCaseStatisticsServiceException(response?.code()))
}
}
} catch (e: Exception) {
Failure(e)
}
} ?: run {
return Failure(Exception())
}
}
}
sealed class GetCaseStatisticsException : Exception() {
class GetGetCaseStatisticsServiceException(val code: Int? = null) : GetCaseStatisticsException()
}

View file

@ -4,8 +4,8 @@ import android.content.Context
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import retrofit2.Response import retrofit2.Response
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.interactor.Either import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success import au.gov.health.covidsafe.interactor.Success
@ -24,8 +24,8 @@ class UpdateBroadcastMessageAndPerformScanWithExponentialBackOff(private val aws
lifecycle: Lifecycle) : UseCase<BroadcastMessageResponse, Void?>(lifecycle) { lifecycle: Lifecycle) : UseCase<BroadcastMessageResponse, Void?>(lifecycle) {
override suspend fun run(params: Void?): Either<Exception, BroadcastMessageResponse> { override suspend fun run(params: Void?): Either<Exception, BroadcastMessageResponse> {
val jwtToken = Preference.getEncrypterJWTToken(context) val token = Preference.getEncrypterJWTToken(context)
return jwtToken?.let { jwtToken -> return token?.let { jwtToken ->
var response = call(jwtToken) var response = call(jwtToken)
var retryCount = 0 var retryCount = 0
while ((response == null || !response.isSuccessful || response.body() == null) && retryCount < RETRIES_LIMIT) { while ((response == null || !response.isSuccessful || response.body() == null) && retryCount < RETRIES_LIMIT) {

View file

@ -2,8 +2,8 @@ package au.gov.health.covidsafe.interactor.usecase
import android.content.Context import android.content.Context
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.interactor.Either import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success import au.gov.health.covidsafe.interactor.Success
@ -26,8 +26,8 @@ class UploadData(private val awsClient: AwsClient,
private val TAG = this.javaClass.simpleName private val TAG = this.javaClass.simpleName
override suspend fun run(params: String): Either<Exception, None> { override suspend fun run(params: String): Either<Exception, None> {
val jwtToken = Preference.getEncrypterJWTToken(context) val token = Preference.getEncrypterJWTToken(context)
return jwtToken?.let { jwtToken -> return token?.let { jwtToken ->
try { try {
val initialUploadResponse = retryRetrofitCall { val initialUploadResponse = retryRetrofitCall {
awsClient.initiateUpload("Bearer $jwtToken", params).execute() awsClient.initiateUpload("Bearer $jwtToken", params).execute()

View file

@ -10,7 +10,7 @@ import android.text.TextPaint
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.view.View import android.view.View
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import java.lang.StringBuilder import java.lang.StringBuilder
import java.util.* import java.util.*
@ -18,11 +18,13 @@ import java.util.*
const val TAG = "LinkBuilder" const val TAG = "LinkBuilder"
private const val PRIVACY_URL = "https://www.health.gov.au/using-our-websites/privacy/privacy-notice-for-covidsafe-app"
private const val DEPARTMENT_OF_HEALTH_URL = "https://www.health.gov.au/" private const val DEPARTMENT_OF_HEALTH_URL = "https://www.health.gov.au/"
private const val HOST_URL = "https://www.covidsafe.gov.au"
private const val HELP_TOPICS_BASE = "https://www.covidsafe.gov.au/help-topics" private const val HELP_TOPICS_BASE = "/help-topics"
private const val HELP_TOPICS_SUFFIX = ".html" private const val PRIVACY_TOPICS_BASE = "/privacy-policy"
private const val TOPICS_EXT_SUFFIX = ".html"
private const val HELP_TOPICS_ENGLISH_PAGE = "" private const val HELP_TOPICS_ENGLISH_PAGE = ""
private const val HELP_TOPICS_S_CHINESE_PAGE = "/zh-hans" private const val HELP_TOPICS_S_CHINESE_PAGE = "/zh-hans"
private const val HELP_TOPICS_T_CHINESE_PAGE = "/zh-hant" private const val HELP_TOPICS_T_CHINESE_PAGE = "/zh-hant"
@ -36,6 +38,7 @@ private const val HELP_TOPICS_TURKISH_PAGE = "/tr"
private const val HELP_TOPICS_ANCHOR_VERIFY_MOBILE_NUMBER_PIN = "#verify-mobile-number-pin" private const val HELP_TOPICS_ANCHOR_VERIFY_MOBILE_NUMBER_PIN = "#verify-mobile-number-pin"
private const val HELP_TOPICS_ANCHOR_BLUETOOTH_PAIRING_REQUEST = "#bluetooth-pairing-request" private const val HELP_TOPICS_ANCHOR_BLUETOOTH_PAIRING_REQUEST = "#bluetooth-pairing-request"
private const val HELP_TOPICS_ANCHOR_LOCATION_PERMISSION_ANDROID = "#location-permission-android"
object LinkBuilder { object LinkBuilder {
@ -47,9 +50,23 @@ object LinkBuilder {
} }
fun getHelpTopicsUrl(): String { fun getHelpTopicsUrl(): String {
val url = buildLocalisedURL(HELP_TOPICS_BASE)
CentralLog.d(TAG, "getHelpTopicsUrl() $url")
return url
}
private fun getPrivacyTopicsUrl(): String {
val url = buildLocalisedURL(PRIVACY_TOPICS_BASE)
CentralLog.d(TAG, "getPrivacyTopicsUrl() $url")
return url
}
private fun buildLocalisedURL(path: String): String {
val localeLanguageTag = Locale.getDefault().toLanguageTag() val localeLanguageTag = Locale.getDefault().toLanguageTag()
val url = HELP_TOPICS_BASE + when { CentralLog.d(TAG, "Locale Language: $localeLanguageTag")
return HOST_URL + path + when {
localeLanguageTag.startsWith("zh-Hans") -> HELP_TOPICS_S_CHINESE_PAGE localeLanguageTag.startsWith("zh-Hans") -> HELP_TOPICS_S_CHINESE_PAGE
localeLanguageTag.startsWith("zh-Hant") -> HELP_TOPICS_T_CHINESE_PAGE localeLanguageTag.startsWith("zh-Hant") -> HELP_TOPICS_T_CHINESE_PAGE
localeLanguageTag.startsWith("ar") -> HELP_TOPICS_ARABIC_PAGE localeLanguageTag.startsWith("ar") -> HELP_TOPICS_ARABIC_PAGE
@ -60,22 +77,19 @@ object LinkBuilder {
localeLanguageTag.startsWith("pa") -> HELP_TOPICS_PUNJABI_PAGE localeLanguageTag.startsWith("pa") -> HELP_TOPICS_PUNJABI_PAGE
localeLanguageTag.startsWith("tr") -> HELP_TOPICS_TURKISH_PAGE localeLanguageTag.startsWith("tr") -> HELP_TOPICS_TURKISH_PAGE
else -> HELP_TOPICS_ENGLISH_PAGE else -> HELP_TOPICS_ENGLISH_PAGE
} + HELP_TOPICS_SUFFIX } + TOPICS_EXT_SUFFIX
CentralLog.d(TAG, "getHelpTopicsUrl() " +
"localeLanguageTag = $localeLanguageTag " +
"url = $url")
return url
} }
fun getHelpTopicsUrlWithAnchor(anchor : String) = fun getHelpTopicsUrlWithAnchor(anchor: String) =
getHelpTopicsUrl() + anchor getHelpTopicsUrl() + anchor
private fun getBluetoothPairingRequestUrl() = private fun getBluetoothPairingRequestUrl() =
getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_BLUETOOTH_PAIRING_REQUEST getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_BLUETOOTH_PAIRING_REQUEST
private fun getLocationPairingRequestUrl() =
getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_LOCATION_PERMISSION_ANDROID
private fun getVerifyMobileNumberPinLink(linkText: String) = buildHtmlText( private fun getVerifyMobileNumberPinLink(linkText: String) = buildHtmlText(
"<a href=\"${getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_VERIFY_MOBILE_NUMBER_PIN}\">$linkText</a>") "<a href=\"${getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_VERIFY_MOBILE_NUMBER_PIN}\">$linkText</a>")
@ -126,15 +140,16 @@ object LinkBuilder {
} }
fun getRegistrationAndPrivacyContent(context: Context): SpannableString { fun getRegistrationAndPrivacyContent(context: Context): SpannableString {
val privacyUrl = getPrivacyTopicsUrl()
return buildSpannableStringContent( return buildSpannableStringContent(
context, context,
TracerApp.AppContext.getString(R.string.data_privacy_content), TracerApp.AppContext.getString(R.string.data_privacy_content),
listOf( listOf(
PRIVACY_URL, privacyUrl,
PRIVACY_URL, privacyUrl,
getHelpTopicsUrl(), getHelpTopicsUrl(),
DEPARTMENT_OF_HEALTH_URL, DEPARTMENT_OF_HEALTH_URL,
PRIVACY_URL privacyUrl
) )
) )
} }
@ -143,7 +158,7 @@ object LinkBuilder {
return buildSpannableStringContent( return buildSpannableStringContent(
context, context,
TracerApp.AppContext.getString(R.string.permission_success_content), TracerApp.AppContext.getString(R.string.permission_success_content),
listOf(getBluetoothPairingRequestUrl()) listOf(getBluetoothPairingRequestUrl(), getLocationPairingRequestUrl())
) )
} }
@ -166,7 +181,7 @@ object LinkBuilder {
return buildSpannableStringContent( return buildSpannableStringContent(
context, context,
TracerApp.AppContext.getString(R.string.upload_step_4_sub_header), TracerApp.AppContext.getString(R.string.upload_step_4_sub_header),
listOf(PRIVACY_URL) listOf(getPrivacyTopicsUrl())
) )
} }
} }

View file

@ -0,0 +1,25 @@
package au.gov.health.covidsafe.networking.response
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class CaseStatisticResponse(@SerializedName("updated_date") val updatedDate: String?,
@SerializedName("stats_published_status") val statsPublishedStatus: String?,
val national: CaseDetailsData?,
val act: CaseDetailsData?,
val nsw: CaseDetailsData?,
val nt: CaseDetailsData?,
val qld: CaseDetailsData?,
val sa: CaseDetailsData?,
val tas: CaseDetailsData?,
val vic: CaseDetailsData?,
val wa: CaseDetailsData?)
@Keep
data class CaseDetailsData(
@SerializedName("total_cases") var totalCases: Int?,
@SerializedName("active_cases") var activeCases: Int?,
@SerializedName("new_cases") var newCases: Int?,
@SerializedName("recovered_cases") var recoveredCases: Int?,
var deaths: Int?)

View file

@ -1,6 +1,7 @@
package au.gov.health.covidsafe.networking.service package au.gov.health.covidsafe.networking.service
import au.gov.health.covidsafe.BuildConfig import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.networking.response.CaseStatisticResponse
import au.gov.health.covidsafe.networking.request.AuthChallengeRequest import au.gov.health.covidsafe.networking.request.AuthChallengeRequest
import au.gov.health.covidsafe.networking.request.OTPChallengeRequest import au.gov.health.covidsafe.networking.request.OTPChallengeRequest
import au.gov.health.covidsafe.networking.response.* import au.gov.health.covidsafe.networking.response.*
@ -40,7 +41,11 @@ interface AwsClient {
@Query("appversion") appversion: String, @Query("appversion") appversion: String,
@Query("token") token: String, @Query("token") token: String,
@Query("healthcheck") healthcheck: String, @Query("healthcheck") healthcheck: String,
@Query("encountershealth") encountershealth: String,
@Query("preferredlanguages") preferredLanguages: String @Query("preferredlanguages") preferredLanguages: String
): Call<MessagesResponse> ): Call<MessagesResponse>
@GET(BuildConfig.END_POINT_PREFIX + "/statistics")
fun getCaseStatistics(@Header("Authorization") jwtToken: String?): Call<CaseStatisticResponse>
} }

View file

@ -0,0 +1,110 @@
package au.gov.health.covidsafe.notifications
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import au.gov.health.covidsafe.HomeActivity
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.ui.utils.Utils.gotoPlayStore
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
private const val TAG = "CovidFirebaseMessagingService"
class CovidFirebaseMessagingService : FirebaseMessagingService(), CoroutineScope {
/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// There are two types of messages data messages and notification messages. Data messages are handled
// here in onMessageReceived whether the app is in the foreground or background. Data messages are the type
// traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app
// is in the foreground. When the app is in the background an automatically generated notification is displayed.
// When the user taps on the notification they are returned to the app. Messages containing both notification
// and data payloads are treated as notification messages. The Firebase console always sends notification
// messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
CentralLog.d(TAG, "onMessageReceived() received message from ${remoteMessage.from}")
// log notification payload.
remoteMessage.notification?.let {
CentralLog.d(TAG, "onMessageReceived() notification = ${it.title} ${it.body} ${it.clickAction}")
if (it.clickAction == getString(R.string.notification_click_action_upgrade_app)) {
gotoPlayStore(applicationContext)
} else {
createPushNotification(it)
}
}
// log data payload.
remoteMessage.data.isNotEmpty().let {
CentralLog.d(TAG, "onMessageReceived() data = " + remoteMessage.data)
}
}
private fun createPushNotification(notification: RemoteMessage.Notification) {
launch(Dispatchers.Main) {
val intent = Intent(this@CovidFirebaseMessagingService, HomeActivity::class.java)
intent.action = notification.clickAction
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(this@CovidFirebaseMessagingService, 0, intent, PendingIntent.FLAG_ONE_SHOT)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val channelId = getString(R.string.default_notification_channel_id)
val notificationBuilder = NotificationCompat.Builder(this@CovidFirebaseMessagingService, channelId)
.setContentTitle(notification.title)
.setContentText(notification.body)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.drawable.ic_notification_icon)
.setContentIntent(pendingIntent)
.setSound(defaultSoundUri)
.setColor(ContextCompat.getColor(this@CovidFirebaseMessagingService, R.color.notification_tint))
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
getString(R.string.default_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(0, notificationBuilder.build())
}
}
/**
* Called when InstanceID token is updated.
*/
override fun onNewToken(token: String) {
CentralLog.d(TAG, "onNewToken() InstanceID = $token")
Preference.putFirebaseInstanceID(TracerApp.AppContext, token)
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + Job()
}

View file

@ -0,0 +1,35 @@
package au.gov.health.covidsafe.notifications
import android.content.Context
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.ui.utils.Utils
class NotificationBuilder {
companion object {
private var isPushNotificationErrorCheck = false
fun handlePushNotification(context: Context, action: String?) {
action?.let {
when (it) {
context.getString(R.string.notification_click_action_upgrade_app) -> {
Utils.gotoPlayStore(context)
}
context.getString(R.string.notification_click_action_no_check_in),
context.getString(R.string.notification_click_action_check_possible_encountered_error),
context.getString(R.string.notification_click_action_check_possible_issue) -> {
isPushNotificationErrorCheck = true
}
}
}
}
fun isShowPossibleIssueNotification() = isPushNotificationErrorCheck
fun clearPossibleIssueNotificationCheck() {
isPushNotificationErrorCheck = false
}
}
}

View file

@ -45,7 +45,11 @@ class NotificationTemplates {
return builder.build() return builder.build()
} }
fun lackingThingsNotification(context: Context, channel: String): Notification { fun lackingThingsNotification(context: Context, contentTextResId: Int, channel: String): Notification {
return lackingThingsNotification(context, context.getString(contentTextResId), channel)
}
fun lackingThingsNotification(context: Context, contentText: String, channel: String): Notification {
val intent = Intent(context, HomeActivity::class.java) val intent = Intent(context, HomeActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra("page", 3) intent.putExtra("page", 3)
@ -57,13 +61,13 @@ class NotificationTemplates {
val builder = NotificationCompat.Builder(context, channel) val builder = NotificationCompat.Builder(context, channel)
.setContentTitle(context.getText(R.string.service_not_ok_title)) .setContentTitle(context.getText(R.string.service_not_ok_title))
.setContentText(context.getText(R.string.service_not_ok_body)) .setContentText(contentText)
.setStyle(NotificationCompat.BigTextStyle().bigText(context.getText(R.string.service_not_ok_body))) .setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
.setOngoing(true) .setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.drawable.ic_notification_warning) .setSmallIcon(R.drawable.ic_notification_warning)
.setTicker(context.getText(R.string.service_not_ok_body)) .setTicker(contentText)
.addAction( .addAction(
R.drawable.ic_notification_setting, R.drawable.ic_notification_setting,
context.getText(R.string.service_not_ok_action), context.getText(R.string.service_not_ok_action),

View file

@ -1,7 +1,8 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.preference
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.security.crypto.EncryptedSharedPreferences import au.gov.health.covidsafe.security.crypto.EncryptedSharedPreferences
import au.gov.health.covidsafe.security.crypto.MasterKeys import au.gov.health.covidsafe.security.crypto.MasterKeys
import au.gov.health.covidsafe.security.crypto.AESEncryptionForPreAndroidM import au.gov.health.covidsafe.security.crypto.AESEncryptionForPreAndroidM
@ -32,6 +33,7 @@ object Preference {
private const val IS_MINOR = "IS_MINOR" private const val IS_MINOR = "IS_MINOR"
private const val POST_CODE = "POST_CODE" private const val POST_CODE = "POST_CODE"
private const val AGE = "AGE" private const val AGE = "AGE"
private const val CASE_STATISTIC = "CASESTATISTIC"
private const val IS_DEVICE_NAME_CHANGE_PROMPT_DISPLAYED = "IS_DEVICE_NAME_CHANGE_DISPLAYED" private const val IS_DEVICE_NAME_CHANGE_PROMPT_DISPLAYED = "IS_DEVICE_NAME_CHANGE_DISPLAYED"
fun putDeviceID(context: Context, value: String) { fun putDeviceID(context: Context, value: String) {
@ -275,4 +277,14 @@ object Preference {
.getBoolean(IS_DEVICE_NAME_CHANGE_PROMPT_DISPLAYED, false) .getBoolean(IS_DEVICE_NAME_CHANGE_PROMPT_DISPLAYED, false)
} }
fun putCaseStatisticData(context: Context, caseStaticData: String): Boolean {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.edit().putString(CASE_STATISTIC, caseStaticData).commit()
}
fun getCaseStatisticData(context: Context): String? {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.getString(CASE_STATISTIC, null)
}
} }

View file

@ -7,7 +7,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.services.BluetoothMonitoringService.Companion.PENDING_PRIVACY_CLEANER_CODE import au.gov.health.covidsafe.services.BluetoothMonitoringService.Companion.PENDING_PRIVACY_CLEANER_CODE
import au.gov.health.covidsafe.services.SensorMonitoringService.Companion.TAG
import au.gov.health.covidsafe.status.persistence.StatusRecordStorage import au.gov.health.covidsafe.status.persistence.StatusRecordStorage
import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordStorage import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordStorage
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -19,8 +18,6 @@ import kotlin.coroutines.CoroutineContext
class PrivacyCleanerReceiver : BroadcastReceiver(), CoroutineScope { class PrivacyCleanerReceiver : BroadcastReceiver(), CoroutineScope {
private val TAG = this.javaClass.simpleName
private var job: Job = Job() private var job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -40,7 +37,7 @@ class PrivacyCleanerReceiver : BroadcastReceiver(), CoroutineScope {
alarm.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), AlarmManager.INTERVAL_DAY, pendingIntent) alarm.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), AlarmManager.INTERVAL_DAY, pendingIntent)
} }
suspend fun cleanDb(context: Context) { fun cleanDb(context: Context) {
val twentyOneDaysAgo = Calendar.getInstance() val twentyOneDaysAgo = Calendar.getInstance()
twentyOneDaysAgo.set(Calendar.HOUR_OF_DAY, 23) twentyOneDaysAgo.set(Calendar.HOUR_OF_DAY, 23)
twentyOneDaysAgo.set(Calendar.MINUTE, 59) twentyOneDaysAgo.set(Calendar.MINUTE, 59)
@ -53,6 +50,8 @@ class PrivacyCleanerReceiver : BroadcastReceiver(), CoroutineScope {
CentralLog.i(TAG, "Street info deleted count : $countStreetDeleted") CentralLog.i(TAG, "Street info deleted count : $countStreetDeleted")
CentralLog.i(TAG, "Status info deleted count : $countStatusDeleted") CentralLog.i(TAG, "Status info deleted count : $countStatusDeleted")
} }
const val TAG = "PrivacyCleanerReceiver"
} }
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {

View file

@ -3,7 +3,7 @@ package au.gov.health.covidsafe.receivers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
class UpgradeReceiver : BroadcastReceiver() { class UpgradeReceiver : BroadcastReceiver() {

View file

@ -7,8 +7,8 @@ import android.app.job.JobService
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import au.gov.health.covidsafe.BuildConfig import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.extensions.isBatteryOptimizationDisabled import au.gov.health.covidsafe.extensions.isBatteryOptimizationDisabled
import au.gov.health.covidsafe.extensions.isBlueToothEnabled import au.gov.health.covidsafe.extensions.isBlueToothEnabled
import au.gov.health.covidsafe.extensions.isLocationEnabledOnDevice import au.gov.health.covidsafe.extensions.isLocationEnabledOnDevice
@ -31,14 +31,16 @@ import java.util.*
private const val TAG = "GetMessagesScheduler" private const val TAG = "GetMessagesScheduler"
private const val GET_MESSAGES_JOB_ID = 1 private const val GET_MESSAGES_JOB_ID = 1
private const val TWENTY_FOUR_HOURS_IN_MILLIS = 24 * 60 * 60 * 1000L private const val ONE_HOURS_IN_MILLIS = 60 * 60 * 1000L
private const val FOUR_HOURS_IN_MILLIS = 4 * ONE_HOURS_IN_MILLIS
private const val TWENTY_FOUR_HOURS_IN_MILLIS = 24 * ONE_HOURS_IN_MILLIS
private const val SEVEN_DAYS_IN_MILLIS = 7 * TWENTY_FOUR_HOURS_IN_MILLIS private const val SEVEN_DAYS_IN_MILLIS = 7 * TWENTY_FOUR_HOURS_IN_MILLIS
private const val HEALTH_CHECK_RESULT_OK = "OK" private const val HEALTH_CHECK_RESULT_OK = "OK"
private const val HEALTH_CHECK_RESULT_POSSIBLE_ERROR = "POSSIBLE_ERROR" private const val HEALTH_CHECK_RESULT_POSSIBLE_ERROR = "POSSIBLE_ERROR"
// for testing only // for testing only
//private const val TWENTY_FOUR_HOURS_IN_MILLIS = 16 * 60 * 1000L //private const val FOUR_HOURS_IN_MILLIS = 16 * 60 * 1000L
class GetMessagesJobSchedulerService : JobService() { class GetMessagesJobSchedulerService : JobService() {
private val mostRecentRecordLiveData = StreetPassRecordDatabase.getDatabase(TracerApp.AppContext).recordDao().getMostRecentRecord() private val mostRecentRecordLiveData = StreetPassRecordDatabase.getDatabase(TracerApp.AppContext).recordDao().getMostRecentRecord()
@ -89,19 +91,27 @@ object GetMessagesScheduler {
(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler?) (context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler?)
?.let { ?.let {
CentralLog.d(TAG, "JobScheduler available") CentralLog.d(TAG, "JobScheduler available")
val schedulerInMillis = getRandomMinsInMillis() + FOUR_HOURS_IN_MILLIS
CentralLog.d(TAG, "Next scheduled Jon run in ${(schedulerInMillis / 1000) / 60} mins")
it.schedule( it.schedule(
JobInfo.Builder(GET_MESSAGES_JOB_ID, JobInfo.Builder(GET_MESSAGES_JOB_ID,
ComponentName(context, GetMessagesJobSchedulerService::class.java)) ComponentName(context, GetMessagesJobSchedulerService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true) .setPersisted(true)
.setPeriodic(TWENTY_FOUR_HOURS_IN_MILLIS) .setPeriodic(schedulerInMillis)
.build() .build()
) )
} }
} }
} }
private fun getRandomMinsInMillis(): Long {
//We don't want everyone calling at the same time eg 9 am, 1 pm, etc we want to add an offset between 0-30 minutes based upon some random number.
val randomSecs = Random().nextInt(30 * 60)
return randomSecs * 1000L
}
fun getMessages(jobService: JobService? = null, params: JobParameters? = null) { fun getMessages(jobService: JobService? = null, params: JobParameters? = null) {
val context = TracerApp.AppContext val context = TracerApp.AppContext
@ -127,14 +137,19 @@ object GetMessagesScheduler {
isBlueToothEnabled && isBlueToothEnabled &&
isBatteryOptimizationDisabled && isBatteryOptimizationDisabled &&
isLocationPermissionAllowed && isLocationPermissionAllowed &&
isLocationEnabledOnDevice && isLocationEnabledOnDevice
isLastRecordWithinSevenDays
) { ) {
HEALTH_CHECK_RESULT_OK HEALTH_CHECK_RESULT_OK
} else { } else {
HEALTH_CHECK_RESULT_POSSIBLE_ERROR HEALTH_CHECK_RESULT_POSSIBLE_ERROR
} }
val encountersHealth = if (isLastRecordWithinSevenDays) {
HEALTH_CHECK_RESULT_OK
} else {
HEALTH_CHECK_RESULT_POSSIBLE_ERROR
}
CentralLog.d(TAG, "healthCheck = $healthCheck") CentralLog.d(TAG, "healthCheck = $healthCheck")
val preferredLanguages = Locale.getDefault().language val preferredLanguages = Locale.getDefault().language
@ -145,6 +160,7 @@ object GetMessagesScheduler {
appVersion, appVersion,
token, token,
healthCheck, healthCheck,
encountersHealth,
preferredLanguages preferredLanguages
) )

View file

@ -3,8 +3,8 @@ package au.gov.health.covidsafe.security.crypto
import android.security.KeyPairGeneratorSpec import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyProperties import android.security.keystore.KeyProperties
import android.util.Base64 import android.util.Base64
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.math.BigInteger import java.math.BigInteger

View file

@ -4,24 +4,31 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
import android.content.* import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.location.LocationManager
import android.os.Build import android.os.Build
import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import au.gov.health.covidsafe.* import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.bluetooth.BLEAdvertiser import au.gov.health.covidsafe.bluetooth.BLEAdvertiser
import au.gov.health.covidsafe.bluetooth.gatt.ACTION_RECEIVED_STATUS import au.gov.health.covidsafe.bluetooth.gatt.ACTION_RECEIVED_STATUS
import au.gov.health.covidsafe.bluetooth.gatt.ACTION_RECEIVED_STREETPASS import au.gov.health.covidsafe.bluetooth.gatt.ACTION_RECEIVED_STREETPASS
import au.gov.health.covidsafe.bluetooth.gatt.STATUS import au.gov.health.covidsafe.bluetooth.gatt.STATUS
import au.gov.health.covidsafe.bluetooth.gatt.STREET_PASS import au.gov.health.covidsafe.bluetooth.gatt.STREET_PASS
import au.gov.health.covidsafe.extensions.isLocationEnabledOnDevice
import au.gov.health.covidsafe.factory.NetworkFactory import au.gov.health.covidsafe.factory.NetworkFactory
import au.gov.health.covidsafe.interactor.usecase.UpdateBroadcastMessageAndPerformScanWithExponentialBackOff import au.gov.health.covidsafe.interactor.usecase.UpdateBroadcastMessageAndPerformScanWithExponentialBackOff
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.notifications.NotificationTemplates import au.gov.health.covidsafe.notifications.NotificationTemplates
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.receivers.PrivacyCleanerReceiver import au.gov.health.covidsafe.receivers.PrivacyCleanerReceiver
import au.gov.health.covidsafe.status.Status import au.gov.health.covidsafe.status.Status
import au.gov.health.covidsafe.status.persistence.StatusRecord import au.gov.health.covidsafe.status.persistence.StatusRecord
@ -39,6 +46,8 @@ import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordDatabase.C
import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordDatabase.Companion.ENCRYPTED_EMPTY_DICT import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordDatabase.Companion.ENCRYPTED_EMPTY_DICT
import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordDatabase.Companion.VERSION_ONE import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordDatabase.Companion.VERSION_ONE
import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordStorage import au.gov.health.covidsafe.streetpass.persistence.StreetPassRecordStorage
import au.gov.health.covidsafe.ui.utils.LocalBlobV2
import au.gov.health.covidsafe.ui.utils.Utils
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -46,15 +55,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions
import java.lang.Exception
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
private const val POWER_SAVE_WHITELIST_CHANGED = "android.os.action.POWER_SAVE_WHITELIST_CHANGED"
@Keep @Keep
class BluetoothMonitoringService : LifecycleService(), CoroutineScope { class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
private var mNotificationManager: NotificationManager? = null
@Keep @Keep
private lateinit var serviceUUID: String private lateinit var serviceUUID: String
@ -78,31 +86,12 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
private lateinit var commandHandler: CommandHandler private lateinit var commandHandler: CommandHandler
private lateinit var mService: SensorMonitoringService
private var mBound: Boolean = false
private lateinit var localBroadcastManager: LocalBroadcastManager private lateinit var localBroadcastManager: LocalBroadcastManager
private val awsClient = NetworkFactory.awsClient private val awsClient = NetworkFactory.awsClient
private val gson: Gson = GsonBuilder().disableHtmlEscaping().create() private val gson: Gson = GsonBuilder().disableHtmlEscaping().create()
/** Defines callbacks for service binding, passed to bindService() */
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
val binder = service as SensorMonitoringService.LocalBinder
mService = binder.getService()
mBound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
mBound = false
}
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
localBroadcastManager = LocalBroadcastManager.getInstance(this) localBroadcastManager = LocalBroadcastManager.getInstance(this)
@ -171,6 +160,10 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
return EasyPermissions.hasPermissions(this.applicationContext, *perms) return EasyPermissions.hasPermissions(this.applicationContext, *perms)
} }
private fun isLocationPermissionEnabled(): Boolean {
return hasLocationPermissions() && this.isLocationEnabledOnDevice()
}
private fun isBluetoothEnabled(): Boolean { private fun isBluetoothEnabled(): Boolean {
var btOn = false var btOn = false
val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) { val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
@ -204,20 +197,13 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
CentralLog.i(TAG, "Service onStartCommand") CentralLog.i(TAG, "Service onStartCommand")
// Bind to LocalService
Intent(this.applicationContext, SensorMonitoringService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
//check for permissions //check for permissions
if (!hasLocationPermissions() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) { if (!isLocationPermissionEnabled() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) {
CentralLog.i( CentralLog.i(
TAG, TAG,
"location permission: ${hasLocationPermissions()} bluetooth: ${isBluetoothEnabled()}" "location permission: ${isLocationPermissionEnabled()} bluetooth: ${isBluetoothEnabled()}"
) )
val notif = showForegroundNotification()
NotificationTemplates.lackingThingsNotification(this.applicationContext, CHANNEL_ID)
startForeground(NOTIFICATION_ID, notif)
return START_STICKY return START_STICKY
} }
@ -242,14 +228,12 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
CentralLog.i(TAG, "Command is:${cmd?.string}") CentralLog.i(TAG, "Command is:${cmd?.string}")
//check for permissions //check for permissions
if (!hasLocationPermissions() || !isBluetoothEnabled()) { if (!isLocationPermissionEnabled() || !isBluetoothEnabled()) {
CentralLog.i( CentralLog.i(
TAG, TAG,
"location permission: ${hasLocationPermissions()} bluetooth: ${isBluetoothEnabled()}" "location permission: ${isLocationPermissionEnabled()} bluetooth: ${isBluetoothEnabled()}"
) )
val notif = showForegroundNotification()
NotificationTemplates.lackingThingsNotification(this.applicationContext, CHANNEL_ID)
startForeground(NOTIFICATION_ID, notif)
return return
} }
@ -326,7 +310,6 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
} }
} }
private fun actionUpdateBm() { private fun actionUpdateBm() {
Utils.scheduleBMUpdateCheck(this.applicationContext, bmCheckInterval) Utils.scheduleBMUpdateCheck(this.applicationContext, bmCheckInterval)
@ -345,7 +328,6 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
} else { } else {
CentralLog.i(TAG, "Don't need to update bm") CentralLog.i(TAG, "Don't need to update bm")
} }
} }
private fun calcPhaseShift(min: Long, max: Long): Long { private fun calcPhaseShift(min: Long, max: Long): Long {
@ -448,11 +430,9 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
CentralLog.i(TAG, "Performing self diagnosis") CentralLog.i(TAG, "Performing self diagnosis")
if (!hasLocationPermissions() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) { if (!isLocationPermissionEnabled() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) {
CentralLog.i(TAG, "no location permission") CentralLog.i(TAG, "no location permission")
val notif = showForegroundNotification()
NotificationTemplates.lackingThingsNotification(this.applicationContext, CHANNEL_ID)
startForeground(NOTIFICATION_ID, notif)
return return
} }
@ -480,8 +460,10 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
} else { } else {
CentralLog.w( CentralLog.w(
TAG, TAG,
"Advertise Schedule present. Should be advertising?: ${advertiser?.shouldBeAdvertising "Advertise Schedule present. Should be advertising?: ${
?: false}. Is Advertising?: ${advertiser?.isAdvertising ?: false}" advertiser?.shouldBeAdvertising
?: false
}. Is Advertising?: ${advertiser?.isAdvertising ?: false}"
) )
} }
} }
@ -498,14 +480,74 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
job.cancel() job.cancel()
if (mBound) {
unbindService(connection)
mBound = false
}
CentralLog.i(TAG, "BluetoothMonitoringService destroyed") CentralLog.i(TAG, "BluetoothMonitoringService destroyed")
} }
private fun showForegroundNotification() {
launch(Dispatchers.Main) {
val notificationContentText: Int = if (!isLocationPermissionEnabled() && isBluetoothEnabled() && isBatteryOptimizationDisabled()) {
//Location Disabled
R.string.notification_location
} else if (!isBluetoothEnabled() && isLocationPermissionEnabled() && isBatteryOptimizationDisabled()) {
//Bluetooth Disabled
R.string.notification_bluetooth
} else if (!isBatteryOptimizationDisabled() && isLocationPermissionEnabled() && isBluetoothEnabled()) {
//Battery optimization Disabled
R.string.notification_battery
} else if (!isBatteryOptimizationDisabled() || !isLocationPermissionEnabled() || !isBluetoothEnabled()) {
//Multiple permission Disabled
R.string.notification_settings
} else {
//All permission are enabled, so we should show Active message.
-1
}
val notificationMessage = if (notificationContentText > 0) {
NotificationTemplates.lackingThingsNotification(
this@BluetoothMonitoringService.applicationContext,
notificationContentText,
CHANNEL_ID)
} else {
//All permissions are enabled
NotificationTemplates.getRunningNotification(this@BluetoothMonitoringService.applicationContext, CHANNEL_ID)
}
startForeground(NOTIFICATION_ID, notificationMessage)
}
}
private val gpsSwitchStateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
intent.action?.let {
if (it == LocationManager.PROVIDERS_CHANGED_ACTION) {
CentralLog.i(TAG, "Location ON/OFF status changed")
showForegroundNotification()
}
}
}
}
private val powerStateChangeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
intent.action?.let {
if (it == POWER_SAVE_WHITELIST_CHANGED) {
CentralLog.i(TAG, "Save mode status changed")
showForegroundNotification()
}
}
}
}
private fun registerLocationChangeReceiver() {
registerReceiver(gpsSwitchStateReceiver, IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION))
}
private fun registerPowerModeChangeReceiver() {
registerReceiver(powerStateChangeReceiver, IntentFilter(POWER_SAVE_WHITELIST_CHANGED))
}
private fun registerReceivers() { private fun registerReceivers() {
val recordAvailableFilter = IntentFilter(ACTION_RECEIVED_STREETPASS) val recordAvailableFilter = IntentFilter(ACTION_RECEIVED_STREETPASS)
localBroadcastManager.registerReceiver(streetPassReceiver, recordAvailableFilter) localBroadcastManager.registerReceiver(streetPassReceiver, recordAvailableFilter)
@ -516,6 +558,9 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
val bluetoothStatusReceivedFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) val bluetoothStatusReceivedFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
registerReceiver(bluetoothStatusReceiver, bluetoothStatusReceivedFilter) registerReceiver(bluetoothStatusReceiver, bluetoothStatusReceivedFilter)
registerLocationChangeReceiver()
registerPowerModeChangeReceiver()
CentralLog.i(TAG, "Receivers registered") CentralLog.i(TAG, "Receivers registered")
} }
@ -534,9 +579,29 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
try { try {
unregisterReceiver(bluetoothStatusReceiver) unregisterReceiver(bluetoothStatusReceiver)
} catch (e: Throwable) { } catch (e: Throwable) {
CentralLog.w(TAG, "bluetoothStatusReceiver is not registered?") CentralLog.w(TAG, "bluetoothStatusReceiver is not registered?")
} }
unregisterLocationReceiver()
unregisterPowerStateChangeReceiver()
}
private fun unregisterLocationReceiver() {
try {
unregisterReceiver(gpsSwitchStateReceiver)
} catch (e: Throwable) {
CentralLog.w(TAG, "Location Receiver is not registered?")
}
}
private fun unregisterPowerStateChangeReceiver() {
try {
unregisterReceiver(powerStateChangeReceiver)
} catch (e: Throwable) {
CentralLog.w(TAG, "Power State Receiver is not registered?")
}
} }
inner class BluetoothStatusReceiver : BroadcastReceiver() { inner class BluetoothStatusReceiver : BroadcastReceiver() {
@ -549,11 +614,7 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) { when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
BluetoothAdapter.STATE_TURNING_OFF -> { BluetoothAdapter.STATE_TURNING_OFF -> {
CentralLog.d(TAG, "BluetoothAdapter.STATE_TURNING_OFF") CentralLog.d(TAG, "BluetoothAdapter.STATE_TURNING_OFF")
val notif = NotificationTemplates.lackingThingsNotification( showForegroundNotification()
this@BluetoothMonitoringService.applicationContext,
CHANNEL_ID
)
startForeground(NOTIFICATION_ID, notif)
teardown() teardown()
} }
BluetoothAdapter.STATE_OFF -> { BluetoothAdapter.STATE_OFF -> {
@ -565,6 +626,7 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
BluetoothAdapter.STATE_ON -> { BluetoothAdapter.STATE_ON -> {
CentralLog.d(TAG, "BluetoothAdapter.STATE_ON") CentralLog.d(TAG, "BluetoothAdapter.STATE_ON")
Utils.startBluetoothMonitoringService(this@BluetoothMonitoringService.applicationContext) Utils.startBluetoothMonitoringService(this@BluetoothMonitoringService.applicationContext)
showForegroundNotification()
} }
} }
} }
@ -580,22 +642,10 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
if (ACTION_RECEIVED_STREETPASS == intent.action) { if (ACTION_RECEIVED_STREETPASS == intent.action) {
val connRecord: ConnectionRecord? = intent.getParcelableExtra(STREET_PASS) val connRecord: ConnectionRecord? = intent.getParcelableExtra(STREET_PASS)
CentralLog.d( CentralLog.d(TAG, "StreetPass received: $connRecord")
TAG,
"StreetPass received: $connRecord"
)
if (connRecord != null && connRecord.msg.isNotEmpty()) { if (connRecord != null && connRecord.msg.isNotEmpty()) {
if (mBound) {
val proximity = mService.proximity
val light = mService.light
CentralLog.d(
TAG,
"Sensor values just before saving StreetPassRecord: proximity=$proximity light=$light"
)
}
val remoteBlob: String = if (connRecord.version == VERSION_ONE) { val remoteBlob: String = if (connRecord.version == VERSION_ONE) {
with(receiver = connRecord) { with(receiver = connRecord) {
val plainRecordByteArray = gson.toJson(StreetPassRecordDatabase.Companion.EncryptedRecord( val plainRecordByteArray = gson.toJson(StreetPassRecordDatabase.Companion.EncryptedRecord(
@ -646,13 +696,15 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (ACTION_RECEIVED_STATUS == intent.action) { if (ACTION_RECEIVED_STATUS == intent.action) {
val statusRecord: Status = intent.getParcelableExtra(STATUS) val status: Status? = intent.getParcelableExtra(STATUS)
CentralLog.d(TAG, "Status received: ${statusRecord.msg}") status?.let {
CentralLog.d(TAG, "Status received: ${it.msg}")
if (statusRecord.msg.isNotEmpty()) { if (it.msg.isNotEmpty()) {
val statusRecord = StatusRecord(statusRecord.msg) val statusRecord = StatusRecord(it.msg)
launch { launch {
statusRecordStorage.saveRecord(statusRecord) statusRecordStorage.saveRecord(statusRecord)
}
} }
} }
} }
@ -713,6 +765,4 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
const val blacklistDuration: Long = BuildConfig.BLACKLIST_DURATION const val blacklistDuration: Long = BuildConfig.BLACKLIST_DURATION
} }
} }

View file

@ -1,53 +0,0 @@
package au.gov.health.covidsafe.services
import au.gov.health.covidsafe.*
import au.gov.health.covidsafe.Utils.gotoPlayStore
import au.gov.health.covidsafe.logging.CentralLog
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
private const val TAG = "CovidFirebaseMessagingService"
class CovidFirebaseMessagingService : FirebaseMessagingService() {
/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// There are two types of messages data messages and notification messages. Data messages are handled
// here in onMessageReceived whether the app is in the foreground or background. Data messages are the type
// traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app
// is in the foreground. When the app is in the background an automatically generated notification is displayed.
// When the user taps on the notification they are returned to the app. Messages containing both notification
// and data payloads are treated as notification messages. The Firebase console always sends notification
// messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
CentralLog.d(TAG, "onMessageReceived() received message from ${remoteMessage.from}")
// log notification payload.
remoteMessage.notification?.let {
CentralLog.d(TAG, "onMessageReceived() notification = ${it.title} ${it.body} ${it.clickAction}")
if (it.clickAction == "au.gov.health.covidsafe.UPGRADE_APP"){
gotoPlayStore(applicationContext)
}
}
// log data payload.
remoteMessage.data.isNotEmpty().let {
CentralLog.d(TAG, "onMessageReceived() data = " + remoteMessage.data)
}
}
/**
* Called when InstanceID token is updated.
*/
override fun onNewToken(token: String) {
CentralLog.d(TAG, "onNewToken() InstanceID = $token")
Preference.putFirebaseInstanceID(TracerApp.AppContext, token)
}
}

View file

@ -1,96 +0,0 @@
package au.gov.health.covidsafe.services
import android.app.Service
import android.content.Context
import android.content.Intent
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Binder
import android.os.IBinder
import au.gov.health.covidsafe.logging.CentralLog
import kotlin.math.sqrt
class SensorMonitoringService : Service(), SensorEventListener {
private lateinit var sensorManager: SensorManager
private var _light: FloatArray? = null
private var _proximity: FloatArray? = null
private val binder = LocalBinder()
override fun onCreate() {
super.onCreate()
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
val lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
if (proximitySensor != null) {
CentralLog.d(TAG, "Proximity sensor: $proximitySensor")
sensorManager.registerListener(this, proximitySensor, SENSOR_DELAY_SUPER_SLOW)
} else {
CentralLog.d(TAG, "Proximity sensor not available")
}
if (lightSensor != null) {
CentralLog.d(TAG, "Light sensor: $lightSensor")
sensorManager.registerListener(this, lightSensor, SENSOR_DELAY_SUPER_SLOW)
} else {
CentralLog.d(TAG, "Light sensor not available")
}
CentralLog.d(TAG, "SensorMonitoringService started")
}
override fun onDestroy() {
sensorManager.unregisterListener(this)
CentralLog.d(TAG, "SensorMonitoringService destroyed")
super.onDestroy()
}
inner class LocalBinder : Binder() {
fun getService(): SensorMonitoringService = this@SensorMonitoringService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
CentralLog.d(TAG, "Sensor accuracy changed! $sensor")
}
override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Sensor.TYPE_PROXIMITY -> {
_proximity = event.values
}
Sensor.TYPE_LIGHT -> {
_light = event.values
}
else -> {
CentralLog.w(TAG, "Unexpected sensor type changed: ${event.sensor.type}")
}
}
}
val proximity
get() = if (_proximity != null) {
sqrt((_proximity as FloatArray).reduce { acc: Float, n: Float -> acc + n * n })
} else {
-1.0f
}
val light
get() = if (_light != null) {
sqrt((_light as FloatArray).reduce { acc: Float, n: Float -> acc + n * n })
} else {
-1.0f
}
companion object {
const val TAG = "SensorMonitoringService"
const val SENSOR_DELAY_SUPER_SLOW = 3_000_000
}
}

View file

@ -5,7 +5,7 @@ import android.bluetooth.le.ScanResult
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.bluetooth.BLEScanner import au.gov.health.covidsafe.bluetooth.BLEScanner
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.status.Status import au.gov.health.covidsafe.status.Status

View file

@ -9,8 +9,8 @@ import android.os.Handler
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import au.gov.health.covidsafe.BuildConfig import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.bluetooth.gatt.* import au.gov.health.covidsafe.bluetooth.gatt.*
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.services.BluetoothMonitoringService import au.gov.health.covidsafe.services.BluetoothMonitoringService

View file

@ -8,14 +8,12 @@ import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import au.gov.health.covidsafe.LocalBlobV2 import au.gov.health.covidsafe.ui.utils.LocalBlobV2
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.status.persistence.StatusRecord import au.gov.health.covidsafe.status.persistence.StatusRecord
import au.gov.health.covidsafe.status.persistence.StatusRecordDao import au.gov.health.covidsafe.status.persistence.StatusRecordDao
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import kotlin.concurrent.thread
const val CURRENT_DB_VERSION = 3 const val CURRENT_DB_VERSION = 3
@ -31,7 +29,7 @@ abstract class StreetPassRecordDatabase : RoomDatabase() {
companion object { companion object {
private val TAG = this.javaClass.simpleName private val TAG = this::class.java.simpleName
private const val ID_COLUMN_INDEX = 0 private const val ID_COLUMN_INDEX = 0
private const val TIMESTAMP_COLUMN_INDEX = 1 private const val TIMESTAMP_COLUMN_INDEX = 1
@ -147,12 +145,12 @@ abstract class StreetPassRecordDatabase : RoomDatabase() {
val localBlob: String = if (version == 1) { val localBlob: String = if (version == 1) {
ENCRYPTED_EMPTY_DICT ENCRYPTED_EMPTY_DICT
} else { } else {
val modelP = if (DUMMY_DEVICE == modelP) null else modelP val colModelP = if (DUMMY_DEVICE == modelP) null else modelP
val modelC = if (DUMMY_DEVICE == modelC) null else modelC val colModelC = if (DUMMY_DEVICE == modelC) null else modelC
val rssi = if (DUMMY_RSSI == rssi) null else rssi val colRssi = if (DUMMY_RSSI == rssi) null else rssi
val txPower = if (DUMMY_TXPOWER == txPower) null else txPower val colTxPower = if (DUMMY_TXPOWER == txPower) null else txPower
val plainRecord = gson.toJson(LocalBlobV2(modelP, modelC, rssi, txPower)).toByteArray(Charsets.UTF_8) val updatedPlainRecord = gson.toJson(LocalBlobV2(colModelP, colModelC, colRssi, colTxPower)).toByteArray(Charsets.UTF_8)
Encryption.encryptPayload(plainRecord) Encryption.encryptPayload(updatedPlainRecord)
} }
contentValues.put("v", VERSION_TWO) contentValues.put("v", VERSION_TWO)
contentValues.put("org", org) contentValues.put("org", org)

View file

@ -3,7 +3,7 @@ package au.gov.health.covidsafe.talkback
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import java.lang.StringBuilder import java.lang.StringBuilder

View file

@ -1,4 +1,4 @@
package au.gov.health.covidsafe.ui package au.gov.health.covidsafe.ui.base
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
@ -6,8 +6,6 @@ import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.Navigator import androidx.navigation.Navigator
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import au.gov.health.covidsafe.HasBlockingState
import kotlinx.android.synthetic.main.fragment_intro.*
open class BaseFragment : Fragment() { open class BaseFragment : Fragment() {

View file

@ -0,0 +1,56 @@
package au.gov.health.covidsafe.ui.base
import android.graphics.Typeface
import android.view.View
import android.widget.TextView
import androidx.databinding.BindingAdapter
import au.gov.health.covidsafe.extensions.fromHtml
import au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
import java.text.SimpleDateFormat
import java.util.*
@BindingAdapter("visibility")
fun setVisibility(view: View, isVisible: Boolean?) {
isVisible?.let {
view.visibility = if (isVisible) View.VISIBLE else View.GONE
}
}
@BindingAdapter("addUnderline")
fun setUnderlineText(textView: TextView, text: String?) {
text?.let {
textView.text = fromHtml("<u>$text</u>")
}
}
@BindingAdapter("externalCardTitle")
fun setExternalCardTitle(view: ExternalLinkCard, text: Int?) {
text?.let {
view.setTitleTextTypeFace(Typeface.DEFAULT_BOLD)
view.setTitleText(String.format("%,d", it))
}
}
@BindingAdapter("stateCaseNumberFormat")
fun setStateCaseNumberFormat(view: TextView, text: Int?) {
text?.let {
view.text = String.format("%,d", it)
}
}
@BindingAdapter("dateFormat")
fun setDateFormat(textView: TextView, dateString: String?) {
dateString?.let {
val cal = Calendar.getInstance()
val timeZoneID = "Australia/Sydney"
val tz = TimeZone.getTimeZone(timeZoneID)
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.getDefault())
dateFormat.timeZone = tz
cal.time = dateFormat.parse(it)
val convertedDateString = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format(cal.time)
val convertedTimeString = SimpleDateFormat("h a", Locale.getDefault()).format(cal.time)
val finalDisplayDateFormat = "$convertedDateString at $convertedTimeString AEST"
textView.text = finalDisplayDateFormat
}
}

View file

@ -1,4 +1,4 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.ui.base
interface HasBlockingState { interface HasBlockingState {
var isUiBlocked: Boolean var isUiBlocked: Boolean

View file

@ -1,4 +1,4 @@
package au.gov.health.covidsafe.ui package au.gov.health.covidsafe.ui.base
import android.view.View import android.view.View
import androidx.annotation.StringRes import androidx.annotation.StringRes

View file

@ -1,4 +1,4 @@
package au.gov.health.covidsafe.ui package au.gov.health.covidsafe.ui.base
import androidx.annotation.StringRes import androidx.annotation.StringRes

View file

@ -1,8 +1,9 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.ui.connection
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.R
import kotlinx.android.synthetic.main.activity_internet_connection_issues.* import kotlinx.android.synthetic.main.activity_internet_connection_issues.*
import kotlinx.android.synthetic.main.activity_onboarding.toolbar import kotlinx.android.synthetic.main.activity_onboarding.toolbar

View file

@ -1,14 +1,10 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.ui.devicename
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.scheduler.GetMessagesScheduler
import au.gov.health.covidsafe.ui.home.HomeFragment
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.iid.FirebaseInstanceId
import kotlinx.android.synthetic.main.activity_device_name_change_prompt.* import kotlinx.android.synthetic.main.activity_device_name_change_prompt.*
import kotlinx.android.synthetic.main.fragment_permission_device_name.* import kotlinx.android.synthetic.main.fragment_permission_device_name.*

View file

@ -0,0 +1,8 @@
package au.gov.health.covidsafe.ui.home
enum class CaseNumbersState(state: Int) {
LOADING(0),
SUCCESS(1),
ERROR_NO_NETWORK(2),
ERROR_UNKNOWN(3),
}

View file

@ -14,7 +14,7 @@ import androidx.core.view.isVisible
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.links.LinkBuilder import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.ui.BaseFragment import au.gov.health.covidsafe.ui.base.BaseFragment
import com.atlassian.mobilekit.module.feedback.FeedbackModule import com.atlassian.mobilekit.module.feedback.FeedbackModule
import kotlinx.android.synthetic.main.fragment_help.* import kotlinx.android.synthetic.main.fragment_help.*
import kotlinx.android.synthetic.main.fragment_help.view.* import kotlinx.android.synthetic.main.fragment_help.view.*

View file

@ -4,11 +4,8 @@ import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.content.* import android.content.*
import android.net.Uri import android.graphics.Typeface
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.text.Html
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -16,92 +13,207 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import android.widget.LinearLayout
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.children import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import au.gov.health.covidsafe.* import au.gov.health.covidsafe.*
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.databinding.FragmentHomeBinding
import au.gov.health.covidsafe.extensions.* import au.gov.health.covidsafe.extensions.*
import au.gov.health.covidsafe.links.LinkBuilder import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.response.MessagesResponse import au.gov.health.covidsafe.notifications.NotificationBuilder
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.BaseFragment import au.gov.health.covidsafe.ui.base.BaseFragment
import au.gov.health.covidsafe.ui.home.view.ExternalLinkCard import au.gov.health.covidsafe.utils.NetworkConnectionCheck
import kotlinx.android.synthetic.main.fragment_home_case_statistics.*
import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.*
import kotlinx.android.synthetic.main.fragment_home_external_links.* import kotlinx.android.synthetic.main.fragment_home_external_links.*
import kotlinx.android.synthetic.main.fragment_home_setup_complete_header.* import kotlinx.android.synthetic.main.fragment_home_header.*
import kotlinx.android.synthetic.main.fragment_home_setup_incomplete_content.* import kotlinx.android.synthetic.main.view_covid_share_tile.*
import kotlinx.android.synthetic.main.view_help_topics_tile.*
import kotlinx.android.synthetic.main.view_home_setup_complete.*
import kotlinx.android.synthetic.main.view_home_setup_incomplete.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import pub.devrel.easypermissions.AppSettingsDialog import pub.devrel.easypermissions.AppSettingsDialog
import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions
import java.lang.ref.WeakReference
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
private const val TAG = "HomeFragment" private const val TAG = "HomeFragment"
private const val ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000L private const val ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000L
private const val FOURTEEN_DAYS_IN_MILLIS = 14 * ONE_DAY_IN_MILLIS private const val FOURTEEN_DAYS_IN_MILLIS = 14 * ONE_DAY_IN_MILLIS
class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks { class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, NetworkConnectionCheck.NetworkConnectionListener {
companion object { private val homeFragmentViewModel: HomeFragmentViewModel by viewModels()
var instanceWeakRef: WeakReference<HomeFragment>? = null
}
private lateinit var presenter: HomePresenter
private var mIsBroadcastListenerRegistered = false private var mIsBroadcastListenerRegistered = false
private var counter: Int = 0 private var counter: Int = 0
private val mBroadcastListener: BroadcastReceiver = object : BroadcastReceiver() { private var checkIsInternetConnected = false
override fun onReceive(context: Context, intent: Intent) { private var isAppWithLatestVersion = false
val action = intent.action
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) { override fun onCreate(savedInstanceState: Bundle?) {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) { super.onCreate(savedInstanceState)
BluetoothAdapter.STATE_OFF -> {
bluetooth_card_view.render(formatBlueToothTitle(false), false) initializeObservers()
refreshSetupCompleteOrIncompleteUi()
}
BluetoothAdapter.STATE_TURNING_OFF -> {
bluetooth_card_view.render(formatBlueToothTitle(false), false)
refreshSetupCompleteOrIncompleteUi()
}
BluetoothAdapter.STATE_ON -> {
bluetooth_card_view.render(formatBlueToothTitle(true), true)
refreshSetupCompleteOrIncompleteUi()
}
}
}
}
} }
override fun onCreateView( override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
inflater: LayoutInflater, return FragmentHomeBinding.inflate(layoutInflater).apply {
container: ViewGroup?, lifecycleOwner = viewLifecycleOwner
savedInstanceState: Bundle? viewModel = homeFragmentViewModel
): View? { }.root
presenter = HomePresenter(this)
return inflater.inflate(R.layout.fragment_home, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
view.home_header_help.setOnClickListener { initializeSettingsNavigation()
setAppVersionNumber()
initializeDebugTestActivity()
initializeNoNetworkError()
initializeRefreshButton()
initializePullToRefresh()
NetworkConnectionCheck.addNetworkChangedListener(requireContext(), this)
}
private fun initializeNoNetworkError() {
no_network_error_text_view.setOnClickListener {
startActivity(Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS))
}
}
private fun initializeRefreshButton() {
refresh_button.setOnClickListener {
initiateFetchingCaseNumbers()
}
}
private fun initializePullToRefresh() {
swipeRefreshLayout.setOnRefreshListener {
initiateFetchingCaseNumbers()
}
}
private fun initiateFetchingCaseNumbers() {
lifecycleScope.launch {
homeFragmentViewModel.fetchGetCaseStatistics(lifecycle)
}
}
private fun initializeObservers() {
(activity as HomeActivity?)?.run {
isAppUpdateAvailableLiveData.observe(this@HomeFragment, latestAppAvailable)
isWindowFocusChangeLiveData.observe(this@HomeFragment, refreshUiObserver)
}
}
private val latestAppAvailable = Observer<Boolean> {
isAppWithLatestVersion = it
refreshSetupCompleteOrIncompleteUi()
showCovidThanksMessage()
}
private val refreshUiObserver = Observer<Boolean> {
refreshSetupCompleteOrIncompleteUi()
if (it) {
initiateFetchingCaseNumbers()
}
}
override fun onResume() {
super.onResume()
// disable the app update reminder for now
app_update_reminder.visibility = GONE
initializePermissionViewButtonClickListeners()
initializeUploadTestDataNavigation()
initializeAppShareNavigation()
initializeHelpTopicsNavigation()
initializeChangeLanguageNavigation()
registerBroadcastListener()
refreshSetupCompleteOrIncompleteUi()
updateHealthOfficialTile()
initializeBluetoothPairingInfo()
}
private fun initializeSettingsNavigation() {
home_header_settings.setOnClickListener {
navigateToSettingsFragment()
}
}
private fun navigateToSettingsFragment() {
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToSettingsFragment())
}
private fun setAppVersionNumber() {
home_version_number.text = context?.getAppVersionNumberDetails()
}
private fun initializePermissionViewButtonClickListeners() {
bluetooth_card_view.setOnClickListener { requestBlueToothPermissionThenNextPermission() }
location_card_view.setOnClickListener { askForLocationPermission() }
battery_card_view.setOnClickListener { excludeFromBatteryOptimization() }
}
private fun initializeUploadTestDataNavigation() {
home_been_tested_button.setOnClickListener {
navigateTo(R.id.action_home_to_selfIsolate)
}
}
private fun registerBroadcastListener() {
if (!mIsBroadcastListenerRegistered) {
registerBroadcast()
}
}
private fun initializeAppShareNavigation() {
app_share.setOnClickListener {
context?.shareThisApp()
}
}
private fun initializeHelpTopicsNavigation() {
help_topics_link.setOnClickListener {
HelpFragment.anchor = null HelpFragment.anchor = null
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToHelpFragment()) findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToHelpFragment())
} }
}
private fun initializeChangeLanguageNavigation() {
change_language_link.setOnClickListener {
HelpFragment.anchor = "#other-languages"
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToHelpFragment())
}
}
private fun initializeBluetoothPairingInfo() {
home_header_no_bluetooth_pairing.text = LinkBuilder.getNoBluetoothPairingContent(requireContext())
home_header_no_bluetooth_pairing.movementMethod = LinkMovementMethod.getInstance()
}
private fun initializeDebugTestActivity() {
if (BuildConfig.ENABLE_DEBUG_SCREEN) { if (BuildConfig.ENABLE_DEBUG_SCREEN) {
view.header_background.setOnClickListener { header_background.setOnClickListener {
counter++ counter++
if (counter >= 2) { if (counter >= 2) {
counter = 0 counter = 0
@ -109,69 +221,25 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
} }
} }
} }
home_version_number.text = getString(R.string.home_version_number, BuildConfig.VERSION_NAME)
}
override fun onResume() {
super.onResume()
instanceWeakRef = WeakReference(this)
// disable the app update reminder for now
app_update_reminder.visibility = GONE
bluetooth_card_view.setOnClickListener { requestBlueToothPermissionThenNextPermission() }
location_card_view.setOnClickListener { askForLocationPermission() }
battery_card_view.setOnClickListener { excludeFromBatteryOptimization() }
home_been_tested_button.setOnClickListener {
navigateTo(R.id.action_home_to_selfIsolate)
}
home_setup_complete_share.setOnClickListener {
shareThisApp()
}
home_setup_complete_news.setOnClickListener {
goToNewsWebsite()
}
home_setup_complete_app.setOnClickListener {
goToCovidApp()
}
help_topics_link.setOnClickListener {
HelpFragment.anchor = null
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToHelpFragment())
}
change_language_link.setOnClickListener {
HelpFragment.anchor = "#other-languages"
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToHelpFragment())
}
if (!mIsBroadcastListenerRegistered) {
registerBroadcast()
}
refreshSetupCompleteOrIncompleteUi()
home_header_no_bluetooth_pairing.text = LinkBuilder.getNoBluetoothPairingContent(requireContext())
home_header_no_bluetooth_pairing.movementMethod = LinkMovementMethod.getInstance()
updateNotificationStatusTile()
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
instanceWeakRef = null unregisterAllClickListener()
unregisterBroadcastListener()
}
private fun unregisterAllClickListener() {
bluetooth_card_view.setOnClickListener(null) bluetooth_card_view.setOnClickListener(null)
location_card_view.setOnClickListener(null) location_card_view.setOnClickListener(null)
battery_card_view.setOnClickListener(null) battery_card_view.setOnClickListener(null)
home_been_tested_button.setOnClickListener(null) home_been_tested_button.setOnClickListener(null)
home_setup_complete_share.setOnClickListener(null) app_share.setOnClickListener(null)
home_setup_complete_news.setOnClickListener(null)
home_setup_complete_app.setOnClickListener(null)
help_topics_link.setOnClickListener(null) help_topics_link.setOnClickListener(null)
}
private fun unregisterBroadcastListener() {
activity?.let { activity -> activity?.let { activity ->
if (mIsBroadcastListenerRegistered) { if (mIsBroadcastListenerRegistered) {
activity.unregisterReceiver(mBroadcastListener) activity.unregisterReceiver(mBroadcastListener)
@ -182,12 +250,26 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
NetworkConnectionCheck.removeNetworkChangedListener(this)
home_root.removeAllViews() home_root.removeAllViews()
} }
override fun onDestroy() {
super.onDestroy()
unregisterObservers()
}
private fun unregisterObservers() {
(activity as HomeActivity?)?.run {
isAppUpdateAvailableLiveData.removeObserver(latestAppAvailable)
isWindowFocusChangeLiveData.removeObserver(refreshUiObserver)
}
}
private fun isDataUploadedInPast14Days(context: Context): Boolean { private fun isDataUploadedInPast14Days(context: Context): Boolean {
val isUploaded = Preference.isDataUploaded(context) val isUploaded = Preference.isDataUploaded(context)
CentralLog.d(TAG, "isDataUploadedInPast14Days : $isUploaded")
if (!isUploaded) { if (!isUploaded) {
return false return false
} }
@ -205,95 +287,86 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun refreshSetupCompleteOrIncompleteUi() { fun refreshSetupCompleteOrIncompleteUi() {
context?.let { lifecycleScope.launch(Dispatchers.Main) {
val isAllPermissionsEnabled = allPermissionsEnabled() CentralLog.d(TAG, "refreshSetupCompleteOrIncompleteUi")
val isDataUploadedInPast14Days = isDataUploadedInPast14Days(it) context?.let {
val isAllPermissionsEnabled = it.allPermissionsEnabled()
if (!isAllPermissionsEnabled) {
NotificationBuilder.clearPossibleIssueNotificationCheck()
}
val isDataUploadedInPast14Days = isDataUploadedInPast14Days(it)
val line1 = it.getString( updateSetupCompleteStatus(isAllPermissionsEnabled)
if (isAllPermissionsEnabled) { updateSetupCompleteHeaderTitle1(it, isAllPermissionsEnabled, isDataUploadedInPast14Days)
R.string.home_header_active_title updateSetupCompleteHeaderTitle2(isAllPermissionsEnabled)
} else { updateHealthOfficialTile(isDataUploadedInPast14Days)
R.string.home_header_inactive_title
}
)
val line2 = if (isDataUploadedInPast14Days) {
it.getString(R.string.home_header_uploaded_on_date, getDataUploadDateHtmlString(it)) + "<br/>"
} else {
""
} }
val line3 = it.getString(
if (isAllPermissionsEnabled) {
R.string.home_header_active_no_action_required
} else {
R.string.home_header_inactive_check_your_permissions
}
)
home_header_setup_complete_header_line_1.text = line1
home_header_setup_complete_header_line_1.setHeading()
home_header_setup_complete_header_line_1.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
val line2and3 = "$line2$line3"
home_header_setup_complete_header_line_2.text =
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
Html.fromHtml(line2and3, Html.FROM_HTML_MODE_COMPACT)
} else {
Html.fromHtml(line2and3)
}
if (isAllPermissionsEnabled) {
home_header_top_left_icon.visibility = GONE
home_header_picture_setup_complete.layoutParams.let { layoutParams ->
val size = resources.getDimensionPixelSize(R.dimen.covidsafe_lottie_size)
layoutParams.height = size
layoutParams.width = size
}
home_header_picture_setup_complete.setAnimation("spinner_home.json")
home_header_picture_setup_complete.resumeAnimation()
content_setup_incomplete_group.visibility = GONE
ContextCompat.getColor(it, R.color.lighter_green).let { bgColor ->
header_background.setBackgroundColor(bgColor)
header_background_overlap.setBackgroundColor(bgColor)
}
} else {
home_header_top_left_icon.visibility = VISIBLE
home_header_picture_setup_complete.layoutParams.let { layoutParams ->
val size = resources.getDimensionPixelSize(R.dimen.keyline_8)
layoutParams.height = size
layoutParams.width = size
}
home_header_picture_setup_complete.setImageResource(R.drawable.ic_red_cross_no_circle)
content_setup_incomplete_group.visibility = VISIBLE
updateBlueToothStatus()
updateBatteryOptimizationStatus()
updateLocationStatus()
ContextCompat.getColor(it, R.color.grey).let { bgColor ->
header_background.setBackgroundColor(bgColor)
header_background_overlap.setBackgroundColor(bgColor)
}
}
home_been_tested_button.visibility = if (isDataUploadedInPast14Days) GONE else VISIBLE
} }
} }
private fun allPermissionsEnabled(): Boolean { private fun updateSetupCompleteHeaderTitle1(context: Context, isAllPermissionsEnabled: Boolean, isDataUploadedInPast14Days: Boolean) {
val context = requireContext() showLastDataUploadedInfo(context, isDataUploadedInPast14Days)
val bluetoothEnabled = context.isBlueToothEnabled() ?: false home_header_setup_complete_header_line_1.run {
val nonBatteryOptimizationAllowed = context.isBatteryOptimizationDisabled() ?: true text = getString(getCovidActiveStatusMessage(isAllPermissionsEnabled))
val locationStatusAllowed = context.isLocationPermissionAllowed() ?: true setHeading()
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
}
return bluetoothEnabled && private fun updateSetupCompleteHeaderTitle2(isAllPermissionsEnabled: Boolean) {
nonBatteryOptimizationAllowed && if (isAllPermissionsEnabled) {
locationStatusAllowed && val isAppPerformanceRequired = !checkIsInternetConnected || !isAppWithLatestVersion
context.isLocationEnabledOnDevice()
home_header_setup_complete_header_line_2.run {
setTextColor(ContextCompat.getColor(TracerApp.AppContext, if (isAppPerformanceRequired) R.color.error_red else R.color.slate_black_2))
typeface = if (isAppPerformanceRequired) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
text = fromHtml(getString(if (isAppPerformanceRequired) R.string.improve else R.string.home_header_active_no_action_required))
}
active_status_layout.setOnClickListener {
if (isAppPerformanceRequired) {
navigateToSettingsFragment()
}
}
}
}
private fun showLastDataUploadedInfo(context: Context, isDataUploadedInPast14Days: Boolean) {
if (isDataUploadedInPast14Days) {
data_last_uploaded_layout.visibility = VISIBLE
val appendUploadInfo = getString(R.string.home_header_uploaded_on_date, getDataUploadDateHtmlString(context))
home_header_no_last_updated_text_view.text = fromHtml(appendUploadInfo)
} else {
data_last_uploaded_layout.visibility = GONE
}
}
private fun updateHealthOfficialTile(isDataUploadedInPast14Days: Boolean) {
home_been_tested_button.visibility = if (isDataUploadedInPast14Days) GONE else VISIBLE
}
private fun updateSetupCompleteStatus(isAllPermissionsEnabled: Boolean) {
if (isAllPermissionsEnabled) {
home_setup_incomplete_permissions_layout.visibility = GONE
home_setup_complete_layout.visibility = VISIBLE
} else {
home_setup_incomplete_permissions_layout.visibility = VISIBLE
home_setup_complete_layout.visibility = GONE
updateBlueToothStatus()
updateBatteryOptimizationStatus()
updateLocationStatus()
}
}
private fun getCovidActiveStatusMessage(isAllPermissionsEnabled: Boolean): Int {
return if (isAllPermissionsEnabled) {
if (isShowThanksCovidMsg(isAllPermissionsEnabled))
R.string.home_header_active_title_thanks
else R.string.home_header_active_title
} else {
R.string.home_header_inactive_title
}
} }
private fun registerBroadcast() { private fun registerBroadcast() {
@ -308,32 +381,33 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
} }
} }
private fun shareThisApp() {
val newIntent = Intent(Intent.ACTION_SEND)
newIntent.type = "text/plain"
newIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_this_app_content))
startActivity(Intent.createChooser(newIntent, null))
}
private fun updateBlueToothStatus() { private fun updateBlueToothStatus() {
requireContext().isBlueToothEnabled()?.let { requireContext().isBlueToothEnabled()?.let {
bluetooth_card_view.visibility = VISIBLE if (!it) {
bluetooth_card_view.render(formatBlueToothTitle(it), it) bluetooth_card_view_layout.visibility = VISIBLE
bluetooth_card_view.render(formatBlueToothTitle(it), it)
} else {
bluetooth_card_view_layout.visibility = GONE
}
} ?: run { } ?: run {
bluetooth_card_view.visibility = GONE bluetooth_card_view_layout.visibility = GONE
} }
} }
private fun updateBatteryOptimizationStatus() { private fun updateBatteryOptimizationStatus() {
requireContext().isBatteryOptimizationDisabled()?.let { requireContext().isBatteryOptimizationDisabled()?.let {
battery_card_view.visibility = VISIBLE if (!it) {
battery_card_view.render( battery_card_view_layout.visibility = VISIBLE
formatNonBatteryOptimizationTitle(!it), battery_card_view.render(
it, formatNonBatteryOptimizationTitle(!it),
getString(R.string.battery_optimisation_prompt) it,
) getString(R.string.battery_optimisation_prompt)
)
} else {
battery_card_view_layout.visibility = GONE
}
} ?: run { } ?: run {
battery_card_view.visibility = GONE battery_card_view_layout.visibility = GONE
} }
} }
@ -341,11 +415,21 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
requireContext().isLocationPermissionAllowed()?.let { requireContext().isLocationPermissionAllowed()?.let {
val locationWorking = it && requireContext().isLocationEnabledOnDevice() val locationWorking = it && requireContext().isLocationEnabledOnDevice()
val locationOffPrompts = getString(R.string.home_set_location_why) val locationOffPrompts = getString(R.string.home_set_location_why)
if (!locationWorking) {
location_card_view.visibility = VISIBLE location_card_view_layout.visibility = VISIBLE
location_card_view.render(formatLocationTitle(locationWorking), locationWorking, locationOffPrompts) location_card_view.render(formatLocationTitle(locationWorking), locationWorking, locationOffPrompts)
} else {
location_card_view_layout.visibility = GONE
}
} ?: run { } ?: run {
location_card_view.visibility = GONE location_card_view_layout.visibility = GONE
}
}
private fun updateHealthOfficialTile() {
home_been_tested_button.run {
setTitleTextTypeFace(Typeface.DEFAULT_BOLD)
setContentTextTypeFace(Typeface.DEFAULT_BOLD)
} }
} }
@ -361,10 +445,6 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
return resources.getString(R.string.home_non_battery_optimization_permission, getEnabledOrDisabledString(on)) return resources.getString(R.string.home_non_battery_optimization_permission, getEnabledOrDisabledString(on))
} }
private fun formatPushNotificationTitle(on: Boolean): String {
return resources.getString(R.string.home_push_notification_permission, getPermissionEnabledTitle(on))
}
private fun getPermissionEnabledTitle(on: Boolean): String { private fun getPermissionEnabledTitle(on: Boolean): String {
return resources.getString(if (on) R.string.home_permission_on else R.string.home_permission_off) return resources.getString(if (on) R.string.home_permission_on else R.string.home_permission_off)
} }
@ -373,34 +453,6 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
return resources.getString(if (isEnabled) R.string.enabled else R.string.disabled) return resources.getString(if (isEnabled) R.string.enabled else R.string.disabled)
} }
private fun goToNewsWebsite() {
val url = getString(R.string.home_set_complete_external_link_news_url)
try {
Intent(Intent.ACTION_VIEW).run {
data = Uri.parse(url)
startActivity(this)
}
} catch (e: ActivityNotFoundException) {
val intent = Intent(activity, WebViewActivity::class.java)
intent.putExtra(WebViewActivity.URL_ARG, url)
startActivity(intent)
}
}
private fun goToCovidApp() {
val url = getString(R.string.home_set_complete_external_link_app_url)
try {
Intent(Intent.ACTION_VIEW).run {
data = Uri.parse(url)
startActivity(this)
}
} catch (e: ActivityNotFoundException) {
val intent = Intent(activity, WebViewActivity::class.java)
intent.putExtra(WebViewActivity.URL_ARG, url)
startActivity(intent)
}
}
override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) { override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
if (requestCode == LOCATION && EasyPermissions.somePermissionPermanentlyDenied(this, listOf(Manifest.permission.ACCESS_COARSE_LOCATION))) { if (requestCode == LOCATION && EasyPermissions.somePermissionPermanentlyDenied(this, listOf(Manifest.permission.ACCESS_COARSE_LOCATION))) {
AppSettingsDialog.Builder(this).build().show() AppSettingsDialog.Builder(this).build().show()
@ -418,111 +470,44 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
} }
fun updateConnectionTile(isInternetConnected: Boolean) { private fun isShowThanksCovidMsg(isAllPermissionsEnabled: Boolean): Boolean {
// called on IO thread; run the UI logic on UI thread return isAllPermissionsEnabled && checkIsInternetConnected && isAppWithLatestVersion && NotificationBuilder.isShowPossibleIssueNotification()
GlobalScope.launch(Dispatchers.Main) { }
CentralLog.d(TAG, "updateConnectionTile() isInternetConnected = $isInternetConnected")
var visibility = if (isInternetConnected) GONE else VISIBLE private fun showCovidThanksMessage() {
activity?.runOnUiThread {
// don't display the tile when there's permission not enabled context?.let {
if (!allPermissionsEnabled()) { home_header_setup_complete_header_line_1.text = it.getString(getCovidActiveStatusMessage(it.allPermissionsEnabled()))
visibility = GONE
}
improve_performance_card.visibility = visibility
internet_connection_tile.visibility = visibility
if (visibility == VISIBLE) {
internet_connection_tile.setOnClickListener {
// startActivity(Intent(ACTION_DATA_ROAMING_SETTINGS))
// startActivity(Intent(ACTION_WIFI_SETTINGS))
startActivity(Intent(requireContext(), InternetConnectionIssuesActivity::class.java))
}
} }
} }
} }
fun updateMessageTiles(messagesResponse: MessagesResponse) { override fun onNetworkStatusChanged(isAvailable: Boolean) {
GlobalScope.launch(Dispatchers.Main) { CentralLog.d(TAG, "onNetworkStatusChanged: $checkIsInternetConnected $isAvailable")
improve_performance_card_linear_layout.children.forEach { checkIsInternetConnected = isAvailable
if (it != internet_connection_tile && it != improve_performance_title) { refreshSetupCompleteOrIncompleteUi()
improve_performance_card_linear_layout.removeView(it)
}
}
if (!messagesResponse.messages.isNullOrEmpty()) { initiateFetchingCaseNumbers()
improve_performance_card.visibility = VISIBLE }
messagesResponse.messages.forEach { message -> private val mBroadcastListener: BroadcastReceiver = object : BroadcastReceiver() {
ExternalLinkCard(requireContext(), null, 0).also { override fun onReceive(context: Context, intent: Intent) {
it.layoutParams = LinearLayout.LayoutParams( val action = intent.action
LinearLayout.LayoutParams.MATCH_PARENT, if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
LinearLayout.LayoutParams.WRAP_CONTENT when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
).also { layoutParams -> BluetoothAdapter.STATE_OFF -> {
layoutParams.setMargins(0, resources.getDimensionPixelSize(R.dimen.divider_height), 0, 0) bluetooth_card_view.render(formatBlueToothTitle(false), false)
} refreshSetupCompleteOrIncompleteUi()
}
it.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white)) BluetoothAdapter.STATE_TURNING_OFF -> {
bluetooth_card_view.render(formatBlueToothTitle(false), false)
it.setMessage(message) refreshSetupCompleteOrIncompleteUi()
it.setErrorTextColor() }
BluetoothAdapter.STATE_ON -> {
improve_performance_card_linear_layout.addView(it) refreshSetupCompleteOrIncompleteUi()
} }
} }
} else {
improve_performance_card.visibility = GONE
} }
} }
} }
private fun updateNotificationStatusTile() {
var title = ""
var body = ""
if (NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()
) {
notification_status_link.setTopRightIcon(R.drawable.ic_green_tick)
title = getString(R.string.home_set_complete_external_link_notifications_title_iOS)
body = getString(R.string.NotificationsEnabledBlurb)
} else {
notification_status_link.setTopRightIcon(R.drawable.ic_red_cross)
title = getString(R.string.home_set_complete_external_link_notifications_title_iOS_off)
body = getString(R.string.NotificationsEnabledBlurb)
}
notification_status_link.setTitleBodyAndClickCallback(title, body) {
openAppNotificationSettings()
}
notification_status_link.setColorForContentWithAction()
}
private fun openAppNotificationSettings() {
val context = requireContext()
val intent = Intent().apply {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
action = "android.settings.APP_NOTIFICATION_SETTINGS"
putExtra("app_package", context.packageName)
putExtra("app_uid", context.applicationInfo.uid)
}
else -> {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
addCategory(Intent.CATEGORY_DEFAULT)
data = Uri.parse("package:" + context.packageName)
}
}
}
context.startActivity(intent)
}
} }

View file

@ -0,0 +1,88 @@
package au.gov.health.covidsafe.ui.home
import android.app.Application
import android.content.Context
import androidx.lifecycle.*
import au.gov.health.covidsafe.factory.RetrofitServiceGenerator
import au.gov.health.covidsafe.interactor.usecase.GetCaseStatisticsUseCase
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.response.CaseStatisticResponse
import au.gov.health.covidsafe.extensions.isInternetAvailable
import au.gov.health.covidsafe.networking.service.AwsClient
import au.gov.health.covidsafe.preference.Preference
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private const val TAG = "HomeFragmentViewModel"
class HomeFragmentViewModel(application: Application) : AndroidViewModel(application) {
val caseNumberDataState = MutableLiveData<CaseNumbersState>()
val caseStatisticsLiveData = MutableLiveData<CaseStatisticResponse>()
val isRefreshing = MutableLiveData<Boolean>()
val awsClient: AwsClient by lazy {
RetrofitServiceGenerator.createService(AwsClient::class.java)
}
fun fetchGetCaseStatistics(lifecycle: Lifecycle) {
if(caseNumberDataState.value != CaseNumbersState.LOADING) {
caseNumberDataState.value = CaseNumbersState.LOADING
viewModelScope.launch(Dispatchers.IO) {
GetCaseStatisticsUseCase(awsClient, lifecycle, getApplication()).invoke("",
onSuccess = {
updateOnSuccess(it)
},
onFailure = {
CentralLog.e(TAG, "On Failure: ${it.message}")
showErrorMessage()
}
)
}
} else {
isRefreshing.value = false
}
}
private fun updateOnSuccess(caseStatisticResponse: CaseStatisticResponse) {
viewModelScope.launch {
isRefreshing.value = false
CentralLog.d(TAG, "On Success: ${caseStatisticResponse.vic?.totalCases}")
caseNumberDataState.value = CaseNumbersState.SUCCESS
cacheCaseStatisticDataInPersistent(caseStatisticResponse)
caseStatisticsLiveData.value = caseStatisticResponse
}
}
private fun showErrorMessage() {
viewModelScope.launch {
isRefreshing.value = false
val context = getApplication() as Context
caseStatisticsLiveData.value = getCachedCaseStatisticDataFromPersistent(context)
if (context.isInternetAvailable()) {
caseNumberDataState.value = CaseNumbersState.ERROR_UNKNOWN
} else {
caseNumberDataState.value = CaseNumbersState.ERROR_NO_NETWORK
}
}
}
private fun cacheCaseStatisticDataInPersistent(caseStatisticResponse: CaseStatisticResponse) {
Preference.putCaseStatisticData(getApplication(), Gson().toJson(caseStatisticResponse))
}
private fun getCachedCaseStatisticDataFromPersistent(context: Context): CaseStatisticResponse? {
val caseStatisticString = Preference.getCaseStatisticData(context)
return caseStatisticString?.let {
val caseStatisticData = Gson().fromJson(it, CaseStatisticResponse::class.java)
CentralLog.d(TAG, "On Preference: ${caseStatisticData.vic?.totalCases}")
caseStatisticData
}
}
}

View file

@ -1,10 +0,0 @@
package au.gov.health.covidsafe.ui.home
import androidx.lifecycle.LifecycleObserver
class HomePresenter(fragment: HomeFragment) : LifecycleObserver {
init {
fragment.lifecycle.addObserver(this)
}
}

View file

@ -3,6 +3,7 @@ package au.gov.health.covidsafe.ui.home.view
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.TypedArray import android.content.res.TypedArray
import android.graphics.Typeface
import android.net.Uri import android.net.Uri
import android.text.Html import android.text.Html
import android.util.AttributeSet import android.util.AttributeSet
@ -26,24 +27,32 @@ class ExternalLinkCard @JvmOverloads constructor(
attrs?.let { attrs?.let {
val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.ExternalLinkCard) val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.ExternalLinkCard)
val icon = a.getDrawable(R.styleable.ExternalLinkCard_external_linkCard_icon) val startIcon = a.getDrawable(R.styleable.ExternalLinkCard_external_linkCard_start_icon)
val iconVisible = a.getBoolean(R.styleable.ExternalLinkCard_external_linkCard_icon_visible, true) val startIconVisible = a.getBoolean(R.styleable.ExternalLinkCard_external_linkCard_start_icon_visible, true)
val title = a.getString(R.styleable.ExternalLinkCard_external_linkCard_title) val title = a.getString(R.styleable.ExternalLinkCard_external_linkCard_title)
val content = a.getString(R.styleable.ExternalLinkCard_external_linkCard_content) val content = a.getString(R.styleable.ExternalLinkCard_external_linkCard_content)
val padding = a.getDimension(R.styleable.ExternalLinkCard_external_linkCard_icon_padding, 0f).toInt() val padding = a.getDimension(R.styleable.ExternalLinkCard_external_linkCard_start_icon_padding, 0f).toInt()
val iconBackground = a.getResourceId(R.styleable.ExternalLinkCard_external_linkCard_icon_background, R.color.transparent) val contentPadding = a.getDimension(R.styleable.ExternalLinkCard_external_linkCard_content_padding, 0f).toInt()
val iconBackground = a.getResourceId(R.styleable.ExternalLinkCard_external_linkCard_start_icon_background, R.color.transparent)
val textColorResId = a.getResourceId(R.styleable.ExternalLinkCard_external_linkCard_text_color, R.color.slack_black) val textColorResId = a.getResourceId(R.styleable.ExternalLinkCard_external_linkCard_text_color, R.color.slack_black)
val endIconVisible = a.getBoolean(R.styleable.ExternalLinkCard_external_linkCard_end_icon_visible, true)
val textColor = ContextCompat.getColor(context, textColorResId) val textColor = ContextCompat.getColor(context, textColorResId)
external_link_round_image.setImageDrawable(icon) external_link_round_image.setImageDrawable(startIcon)
external_link_round_image.visibility = if (iconVisible) View.VISIBLE else View.GONE external_link_round_image.visibility = if (startIconVisible) View.VISIBLE else View.GONE
external_link_round_image.setBackgroundResource(iconBackground) external_link_round_image.setBackgroundResource(iconBackground)
external_link_round_image.setPadding(padding, padding, padding, padding) external_link_round_image.setPadding(padding, padding, padding, padding)
external_link_headline.text = title external_link_headline.text = title
external_link_content.text = content external_link_content.text = content
if (contentPadding > 0) {
external_link_content.setPadding(0, contentPadding, 0, 0)
}
external_link_end_image_view.visibility = if (endIconVisible) View.VISIBLE else View.GONE
setTextColor(textColor) setTextColor(textColor)
a.recycle() a.recycle()
} }
} }
@ -53,19 +62,33 @@ class ExternalLinkCard @JvmOverloads constructor(
external_link_content.setTextColor(textColor) external_link_content.setTextColor(textColor)
val icChevron = val icChevron =
if (textColor == ContextCompat.getColor(context, R.color.error)) { when (textColor) {
R.drawable.ic_chevron_right_red ContextCompat.getColor(context, R.color.error_red) -> {
} else { R.drawable.ic_chevron_right_red
R.drawable.ic_chevron_right }
ContextCompat.getColor(context, R.color.white) -> {
R.drawable.ic_chevron_right_white
}
else -> {
R.drawable.ic_chevron_right_black
}
} }
next_arrow.setImageDrawable( external_link_end_image_view.setImageDrawable(
ContextCompat.getDrawable(context, icChevron).also { ContextCompat.getDrawable(context, icChevron).also {
it?.isAutoMirrored = true it?.isAutoMirrored = true
} }
) )
} }
fun setTitleTextTypeFace(typeface: Typeface) {
external_link_headline.typeface = typeface
}
fun setContentTextTypeFace(typeface: Typeface) {
external_link_content.typeface = typeface
}
fun setMessage(message: Message) { fun setMessage(message: Message) {
external_link_round_image.visibility = View.GONE external_link_round_image.visibility = View.GONE
@ -79,6 +102,10 @@ class ExternalLinkCard @JvmOverloads constructor(
} }
} }
fun setTitleText(title: String) {
external_link_headline.text = title
}
fun setTitleBodyAndClickCallback(title: String, body: String, clickCallBack: () -> Unit) { fun setTitleBodyAndClickCallback(title: String, body: String, clickCallBack: () -> Unit) {
external_link_headline.text = title external_link_headline.text = title
external_link_content.text = body external_link_content.text = body
@ -89,11 +116,11 @@ class ExternalLinkCard @JvmOverloads constructor(
} }
fun setErrorTextColor() { fun setErrorTextColor() {
setTextColor(ContextCompat.getColor(context, R.color.error)) setTextColor(ContextCompat.getColor(context, R.color.error_red))
} }
fun setTopRightIcon(iconResID: Int) { fun setTopRightIcon(iconResID: Int) {
next_arrow.setImageResource(iconResID) external_link_end_image_view.setImageResource(iconResID)
} }
fun setColorForContentWithAction() { fun setColorForContentWithAction() {

View file

@ -9,7 +9,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import kotlinx.android.synthetic.main.view_card_permission_card.view.* import kotlinx.android.synthetic.main.view_card_permission_card.view.*
class PermissionStatusCard @JvmOverloads constructor( class PermissionStatusCard @JvmOverloads constructor(
@ -32,7 +32,7 @@ class PermissionStatusCard @JvmOverloads constructor(
} }
fun render(title: String, correct: Boolean, body: String? = null) { fun render(title: String, correct: Boolean, body: String? = null) {
val errorTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.error) val errorTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.error_red)
val normalTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.slack_black) val normalTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.slack_black)
permission_icon.isSelected = correct permission_icon.isSelected = correct

View file

@ -7,9 +7,9 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
const val VIEW_TYPE_GROUP_TITLE = 1 const val VIEW_TYPE_GROUP_TITLE = 1
const val VIEW_TYPE_COUNTRY = 2 const val VIEW_TYPE_COUNTRY = 2

View file

@ -8,11 +8,11 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.HasBlockingState import au.gov.health.covidsafe.ui.base.HasBlockingState
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.ui.PagerContainer import au.gov.health.covidsafe.ui.base.PagerContainer
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import com.github.razir.progressbutton.bindProgressButton import com.github.razir.progressbutton.bindProgressButton
import com.github.razir.progressbutton.hideProgress import com.github.razir.progressbutton.hideProgress
import com.github.razir.progressbutton.showProgress import com.github.razir.progressbutton.showProgress

View file

@ -9,8 +9,8 @@ import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.links.LinkBuilder import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_data_privacy.* import kotlinx.android.synthetic.main.fragment_data_privacy.*
import kotlinx.android.synthetic.main.fragment_data_privacy.root import kotlinx.android.synthetic.main.fragment_data_privacy.root
import kotlinx.android.synthetic.main.fragment_data_privacy.view.* import kotlinx.android.synthetic.main.fragment_data_privacy.view.*

View file

@ -16,12 +16,12 @@ import androidx.annotation.NavigationRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.CountryCodeSelectionActivity import au.gov.health.covidsafe.ui.onboarding.CountryCodeSelectionActivity
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_CALLING_CODE import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_CALLING_CODE
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_CHALLENGE_NAME import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_CHALLENGE_NAME
@ -50,7 +50,7 @@ class EnterNumberFragment : PagerChildFragment() {
private var callingCode: Int = 0 private var callingCode: Int = 0
private var nationalFlagResID: Int = 0 private var nationalFlagResID: Int = 0
private val errorTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.error) private val errorTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.error_red)
private val normalTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.slack_black) private val normalTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.slack_black)
private fun updateSelectedCountry() { private fun updateSelectedCountry() {

View file

@ -4,7 +4,7 @@ package au.gov.health.covidsafe.ui.onboarding.fragment.enternumber
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.extensions.isInternetAvailable import au.gov.health.covidsafe.extensions.isInternetAvailable
import au.gov.health.covidsafe.factory.NetworkFactory import au.gov.health.covidsafe.factory.NetworkFactory

View file

@ -1,6 +1,7 @@
package au.gov.health.covidsafe.ui.onboarding.fragment.enterpin package au.gov.health.covidsafe.ui.onboarding.fragment.enterpin
import android.app.AlertDialog import android.app.AlertDialog
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
@ -9,15 +10,14 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import androidx.annotation.NavigationRes import androidx.annotation.NavigationRes
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.Utils.announceForAccessibility import au.gov.health.covidsafe.ui.utils.Utils.announceForAccessibility
import au.gov.health.covidsafe.extensions.toHyperlink import au.gov.health.covidsafe.extensions.toHyperlink
import au.gov.health.covidsafe.links.LinkBuilder import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import com.atlassian.mobilekit.module.core.utils.SystemUtils import com.atlassian.mobilekit.module.core.utils.SystemUtils
import kotlinx.android.synthetic.main.fragment_enter_pin.* import kotlinx.android.synthetic.main.fragment_enter_pin.*
import kotlinx.android.synthetic.main.fragment_enter_pin.view.* import kotlinx.android.synthetic.main.fragment_enter_pin.view.*
@ -107,23 +107,15 @@ class EnterPinFragment : PagerChildFragment() {
} else { } else {
"$numberOfSecondsInt" "$numberOfSecondsInt"
} }
enter_pin_timer_value?.text = "$numberOfMinsInt:$finalNumberOfSecondsString" enter_pin_timer_value?.text = "$numberOfMinsInt:$finalNumberOfSecondsString"
} }
override fun onFinish() { override fun onFinish() {
enter_pin_timer_value?.text = "0:00" enter_pin_timer_value?.text = "0:00"
enter_pin_resend_pin.isEnabled = true setupTimer(false)
activity?.let {
enter_pin_resend_pin.setLinkTextColor(ContextCompat.getColor(it, R.color.hyperlink_enabled))
}
} }
} }
stopWatch?.start() stopWatch?.start()
enter_pin_resend_pin.isEnabled = false setupTimer(true)
activity?.let {
enter_pin_resend_pin.setLinkTextColor(ContextCompat.getColor(it, R.color.hyperlink_disabled))
}
} }
fun resetTimer() { fun resetTimer() {
@ -200,4 +192,10 @@ class EnterPinFragment : PagerChildFragment() {
.setPositiveButton(android.R.string.yes, null).show() .setPositiveButton(android.R.string.yes, null).show()
} }
private fun setupTimer(isTimerOn: Boolean) {
enter_pin_resend_pin.visibility = if (isTimerOn) View.GONE else View.VISIBLE
enter_pin_timer_label.text = if (isTimerOn) context?.getString(R.string.enter_pin_timer_expire) else context?.getString(R.string.CodeHasExpired)
enter_pin_timer_label.setTextColor(if (isTimerOn) Color.BLACK else Color.RED)
enter_pin_timer_value.visibility = if (isTimerOn) View.VISIBLE else View.INVISIBLE
}
} }

View file

@ -4,7 +4,7 @@ import android.text.TextUtils
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.extensions.isInternetAvailable import au.gov.health.covidsafe.extensions.isInternetAvailable
import au.gov.health.covidsafe.factory.NetworkFactory import au.gov.health.covidsafe.factory.NetworkFactory
import au.gov.health.covidsafe.interactor.usecase.GetOnboardingOtp import au.gov.health.covidsafe.interactor.usecase.GetOnboardingOtp

View file

@ -9,8 +9,8 @@ import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.links.LinkBuilder import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_how_it_works.* import kotlinx.android.synthetic.main.fragment_how_it_works.*
import kotlinx.android.synthetic.main.fragment_how_it_works.view.* import kotlinx.android.synthetic.main.fragment_how_it_works.view.*

View file

@ -7,8 +7,8 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_intro.* import kotlinx.android.synthetic.main.fragment_intro.*
class IntroductionFragment : PagerChildFragment() { class IntroductionFragment : PagerChildFragment() {

View file

@ -7,11 +7,11 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_permission.root import kotlinx.android.synthetic.main.fragment_permission.root
import kotlinx.android.synthetic.main.fragment_permission_device_name.* import kotlinx.android.synthetic.main.fragment_permission_device_name.*

View file

@ -4,6 +4,7 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.PowerManager import android.os.PowerManager
@ -13,13 +14,13 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import au.gov.health.covidsafe.HomeActivity import au.gov.health.covidsafe.HomeActivity
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.extensions.* import au.gov.health.covidsafe.extensions.*
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_permission.* import kotlinx.android.synthetic.main.fragment_permission.*
import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions
@ -74,7 +75,7 @@ class PermissionFragment : PagerChildFragment(), EasyPermissions.PermissionCallb
private fun navigateToNextPage() { private fun navigateToNextPage() {
navigationStarted = false navigationStarted = false
if (hasAllPermissionsAndBluetoothOn()) { if (hasAllPermissionsAndBluetoothOn()) {
navigateTo(R.id.action_permissionFragment_to_permissionDeviceNameFragment ) navigateTo(R.id.action_permissionFragment_to_permissionDeviceNameFragment)
} else { } else {
navigateToMainActivity() navigateToMainActivity()
} }
@ -82,9 +83,25 @@ class PermissionFragment : PagerChildFragment(), EasyPermissions.PermissionCallb
private fun hasAllPermissionsAndBluetoothOn(): Boolean { private fun hasAllPermissionsAndBluetoothOn(): Boolean {
val context = TracerApp.AppContext val context = TracerApp.AppContext
return context.isBlueToothEnabled() == true return context.isBlueToothEnabled() == true && checkAllRequiredPermissions() && checkIgnoreBatteryOptimization()
&& requiredPermissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } }
&& ContextCompat.getSystemService(context, PowerManager::class.java)?.isIgnoringBatteryOptimizations(context.packageName) ?: true
private fun checkAllRequiredPermissions(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val context = TracerApp.AppContext
requiredPermissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED }
} else {
true
}
}
private fun checkIgnoreBatteryOptimization(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val context = TracerApp.AppContext
ContextCompat.getSystemService(context, PowerManager::class.java)?.isIgnoringBatteryOptimizations(context.packageName) ?: true
} else {
true
}
} }
private fun navigateToMainActivity() { private fun navigateToMainActivity() {

View file

@ -11,8 +11,8 @@ import au.gov.health.covidsafe.HomeActivity
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.links.LinkBuilder import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_permission_success.* import kotlinx.android.synthetic.main.fragment_permission_success.*
class PermissionSuccessFragment : PagerChildFragment() { class PermissionSuccessFragment : PagerChildFragment() {

View file

@ -13,12 +13,12 @@ import android.widget.ArrayAdapter
import android.widget.TextView.OnEditorActionListener import android.widget.TextView.OnEditorActionListener
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment
import kotlinx.android.synthetic.main.fragment_personal_details.* import kotlinx.android.synthetic.main.fragment_personal_details.*
import java.util.regex.Pattern import java.util.regex.Pattern
@ -27,7 +27,7 @@ import java.util.regex.Pattern
private const val TAG = "PersonalDetailsFragment" private const val TAG = "PersonalDetailsFragment"
private val POST_CODE_REGEX = Pattern.compile("^(?:(?:[2-8]\\d|9[0-7]|0?[28]|0?9(?=09))(?:\\d{2}))$") private val POST_CODE_REGEX = Pattern.compile("^(?:(?:[2-8]\\d|9[0-7]|0?[28]|0?9(?=09))(?:\\d{2}))$")
private val NAME_REGEX = Pattern.compile("^[A-Za-z0-9\\u00C0-\\u017F][A-Za-z'0-9\\-\\u00C0-\\u017F ]{0,80}\$") private val NAME_REGEX = Pattern.compile("^[.A-Za-z0-9\\u00C0-\\u017F][.A-Za-z'0-9\\-\\u00C0-\\u017F ]{0,80}\$")
class PersonalDetailsFragment : PagerChildFragment() { class PersonalDetailsFragment : PagerChildFragment() {
@ -121,7 +121,7 @@ class PersonalDetailsFragment : PagerChildFragment() {
} }
} }
personal_details_name.setOnEditorActionListener(OnEditorActionListener { textView, actionId, event -> personal_details_name.setOnEditorActionListener(OnEditorActionListener { textView, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_NEXT) { if (actionId == EditorInfo.IME_ACTION_NEXT) {
hideKeyboard() hideKeyboard()
textView.clearFocus() textView.clearFocus()

View file

@ -7,9 +7,9 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.PagerContainer import au.gov.health.covidsafe.ui.base.PagerContainer
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_registration_consent.* import kotlinx.android.synthetic.main.fragment_registration_consent.*
class RegistrationConsentFragment : PagerChildFragment() { class RegistrationConsentFragment : PagerChildFragment() {

View file

@ -8,8 +8,8 @@ import android.view.accessibility.AccessibilityEvent
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment
import kotlinx.android.synthetic.main.fragment_under_sixteen.* import kotlinx.android.synthetic.main.fragment_under_sixteen.*

View file

@ -0,0 +1,226 @@
package au.gov.health.covidsafe.ui.settings
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import au.gov.health.covidsafe.HomeActivity
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.extensions.allPermissionsEnabled
import au.gov.health.covidsafe.extensions.getAppVersionNumberDetails
import au.gov.health.covidsafe.extensions.shareThisApp
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.response.MessagesResponse
import au.gov.health.covidsafe.ui.base.BaseFragment
import au.gov.health.covidsafe.ui.connection.InternetConnectionIssuesActivity
import au.gov.health.covidsafe.ui.home.HelpFragment
import au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
import au.gov.health.covidsafe.utils.NetworkConnectionCheck
import com.atlassian.mobilekit.module.feedback.FeedbackModule
import kotlinx.android.synthetic.main.fragment_settings.*
import kotlinx.android.synthetic.main.fragment_settings_external_link.*
import kotlinx.android.synthetic.main.view_covid_share_tile.*
import kotlinx.android.synthetic.main.view_help_topics_tile.*
import kotlinx.android.synthetic.main.view_settings_improve_app_performance_tile.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
private const val TAG = "SettingsFragment"
class SettingsFragment : BaseFragment(), NetworkConnectionCheck.NetworkConnectionListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_settings, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeToolbarNavigation()
initializeObservers()
NetworkConnectionCheck.addNetworkChangedListener(requireContext(), this)
}
private fun initializeToolbarNavigation() {
settings_toolbar.setNavigationOnClickListener { findNavController().popBackStack() }
}
private fun initializeObservers() {
(activity as HomeActivity?)?.run {
appUpdateAvailableMessageResponseLiveData.observe(viewLifecycleOwner, latestAppAvailable)
}
}
override fun onResume() {
super.onResume()
updateNotificationStatusTile()
initializeHelpTopicsNavigation()
initializeSupportNavigation()
initializeAppShareNavigation()
setAppVersionNumber()
}
private fun initializeHelpTopicsNavigation() {
help_topics_link.setOnClickListener {
HelpFragment.anchor = null
findNavController().navigate(SettingsFragmentDirections.actionSettingsFragmentToHelpFragment())
}
}
private fun initializeSupportNavigation() {
settings_support_view.setOnClickListener {
FeedbackModule.showFeedbackScreen()
}
}
private fun initializeAppShareNavigation() {
app_share.setOnClickListener {
context?.shareThisApp()
}
}
private fun setAppVersionNumber() {
settings_version_number.text = context?.getAppVersionNumberDetails()
}
private val latestAppAvailable = Observer<MessagesResponse> {
updateMessageTiles(it)
}
override fun onDestroyView() {
super.onDestroyView()
NetworkConnectionCheck.removeNetworkChangedListener(this)
removeObservers()
}
private fun removeObservers() {
(activity as HomeActivity?)?.run {
appUpdateAvailableMessageResponseLiveData.removeObserver(latestAppAvailable)
}
}
private fun updateNotificationStatusTile() {
val title: String
val body: String
if (NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()) {
notification_status_link.setTopRightIcon(R.drawable.ic_green_tick)
title = getString(R.string.home_set_complete_external_link_notifications_title_iOS)
body = getString(R.string.NotificationsEnabledBlurb)
} else {
notification_status_link.setTopRightIcon(R.drawable.ic_white_background_red_error)
title = getString(R.string.home_set_complete_external_link_notifications_title_iOS_off)
body = getString(R.string.NotificationsEnabledBlurb)
}
notification_status_link.setTitleBodyAndClickCallback(title, body) {
openAppNotificationSettings()
}
notification_status_link.setColorForContentWithAction()
}
private fun updateInternetConnectionTile(isInternetConnected: Boolean) {
// called on IO thread; run the UI logic on UI thread
GlobalScope.launch(Dispatchers.Main) {
context?.let {
CentralLog.d(TAG, "updateConnectionTile() isInternetConnected = $isInternetConnected")
var visibility = if (isInternetConnected) View.GONE else View.VISIBLE
// don't display the tile when there's permission not enabled
val isAllPermissionsEnabled = it.allPermissionsEnabled()
if (!isAllPermissionsEnabled) {
visibility = View.GONE
}
improve_performance_group.visibility = visibility
internet_connection_tile.visibility = visibility
if (visibility == View.VISIBLE) {
internet_connection_tile.setOnClickListener {
startActivity(Intent(requireContext(), InternetConnectionIssuesActivity::class.java))
}
}
}
}
}
private fun updateMessageTiles(messagesResponse: MessagesResponse) {
GlobalScope.launch(Dispatchers.Main) {
improve_performance_card_linear_layout.children.forEach {
if (it != internet_connection_tile && it != improve_performance_title) {
improve_performance_card_linear_layout.removeView(it)
}
}
if (!messagesResponse.messages.isNullOrEmpty()) {
improve_performance_group.visibility = View.VISIBLE
messagesResponse.messages.forEach { message ->
ExternalLinkCard(requireContext(), null, 0).also {
it.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).also { layoutParams ->
layoutParams.setMargins(0, resources.getDimensionPixelSize(R.dimen.divider_height), 0, 0)
}
it.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
it.setMessage(message)
it.setErrorTextColor()
improve_performance_card_linear_layout.addView(it)
}
}
} else {
improve_performance_group.visibility = View.GONE
}
}
}
private fun openAppNotificationSettings() {
val context = requireContext()
val intent = Intent().apply {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
action = "android.settings.APP_NOTIFICATION_SETTINGS"
putExtra("app_package", context.packageName)
putExtra("app_uid", context.applicationInfo.uid)
}
else -> {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
addCategory(Intent.CATEGORY_DEFAULT)
data = Uri.parse("package:" + context.packageName)
}
}
}
context.startActivity(intent)
}
override fun onNetworkStatusChanged(isAvailable: Boolean) {
CentralLog.d(TAG, "Settings onNetworkStatusChanged: $isAvailable")
updateInternetConnectionTile(isAvailable)
}
}

View file

@ -1,4 +1,4 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.ui.splash
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -10,12 +10,11 @@ import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import au.gov.health.covidsafe.HomeActivity
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.ui.onboarding.OnboardingActivity import au.gov.health.covidsafe.ui.onboarding.OnboardingActivity
import au.gov.health.covidsafe.ui.splash.SplashNavigationEvent
import au.gov.health.covidsafe.ui.splash.SplashViewModel
import au.gov.health.covidsafe.ui.splash.SplashViewModelFactory
import kotlinx.android.synthetic.main.activity_splash.* import kotlinx.android.synthetic.main.activity_splash.*
import java.util.*
class SplashActivity : AppCompatActivity() { class SplashActivity : AppCompatActivity() {

View file

@ -9,8 +9,8 @@ import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.ui.PagerContainer import au.gov.health.covidsafe.ui.base.PagerContainer
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import com.github.razir.progressbutton.hideProgress import com.github.razir.progressbutton.hideProgress
import com.github.razir.progressbutton.showProgress import com.github.razir.progressbutton.showProgress
import kotlinx.android.synthetic.main.fragment_upload_master.* import kotlinx.android.synthetic.main.fragment_upload_master.*
@ -26,9 +26,8 @@ class UploadContainerFragment : Fragment(), PagerContainer {
activity?.onBackPressed() activity?.onBackPressed()
} }
if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) { if (resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL) {
toolbar.navigationIcon = toolbar.navigationIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up_rtl)
requireContext().getDrawable(R.drawable.ic_up_rtl)
} }
} }
@ -51,13 +50,13 @@ class UploadContainerFragment : Fragment(), PagerContainer {
} }
} }
override fun refreshButton(uploadButtonLayout: UploadButtonLayout) { override fun refreshButton(updateButtonLayout: UploadButtonLayout) {
when (uploadButtonLayout) { when (updateButtonLayout) {
is UploadButtonLayout.ContinueLayout -> { is UploadButtonLayout.ContinueLayout -> {
upload_continue.setOnClickListener { upload_continue.setOnClickListener {
uploadButtonLayout.buttonListener?.invoke() updateButtonLayout.buttonListener?.invoke()
} }
upload_continue.setText(uploadButtonLayout.buttonText) upload_continue.setText(updateButtonLayout.buttonText)
upload_continue.visibility = VISIBLE upload_continue.visibility = VISIBLE
upload_answerNo.setOnClickListener(null) upload_answerNo.setOnClickListener(null)
upload_answerYes.setOnClickListener(null) upload_answerYes.setOnClickListener(null)
@ -68,10 +67,10 @@ class UploadContainerFragment : Fragment(), PagerContainer {
upload_continue.setOnClickListener(null) upload_continue.setOnClickListener(null)
upload_continue.visibility = GONE upload_continue.visibility = GONE
upload_answerNo.setOnClickListener { upload_answerNo.setOnClickListener {
uploadButtonLayout.buttonNoListener.invoke() updateButtonLayout.buttonNoListener.invoke()
} }
upload_answerYes.setOnClickListener { upload_answerYes.setOnClickListener {
uploadButtonLayout.buttonYesListener.invoke() updateButtonLayout.buttonYesListener.invoke()
} }
upload_answerNo.visibility = VISIBLE upload_answerNo.visibility = VISIBLE
upload_answerYes.visibility = VISIBLE upload_answerYes.visibility = VISIBLE

View file

@ -7,8 +7,8 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_upload_finished.* import kotlinx.android.synthetic.main.fragment_upload_finished.*
class UploadFinishedFragment : PagerChildFragment() { class UploadFinishedFragment : PagerChildFragment() {

View file

@ -7,8 +7,8 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_upload_initial.* import kotlinx.android.synthetic.main.fragment_upload_initial.*
import kotlinx.android.synthetic.main.fragment_upload_page_4.root import kotlinx.android.synthetic.main.fragment_upload_page_4.root
@ -45,5 +45,4 @@ class UploadInitialFragment : PagerChildFragment() {
super.onDestroyView() super.onDestroyView()
root.removeAllViews() root.removeAllViews()
} }
} }

View file

@ -10,8 +10,8 @@ import android.view.accessibility.AccessibilityEvent
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.links.LinkBuilder import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_upload_page_4.* import kotlinx.android.synthetic.main.fragment_upload_page_4.*
class UploadStepFourFragment : PagerChildFragment() { class UploadStepFourFragment : PagerChildFragment() {

View file

@ -11,8 +11,8 @@ import androidx.core.os.bundleOf
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import au.gov.health.covidsafe.R import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment
import au.gov.health.covidsafe.ui.view.UploadingDialog import au.gov.health.covidsafe.ui.view.UploadingDialog
import au.gov.health.covidsafe.ui.view.UploadingErrorDialog import au.gov.health.covidsafe.ui.view.UploadingErrorDialog

View file

@ -4,7 +4,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import au.gov.health.covidsafe.BuildConfig import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.Preference import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.extensions.isInternetAvailable import au.gov.health.covidsafe.extensions.isInternetAvailable
import au.gov.health.covidsafe.factory.NetworkFactory import au.gov.health.covidsafe.factory.NetworkFactory
import au.gov.health.covidsafe.interactor.usecase.UploadData import au.gov.health.covidsafe.interactor.usecase.UploadData

View file

@ -1,3 +1,3 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.ui.utils
class LocalBlobV2(val modelP : String?, val modelC : String?, val txPower : Int?, val rssi : Int?) class LocalBlobV2(val modelP : String?, val modelC : String?, val txPower : Int?, val rssi : Int?)

View file

@ -1,4 +1,4 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.ui.utils
import android.Manifest import android.Manifest
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
@ -12,6 +12,9 @@ import android.provider.Settings
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.bluetooth.gatt.* import au.gov.health.covidsafe.bluetooth.gatt.*
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.scheduler.Scheduler import au.gov.health.covidsafe.scheduler.Scheduler
@ -29,7 +32,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.net.InetAddress import java.net.InetSocketAddress
import java.net.Socket
import java.net.SocketAddress
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -295,17 +300,21 @@ object Utils {
private fun checkInternetConnectionToURL(url: String, callback: (Boolean) -> Unit) { private fun checkInternetConnectionToURL(url: String, callback: (Boolean) -> Unit) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
var reachable = false
reachable = try { val reachable = try {
InetAddress.getByName(url).isReachable(2000) // InetAddress.getByName(url).isReachable(2000)
val timeoutMs = 2000
val sock = Socket()
val sockaddr: SocketAddress = InetSocketAddress(url, 53)
sock.connect(sockaddr, timeoutMs)
sock.close()
true
} catch (e: Exception) { } catch (e: Exception) {
CentralLog.w(TAG, "checkInternetConnectionToURL() failed to reach the url $url") CentralLog.w(TAG, "checkInternetConnectionToURL() failed to reach the url $url")
false false
} }
CentralLog.w(TAG, "checkInternetConnectionToURL() reachability to url $url is $reachable") CentralLog.w(TAG, "checkInternetConnectionToURL() reachability to url $url is $reachable")
callback(reachable) callback(reachable)
} }
} }

View file

@ -1,10 +1,11 @@
package au.gov.health.covidsafe package au.gov.health.covidsafe.ui.webview
import android.os.Bundle import android.os.Bundle
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
class WebViewActivity : FragmentActivity() { class WebViewActivity : FragmentActivity() {

View file

@ -0,0 +1,93 @@
package au.gov.health.covidsafe.utils
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkRequest
import android.os.Build
import au.gov.health.covidsafe.ui.utils.Utils
import kotlin.collections.ArrayList
object NetworkConnectionCheck {
private var networkConnectionListener: ArrayList<NetworkConnectionListener>? = null
private var isInternetConnected:Boolean? = null
fun addNetworkChangedListener(context: Context, listener: NetworkConnectionListener) {
if (networkConnectionListener == null) {
networkConnectionListener = ArrayList()
registerNetworkChange(context)
}
isInternetConnected?.let {
listener.onNetworkStatusChanged(it)
}
networkConnectionListener?.add(listener)
}
fun removeNetworkChangedListener(listener: NetworkConnectionListener) {
networkConnectionListener?.let {
val i: Int = it.indexOf(listener)
if (i >= 0) {
it.removeAt(i)
}
}
}
private fun registerNetworkChange(context: Context) {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
it.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
networkStatusUpdate(true)
}
override fun onLost(network: Network?) {
networkStatusUpdate(false)
}
})
} else {
it.registerNetworkCallback(NetworkRequest.Builder().build(),
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network?) {
networkStatusUpdate(true)
}
override fun onLost(network: Network?) {
networkStatusUpdate(false)
}
})
}
}
}
private fun networkStatusUpdate(isAvailable: Boolean) {
if (!isAvailable) {
isInternetConnected = isAvailable
sendNetworkStatusToListeners(isAvailable)
} else {
checkInternetConnection()
}
}
private fun checkInternetConnection() {
Utils.checkInternetConnectionToGoogle {
isInternetConnected = it
sendNetworkStatusToListeners(it)
}
}
private fun sendNetworkStatusToListeners(isAvailable: Boolean) {
networkConnectionListener?.forEach {
it.onNetworkStatusChanged(isAvailable)
}
}
interface NetworkConnectionListener {
fun onNetworkStatusChanged(isAvailable: Boolean)
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/death_icon_background_color_grey" />
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/white" />
</shape>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" />
</vector>

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M21.5611,5.6551C21.9849,5.6551 22.4101,5.4929 22.7331,5.17C23.379,4.5241 23.379,3.472 22.7331,2.8261C22.0872,2.1802 21.0351,2.1802 20.3892,2.8261C19.9084,3.3069 19.7857,4.0127 20.0209,4.6074L17.414,7.2143C16.2859,6.32 14.8948,5.7443 13.3736,5.6259C13.3736,5.623 13.3751,5.6201 13.3751,5.6171V3.1768C13.9625,2.9211 14.3746,2.3366 14.3746,1.6571C14.3746,0.7438 13.6308,0 12.7175,0C11.8042,0 11.0604,0.7438 11.0604,1.6571C11.0604,2.3366 11.4725,2.9225 12.0599,3.1768V5.6171C12.0599,5.6245 12.0614,5.6303 12.0614,5.6361C10.4993,5.7867 9.0789,6.4208 7.9493,7.3853L6.9805,6.4165C7.2158,5.8203 7.093,5.1159 6.6123,4.6352C5.9664,3.9893 4.9157,3.9893 4.2684,4.6352C3.6225,5.2811 3.6225,6.3332 4.2684,6.9791C4.5811,7.2918 4.9976,7.4642 5.4403,7.4642C5.6537,7.4642 5.8597,7.4233 6.0511,7.3473L7.0273,8.3234C6.0935,9.4588 5.4915,10.8748 5.3614,12.4252H3.1768C2.9211,11.8378 2.3366,11.4257 1.6571,11.4257C0.7438,11.4257 0,12.1695 0,13.0828C0,13.9961 0.7438,14.7399 1.6571,14.7399C2.3366,14.7399 2.9225,14.3278 3.1768,13.7404H5.3658C5.5075,15.2864 6.1198,16.6965 7.0594,17.8232C7.0331,17.8422 7.0083,17.8612 6.9849,17.8846L6.1213,18.7482C6.0497,18.8198 6.0015,18.9031 5.9693,18.9907C5.7604,18.8972 5.5309,18.8461 5.2957,18.8461C4.8529,18.8461 4.4364,19.0185 4.1237,19.3312C3.4778,19.9771 3.4778,21.0278 4.1237,21.6751C4.4364,21.9878 4.8529,22.1603 5.2957,22.1603C5.7384,22.1603 6.1549,21.9878 6.4676,21.6751C6.9659,21.1768 7.0784,20.4374 6.8081,19.8295C6.8972,19.7974 6.9805,19.7477 7.0521,19.6775L7.9157,18.8139C7.9377,18.792 7.9552,18.7672 7.9742,18.7423C9.0672,19.6688 10.4306,20.284 11.9299,20.4549C11.2767,20.7414 10.8193,21.3931 10.8193,22.15C10.8193,23.17 11.6493,24 12.6693,24C13.6892,24 14.5192,23.17 14.5192,22.15C14.5192,21.4092 14.0794,20.7691 13.4496,20.4739C14.9868,20.338 16.3897,19.7375 17.5163,18.811L18.7219,20.0166C18.4866,20.6128 18.6094,21.3171 19.0901,21.7979C19.4028,22.1106 19.8193,22.283 20.2621,22.283C20.7048,22.283 21.1213,22.1106 21.434,21.7979C22.0799,21.152 22.0799,20.0999 21.434,19.454C20.9532,18.9732 20.2474,18.8505 19.6527,19.0857L18.4545,17.8875C19.5665,16.5884 20.2387,14.9006 20.2387,13.055C20.2387,11.1627 19.5314,9.4355 18.3697,8.1203L20.9518,5.5382C21.1461,5.6157 21.3536,5.6551 21.5611,5.6551ZM12.7862,19.1924C9.4018,19.1924 6.6488,16.4394 6.6488,13.055C6.6488,9.6707 9.4018,6.9177 12.7862,6.9177C16.1705,6.9177 18.9235,9.6707 18.9235,13.055C18.9235,16.4394 16.1705,19.1924 12.7862,19.1924Z"
android:fillColor="#131313"/>
<path
android:pathData="M10.1106,13.4657C9.3975,13.4657 8.8174,14.0458 8.8174,14.7589C8.8174,15.472 9.3975,16.0522 10.1106,16.0522C10.8237,16.0522 11.4038,15.472 11.4038,14.7589C11.4038,14.0458 10.8237,13.4657 10.1106,13.4657Z"
android:fillColor="#131313"/>
<path
android:pathData="M10.4964,9.3346C9.6766,9.3346 9.0103,10.0009 9.0103,10.8207C9.0103,11.6405 9.6766,12.3068 10.4964,12.3068C11.3161,12.3068 11.9825,11.6405 11.9825,10.8207C11.9825,10.0009 11.3161,9.3346 10.4964,9.3346Z"
android:fillColor="#131313"/>
<path
android:pathData="M13.6179,11.9503C12.9048,11.9503 12.3247,12.5304 12.3247,13.2435C12.3247,13.9566 12.9048,14.5368 13.6179,14.5368C14.331,14.5368 14.9112,13.9566 14.9112,13.2435C14.9112,12.5304 14.331,11.9503 13.6179,11.9503Z"
android:fillColor="#131313"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20.8401,4.61C20.3294,4.099 19.7229,3.6936 19.0555,3.4171C18.388,3.1405 17.6726,2.9982 16.9501,2.9982C16.2276,2.9982 15.5122,3.1405 14.8448,3.4171C14.1773,3.6936 13.5709,4.099 13.0601,4.61L12.0001,5.67L10.9401,4.61C9.9084,3.5783 8.5091,2.9987 7.0501,2.9987C5.5911,2.9987 4.1918,3.5783 3.1601,4.61C2.1284,5.6417 1.5488,7.041 1.5488,8.5C1.5488,9.959 2.1284,11.3583 3.1601,12.39L4.2201,13.45L12.0001,21.23L19.7801,13.45L20.8401,12.39C21.3511,11.8792 21.7565,11.2728 22.033,10.6053C22.3096,9.9379 22.4519,9.2225 22.4519,8.5C22.4519,7.7775 22.3096,7.0621 22.033,6.3946C21.7565,5.7272 21.3511,5.1207 20.8401,4.61V4.61Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="21dp"
android:viewportWidth="14"
android:viewportHeight="21">
<path
android:pathData="M6.9473,3.3813C6.1715,3.2482 5.3735,3.394 4.6948,3.7929C4.0162,4.1918 3.5006,4.8179 3.2393,5.5605C2.9339,6.4289 1.9824,6.8851 1.1141,6.5797C0.2457,6.2742 -0.2106,5.3227 0.0949,4.4544C0.6173,2.9692 1.6486,1.7169 3.0059,0.9191C4.3632,0.1214 5.9591,-0.1702 7.5108,0.096C9.0626,0.3621 10.47,1.1689 11.484,2.3733C12.4977,3.5775 13.0526,5.1016 13.0505,6.6757C13.0497,9.2262 11.1587,10.9106 9.8083,11.8109C9.0822,12.2949 8.368,12.6508 7.8419,12.8846C7.5765,13.0026 7.353,13.0921 7.192,13.1535C7.1113,13.1842 7.0459,13.208 6.9983,13.225L6.9404,13.2452L6.922,13.2515L6.9155,13.2537L6.9129,13.2546C6.9124,13.2547 6.9108,13.2553 6.3838,11.6741L6.9108,13.2553C6.0376,13.5463 5.0937,13.0744 4.8026,12.2012C4.5118,11.3285 4.9829,10.3853 5.855,10.0936L5.8529,10.0943C5.853,10.0942 5.8532,10.0942 5.855,10.0936L5.8816,10.0842C5.9072,10.0751 5.9493,10.0599 6.0053,10.0385C6.1177,9.9957 6.2848,9.929 6.4881,9.8386C6.8995,9.6558 7.4353,9.3867 7.9593,9.0374C9.1086,8.2712 9.7171,7.456 9.7171,6.6741L9.7171,6.6716C9.7183,5.8844 9.4408,5.1222 8.9339,4.52C8.4269,3.9178 7.7232,3.5144 6.9473,3.3813Z"
android:fillColor="#131313"
android:fillType="evenOdd"/>
<path
android:pathData="M4.8506,18.3405C4.8506,17.42 5.5968,16.6738 6.5173,16.6738H6.5339C7.4544,16.6738 8.2006,17.42 8.2006,18.3405C8.2006,19.261 7.4544,20.0072 6.5339,20.0072H6.5173C5.5968,20.0072 4.8506,19.261 4.8506,18.3405Z"
android:fillColor="#131313"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,4H20C21.1,4 22,4.9 22,6V18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M22,6L12,13L2,6"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,18 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M19.9998,5C11.7156,5 4.9998,11.7157 4.9998,20C4.9998,28.2842 11.7156,35 19.9998,35C28.2841,35 34.9998,28.2842 34.9998,20C34.9998,11.7157 28.2841,5 19.9998,5ZM1.6665,20C1.6665,9.8747 9.8746,1.6666 19.9998,1.6666C30.1251,1.6666 38.3332,9.8747 38.3332,20C38.3332,30.1252 30.1251,38.3333 19.9998,38.3333C9.8746,38.3333 1.6665,30.1252 1.6665,20Z"
android:fillColor="#131313"
android:fillType="evenOdd"/>
<path
android:pathData="M20.4302,13.3739C19.6544,13.2408 18.8564,13.3866 18.1778,13.7855C17.4991,14.1843 16.9835,14.8105 16.7223,15.5531C16.4168,16.4214 15.4653,16.8777 14.597,16.5722C13.7286,16.2668 13.2724,15.3153 13.5778,14.4469C14.1003,12.9618 15.1315,11.7094 16.4888,10.9117C17.8462,10.114 19.442,9.8224 20.9938,10.0885C22.5455,10.3547 23.953,11.1614 24.9669,12.3659C25.9806,13.5701 26.5355,15.0941 26.5334,16.6682C26.5326,19.2188 24.6416,20.9032 23.2912,21.8034C22.5651,22.2875 21.851,22.6434 21.3248,22.8772C21.0594,22.9952 20.8359,23.0847 20.6749,23.146C20.5942,23.1768 20.5288,23.2006 20.4812,23.2175L20.4233,23.2378L20.4049,23.2441L20.3984,23.2463L20.3958,23.2471C20.3953,23.2473 20.3937,23.2478 19.8667,21.6667L20.3937,23.2478C19.5205,23.5389 18.5766,23.067 18.2856,22.1937C17.9947,21.3211 18.4658,20.3779 19.3379,20.0861L19.3358,20.0868C19.3359,20.0868 19.3361,20.0868 19.3379,20.0861L19.3645,20.0768C19.3901,20.0677 19.4322,20.0524 19.4882,20.0311C19.6006,19.9883 19.7677,19.9215 19.971,19.8312C20.3824,19.6483 20.9182,19.3792 21.4422,19.0299C22.5915,18.2637 23.2,17.4486 23.2,16.6667L23.2,16.6642C23.2012,15.877 22.9237,15.1148 22.4168,14.5126C21.9098,13.9103 21.2061,13.507 20.4302,13.3739Z"
android:fillColor="#131313"
android:fillType="evenOdd"/>
<path
android:pathData="M18.3335,28.3333C18.3335,27.4128 19.0797,26.6666 20.0002,26.6666H20.0168C20.9373,26.6666 21.6835,27.4128 21.6835,28.3333C21.6835,29.2538 20.9373,30 20.0168,30H20.0002C19.0797,30 18.3335,29.2538 18.3335,28.3333Z"
android:fillColor="#131313"
android:fillType="evenOdd"/>
</vector>

View file

@ -1,20 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M4,4L44,44"
android:strokeLineJoin="round"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#A31919"
android:strokeLineCap="round"/>
<path
android:pathData="M44,4L4,44"
android:strokeLineJoin="round"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#A31919"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
android:fillColor="#ffffff"/>
<path
android:pathData="M12,12.6827V8"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#A31919"
android:strokeLineCap="round"/>
<path
android:pathData="M12,17.3652H12.012"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#A31919"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,15C13.6569,15 15,13.6569 15,12C15,10.3431 13.6569,9 12,9C10.3431,9 9,10.3431 9,12C9,13.6569 10.3431,15 12,15Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M19.4,15C19.2669,15.3016 19.2272,15.6362 19.286,15.9606C19.3448,16.285 19.4995,16.5843 19.73,16.82L19.79,16.88C19.976,17.0657 20.1235,17.2863 20.2241,17.5291C20.3248,17.7719 20.3766,18.0322 20.3766,18.295C20.3766,18.5578 20.3248,18.8181 20.2241,19.0609C20.1235,19.3037 19.976,19.5243 19.79,19.71C19.6043,19.896 19.3837,20.0435 19.1409,20.1441C18.8981,20.2448 18.6378,20.2966 18.375,20.2966C18.1122,20.2966 17.8519,20.2448 17.6091,20.1441C17.3663,20.0435 17.1457,19.896 16.96,19.71L16.9,19.65C16.6643,19.4195 16.365,19.2648 16.0406,19.206C15.7162,19.1472 15.3816,19.1869 15.08,19.32C14.7842,19.4468 14.532,19.6572 14.3543,19.9255C14.1766,20.1938 14.0813,20.5082 14.08,20.83V21C14.08,21.5304 13.8693,22.0391 13.4942,22.4142C13.1191,22.7893 12.6104,23 12.08,23C11.5496,23 11.0409,22.7893 10.6658,22.4142C10.2907,22.0391 10.08,21.5304 10.08,21V20.91C10.0723,20.579 9.9651,20.258 9.7725,19.9887C9.5799,19.7194 9.3107,19.5143 9,19.4C8.6984,19.2669 8.3638,19.2272 8.0394,19.286C7.715,19.3448 7.4157,19.4995 7.18,19.73L7.12,19.79C6.9342,19.976 6.7137,20.1235 6.4709,20.2241C6.2281,20.3248 5.9678,20.3766 5.705,20.3766C5.4422,20.3766 5.1819,20.3248 4.9391,20.2241C4.6963,20.1235 4.4757,19.976 4.29,19.79C4.1041,19.6043 3.9565,19.3837 3.8559,19.1409C3.7552,18.8981 3.7034,18.6378 3.7034,18.375C3.7034,18.1122 3.7552,17.8519 3.8559,17.6091C3.9565,17.3663 4.1041,17.1457 4.29,16.96L4.35,16.9C4.5805,16.6643 4.7352,16.365 4.794,16.0406C4.8528,15.7162 4.8131,15.3816 4.68,15.08C4.5532,14.7842 4.3428,14.532 4.0745,14.3543C3.8062,14.1766 3.4918,14.0813 3.17,14.08H3C2.4696,14.08 1.9609,13.8693 1.5858,13.4942C1.2107,13.1191 1,12.6104 1,12.08C1,11.5496 1.2107,11.0409 1.5858,10.6658C1.9609,10.2907 2.4696,10.08 3,10.08H3.09C3.421,10.0723 3.742,9.9651 4.0113,9.7725C4.2806,9.5799 4.4857,9.3107 4.6,9C4.7331,8.6984 4.7728,8.3638 4.714,8.0394C4.6552,7.715 4.5005,7.4157 4.27,7.18L4.21,7.12C4.0241,6.9342 3.8765,6.7137 3.7759,6.4709C3.6752,6.2281 3.6234,5.9678 3.6234,5.705C3.6234,5.4422 3.6752,5.1819 3.7759,4.9391C3.8765,4.6963 4.0241,4.4757 4.21,4.29C4.3958,4.1041 4.6163,3.9565 4.8591,3.8559C5.1019,3.7552 5.3622,3.7034 5.625,3.7034C5.8878,3.7034 6.1481,3.7552 6.3909,3.8559C6.6337,3.9565 6.8542,4.1041 7.04,4.29L7.1,4.35C7.3357,4.5805 7.635,4.7352 7.9594,4.794C8.2838,4.8528 8.6184,4.8131 8.92,4.68H9C9.2958,4.5532 9.548,4.3428 9.7257,4.0745C9.9034,3.8062 9.9987,3.4918 10,3.17V3C10,2.4696 10.2107,1.9609 10.5858,1.5858C10.9609,1.2107 11.4696,1 12,1C12.5304,1 13.0391,1.2107 13.4142,1.5858C13.7893,1.9609 14,2.4696 14,3V3.09C14.0013,3.4118 14.0966,3.7262 14.2743,3.9945C14.452,4.2628 14.7042,4.4732 15,4.6C15.3016,4.7331 15.6362,4.7728 15.9606,4.714C16.285,4.6552 16.5843,4.5005 16.82,4.27L16.88,4.21C17.0657,4.0241 17.2863,3.8765 17.5291,3.7759C17.7719,3.6752 18.0322,3.6234 18.295,3.6234C18.5578,3.6234 18.8181,3.6752 19.0609,3.7759C19.3037,3.8765 19.5243,4.0241 19.71,4.21C19.896,4.3958 20.0435,4.6163 20.1441,4.8591C20.2448,5.1019 20.2966,5.3622 20.2966,5.625C20.2966,5.8878 20.2448,6.1481 20.1441,6.3909C20.0435,6.6337 19.896,6.8542 19.71,7.04L19.65,7.1C19.4195,7.3357 19.2648,7.635 19.206,7.9594C19.1472,8.2838 19.1869,8.6184 19.32,8.92V9C19.4468,9.2958 19.6572,9.548 19.9255,9.7257C20.1938,9.9034 20.5082,9.9987 20.83,10H21C21.5304,10 22.0391,10.2107 22.4142,10.5858C22.7893,10.9609 23,11.4696 23,12C23,12.5304 22.7893,13.0391 22.4142,13.4142C22.0391,13.7893 21.5304,14 21,14H20.91C20.5882,14.0013 20.2738,14.0966 20.0055,14.2743C19.7372,14.452 19.5268,14.7042 19.4,15V15Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M23,6L13.5,15.5L8.5,10.5L1,18"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M17,6H23V12"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -7,16 +7,16 @@
android:pathData="M12,12m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0" android:pathData="M12,12m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
android:fillColor="#A31919"/> android:fillColor="#A31919"/>
<path <path
android:pathData="M16.5,7.5L7.5,16.5" android:pathData="M12,12.6827V8"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="3"
android:fillColor="#00000000" android:fillColor="#00000000"
android:strokeColor="#ffffff" android:strokeColor="#ffffff"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
<path <path
android:pathData="M7.5,7.5L16.5,16.5" android:pathData="M12,17.3652H12.012"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="3"
android:fillColor="#00000000" android:fillColor="#00000000"
android:strokeColor="#ffffff" android:strokeColor="#ffffff"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item
android:state_enabled="false"
android:drawable="@drawable/ic_red_cross" />
<item
android:state_activated="true"
android:drawable="@drawable/ic_green_tick" />
<item
android:state_selected="true"
android:drawable="@drawable/ic_green_tick" />
<item
android:state_selected="false"
android:drawable="@drawable/ic_red_cross" />
<item
android:state_checked="true"
android:drawable="@drawable/ic_green_tick" />
<item
android:state_pressed="true"
android:drawable="@drawable/ic_green_tick" />
<item
android:state_focused="true"
android:drawable="@drawable/ic_green_tick" />
<item
android:drawable="@drawable/ic_green_tick" />
</selector>

View file

@ -3,6 +3,6 @@
android:shape="rectangle"> android:shape="rectangle">
<stroke <stroke
android:width="1dp" android:width="1dp"
android:color="@color/error" /> android:color="@color/error_red" />
<corners android:radius="2dp" /> <corners android:radius="2dp" />
</shape> </shape>

View file

@ -4,7 +4,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".DeviceNameChangePromptActivity"> tools:context=".ui.devicename.DeviceNameChangePromptActivity">
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -7,7 +7,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:background="@color/splash_background" android:background="@color/splash_background"
android:backgroundTint="@color/splash_frame_background" android:backgroundTint="@color/splash_frame_background"
tools:context=".SplashActivity" tools:context=".ui.splash.SplashActivity"
> >
<ImageView <ImageView

View file

@ -5,6 +5,27 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView
android:id="@+id/home_push_notification_token"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/space_16"
android:background="@color/screen_background"
android:gravity="center"
android:paddingStart="@dimen/space_8"
android:paddingEnd="@dimen/space_8"
android:text="Firebase Push Token:"
style="?textAppearanceBody2"
android:textColor="@color/slate_black_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Space
android:id="@+id/push_token_space"
android:layout_width="match_parent"
android:layout_height="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/home_push_notification_token" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview" android:id="@+id/recyclerview"
android:layout_width="0dp" android:layout_width="0dp"
@ -12,22 +33,11 @@
android:background="@android:color/darker_gray" android:background="@android:color/darker_gray"
android:backgroundTint="@color/lighter" android:backgroundTint="@color/lighter"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/push_token_space"
tools:listitem="@layout/recycler_view_item" /> tools:listitem="@layout/recycler_view_item" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/delete" android:id="@+id/delete"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -35,9 +45,7 @@
android:layout_margin="8dp" android:layout_margin="8dp"
android:src="@drawable/ic_delete_black_24dp" android:src="@drawable/ic_delete_black_24dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toBottomOf="@+id/home_push_notification_token" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -10,6 +10,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="@dimen/keyline_1"
android:layout_marginTop="@dimen/keyline_1"> android:layout_marginTop="@dimen/keyline_1">
<TextView <TextView
@ -132,7 +133,7 @@
android:layout_marginEnd="@dimen/keyline_5" android:layout_marginEnd="@dimen/keyline_5"
android:text="@string/invalid_phone_number" android:text="@string/invalid_phone_number"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColor="@color/error" android:textColor="@color/error_red"
android:textSize="16sp" android:textSize="16sp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -56,7 +56,7 @@
android:layout_marginStart="@dimen/keyline_2" android:layout_marginStart="@dimen/keyline_2"
android:text="@string/wrong_ping_number" android:text="@string/wrong_ping_number"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColor="@color/error" android:textColor="@color/error_red"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pin" app:layout_constraintTop_toBottomOf="@+id/pin"

View file

@ -6,7 +6,7 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
style="@style/HelpToolbarStyle" app:titleTextAppearance="@style/ToolbarStyle.TitleTextStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"

View file

@ -1,120 +1,116 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
android:id="@+id/home_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f6f6f6">
<ScrollView <data>
<variable
name="viewModel"
type="au.gov.health.covidsafe.ui.home.HomeFragmentViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/home_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:fillViewport="true"> android:background="@color/screen_background">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:refreshing="@{safeUnbox(viewModel.isRefreshing)}">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/fragment_home_header" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/home_header_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="header_background" />
<include layout="@layout/fragment_home_setup_status_header" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/case_top_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="active_status_card" />
<include
layout="@layout/fragment_home_case_statistics"
app:viewModel="@{viewModel}" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/header_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="case_number_card" />
<include layout="@layout/fragment_home_external_links" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/content_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="health_link_card_view" />
<TextView
android:id="@+id/home_version_number"
style="?textAppearanceBody2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_24"
android:gravity="center"
android:textColor="@color/slate_black_2"
app:layout_constraintTop_toBottomOf="@+id/content_barrier" />
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/home_version_number" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/app_update_reminder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:background="@color/white"
android:orientation="vertical"
android:visibility="gone"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
tools:visibility="visible">
<View <View
android:id="@+id/header_background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="@dimen/space_12"
android:background="@color/lighter_green" android:background="@color/error_red" />
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="@+id/header_background_overlap"
app:layout_constraintTop_toTopOf="parent" />
<View </LinearLayout>
android:id="@+id/header_background_overlap"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/lighter_green"
app:layout_constraintTop_toBottomOf="@+id/header_barrier" />
<ImageView </androidx.coordinatorlayout.widget.CoordinatorLayout>
android:id="@+id/home_header_top_left_icon"
android:layout_width="@dimen/keyline_8"
android:layout_height="@dimen/keyline_8"
android:layout_marginLeft="@dimen/keyline_4"
android:layout_marginTop="@dimen/keyline_7"
android:src="@drawable/ic_splash_screen_logo"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<Button </layout>
android:id="@+id/home_header_help"
android:layout_width="@dimen/keyline_9"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_7"
android:accessibilityTraversalAfter="@id/home_header_setup_complete_header_line_2"
android:background="?attr/selectableItemBackground"
android:drawableTop="@drawable/ic_help_outline_black"
android:drawablePadding="-4dp"
android:gravity="center"
android:text="@string/title_help"
android:textAlignment="center"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include layout="@layout/fragment_home_setup_complete_header" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/header_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="home_header_no_bluetooth_pairing" />
<include layout="@layout/fragment_home_setup_incomplete_content" />
<include layout="@layout/fragment_home_external_links" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/content_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="push_card_view, change_language_card" />
<TextView
android:id="@+id/home_version_number"
style="?textAppearanceBody2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_4"
android:layout_marginBottom="@dimen/keyline_5"
android:gravity="center"
android:textColor="@color/cadet_grey"
app:layout_constraintTop_toBottomOf="@+id/content_barrier" />
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/keyline_2"
app:layout_constraintTop_toBottomOf="@+id/home_version_number" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<LinearLayout
android:id="@+id/app_update_reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical"
android:visibility="gone"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
tools:visibility="visible">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/keyline_2"
android:background="@color/error" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,166 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="au.gov.health.covidsafe.ui.home.HomeFragmentViewModel" />
<import type="au.gov.health.covidsafe.ui.home.CaseNumbersState" />
</data>
<merge>
<androidx.cardview.widget.CardView
android:id="@+id/case_number_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/case_top_barrier"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="0dp"
card_view:cardMaxElevation="@dimen/card_elevation_10dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/case_loading_view"
visibility="@{viewModel.caseNumberDataState == CaseNumbersState.LOADING}"
android:layout_width="match_parent"
android:layout_height="@dimen/case_number_loading_card_height"
android:visibility="gone">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/case_loading_animation_view"
android:layout_width="@dimen/covidsafe_loading_animation_size"
android:layout_height="@dimen/covidsafe_loading_animation_size"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/space_24"
android:layout_marginBottom="@dimen/space_24"
app:lottie_autoPlay="true"
app:lottie_fileName="loading_upload.json"
app:lottie_loop="true"
app:lottie_speed="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/case_loading_animation_view"
android:layout_centerHorizontal="true"
android:text="@string/loading_numbers"
android:textAppearance="@style/fontRobotoRegular16"
android:textColor="@color/slate_black_1" />
</RelativeLayout>
<LinearLayout
visibility="@{viewModel.caseNumberDataState != CaseNumbersState.LOADING}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/national_numbers_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginTop="@dimen/space_16"
android:layout_marginBottom="@dimen/space_8"
android:text="@string/national_numbers"
android:textAppearance="@style/fontRobotoRegular28"
android:textColor="@color/slate_black_1"
android:textStyle="bold" />
<TextView
android:id="@+id/date_text_view"
dateFormat="@{viewModel.caseStatisticsLiveData.updatedDate}"
visibility="@{viewModel.caseStatisticsLiveData != null}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16"
android:layout_marginBottom="@dimen/space_16"
android:text="27 August 2020 at 3pm AEST"
android:textAppearance="@style/fontRobotoRegular16"
android:textColor="@color/slate_black_2"
android:visibility="visible"
tools:text="27 August 2020 at 3pm AEST" />
<FrameLayout
android:id="@+id/show_error_message"
visibility="@{viewModel.caseNumberDataState == CaseNumbersState.ERROR_NO_NETWORK || viewModel.caseNumberDataState == CaseNumbersState.ERROR_UNKNOWN}"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/no_network_error_text_view"
visibility="@{viewModel.caseNumberDataState == CaseNumbersState.ERROR_NO_NETWORK}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16"
android:layout_marginBottom="@dimen/space_16"
android:text="@string/numbers_no_internet"
android:textAppearance="@style/fontRobotoRegular16"
android:textColor="@color/error_red"
android:visibility="gone" />
<TextView
android:id="@+id/unknown_error_text_view"
visibility="@{viewModel.caseNumberDataState == CaseNumbersState.ERROR_UNKNOWN}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16"
android:clickable="true"
android:focusable="true"
android:text="@string/numbers_error"
android:textAppearance="@style/fontRobotoRegular16"
android:textColor="@color/error_red"
android:visibility="gone" />
</FrameLayout>
<TextView
android:id="@+id/refresh_button"
addUnderline="@{@string/numbers_refresh}"
visibility="@{viewModel.caseNumberDataState == CaseNumbersState.ERROR_UNKNOWN}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:clickable="true"
android:focusable="true"
android:paddingStart="@dimen/space_16"
android:paddingTop="@dimen/space_16"
android:paddingEnd="@dimen/space_16"
android:paddingBottom="@dimen/space_16"
android:textAppearance="@style/fontRobotoRegular16"
android:textColor="@color/dark_green"
android:visibility="gone" />
<include
layout="@layout/view_national_case_statistics"
app:viewModel="@{viewModel}" />
<include
layout="@layout/view_state_case_statistics"
app:viewModel="@{viewModel}" />
</LinearLayout>
</FrameLayout>
</androidx.cardview.widget.CardView>
</merge>
</layout>

View file

@ -3,227 +3,31 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"> xmlns:card_view="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.Barrier
android:id="@+id/cards_top_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="permissions_card_bottom_space,header_barrier" />
<androidx.cardview.widget.CardView
android:id="@+id/improve_performance_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/cards_top_barrier"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp"
card_view:cardMaxElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
<LinearLayout
android:id="@+id/improve_performance_card_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/slack_black_3"
android:orientation="vertical">
<TextView
android:id="@+id/improve_performance_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:padding="@dimen/keyline_4"
android:text="@string/improve_heading"
android:textSize="28sp" />
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
android:id="@+id/internet_connection_tile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:background="@color/white"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
app:external_linkCard_content="@string/internet_connection_content"
app:external_linkCard_icon="@drawable/ic_home_share"
app:external_linkCard_icon_visible="false"
app:external_linkCard_text_color="@color/error"
app:external_linkCard_title="@string/internet_connection_heading" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/external_links_top_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4"
app:layout_constraintTop_toBottomOf="@+id/improve_performance_card"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp"
card_view:cardMaxElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
android:id="@+id/home_setup_complete_share"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
app:external_linkCard_content="@string/home_set_complete_external_link_share_content"
app:external_linkCard_icon="@drawable/ic_home_share"
app:external_linkCard_icon_background="@drawable/background_circular_green"
app:external_linkCard_icon_padding="@dimen/keyline_1"
app:external_linkCard_title="@string/home_set_complete_external_link_share_title" />
<View
android:id="@+id/home_setup_complete_share_space"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/slack_black_3"
app:layout_constraintTop_toBottomOf="@+id/home_setup_complete_share" />
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
android:id="@+id/home_been_tested_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
app:external_linkCard_content="@string/home_set_complete_external_link_been_contacted_content"
app:external_linkCard_icon="@drawable/ic_upload_icon"
app:external_linkCard_icon_background="@drawable/background_circular_green"
app:external_linkCard_icon_padding="@dimen/keyline_1"
app:external_linkCard_title="@string/home_set_complete_external_link_been_contacted_title" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/external_links_bottom_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4"
app:layout_constraintTop_toBottomOf="@+id/external_links_top_card"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp"
card_view:cardMaxElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
android:id="@+id/home_setup_complete_app"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
app:external_linkCard_content="@string/home_set_complete_external_link_app_content"
app:external_linkCard_icon="@drawable/ic_home_news"
app:external_linkCard_icon_background="@drawable/background_circular_black"
app:external_linkCard_title="@string/home_set_complete_external_link_app_title"
app:layout_constraintTop_toBottomOf="@+id/home_setup_complete_share_space" />
<View
android:id="@+id/home_setup_complete_news_space"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/slack_black_3"
app:layout_constraintTop_toBottomOf="@+id/home_setup_complete_app" />
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
android:id="@+id/home_setup_complete_news"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
app:external_linkCard_content="@string/home_set_complete_external_link_news_content"
app:external_linkCard_icon="@drawable/ic_home_news"
app:external_linkCard_icon_background="@drawable/background_circular_dark_cerulean_1"
app:external_linkCard_title="@string/home_set_complete_external_link_news_title"
app:layout_constraintTop_toBottomOf="@+id/home_setup_complete_news_space" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/help_topics_card" android:id="@+id/help_topics_card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4" android:layout_marginTop="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/external_links_bottom_card" app:layout_constraintTop_toBottomOf="@+id/header_barrier"
card_view:cardBackgroundColor="@color/white" card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp" card_view:cardCornerRadius="0dp"
card_view:cardMaxElevation="2dp" card_view:cardMaxElevation="@dimen/card_elevation_10dp"
card_view:cardUseCompatPadding="true" card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp"> card_view:contentPadding="0dp">
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard <include layout="@layout/view_help_topics_tile" />
android:id="@+id/help_topics_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
app:external_linkCard_content="@string/home_set_complete_external_link_help_topics_content"
app:external_linkCard_icon="@drawable/ic_question_circle"
app:external_linkCard_title="@string/home_set_complete_external_link_help_topics_title" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/notification_status_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4"
app:layout_constraintTop_toBottomOf="@+id/help_topics_card"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp"
card_view:cardMaxElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
android:id="@+id/notification_status_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
app:external_linkCard_content="@string/NotificationsEnabledBlurb"
app:external_linkCard_icon="@drawable/ic_bell"
app:external_linkCard_title="@string/home_set_complete_external_link_notifications_title_iOS" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/change_language_card" android:id="@+id/change_language_card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4" android:layout_marginTop="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/notification_status_card" app:layout_constraintTop_toBottomOf="@+id/help_topics_card"
card_view:cardBackgroundColor="@color/white" card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp" card_view:cardCornerRadius="0dp"
card_view:cardMaxElevation="2dp" card_view:cardMaxElevation="@dimen/card_elevation_10dp"
card_view:cardUseCompatPadding="true" card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp"> card_view:contentPadding="0dp">
@ -233,11 +37,62 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/white" android:background="@color/white"
android:minHeight="@dimen/external_link_height" android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/keyline_0" android:paddingTop="@dimen/space_4"
android:paddingBottom="@dimen/keyline_0" android:paddingBottom="@dimen/space_4"
app:external_linkCard_content="@string/change_language_content" app:external_linkCard_content="@string/change_language_content"
app:external_linkCard_icon="@drawable/ic_globe" app:external_linkCard_start_icon="@drawable/ic_globe"
app:external_linkCard_start_icon_background="@drawable/background_circular_green"
app:external_linkCard_start_icon_padding="@dimen/space_8"
app:external_linkCard_title="@string/change_language" /> app:external_linkCard_title="@string/change_language" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/share_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_24"
app:cardBackgroundColor="@color/white"
app:layout_constraintTop_toBottomOf="@+id/change_language_card"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="0dp"
card_view:cardMaxElevation="@dimen/card_elevation_10dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
<include layout="@layout/view_covid_share_tile" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/health_link_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/share_card_view"
card_view:cardBackgroundColor="@color/dark_green"
card_view:cardCornerRadius="0dp"
card_view:cardMaxElevation="@dimen/card_elevation_10dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
<au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
android:id="@+id/home_been_tested_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/dark_green"
android:minHeight="@dimen/external_link_height"
android:paddingTop="@dimen/space_4"
android:paddingBottom="@dimen/space_4"
app:external_linkCard_content="@string/home_set_complete_external_link_been_contacted_content"
app:external_linkCard_start_icon="@drawable/ic_upload_icon"
app:external_linkCard_start_icon_background="@drawable/background_circular_white"
app:external_linkCard_start_icon_padding="@dimen/space_8"
app:external_linkCard_text_color="@color/white"
app:external_linkCard_content_padding="@dimen/space_8"
app:external_linkCard_title="@string/home_set_complete_external_link_been_contacted_title" />
</androidx.cardview.widget.CardView>
</merge> </merge>

Some files were not shown because too many files have changed in this diff Show more