1 /** <lambda>null2 * Copyright (C) 2022 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 * ``` 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * ``` 10 * 11 * Unless required by applicable law or agreed to in writing, software distributed under the License 12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 * or implied. See the License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.healthconnect.testapps.toolbox.ui 17 18 import android.content.ComponentName 19 import android.content.Intent 20 import android.content.pm.PackageManager 21 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT 22 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED 23 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED 24 import android.health.connect.HealthConnectManager 25 import android.os.Bundle 26 import android.view.LayoutInflater 27 import android.view.View 28 import android.view.ViewGroup 29 import android.widget.Button 30 import android.widget.Toast 31 import androidx.activity.result.ActivityResultLauncher 32 import androidx.activity.result.contract.ActivityResultContracts 33 import androidx.core.content.ContextCompat 34 import androidx.fragment.app.Fragment 35 import androidx.fragment.app.viewModels 36 import androidx.navigation.NavController 37 import androidx.navigation.fragment.findNavController 38 import com.android.healthconnect.testapps.toolbox.Constants.ADDITIONAL_PERMISSIONS 39 import com.android.healthconnect.testapps.toolbox.Constants.FITNESS_PERMISSIONS 40 import com.android.healthconnect.testapps.toolbox.Constants.FITNESS_READ_PERMISSIONS 41 import com.android.healthconnect.testapps.toolbox.Constants.FITNESS_WRITE_PERMISSIONS 42 import com.android.healthconnect.testapps.toolbox.Constants.HEALTH_PERMISSIONS 43 import com.android.healthconnect.testapps.toolbox.Constants.MEDICAL_PERMISSIONS 44 import com.android.healthconnect.testapps.toolbox.Constants.MEDICAL_READ_PERMISSIONS 45 import com.android.healthconnect.testapps.toolbox.Constants.MEDICAL_WRITE_AND_OTHERS 46 import com.android.healthconnect.testapps.toolbox.Constants.READ_HEALTH_DATA_HISTORY 47 import com.android.healthconnect.testapps.toolbox.Constants.READ_HEALTH_DATA_IN_BACKGROUND 48 import com.android.healthconnect.testapps.toolbox.Constants.WRITE_ALL_MEDICAL_DATA 49 import com.android.healthconnect.testapps.toolbox.PerformanceTestingFragment 50 import com.android.healthconnect.testapps.toolbox.R 51 import com.android.healthconnect.testapps.toolbox.seed.SeedData 52 import com.android.healthconnect.testapps.toolbox.viewmodels.HomeFragmentViewModel 53 import com.android.healthconnect.testapps.toolbox.viewmodels.PerformanceTestingViewModel 54 import kotlin.system.exitProcess 55 56 /** Home fragment for Health Connect Toolbox. */ 57 class HomeFragment : Fragment() { 58 59 override fun onCreateView( 60 inflater: LayoutInflater, 61 container: ViewGroup?, 62 savedInstanceState: Bundle?, 63 ): View? { 64 return inflater.inflate(R.layout.fragment_home, container, false) 65 } 66 67 private lateinit var mRequestPermissionLauncher: ActivityResultLauncher<Array<String>> 68 private lateinit var mNavigationController: NavController 69 private val performanceTestingViewModel: PerformanceTestingViewModel by viewModels() 70 private val homeFragmentViewModel: HomeFragmentViewModel by viewModels() 71 72 private val manager by lazy { 73 requireContext().getSystemService(HealthConnectManager::class.java) 74 } 75 76 override fun onCreate(savedInstanceState: Bundle?) { 77 super.onCreate(savedInstanceState) 78 79 // Starting API Level 30 If permission is denied more than once, user doesn't see the dialog 80 // asking permissions again unless they grant the permission from settings. 81 mRequestPermissionLauncher = 82 registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { 83 permissionMap: Map<String, Boolean> -> 84 requestPermissionResultHandler(permissionMap) 85 } 86 } 87 88 private fun requestPermissionResultHandler(permissionMap: Map<String, Boolean>) { 89 var numberOfPermissionsMissing = HEALTH_PERMISSIONS.size 90 for (value in permissionMap.values) { 91 if (value) { 92 numberOfPermissionsMissing-- 93 } 94 } 95 96 if (numberOfPermissionsMissing == 0) { 97 Toast.makeText( 98 this.requireContext(), 99 R.string.all_permissions_success, 100 Toast.LENGTH_SHORT, 101 ) 102 .show() 103 } else { 104 Toast.makeText( 105 this.requireContext(), 106 getString( 107 R.string.number_of_permissions_not_granted, 108 numberOfPermissionsMissing, 109 ), 110 Toast.LENGTH_SHORT, 111 ) 112 .show() 113 } 114 } 115 116 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 117 super.onViewCreated(view, savedInstanceState) 118 val performanceTesting = PerformanceTestingFragment() 119 childFragmentManager 120 .beginTransaction() 121 .add(performanceTesting, "PERFORMANCE_TESTING_FRAGMENT") 122 .commit() 123 view.requireViewById<Button>(R.id.launch_health_connect_button).setOnClickListener { 124 launchHealthConnect() 125 } 126 view.requireViewById<Button>(R.id.request_data_type_permissions_button).setOnClickListener { 127 requestDataTypePermissions() 128 } 129 view.requireViewById<Button>(R.id.request_route_button).setOnClickListener { 130 goToRequestRoute() 131 } 132 view.requireViewById<Button>(R.id.insert_update_data_button).setOnClickListener { 133 goToCategoryListPage() 134 } 135 view.requireViewById<Button>(R.id.seed_random_data_button).setOnClickListener { 136 seedDataButtonPressed() 137 } 138 view.requireViewById<Button>(R.id.seed_all_data_button).setOnClickListener { 139 seedAllDataButtonPressed() 140 } 141 view.requireViewById<Button>(R.id.seed_performance_read_data_button).setOnClickListener { 142 performanceTestingViewModel.beginReadingData() 143 } 144 view.requireViewById<Button>(R.id.seed_performance_insert_data_button).setOnClickListener { 145 performanceTestingViewModel.beginInsertingData(false) 146 } 147 view.requireViewById<Button>(R.id.toggle_permission_intent_filter).setOnClickListener { 148 togglePermissionIntentFilter() 149 } 150 view.requireViewById<Button>(R.id.read_data_in_background_button).setOnClickListener { 151 goToReadDataInBackgroundPage() 152 } 153 view.requireViewById<Button>(R.id.read_data_in_foreground_button).setOnClickListener { 154 goToReadDataInForegroundPage() 155 } 156 view.findViewById<Button>(R.id.phr_options_button).setOnClickListener { goToPhrOptions() } 157 view.requireViewById<Button>(R.id.exit_process_button).setOnClickListener { 158 exitProcess(status = 0) 159 } 160 161 view.findViewById<Button>(R.id.request_health_permissions).setOnClickListener { 162 requestHealthPermissions() 163 } 164 165 view.findViewById<Button>(R.id.request_additional_permissions).setOnClickListener { 166 requestAdditionalPermissions() 167 } 168 169 view.findViewById<Button>(R.id.request_bg_read_permission).setOnClickListener { 170 requestBgReadPermission() 171 } 172 173 view.findViewById<Button>(R.id.request_history_read_permission).setOnClickListener { 174 requestHistoryReadPermission() 175 } 176 177 view.findViewById<Button>(R.id.request_medical_write_permission).setOnClickListener { 178 requestMedicalWritePermission() 179 } 180 181 view 182 .findViewById<Button>(R.id.request_medical_write_and_other_health_permissions) 183 .setOnClickListener { requestMedicalWriteAndOtherPermissions() } 184 185 view.findViewById<Button>(R.id.request_medical_read).setOnClickListener { 186 requestMedicalReadPermissions() 187 } 188 189 view.findViewById<Button>(R.id.request_fitness_read).setOnClickListener { 190 requestFitnessRead() 191 } 192 193 view.findViewById<Button>(R.id.request_fitness_write).setOnClickListener { 194 requestFitnessWrite() 195 } 196 197 // view 198 // .findViewById<Button>(R.id.seed_performance_insert_data_button_in_parallel) 199 // .setOnClickListener { performanceTestingViewModel.beginInsertingData(true) } 200 mNavigationController = findNavController() 201 202 homeFragmentViewModel.seedAllDataState.observe(viewLifecycleOwner) { state -> 203 if (state is HomeFragmentViewModel.SeedAllDataState.Error) { 204 Toast.makeText(context, state.errorMessage, Toast.LENGTH_SHORT).show() 205 } else { 206 Toast.makeText(context, R.string.toast_seed_data_success, Toast.LENGTH_SHORT).show() 207 } 208 } 209 } 210 211 private fun launchHealthConnect() { 212 val intent = Intent("android.health.connect.action.HEALTH_HOME_SETTINGS") 213 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK 214 startActivity(intent) 215 } 216 217 private fun seedDataButtonPressed() { 218 try { 219 SeedData(requireContext(), manager).seedData() 220 Toast.makeText( 221 this.requireContext(), 222 R.string.toast_seed_data_success, 223 Toast.LENGTH_SHORT, 224 ) 225 .show() 226 } catch (ex: Exception) { 227 Toast.makeText(this.requireContext(), ex.localizedMessage, Toast.LENGTH_SHORT).show() 228 } 229 } 230 231 private fun seedAllDataButtonPressed() { 232 homeFragmentViewModel.seedAllDataViewModel(requireContext(), manager) 233 } 234 235 private fun isPermissionGranted(permission: String): Boolean { 236 return ContextCompat.checkSelfPermission(this.requireContext(), permission) == 237 PackageManager.PERMISSION_GRANTED 238 } 239 240 private fun isMedicalPermissionMissing(): Boolean { 241 for (permission in MEDICAL_PERMISSIONS) { 242 if (!isPermissionGranted(permission)) { 243 return true 244 } 245 } 246 return false 247 } 248 249 private fun isDataTypePermissionMissing(): Boolean { 250 for (permission in FITNESS_PERMISSIONS) { 251 if (!isPermissionGranted(permission)) { 252 return true 253 } 254 } 255 return false 256 } 257 258 private fun isAdditionalPermissionMissing(): Boolean { 259 for (permission in ADDITIONAL_PERMISSIONS) { 260 if (!isPermissionGranted(permission)) { 261 return true 262 } 263 } 264 return false 265 } 266 267 private fun togglePermissionIntentFilter() { 268 val pm = requireActivity().applicationContext.packageManager 269 val packageName = requireActivity().packageName 270 val compName = ComponentName(packageName, "$packageName.AliasMainActivity") 271 val componentState = pm.getComponentEnabledSetting(compName) 272 var desiredState = COMPONENT_ENABLED_STATE_ENABLED 273 if ( 274 componentState == COMPONENT_ENABLED_STATE_DEFAULT || 275 componentState == COMPONENT_ENABLED_STATE_ENABLED 276 ) { 277 desiredState = COMPONENT_ENABLED_STATE_DISABLED 278 } 279 pm.setComponentEnabledSetting(compName, desiredState, PackageManager.DONT_KILL_APP) 280 281 val toastText = 282 if (desiredState == COMPONENT_ENABLED_STATE_ENABLED) 283 R.string.toast_permission_filter_enabled 284 else R.string.toast_permission_filter_disabled 285 286 Toast.makeText(this.requireContext(), toastText, Toast.LENGTH_SHORT).show() 287 } 288 289 private fun requestHealthPermissions() { 290 mRequestPermissionLauncher.launch(HEALTH_PERMISSIONS) 291 return 292 } 293 294 private fun requestDataTypePermissions() { 295 if (isDataTypePermissionMissing()) { 296 mRequestPermissionLauncher.launch(FITNESS_PERMISSIONS) 297 return 298 } 299 Toast.makeText( 300 this.requireContext(), 301 R.string.all_permissions_already_granted_toast, 302 Toast.LENGTH_LONG, 303 ) 304 .show() 305 } 306 307 private fun requestAdditionalPermissions() { 308 if (isAdditionalPermissionMissing()) { 309 mRequestPermissionLauncher.launch(ADDITIONAL_PERMISSIONS) 310 return 311 } 312 Toast.makeText( 313 this.requireContext(), 314 R.string.all_permissions_already_granted_toast, 315 Toast.LENGTH_LONG, 316 ) 317 .show() 318 } 319 320 private fun requestBgReadPermission() { 321 if (!isPermissionGranted(READ_HEALTH_DATA_IN_BACKGROUND)) { 322 mRequestPermissionLauncher.launch(arrayOf(READ_HEALTH_DATA_IN_BACKGROUND)) 323 } 324 325 Toast.makeText( 326 this.requireContext(), 327 R.string.all_permissions_already_granted_toast, 328 Toast.LENGTH_LONG, 329 ) 330 .show() 331 } 332 333 private fun requestHistoryReadPermission() { 334 if (!isPermissionGranted(READ_HEALTH_DATA_HISTORY)) { 335 mRequestPermissionLauncher.launch(arrayOf(READ_HEALTH_DATA_HISTORY)) 336 } 337 338 Toast.makeText( 339 this.requireContext(), 340 R.string.all_permissions_already_granted_toast, 341 Toast.LENGTH_LONG, 342 ) 343 .show() 344 } 345 346 private fun requestMedicalWritePermission() { 347 if (!isPermissionGranted(WRITE_ALL_MEDICAL_DATA)) { 348 mRequestPermissionLauncher.launch(arrayOf(WRITE_ALL_MEDICAL_DATA)) 349 } 350 351 Toast.makeText( 352 this.requireContext(), 353 R.string.all_permissions_already_granted_toast, 354 Toast.LENGTH_LONG, 355 ) 356 .show() 357 } 358 359 private fun requestMedicalWriteAndOtherPermissions() { 360 if ( 361 !isPermissionGranted(WRITE_ALL_MEDICAL_DATA) && 362 isDataTypePermissionMissing() && 363 isAdditionalPermissionMissing() 364 ) { 365 mRequestPermissionLauncher.launch(MEDICAL_WRITE_AND_OTHERS) 366 } 367 Toast.makeText( 368 this.requireContext(), 369 R.string.all_permissions_already_granted_toast, 370 Toast.LENGTH_LONG, 371 ) 372 .show() 373 } 374 375 private fun requestMedicalReadPermissions() { 376 mRequestPermissionLauncher.launch(MEDICAL_READ_PERMISSIONS) 377 } 378 379 private fun requestFitnessRead() { 380 mRequestPermissionLauncher.launch(FITNESS_READ_PERMISSIONS) 381 } 382 383 private fun requestFitnessWrite() { 384 mRequestPermissionLauncher.launch(FITNESS_WRITE_PERMISSIONS) 385 } 386 387 private fun goToRequestRoute() { 388 mNavigationController.navigate(R.id.action_homeFragment_to_routeRequest) 389 } 390 391 private fun goToCategoryListPage() { 392 mNavigationController.navigate(R.id.action_homeFragment_to_categoryList) 393 } 394 395 private fun goToReadDataInBackgroundPage() { 396 mNavigationController.navigate(R.id.action_homeFragment_to_readDataInBackground) 397 } 398 399 private fun goToReadDataInForegroundPage() { 400 mNavigationController.navigate(R.id.action_homeFragment_to_readDataInForeground) 401 } 402 403 private fun goToPhrOptions() { 404 mNavigationController.navigate(R.id.action_homeFragment_to_phrOptions) 405 } 406 } 407