blob: 90fa35e3a303cdbe5e76f2f46bea8cd442558179 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.healthconnect.controller.route
import android.app.Activity
import android.content.Intent
import android.health.connect.HealthConnectManager.EXTRA_EXERCISE_ROUTE
import android.health.connect.HealthConnectManager.EXTRA_SESSION_ID
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity
import com.android.healthconnect.controller.R
import com.android.healthconnect.controller.dataentries.formatters.ExerciseSessionFormatter
import com.android.healthconnect.controller.migration.MigrationActivity.Companion.maybeShowWhatsNewDialog
import com.android.healthconnect.controller.migration.MigrationActivity.Companion.showMigrationInProgressDialog
import com.android.healthconnect.controller.migration.MigrationActivity.Companion.showMigrationPendingDialog
import com.android.healthconnect.controller.migration.MigrationViewModel
import com.android.healthconnect.controller.migration.api.MigrationState
import com.android.healthconnect.controller.route.ExerciseRouteViewModel.SessionWithAttribution
import com.android.healthconnect.controller.shared.app.AppInfoReader
import com.android.healthconnect.controller.shared.dialog.AlertDialogBuilder
import com.android.healthconnect.controller.shared.map.MapView
import com.android.healthconnect.controller.utils.FeatureUtils
import com.android.healthconnect.controller.utils.LocalDateTimeFormatter
import com.android.healthconnect.controller.utils.logging.ErrorPageElement
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
/** Request route activity for Health Connect. */
@AndroidEntryPoint(FragmentActivity::class)
class RouteRequestActivity : Hilt_RouteRequestActivity() {
companion object {
private const val TAG = "RouteRequestActivity"
}
@Inject lateinit var appInfoReader: AppInfoReader
@Inject lateinit var featureUtils: FeatureUtils
@VisibleForTesting var dialog: AlertDialog? = null
@VisibleForTesting lateinit var infoDialog: AlertDialog
private val viewModel: ExerciseRouteViewModel by viewModels()
private val migrationViewModel: MigrationViewModel by viewModels()
private var requester: String? = null
private var migrationState = MigrationState.UNKNOWN
private var sessionWithAttribution: SessionWithAttribution? = null
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!featureUtils.isExerciseRouteEnabled()) {
Log.e(TAG, "Exercise routes not available, finishing.")
setResult(Activity.RESULT_CANCELED, Intent())
finish()
return
}
if (!intent.hasExtra(EXTRA_SESSION_ID) ||
intent.getStringExtra(EXTRA_SESSION_ID) == null ||
callingPackage == null) {
Log.e(TAG, "Invalid Intent Extras, finishing.")
setResult(Activity.RESULT_CANCELED, Intent())
finish()
return
}
viewModel.getExerciseWithRoute(intent.getStringExtra(EXTRA_SESSION_ID)!!)
runBlocking { requester = appInfoReader.getAppMetadata(callingPackage!!).appName }
viewModel.exerciseSession.observe(this) { session ->
this.sessionWithAttribution = session
setupRequestDialog(session)
}
migrationViewModel.migrationState.observe(this) { migrationState ->
when (migrationState) {
is MigrationViewModel.MigrationFragmentState.WithData -> {
maybeShowMigrationDialog(migrationState.migrationState)
this.migrationState = migrationState.migrationState
}
else -> {
// do nothing
}
}
}
}
private fun setupRequestDialog(data: SessionWithAttribution?) {
if ((data == null) ||
(data.session?.route == null) ||
data.session?.route!!.routeLocations.isEmpty()) {
Log.e(TAG, "No route or empty route, finishing.")
val result = Intent()
result.putExtra(EXTRA_SESSION_ID, intent.getStringExtra(EXTRA_SESSION_ID))
setResult(Activity.RESULT_CANCELED, result)
finish()
return
}
val session = data.session!!
val sessionId = intent.getStringExtra(EXTRA_SESSION_ID)
val sessionDetails =
applicationContext.getString(
R.string.date_owner_format,
LocalDateTimeFormatter(applicationContext).formatLongDate(session.startTime),
data.appInfo.appName)
val sessionTitle =
if (session.title.isNullOrBlank())
ExerciseSessionFormatter.Companion.getExerciseType(
applicationContext, session.exerciseType)
else session.title
val view = layoutInflater.inflate(R.layout.route_request_dialog, null)
val title = applicationContext.getString(R.string.request_route_header_title, requester)
view.findViewById<MapView>(R.id.map_view).setRoute(session.route!!)
view.findViewById<TextView>(R.id.session_title).text = sessionTitle
view.findViewById<TextView>(R.id.date_app).text = sessionDetails
view.findViewById<LinearLayout>(R.id.more_info).setOnClickListener {
dialog?.hide()
setupInfoDialog()
infoDialog.show()
}
view.findViewById<Button>(R.id.route_dont_allow_button).setOnClickListener {
val result = Intent()
result.putExtra(EXTRA_SESSION_ID, sessionId)
setResult(Activity.RESULT_CANCELED, result)
finish()
}
view.findViewById<Button>(R.id.route_allow_button).setOnClickListener {
val result = Intent()
result.putExtra(EXTRA_SESSION_ID, intent.getStringExtra(EXTRA_SESSION_ID))
result.putExtra(EXTRA_EXERCISE_ROUTE, session.route)
setResult(Activity.RESULT_OK, result)
finish()
}
dialog =
AlertDialogBuilder(this)
.setIcon(R.attr.healthConnectIcon)
.setTitle(title)
.setView(view)
.setCancelable(false)
.create()
if (!dialog!!.isShowing && migrationState in listOf(
MigrationState.IDLE, MigrationState.COMPLETE, MigrationState.COMPLETE_IDLE,
MigrationState.ALLOWED_MIGRATOR_DISABLED, MigrationState.ALLOWED_ERROR
)) {
dialog?.show()
}
}
private fun setupInfoDialog() {
val view = layoutInflater.inflate(R.layout.route_sharing_info_dialog, null)
infoDialog =
AlertDialogBuilder(this)
.setIcon(R.attr.privacyPolicyIcon)
.setTitle(getString(R.string.request_route_info_header_title))
.setNegativeButton(R.string.back_button, ErrorPageElement.UNKNOWN_ELEMENT) { _, _ ->
dialog?.show()
}
.setView(view)
.setCancelable(false)
.create()
}
private fun maybeShowMigrationDialog(migrationState: MigrationState) {
when (migrationState) {
MigrationState.IN_PROGRESS -> {
showMigrationInProgressDialog(
this,
applicationContext.getString(
R.string.migration_in_progress_permissions_dialog_content, requester)) {
_,
_ ->
finish()
}
}
MigrationState.ALLOWED_PAUSED,
MigrationState.ALLOWED_NOT_STARTED,
MigrationState.APP_UPGRADE_REQUIRED,
MigrationState.MODULE_UPGRADE_REQUIRED -> {
showMigrationPendingDialog(
this,
applicationContext.getString(
R.string.migration_pending_permissions_dialog_content, requester),
positiveButtonAction = { _, _ -> dialog?.show() },
negativeButtonAction = { _, _ ->
val result = Intent()
result.putExtra(EXTRA_SESSION_ID, intent.getStringExtra(EXTRA_SESSION_ID))
setResult(Activity.RESULT_CANCELED, result)
finish()
})
}
MigrationState.COMPLETE -> {
maybeShowWhatsNewDialog(this) { _, _ ->
dialog?.show()
}
}
else -> {
// Show the request dialog
dialog?.show()
}
}
}
override fun onDestroy() {
dialog?.dismiss()
super.onDestroy()
}
}