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: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'com.google.gms.google-services'
buildscript {
repositories {
google()
jcenter()
}
}
@ -27,26 +22,22 @@ def getGitHash = { ->
}
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
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
Before you increase the targetSdkVersion make sure that all its usage are still working
*/
targetSdkVersion 28
versionCode 54
versionName "1.0.54"
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 72
versionName "1.11.0"
buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
@ -107,7 +98,7 @@ android {
String ssid = STAGING_SERVICE_UUID
versionNameSuffix "-debug-${getGitHash()}-${ssid.substring(ssid.length() - 5, ssid.length() - 1)}"
resValue "string", "app_name", "COVIDSafe Debug"
applicationIdSuffix "debug"
applicationIdSuffix ".debug"
signingConfig signingConfigs.debug
}
@ -119,15 +110,12 @@ android {
buildConfigField "String", "IOS_BACKGROUND_UUID", STAGING_BACKGROUND_IOS_SERVICE_UUID
buildConfigField "String", "ENCRYPTION_PUBLIC_KEY", STAGING_ENCRYPTION_PUBLIC_KEY
// Retrieve bluetooth ssid from staging's strings.xml
String ssid = STAGING_SERVICE_UUID
versionNameSuffix "-beta-${getGitHash()}-${ssid.substring(ssid.length() - 5, ssid.length() - 1)}"
debuggable false
applicationIdSuffix "beta"
applicationIdSuffix ".beta"
resValue "string", "app_name", "COVIDSafe beta"
lintOptions {
@ -137,6 +125,7 @@ android {
matchingFallbacks = ['release']
signingConfig signingConfigs.staging
}
release {
buildConfigField "String", "BLE_SSID", PRODUCTION_SERVICE_UUID
@ -145,8 +134,6 @@ android {
buildConfigField "String", "IOS_BACKGROUND_UUID", PRODUCTION_BACKGROUND_IOS_SERVICE_UUID
buildConfigField "String", "ENCRYPTION_PUBLIC_KEY", PRODUCTION_ENCRYPTION_PUBLIC_KEY
debuggable false
jniDebuggable false
renderscriptDebuggable false
@ -161,10 +148,13 @@ android {
abortOnError false
}
signingConfig signingConfigs.release
}
}
buildFeatures {
dataBinding true
}
sourceSets {
staging {
java.srcDirs = ['src/debug/java']
@ -185,45 +175,31 @@ android {
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
}
repositories {
jcenter()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation project(":feedback-android")
// kotlin
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"
//androidx
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.recyclerview:recyclerview: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.constraintlayout:constraintlayout:1.1.3'
implementation 'pub.devrel:easypermissions:3.0.0'
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
def room_version = "2.2.5"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-runtime:$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"
// rx
@ -233,11 +209,10 @@ dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
//cardview
implementation 'androidx.cardview:cardview:1.0.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version"
//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 "androidx.collection:collection:1.1.0"
@ -251,7 +226,9 @@ dependencies {
// Firebase Cloud Messaging
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"
}

View file

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

View file

@ -12,14 +12,18 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.extensions.fromHtml
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.view.RecordViewModel
import au.gov.health.covidsafe.ui.utils.Utils
import com.google.android.material.floatingactionbutton.FloatingActionButton
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.database_peek.*
import kotlinx.android.synthetic.main.database_peek.home_push_notification_token
import java.io.File
private const val TAG = "PeekActivity"
@ -72,7 +76,7 @@ class PeekActivity : AppCompatActivity() {
.setTitle("Are you sure?")
.setCancelable(false)
.setMessage("Deleting the DB records is irreversible")
.setPositiveButton("DELETE") { dialog, which ->
.setPositiveButton("DELETE") { dialog, _ ->
Observable.create<Boolean> {
StreetPassRecordStorage(this).nukeDb()
it.onNext(true)
@ -87,7 +91,7 @@ class PeekActivity : AppCompatActivity() {
}
}
.setNegativeButton("DON'T DELETE") { dialog, which ->
.setNegativeButton("DON'T DELETE") { dialog, _ ->
view.isEnabled = true
dialog.cancel()
}
@ -123,6 +127,8 @@ class PeekActivity : AppCompatActivity() {
}
}
showPushTokenOnDebugBuild()
if (!BuildConfig.DEBUG) {
start.visibility = View.GONE
stop.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 fun nextTimePeriod(): Int {

View file

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

View file

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

View file

@ -3,22 +3,94 @@ package au.gov.health.covidsafe
import android.content.Intent
import android.os.Bundle
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.networking.response.Message
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.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.firebase.iid.FirebaseInstanceId
private const val TAG = "HomeActivity"
class HomeActivity : FragmentActivity() {
private fun checkInternetConnection() {
checkInternetConnectionToGoogle {
HomeFragment.instanceWeakRef?.get()?.updateConnectionTile(it)
class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectionListener {
var isAppUpdateAvailableLiveData = MutableLiveData<Boolean>()
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)
}
/**
@ -46,76 +118,12 @@ class HomeActivity : FragmentActivity() {
})
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CentralLog.d(TAG, "onCreate() intent.action = ${intent.action}")
setContentView(R.layout.activity_home)
Utils.startBluetoothMonitoringService(this)
// messages API related
getInstanceID()
private var previousInternetConnection = true
override fun onNetworkStatusChanged(isAvailable: Boolean) {
if (!previousInternetConnection && isAvailable) {
checkAndUpdateHealthStatus()
}
previousInternetConnection = isAvailable
}
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.content.Context
import android.os.Build
import au.gov.health.covidsafe.BuildConfig
import com.atlassian.mobilekit.module.feedback.FeedbackModule
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.streetpass.CentralDevice
import au.gov.health.covidsafe.streetpass.PeripheralDevice
@ -23,6 +23,7 @@ class TracerApp : Application() {
companion object {
private const val TAG = "TracerApp"
const val ORG = BuildConfig.ORG
const val protocolVersion = BuildConfig.PROTOCOL_VERSION

View file

@ -10,7 +10,7 @@ import android.os.ParcelUuid
import android.util.Base64
import android.util.Base64.decode
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.BACKGROUND_IOS_SERVICE_UUID
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_SUCCESS
import android.content.Context
import au.gov.health.covidsafe.TracerApp
import au.gov.health.covidsafe.Utils
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.streetpass.CentralDevice
import au.gov.health.covidsafe.streetpass.ConnectionRecord
@ -41,8 +41,7 @@ class GattServer constructor(val context: Context, serviceUUIDString: String) {
BluetoothProfile.STATE_CONNECTED -> {
CentralLog.i(TAG, "${device?.address} Connected to local GATT server")
device?.let {
val b = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)
.contains(device)
bluetoothManager.getConnectedDevices(BluetoothProfile.GATT).contains(device)
}
}

View file

@ -3,23 +3,33 @@ package au.gov.health.covidsafe.boot
import android.content.BroadcastReceiver
import android.content.Context
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.scheduler.GetMessagesScheduler
private const val TAG = "StartOnBootReceiver"
class StartOnBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
CentralLog.d("StartOnBootReceiver", "boot completed received")
CentralLog.d(TAG, "boot completed received")
try {
CentralLog.d("StartOnBootReceiver", "Attempting to start service")
Utils.scheduleStartMonitoringService(context, 500)
checkAndUpdateHealthStatus()
} catch (e: Throwable) {
CentralLog.e("StartOnBootReceiver", e.localizedMessage)
CentralLog.e(TAG, e.localizedMessage ?: "Error message is empty:")
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 androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
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.PermissionRequest
@ -62,6 +61,7 @@ private fun Fragment.requestFineLocationAndCheckBleSupportThenNextPermission(onE
}
} else {
checkBLESupport()
onEndCallback.invoke()
}
}

View file

@ -1,10 +1,15 @@
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.Spanned
import android.text.style.URLSpan
import android.widget.TextView
import androidx.core.content.ContextCompat
import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.R
fun TextView.toHyperlink(textToHyperLink: String? = null, onClick: () -> Unit) {
@ -28,3 +33,34 @@ fun TextView.toHyperlink(textToHyperLink: String? = null, onClick: () -> Unit) {
}
}
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)
}
private const val USER_AGENT_TAG = "User-Agent"
val okHttpClient: OkHttpClient by lazy {
val okHttpClientBuilder = OkHttpClient.Builder()
@ -26,6 +28,15 @@ interface NetworkFactory {
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.
// For Android 24 and above, the pinning is set up in AndroidManifest.xml
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
@ -52,6 +63,10 @@ interface NetworkFactory {
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 kotlinx.coroutines.delay
import retrofit2.Response
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.Utils
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success
@ -24,8 +24,8 @@ class UpdateBroadcastMessageAndPerformScanWithExponentialBackOff(private val aws
lifecycle: Lifecycle) : UseCase<BroadcastMessageResponse, Void?>(lifecycle) {
override suspend fun run(params: Void?): Either<Exception, BroadcastMessageResponse> {
val jwtToken = Preference.getEncrypterJWTToken(context)
return jwtToken?.let { jwtToken ->
val token = Preference.getEncrypterJWTToken(context)
return token?.let { jwtToken ->
var response = call(jwtToken)
var retryCount = 0
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 androidx.lifecycle.Lifecycle
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.TracerApp
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success
@ -26,8 +26,8 @@ class UploadData(private val awsClient: AwsClient,
private val TAG = this.javaClass.simpleName
override suspend fun run(params: String): Either<Exception, None> {
val jwtToken = Preference.getEncrypterJWTToken(context)
return jwtToken?.let { jwtToken ->
val token = Preference.getEncrypterJWTToken(context)
return token?.let { jwtToken ->
try {
val initialUploadResponse = retryRetrofitCall {
awsClient.initiateUpload("Bearer $jwtToken", params).execute()

View file

@ -10,7 +10,7 @@ import android.text.TextPaint
import android.text.style.ClickableSpan
import android.view.View
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 java.lang.StringBuilder
import java.util.*
@ -18,11 +18,13 @@ import java.util.*
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 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_SUFFIX = ".html"
private const val HELP_TOPICS_BASE = "/help-topics"
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_S_CHINESE_PAGE = "/zh-hans"
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_BLUETOOTH_PAIRING_REQUEST = "#bluetooth-pairing-request"
private const val HELP_TOPICS_ANCHOR_LOCATION_PERMISSION_ANDROID = "#location-permission-android"
object LinkBuilder {
@ -47,9 +50,23 @@ object LinkBuilder {
}
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 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-Hant") -> HELP_TOPICS_T_CHINESE_PAGE
localeLanguageTag.startsWith("ar") -> HELP_TOPICS_ARABIC_PAGE
@ -60,14 +77,8 @@ object LinkBuilder {
localeLanguageTag.startsWith("pa") -> HELP_TOPICS_PUNJABI_PAGE
localeLanguageTag.startsWith("tr") -> HELP_TOPICS_TURKISH_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) =
@ -76,6 +87,9 @@ object LinkBuilder {
private fun getBluetoothPairingRequestUrl() =
getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_BLUETOOTH_PAIRING_REQUEST
private fun getLocationPairingRequestUrl() =
getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_LOCATION_PERMISSION_ANDROID
private fun getVerifyMobileNumberPinLink(linkText: String) = buildHtmlText(
"<a href=\"${getHelpTopicsUrl() + HELP_TOPICS_ANCHOR_VERIFY_MOBILE_NUMBER_PIN}\">$linkText</a>")
@ -126,15 +140,16 @@ object LinkBuilder {
}
fun getRegistrationAndPrivacyContent(context: Context): SpannableString {
val privacyUrl = getPrivacyTopicsUrl()
return buildSpannableStringContent(
context,
TracerApp.AppContext.getString(R.string.data_privacy_content),
listOf(
PRIVACY_URL,
PRIVACY_URL,
privacyUrl,
privacyUrl,
getHelpTopicsUrl(),
DEPARTMENT_OF_HEALTH_URL,
PRIVACY_URL
privacyUrl
)
)
}
@ -143,7 +158,7 @@ object LinkBuilder {
return buildSpannableStringContent(
context,
TracerApp.AppContext.getString(R.string.permission_success_content),
listOf(getBluetoothPairingRequestUrl())
listOf(getBluetoothPairingRequestUrl(), getLocationPairingRequestUrl())
)
}
@ -166,7 +181,7 @@ object LinkBuilder {
return buildSpannableStringContent(
context,
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
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.OTPChallengeRequest
import au.gov.health.covidsafe.networking.response.*
@ -40,7 +41,11 @@ interface AwsClient {
@Query("appversion") appversion: String,
@Query("token") token: String,
@Query("healthcheck") healthcheck: String,
@Query("encountershealth") encountershealth: String,
@Query("preferredlanguages") preferredLanguages: String
): 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()
}
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)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra("page", 3)
@ -57,13 +61,13 @@ class NotificationTemplates {
val builder = NotificationCompat.Builder(context, channel)
.setContentTitle(context.getText(R.string.service_not_ok_title))
.setContentText(context.getText(R.string.service_not_ok_body))
.setStyle(NotificationCompat.BigTextStyle().bigText(context.getText(R.string.service_not_ok_body)))
.setContentText(contentText)
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.drawable.ic_notification_warning)
.setTicker(context.getText(R.string.service_not_ok_body))
.setTicker(contentText)
.addAction(
R.drawable.ic_notification_setting,
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.os.Build
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.security.crypto.EncryptedSharedPreferences
import au.gov.health.covidsafe.security.crypto.MasterKeys
import au.gov.health.covidsafe.security.crypto.AESEncryptionForPreAndroidM
@ -32,6 +33,7 @@ object Preference {
private const val IS_MINOR = "IS_MINOR"
private const val POST_CODE = "POST_CODE"
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"
fun putDeviceID(context: Context, value: String) {
@ -275,4 +277,14 @@ object Preference {
.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 au.gov.health.covidsafe.logging.CentralLog
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.streetpass.persistence.StreetPassRecordStorage
import kotlinx.coroutines.CoroutineScope
@ -19,8 +18,6 @@ import kotlin.coroutines.CoroutineContext
class PrivacyCleanerReceiver : BroadcastReceiver(), CoroutineScope {
private val TAG = this.javaClass.simpleName
private var job: Job = Job()
override val coroutineContext: CoroutineContext
@ -40,7 +37,7 @@ class PrivacyCleanerReceiver : BroadcastReceiver(), CoroutineScope {
alarm.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), AlarmManager.INTERVAL_DAY, pendingIntent)
}
suspend fun cleanDb(context: Context) {
fun cleanDb(context: Context) {
val twentyOneDaysAgo = Calendar.getInstance()
twentyOneDaysAgo.set(Calendar.HOUR_OF_DAY, 23)
twentyOneDaysAgo.set(Calendar.MINUTE, 59)
@ -53,6 +50,8 @@ class PrivacyCleanerReceiver : BroadcastReceiver(), CoroutineScope {
CentralLog.i(TAG, "Street info deleted count : $countStreetDeleted")
CentralLog.i(TAG, "Status info deleted count : $countStatusDeleted")
}
const val TAG = "PrivacyCleanerReceiver"
}
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.Context
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
class UpgradeReceiver : BroadcastReceiver() {

View file

@ -7,8 +7,8 @@ import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.TracerApp
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.extensions.isBatteryOptimizationDisabled
import au.gov.health.covidsafe.extensions.isBlueToothEnabled
import au.gov.health.covidsafe.extensions.isLocationEnabledOnDevice
@ -31,14 +31,16 @@ import java.util.*
private const val TAG = "GetMessagesScheduler"
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 HEALTH_CHECK_RESULT_OK = "OK"
private const val HEALTH_CHECK_RESULT_POSSIBLE_ERROR = "POSSIBLE_ERROR"
// 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() {
private val mostRecentRecordLiveData = StreetPassRecordDatabase.getDatabase(TracerApp.AppContext).recordDao().getMostRecentRecord()
@ -89,19 +91,27 @@ object GetMessagesScheduler {
(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler?)
?.let {
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(
JobInfo.Builder(GET_MESSAGES_JOB_ID,
ComponentName(context, GetMessagesJobSchedulerService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.setPeriodic(TWENTY_FOUR_HOURS_IN_MILLIS)
.setPeriodic(schedulerInMillis)
.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) {
val context = TracerApp.AppContext
@ -127,14 +137,19 @@ object GetMessagesScheduler {
isBlueToothEnabled &&
isBatteryOptimizationDisabled &&
isLocationPermissionAllowed &&
isLocationEnabledOnDevice &&
isLastRecordWithinSevenDays
isLocationEnabledOnDevice
) {
HEALTH_CHECK_RESULT_OK
} else {
HEALTH_CHECK_RESULT_POSSIBLE_ERROR
}
val encountersHealth = if (isLastRecordWithinSevenDays) {
HEALTH_CHECK_RESULT_OK
} else {
HEALTH_CHECK_RESULT_POSSIBLE_ERROR
}
CentralLog.d(TAG, "healthCheck = $healthCheck")
val preferredLanguages = Locale.getDefault().language
@ -145,6 +160,7 @@ object GetMessagesScheduler {
appVersion,
token,
healthCheck,
encountersHealth,
preferredLanguages
)

View file

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

View file

@ -4,24 +4,31 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.bluetooth.BluetoothAdapter
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.IBinder
import android.os.PowerManager
import androidx.annotation.Keep
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LifecycleService
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.gatt.ACTION_RECEIVED_STATUS
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.STREET_PASS
import au.gov.health.covidsafe.extensions.isLocationEnabledOnDevice
import au.gov.health.covidsafe.factory.NetworkFactory
import au.gov.health.covidsafe.interactor.usecase.UpdateBroadcastMessageAndPerformScanWithExponentialBackOff
import au.gov.health.covidsafe.logging.CentralLog
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.status.Status
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.VERSION_ONE
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.GsonBuilder
import kotlinx.coroutines.CoroutineScope
@ -46,15 +55,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pub.devrel.easypermissions.EasyPermissions
import java.lang.Exception
import java.lang.ref.WeakReference
import kotlin.coroutines.CoroutineContext
private const val POWER_SAVE_WHITELIST_CHANGED = "android.os.action.POWER_SAVE_WHITELIST_CHANGED"
@Keep
class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
private var mNotificationManager: NotificationManager? = null
@Keep
private lateinit var serviceUUID: String
@ -78,31 +86,12 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
private lateinit var commandHandler: CommandHandler
private lateinit var mService: SensorMonitoringService
private var mBound: Boolean = false
private lateinit var localBroadcastManager: LocalBroadcastManager
private val awsClient = NetworkFactory.awsClient
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() {
super.onCreate()
localBroadcastManager = LocalBroadcastManager.getInstance(this)
@ -171,6 +160,10 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
return EasyPermissions.hasPermissions(this.applicationContext, *perms)
}
private fun isLocationPermissionEnabled(): Boolean {
return hasLocationPermissions() && this.isLocationEnabledOnDevice()
}
private fun isBluetoothEnabled(): Boolean {
var btOn = false
val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
@ -204,20 +197,13 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
super.onStartCommand(intent, flags, startId)
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
if (!hasLocationPermissions() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) {
if (!isLocationPermissionEnabled() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) {
CentralLog.i(
TAG,
"location permission: ${hasLocationPermissions()} bluetooth: ${isBluetoothEnabled()}"
"location permission: ${isLocationPermissionEnabled()} bluetooth: ${isBluetoothEnabled()}"
)
val notif =
NotificationTemplates.lackingThingsNotification(this.applicationContext, CHANNEL_ID)
startForeground(NOTIFICATION_ID, notif)
showForegroundNotification()
return START_STICKY
}
@ -242,14 +228,12 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
CentralLog.i(TAG, "Command is:${cmd?.string}")
//check for permissions
if (!hasLocationPermissions() || !isBluetoothEnabled()) {
if (!isLocationPermissionEnabled() || !isBluetoothEnabled()) {
CentralLog.i(
TAG,
"location permission: ${hasLocationPermissions()} bluetooth: ${isBluetoothEnabled()}"
"location permission: ${isLocationPermissionEnabled()} bluetooth: ${isBluetoothEnabled()}"
)
val notif =
NotificationTemplates.lackingThingsNotification(this.applicationContext, CHANNEL_ID)
startForeground(NOTIFICATION_ID, notif)
showForegroundNotification()
return
}
@ -326,7 +310,6 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
}
}
private fun actionUpdateBm() {
Utils.scheduleBMUpdateCheck(this.applicationContext, bmCheckInterval)
@ -345,7 +328,6 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
} else {
CentralLog.i(TAG, "Don't need to update bm")
}
}
private fun calcPhaseShift(min: Long, max: Long): Long {
@ -448,11 +430,9 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
CentralLog.i(TAG, "Performing self diagnosis")
if (!hasLocationPermissions() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) {
if (!isLocationPermissionEnabled() || !isBluetoothEnabled() || !isBatteryOptimizationDisabled()) {
CentralLog.i(TAG, "no location permission")
val notif =
NotificationTemplates.lackingThingsNotification(this.applicationContext, CHANNEL_ID)
startForeground(NOTIFICATION_ID, notif)
showForegroundNotification()
return
}
@ -480,8 +460,10 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
} else {
CentralLog.w(
TAG,
"Advertise Schedule present. Should be advertising?: ${advertiser?.shouldBeAdvertising
?: false}. Is Advertising?: ${advertiser?.isAdvertising ?: false}"
"Advertise Schedule present. Should be advertising?: ${
advertiser?.shouldBeAdvertising
?: false
}. Is Advertising?: ${advertiser?.isAdvertising ?: false}"
)
}
}
@ -498,12 +480,72 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
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() {
@ -516,6 +558,9 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
val bluetoothStatusReceivedFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
registerReceiver(bluetoothStatusReceiver, bluetoothStatusReceivedFilter)
registerLocationChangeReceiver()
registerPowerModeChangeReceiver()
CentralLog.i(TAG, "Receivers registered")
}
@ -534,9 +579,29 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
try {
unregisterReceiver(bluetoothStatusReceiver)
} catch (e: Throwable) {
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() {
@ -549,11 +614,7 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
BluetoothAdapter.STATE_TURNING_OFF -> {
CentralLog.d(TAG, "BluetoothAdapter.STATE_TURNING_OFF")
val notif = NotificationTemplates.lackingThingsNotification(
this@BluetoothMonitoringService.applicationContext,
CHANNEL_ID
)
startForeground(NOTIFICATION_ID, notif)
showForegroundNotification()
teardown()
}
BluetoothAdapter.STATE_OFF -> {
@ -565,6 +626,7 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
BluetoothAdapter.STATE_ON -> {
CentralLog.d(TAG, "BluetoothAdapter.STATE_ON")
Utils.startBluetoothMonitoringService(this@BluetoothMonitoringService.applicationContext)
showForegroundNotification()
}
}
}
@ -580,22 +642,10 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
if (ACTION_RECEIVED_STREETPASS == intent.action) {
val connRecord: ConnectionRecord? = intent.getParcelableExtra(STREET_PASS)
CentralLog.d(
TAG,
"StreetPass received: $connRecord"
)
CentralLog.d(TAG, "StreetPass received: $connRecord")
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) {
with(receiver = connRecord) {
val plainRecordByteArray = gson.toJson(StreetPassRecordDatabase.Companion.EncryptedRecord(
@ -646,11 +696,12 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
override fun onReceive(context: Context, intent: Intent) {
if (ACTION_RECEIVED_STATUS == intent.action) {
val statusRecord: Status = intent.getParcelableExtra(STATUS)
CentralLog.d(TAG, "Status received: ${statusRecord.msg}")
val status: Status? = intent.getParcelableExtra(STATUS)
status?.let {
CentralLog.d(TAG, "Status received: ${it.msg}")
if (statusRecord.msg.isNotEmpty()) {
val statusRecord = StatusRecord(statusRecord.msg)
if (it.msg.isNotEmpty()) {
val statusRecord = StatusRecord(it.msg)
launch {
statusRecordStorage.saveRecord(statusRecord)
}
@ -658,6 +709,7 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
}
}
}
}
enum class Command(val index: Int, val string: String) {
INVALID(-1, "INVALID"),
@ -713,6 +765,4 @@ class BluetoothMonitoringService : LifecycleService(), CoroutineScope {
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.os.Build
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.logging.CentralLog
import au.gov.health.covidsafe.status.Status

View file

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

View file

@ -8,14 +8,12 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
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.status.persistence.StatusRecord
import au.gov.health.covidsafe.status.persistence.StatusRecordDao
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import kotlin.concurrent.thread
const val CURRENT_DB_VERSION = 3
@ -31,7 +29,7 @@ abstract class StreetPassRecordDatabase : RoomDatabase() {
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 TIMESTAMP_COLUMN_INDEX = 1
@ -147,12 +145,12 @@ abstract class StreetPassRecordDatabase : RoomDatabase() {
val localBlob: String = if (version == 1) {
ENCRYPTED_EMPTY_DICT
} else {
val modelP = if (DUMMY_DEVICE == modelP) null else modelP
val modelC = if (DUMMY_DEVICE == modelC) null else modelC
val rssi = if (DUMMY_RSSI == rssi) null else rssi
val txPower = if (DUMMY_TXPOWER == txPower) null else txPower
val plainRecord = gson.toJson(LocalBlobV2(modelP, modelC, rssi, txPower)).toByteArray(Charsets.UTF_8)
Encryption.encryptPayload(plainRecord)
val colModelP = if (DUMMY_DEVICE == modelP) null else modelP
val colModelC = if (DUMMY_DEVICE == modelC) null else modelC
val colRssi = if (DUMMY_RSSI == rssi) null else rssi
val colTxPower = if (DUMMY_TXPOWER == txPower) null else txPower
val updatedPlainRecord = gson.toJson(LocalBlobV2(colModelP, colModelC, colRssi, colTxPower)).toByteArray(Charsets.UTF_8)
Encryption.encryptPayload(updatedPlainRecord)
}
contentValues.put("v", VERSION_TWO)
contentValues.put("org", org)

View file

@ -3,7 +3,7 @@ package au.gov.health.covidsafe.talkback
import android.view.View
import android.widget.TextView
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 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.os.Bundle
@ -6,8 +6,6 @@ import android.view.View
import androidx.fragment.app.Fragment
import androidx.navigation.Navigator
import androidx.navigation.fragment.NavHostFragment
import au.gov.health.covidsafe.HasBlockingState
import kotlinx.android.synthetic.main.fragment_intro.*
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 {
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 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

View file

@ -1,8 +1,9 @@
package au.gov.health.covidsafe
package au.gov.health.covidsafe.ui.connection
import android.annotation.SuppressLint
import android.os.Bundle
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_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.os.Bundle
import android.provider.Settings
import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.logging.CentralLog
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 au.gov.health.covidsafe.R
import kotlinx.android.synthetic.main.activity_device_name_change_prompt.*
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 au.gov.health.covidsafe.R
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 kotlinx.android.synthetic.main.fragment_help.*
import kotlinx.android.synthetic.main.fragment_help.view.*

View file

@ -4,11 +4,8 @@ import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.content.*
import android.net.Uri
import android.os.Build
import android.graphics.Typeface
import android.os.Bundle
import android.provider.Settings
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
@ -16,92 +13,207 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.widget.LinearLayout
import androidx.core.app.NotificationManagerCompat
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 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.links.LinkBuilder
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.ui.BaseFragment
import au.gov.health.covidsafe.ui.home.view.ExternalLinkCard
import au.gov.health.covidsafe.ui.base.BaseFragment
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.view.*
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_setup_incomplete_content.*
import kotlinx.android.synthetic.main.fragment_home_header.*
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.GlobalScope
import kotlinx.coroutines.launch
import pub.devrel.easypermissions.AppSettingsDialog
import pub.devrel.easypermissions.EasyPermissions
import java.lang.ref.WeakReference
import java.text.SimpleDateFormat
import java.util.*
private const val TAG = "HomeFragment"
private const val ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000L
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 {
var instanceWeakRef: WeakReference<HomeFragment>? = null
}
private lateinit var presenter: HomePresenter
private val homeFragmentViewModel: HomeFragmentViewModel by viewModels()
private var mIsBroadcastListenerRegistered = false
private var counter: Int = 0
private val mBroadcastListener: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
BluetoothAdapter.STATE_OFF -> {
bluetooth_card_view.render(formatBlueToothTitle(false), false)
refreshSetupCompleteOrIncompleteUi()
}
BluetoothAdapter.STATE_TURNING_OFF -> {
bluetooth_card_view.render(formatBlueToothTitle(false), false)
refreshSetupCompleteOrIncompleteUi()
}
BluetoothAdapter.STATE_ON -> {
bluetooth_card_view.render(formatBlueToothTitle(true), true)
refreshSetupCompleteOrIncompleteUi()
}
}
}
}
private var checkIsInternetConnected = false
private var isAppWithLatestVersion = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initializeObservers()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
presenter = HomePresenter(this)
return inflater.inflate(R.layout.fragment_home, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return FragmentHomeBinding.inflate(layoutInflater).apply {
lifecycleOwner = viewLifecycleOwner
viewModel = homeFragmentViewModel
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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
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) {
view.header_background.setOnClickListener {
header_background.setOnClickListener {
counter++
if (counter >= 2) {
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() {
super.onPause()
instanceWeakRef = null
unregisterAllClickListener()
unregisterBroadcastListener()
}
private fun unregisterAllClickListener() {
bluetooth_card_view.setOnClickListener(null)
location_card_view.setOnClickListener(null)
battery_card_view.setOnClickListener(null)
home_been_tested_button.setOnClickListener(null)
home_setup_complete_share.setOnClickListener(null)
home_setup_complete_news.setOnClickListener(null)
home_setup_complete_app.setOnClickListener(null)
app_share.setOnClickListener(null)
help_topics_link.setOnClickListener(null)
}
private fun unregisterBroadcastListener() {
activity?.let { activity ->
if (mIsBroadcastListenerRegistered) {
activity.unregisterReceiver(mBroadcastListener)
@ -182,12 +250,26 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
override fun onDestroyView() {
super.onDestroyView()
NetworkConnectionCheck.removeNetworkChangedListener(this)
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 {
val isUploaded = Preference.isDataUploaded(context)
CentralLog.d(TAG, "isDataUploadedInPast14Days : $isUploaded")
if (!isUploaded) {
return false
}
@ -205,97 +287,88 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
@SuppressLint("SetTextI18n")
fun refreshSetupCompleteOrIncompleteUi() {
lifecycleScope.launch(Dispatchers.Main) {
CentralLog.d(TAG, "refreshSetupCompleteOrIncompleteUi")
context?.let {
val isAllPermissionsEnabled = allPermissionsEnabled()
val isAllPermissionsEnabled = it.allPermissionsEnabled()
if (!isAllPermissionsEnabled) {
NotificationBuilder.clearPossibleIssueNotificationCheck()
}
val isDataUploadedInPast14Days = isDataUploadedInPast14Days(it)
val line1 = it.getString(
updateSetupCompleteStatus(isAllPermissionsEnabled)
updateSetupCompleteHeaderTitle1(it, isAllPermissionsEnabled, isDataUploadedInPast14Days)
updateSetupCompleteHeaderTitle2(isAllPermissionsEnabled)
updateHealthOfficialTile(isDataUploadedInPast14Days)
}
}
}
private fun updateSetupCompleteHeaderTitle1(context: Context, isAllPermissionsEnabled: Boolean, isDataUploadedInPast14Days: Boolean) {
showLastDataUploadedInfo(context, isDataUploadedInPast14Days)
home_header_setup_complete_header_line_1.run {
text = getString(getCovidActiveStatusMessage(isAllPermissionsEnabled))
setHeading()
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
}
private fun updateSetupCompleteHeaderTitle2(isAllPermissionsEnabled: Boolean) {
if (isAllPermissionsEnabled) {
R.string.home_header_active_title
} else {
R.string.home_header_inactive_title
}
)
val isAppPerformanceRequired = !checkIsInternetConnected || !isAppWithLatestVersion
val line2 = if (isDataUploadedInPast14Days) {
it.getString(R.string.home_header_uploaded_on_date, getDataUploadDateHtmlString(it)) + "<br/>"
} else {
""
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))
}
val line3 = it.getString(
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) {
R.string.home_header_active_no_action_required
home_setup_incomplete_permissions_layout.visibility = GONE
home_setup_complete_layout.visibility = VISIBLE
} 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
home_setup_incomplete_permissions_layout.visibility = VISIBLE
home_setup_complete_layout.visibility = GONE
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 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 allPermissionsEnabled(): Boolean {
val context = requireContext()
val bluetoothEnabled = context.isBlueToothEnabled() ?: false
val nonBatteryOptimizationAllowed = context.isBatteryOptimizationDisabled() ?: true
val locationStatusAllowed = context.isLocationPermissionAllowed() ?: true
return bluetoothEnabled &&
nonBatteryOptimizationAllowed &&
locationStatusAllowed &&
context.isLocationEnabledOnDevice()
}
private fun registerBroadcast() {
activity?.let { activity ->
var f = IntentFilter()
@ -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() {
requireContext().isBlueToothEnabled()?.let {
bluetooth_card_view.visibility = VISIBLE
if (!it) {
bluetooth_card_view_layout.visibility = VISIBLE
bluetooth_card_view.render(formatBlueToothTitle(it), it)
} else {
bluetooth_card_view_layout.visibility = GONE
}
} ?: run {
bluetooth_card_view.visibility = GONE
bluetooth_card_view_layout.visibility = GONE
}
}
private fun updateBatteryOptimizationStatus() {
requireContext().isBatteryOptimizationDisabled()?.let {
battery_card_view.visibility = VISIBLE
if (!it) {
battery_card_view_layout.visibility = VISIBLE
battery_card_view.render(
formatNonBatteryOptimizationTitle(!it),
it,
getString(R.string.battery_optimisation_prompt)
)
} else {
battery_card_view_layout.visibility = GONE
}
} ?: run {
battery_card_view.visibility = GONE
battery_card_view_layout.visibility = GONE
}
}
@ -341,11 +415,21 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
requireContext().isLocationPermissionAllowed()?.let {
val locationWorking = it && requireContext().isLocationEnabledOnDevice()
val locationOffPrompts = getString(R.string.home_set_location_why)
location_card_view.visibility = VISIBLE
if (!locationWorking) {
location_card_view_layout.visibility = VISIBLE
location_card_view.render(formatLocationTitle(locationWorking), locationWorking, locationOffPrompts)
} else {
location_card_view_layout.visibility = GONE
}
} ?: 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))
}
private fun formatPushNotificationTitle(on: Boolean): String {
return resources.getString(R.string.home_push_notification_permission, getPermissionEnabledTitle(on))
}
private fun getPermissionEnabledTitle(on: Boolean): String {
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)
}
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>) {
if (requestCode == LOCATION && EasyPermissions.somePermissionPermanentlyDenied(this, listOf(Manifest.permission.ACCESS_COARSE_LOCATION))) {
AppSettingsDialog.Builder(this).build().show()
@ -418,111 +470,44 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
fun updateConnectionTile(isInternetConnected: Boolean) {
// called on IO thread; run the UI logic on UI thread
GlobalScope.launch(Dispatchers.Main) {
CentralLog.d(TAG, "updateConnectionTile() isInternetConnected = $isInternetConnected")
var visibility = if (isInternetConnected) GONE else VISIBLE
// don't display the tile when there's permission not enabled
if (!allPermissionsEnabled()) {
visibility = GONE
private fun isShowThanksCovidMsg(isAllPermissionsEnabled: Boolean): Boolean {
return isAllPermissionsEnabled && checkIsInternetConnected && isAppWithLatestVersion && NotificationBuilder.isShowPossibleIssueNotification()
}
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))
}
private fun showCovidThanksMessage() {
activity?.runOnUiThread {
context?.let {
home_header_setup_complete_header_line_1.text = it.getString(getCovidActiveStatusMessage(it.allPermissionsEnabled()))
}
}
}
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)
}
override fun onNetworkStatusChanged(isAvailable: Boolean) {
CentralLog.d(TAG, "onNetworkStatusChanged: $checkIsInternetConnected $isAvailable")
checkIsInternetConnected = isAvailable
refreshSetupCompleteOrIncompleteUi()
initiateFetchingCaseNumbers()
}
if (!messagesResponse.messages.isNullOrEmpty()) {
improve_performance_card.visibility = 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)
private val mBroadcastListener: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
BluetoothAdapter.STATE_OFF -> {
bluetooth_card_view.render(formatBlueToothTitle(false), false)
refreshSetupCompleteOrIncompleteUi()
}
it.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
it.setMessage(message)
it.setErrorTextColor()
improve_performance_card_linear_layout.addView(it)
BluetoothAdapter.STATE_TURNING_OFF -> {
bluetooth_card_view.render(formatBlueToothTitle(false), false)
refreshSetupCompleteOrIncompleteUi()
}
}
} else {
improve_performance_card.visibility = GONE
BluetoothAdapter.STATE_ON -> {
refreshSetupCompleteOrIncompleteUi()
}
}
}
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.Intent
import android.content.res.TypedArray
import android.graphics.Typeface
import android.net.Uri
import android.text.Html
import android.util.AttributeSet
@ -26,24 +27,32 @@ class ExternalLinkCard @JvmOverloads constructor(
attrs?.let {
val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.ExternalLinkCard)
val icon = a.getDrawable(R.styleable.ExternalLinkCard_external_linkCard_icon)
val iconVisible = a.getBoolean(R.styleable.ExternalLinkCard_external_linkCard_icon_visible, true)
val startIcon = a.getDrawable(R.styleable.ExternalLinkCard_external_linkCard_start_icon)
val startIconVisible = a.getBoolean(R.styleable.ExternalLinkCard_external_linkCard_start_icon_visible, true)
val title = a.getString(R.styleable.ExternalLinkCard_external_linkCard_title)
val content = a.getString(R.styleable.ExternalLinkCard_external_linkCard_content)
val padding = a.getDimension(R.styleable.ExternalLinkCard_external_linkCard_icon_padding, 0f).toInt()
val iconBackground = a.getResourceId(R.styleable.ExternalLinkCard_external_linkCard_icon_background, R.color.transparent)
val padding = a.getDimension(R.styleable.ExternalLinkCard_external_linkCard_start_icon_padding, 0f).toInt()
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 endIconVisible = a.getBoolean(R.styleable.ExternalLinkCard_external_linkCard_end_icon_visible, true)
val textColor = ContextCompat.getColor(context, textColorResId)
external_link_round_image.setImageDrawable(icon)
external_link_round_image.visibility = if (iconVisible) View.VISIBLE else View.GONE
external_link_round_image.setImageDrawable(startIcon)
external_link_round_image.visibility = if (startIconVisible) View.VISIBLE else View.GONE
external_link_round_image.setBackgroundResource(iconBackground)
external_link_round_image.setPadding(padding, padding, padding, padding)
external_link_headline.text = title
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)
a.recycle()
}
}
@ -53,19 +62,33 @@ class ExternalLinkCard @JvmOverloads constructor(
external_link_content.setTextColor(textColor)
val icChevron =
if (textColor == ContextCompat.getColor(context, R.color.error)) {
when (textColor) {
ContextCompat.getColor(context, R.color.error_red) -> {
R.drawable.ic_chevron_right_red
} else {
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 {
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) {
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) {
external_link_headline.text = title
external_link_content.text = body
@ -89,11 +116,11 @@ class ExternalLinkCard @JvmOverloads constructor(
}
fun setErrorTextColor() {
setTextColor(ContextCompat.getColor(context, R.color.error))
setTextColor(ContextCompat.getColor(context, R.color.error_red))
}
fun setTopRightIcon(iconResID: Int) {
next_arrow.setImageResource(iconResID)
external_link_end_image_view.setImageResource(iconResID)
}
fun setColorForContentWithAction() {

View file

@ -9,7 +9,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
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.*
class PermissionStatusCard @JvmOverloads constructor(
@ -32,7 +32,7 @@ class PermissionStatusCard @JvmOverloads constructor(
}
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)
permission_icon.isSelected = correct

View file

@ -7,9 +7,9 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
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.TracerApp
import au.gov.health.covidsafe.app.TracerApp
const val VIEW_TYPE_GROUP_TITLE = 1
const val VIEW_TYPE_COUNTRY = 2

View file

@ -8,11 +8,11 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.HasBlockingState
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.ui.base.HasBlockingState
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.ui.PagerContainer
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerContainer
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import com.github.razir.progressbutton.bindProgressButton
import com.github.razir.progressbutton.hideProgress
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.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
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.view.*

View file

@ -16,12 +16,12 @@ import androidx.annotation.NavigationRes
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
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.TracerApp
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
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_CHALLENGE_NAME
@ -50,7 +50,7 @@ class EnterNumberFragment : PagerChildFragment() {
private var callingCode: 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 fun updateSelectedCountry() {

View file

@ -4,7 +4,7 @@ package au.gov.health.covidsafe.ui.onboarding.fragment.enternumber
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
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.extensions.isInternetAvailable
import au.gov.health.covidsafe.factory.NetworkFactory

View file

@ -1,6 +1,7 @@
package au.gov.health.covidsafe.ui.onboarding.fragment.enterpin
import android.app.AlertDialog
import android.graphics.Color
import android.os.Bundle
import android.os.CountDownTimer
import android.text.method.LinkMovementMethod
@ -9,15 +10,14 @@ import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.NavigationRes
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged
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.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import com.atlassian.mobilekit.module.core.utils.SystemUtils
import kotlinx.android.synthetic.main.fragment_enter_pin.*
import kotlinx.android.synthetic.main.fragment_enter_pin.view.*
@ -107,23 +107,15 @@ class EnterPinFragment : PagerChildFragment() {
} else {
"$numberOfSecondsInt"
}
enter_pin_timer_value?.text = "$numberOfMinsInt:$finalNumberOfSecondsString"
}
override fun onFinish() {
enter_pin_timer_value?.text = "0:00"
enter_pin_resend_pin.isEnabled = true
activity?.let {
enter_pin_resend_pin.setLinkTextColor(ContextCompat.getColor(it, R.color.hyperlink_enabled))
}
setupTimer(false)
}
}
stopWatch?.start()
enter_pin_resend_pin.isEnabled = false
activity?.let {
enter_pin_resend_pin.setLinkTextColor(ContextCompat.getColor(it, R.color.hyperlink_disabled))
}
setupTimer(true)
}
fun resetTimer() {
@ -200,4 +192,10 @@ class EnterPinFragment : PagerChildFragment() {
.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.LifecycleObserver
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.factory.NetworkFactory
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.links.LinkBuilder
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
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.view.*

View file

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

View file

@ -7,11 +7,11 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_permission.root
import kotlinx.android.synthetic.main.fragment_permission_device_name.*

View file

@ -4,6 +4,7 @@ import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.PowerManager
@ -13,13 +14,13 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import androidx.core.content.ContextCompat
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.TracerApp
import au.gov.health.covidsafe.app.TracerApp
import au.gov.health.covidsafe.extensions.*
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import kotlinx.android.synthetic.main.fragment_permission.*
import pub.devrel.easypermissions.EasyPermissions
@ -82,9 +83,25 @@ class PermissionFragment : PagerChildFragment(), EasyPermissions.PermissionCallb
private fun hasAllPermissionsAndBluetoothOn(): Boolean {
val context = TracerApp.AppContext
return context.isBlueToothEnabled() == true
&& requiredPermissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED }
&& ContextCompat.getSystemService(context, PowerManager::class.java)?.isIgnoringBatteryOptimizations(context.packageName) ?: true
return context.isBlueToothEnabled() == true && checkAllRequiredPermissions() && checkIgnoreBatteryOptimization()
}
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() {

View file

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

View file

@ -13,12 +13,12 @@ import android.widget.ArrayAdapter
import android.widget.TextView.OnEditorActionListener
import androidx.appcompat.app.AlertDialog
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.logging.CentralLog
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment
import kotlinx.android.synthetic.main.fragment_personal_details.*
import java.util.regex.Pattern
@ -27,7 +27,7 @@ import java.util.regex.Pattern
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 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() {
@ -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) {
hideKeyboard()
textView.clearFocus()

View file

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

View file

@ -8,8 +8,8 @@ import android.view.accessibility.AccessibilityEvent
import androidx.core.os.bundleOf
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment
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.os.Bundle
@ -10,12 +10,11 @@ import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
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.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 java.util.*
class SplashActivity : AppCompatActivity() {

View file

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

View file

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

View file

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

View file

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

View file

@ -11,8 +11,8 @@ import androidx.core.os.bundleOf
import androidx.core.widget.doOnTextChanged
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.talkback.setHeading
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.base.PagerChildFragment
import au.gov.health.covidsafe.ui.base.UploadButtonLayout
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.UploadingErrorDialog

View file

@ -4,7 +4,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
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.factory.NetworkFactory
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?)

View file

@ -1,4 +1,4 @@
package au.gov.health.covidsafe
package au.gov.health.covidsafe.ui.utils
import android.Manifest
import android.bluetooth.BluetoothAdapter
@ -12,6 +12,9 @@ import android.provider.Settings
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
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.logging.CentralLog
import au.gov.health.covidsafe.scheduler.Scheduler
@ -29,7 +32,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
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.util.*
@ -295,17 +300,21 @@ object Utils {
private fun checkInternetConnectionToURL(url: String, callback: (Boolean) -> Unit) {
GlobalScope.launch(Dispatchers.IO) {
var reachable = false
reachable = try {
InetAddress.getByName(url).isReachable(2000)
val reachable = try {
// 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) {
CentralLog.w(TAG, "checkInternetConnectionToURL() failed to reach the url $url")
false
}
CentralLog.w(TAG, "checkInternetConnectionToURL() reachability to url $url is $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.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.fragment.app.FragmentActivity
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.logging.CentralLog
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:fillColor="#A31919"/>
<path
android:pathData="M16.5,7.5L7.5,16.5"
android:pathData="M12,12.6827V8"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M7.5,7.5L16.5,16.5"
android:pathData="M12,17.3652H12.012"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
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">
<stroke
android:width="1dp"
android:color="@color/error" />
android:color="@color/error_red" />
<corners android:radius="2dp" />
</shape>

View file

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

View file

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

View file

@ -5,6 +5,27 @@
android:layout_width="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
android:id="@+id/recyclerview"
android:layout_width="0dp"
@ -12,22 +33,11 @@
android:background="@android:color/darker_gray"
android:backgroundTint="@color/lighter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="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" />
<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
android:id="@+id/delete"
android:layout_width="wrap_content"
@ -35,9 +45,7 @@
android:layout_margin="8dp"
android:src="@drawable/ic_delete_black_24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/home_push_notification_token" />
<LinearLayout
android:layout_width="wrap_content"

View file

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

View file

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

View file

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

View file

@ -1,76 +1,68 @@
<?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:tools="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools">
<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_height="match_parent"
android:background="#f6f6f6">
android:background="@color/screen_background">
<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">
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">
<View
android:id="@+id/header_background"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/lighter_green"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="@+id/header_background_overlap"
app:layout_constraintTop_toTopOf="parent" />
<include layout="@layout/fragment_home_header" />
<View
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
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
android:id="@+id/home_header_help"
android:layout_width="@dimen/keyline_9"
<androidx.constraintlayout.widget.Barrier
android:id="@+id/home_header_barrier"
android:layout_width="wrap_content"
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" />
app:barrierDirection="bottom"
app:constraint_referenced_ids="header_background" />
<include layout="@layout/fragment_home_setup_complete_header" />
<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="home_header_no_bluetooth_pairing" />
<include layout="@layout/fragment_home_setup_incomplete_content" />
app:constraint_referenced_ids="case_number_card" />
<include layout="@layout/fragment_home_external_links" />
@ -79,28 +71,29 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="push_card_view, change_language_card" />
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/keyline_4"
android:layout_marginBottom="@dimen/keyline_5"
android:layout_marginTop="@dimen/space_24"
android:gravity="center"
android:textColor="@color/cadet_grey"
android:textColor="@color/slate_black_2"
app:layout_constraintTop_toBottomOf="@+id/content_barrier" />
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/keyline_2"
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"
@ -113,8 +106,11 @@
<View
android:layout_width="match_parent"
android:layout_height="@dimen/keyline_2"
android:background="@color/error" />
android:layout_height="@dimen/space_12"
android:background="@color/error_red" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

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: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
android:id="@+id/help_topics_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4"
app:layout_constraintTop_toBottomOf="@+id/external_links_bottom_card"
android:layout_marginTop="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/header_barrier"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp"
card_view:cardMaxElevation="2dp"
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/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>
<include layout="@layout/view_help_topics_tile" />
<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
android:id="@+id/change_language_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4"
app:layout_constraintTop_toBottomOf="@+id/notification_status_card"
android:layout_marginTop="@dimen/space_24"
app:layout_constraintTop_toBottomOf="@+id/help_topics_card"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="6dp"
card_view:cardMaxElevation="2dp"
card_view:cardCornerRadius="0dp"
card_view:cardMaxElevation="@dimen/card_elevation_10dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="0dp">
@ -233,11 +37,62 @@
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"
android:paddingTop="@dimen/space_4"
android:paddingBottom="@dimen/space_4"
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" />
</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>

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