1 /** <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package com.android.healthconnect.controller.permissions.shared 15 16 import android.app.Dialog 17 import android.os.Bundle 18 import android.view.View 19 import android.widget.CheckBox 20 import android.widget.ImageView 21 import android.widget.TextView 22 import androidx.core.os.bundleOf 23 import androidx.core.view.isVisible 24 import androidx.fragment.app.DialogFragment 25 import androidx.fragment.app.activityViewModels 26 import androidx.fragment.app.setFragmentResult 27 import com.android.healthconnect.controller.R 28 import com.android.healthconnect.controller.permissions.additionalaccess.AdditionalAccessViewModel 29 import com.android.healthconnect.controller.shared.dialog.AlertDialogBuilder 30 import com.android.healthconnect.controller.utils.AttributeResolver 31 import com.android.healthconnect.controller.utils.logging.DisconnectAppDialogElement 32 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger 33 import dagger.hilt.android.AndroidEntryPoint 34 import javax.inject.Inject 35 36 /** A Dialog Fragment to get confirmation from user for disconnecting from Health Connect. */ 37 @AndroidEntryPoint(DialogFragment::class) 38 class DisconnectDialogFragment constructor() : Hilt_DisconnectDialogFragment() { 39 40 private val viewModel: AdditionalAccessViewModel by activityViewModels() 41 42 constructor(appName: String, enableDeleteData: Boolean = true) : this() { 43 this.appName = appName 44 this.enableDeleteData = enableDeleteData 45 } 46 47 companion object { 48 const val TAG = "DisconnectDialogFragment" 49 const val DISCONNECT_CANCELED_EVENT = "DISCONNECT_CANCELED_EVENT" 50 const val DISCONNECT_ALL_EVENT = "DISCONNECT_ALL_EVENT" 51 const val KEY_DELETE_DATA = "KEY_DELETE_DATA" 52 const val KEY_APP_NAME = "KEY_APP_NAME" 53 const val KEY_ENABLE_DELETE_DATA = "KEY_ENABLE_DELETE_DATA" 54 const val KEY_INCLUDE_BACKGROUND_READ = "KEY_INCLUDE_BACKGROUND_READ" 55 const val KEY_INCLUDE_HISTORY_READ = "KEY_INCLUDE_HISTORY_READ" 56 } 57 58 lateinit var appName: String 59 private var enableDeleteData: Boolean = true 60 private var includeBackgroundRead: Boolean = false 61 private var includeHistoryRead: Boolean = false 62 63 @Inject lateinit var logger: HealthConnectLogger 64 65 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 66 if (savedInstanceState != null) { 67 appName = savedInstanceState.getString(KEY_APP_NAME, "") 68 enableDeleteData = savedInstanceState.getBoolean(KEY_ENABLE_DELETE_DATA, true) 69 includeBackgroundRead = 70 savedInstanceState.getBoolean(KEY_INCLUDE_BACKGROUND_READ, false) 71 includeHistoryRead = savedInstanceState.getBoolean(KEY_INCLUDE_HISTORY_READ, false) 72 } 73 74 val additionalPermissionsState = 75 viewModel.additionalAccessState.value ?: AdditionalAccessViewModel.State() 76 includeHistoryRead = additionalPermissionsState.historyReadUIState.isDeclared 77 includeBackgroundRead = additionalPermissionsState.backgroundReadUIState.isDeclared 78 79 val body = layoutInflater.inflate(R.layout.dialog_message_with_checkbox, null) 80 body.findViewById<TextView>(R.id.dialog_message).apply { 81 text = 82 if (includeBackgroundRead && includeHistoryRead) { 83 getString(R.string.permissions_disconnect_dialog_message_combined, appName) 84 } else if (includeBackgroundRead) { 85 getString(R.string.permissions_disconnect_dialog_message_background, appName) 86 } else if (includeHistoryRead) { 87 getString(R.string.permissions_disconnect_dialog_message_history, appName) 88 } else { 89 getString(R.string.permissions_disconnect_dialog_message, appName) 90 } 91 } 92 body.findViewById<TextView>(R.id.dialog_title).apply { 93 text = getString(R.string.permissions_disconnect_dialog_title) 94 } 95 val iconView = body.findViewById(R.id.dialog_icon) as ImageView 96 val iconDrawable = 97 AttributeResolver.getNullableDrawable(body.context, R.attr.disconnectIcon) 98 iconDrawable?.let { 99 iconView.setImageDrawable(it) 100 iconView.visibility = View.VISIBLE 101 } 102 val checkBox = 103 body.findViewById<CheckBox>(R.id.dialog_checkbox).apply { 104 text = getString(R.string.permissions_disconnect_dialog_checkbox, appName) 105 isVisible = enableDeleteData 106 } 107 checkBox.setOnCheckedChangeListener { _, _ -> 108 logger.logInteraction(DisconnectAppDialogElement.DISCONNECT_APP_DIALOG_DELETE_CHECKBOX) 109 } 110 111 val dialog = 112 AlertDialogBuilder(this, DisconnectAppDialogElement.DISCONNECT_APP_DIALOG_CONTAINER) 113 .setView(body) 114 .setNeutralButton( 115 android.R.string.cancel, 116 DisconnectAppDialogElement.DISCONNECT_APP_DIALOG_CANCEL_BUTTON) { _, _ -> 117 setFragmentResult(DISCONNECT_CANCELED_EVENT, bundleOf()) 118 } 119 .setPositiveButton( 120 R.string.permissions_disconnect_dialog_disconnect, 121 DisconnectAppDialogElement.DISCONNECT_APP_DIALOG_CONFIRM_BUTTON) { _, _ -> 122 setFragmentResult( 123 DISCONNECT_ALL_EVENT, bundleOf(KEY_DELETE_DATA to checkBox.isChecked)) 124 } 125 .setAdditionalLogging { 126 logger.logImpression( 127 DisconnectAppDialogElement.DISCONNECT_APP_DIALOG_DELETE_CHECKBOX) 128 } 129 .create() 130 dialog.setCanceledOnTouchOutside(false) 131 return dialog 132 } 133 134 override fun onSaveInstanceState(outState: Bundle) { 135 super.onSaveInstanceState(outState) 136 outState.putString(KEY_APP_NAME, appName) 137 outState.putBoolean(KEY_ENABLE_DELETE_DATA, enableDeleteData) 138 outState.putBoolean(KEY_INCLUDE_BACKGROUND_READ, includeBackgroundRead) 139 outState.putBoolean(KEY_INCLUDE_HISTORY_READ, includeHistoryRead) 140 } 141 } 142