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.HealthConnectException 25 import android.health.connect.HealthConnectManager 26 import android.health.connect.HealthPermissions 27 import android.health.connect.datatypes.ExerciseSessionRecord 28 import android.health.connect.datatypes.ExerciseSessionType 29 import android.os.Bundle 30 import android.view.LayoutInflater 31 import android.view.View 32 import android.view.ViewGroup 33 import android.widget.Button 34 import android.widget.Toast 35 import androidx.activity.result.ActivityResultLauncher 36 import androidx.activity.result.contract.ActivityResultContracts 37 import androidx.core.content.ContextCompat 38 import androidx.fragment.app.Fragment 39 import androidx.fragment.app.viewModels 40 import androidx.navigation.NavController 41 import androidx.navigation.fragment.findNavController 42 import com.android.healthconnect.testapps.toolbox.Constants.ALL_PERMISSIONS 43 import com.android.healthconnect.testapps.toolbox.PerformanceTesting 44 import com.android.healthconnect.testapps.toolbox.R 45 import com.android.healthconnect.testapps.toolbox.data.ExerciseRoutesTestData.Companion.WARSAW_ROUTE 46 import com.android.healthconnect.testapps.toolbox.data.ExerciseRoutesTestData.Companion.generateExerciseRouteFromLocations 47 import com.android.healthconnect.testapps.toolbox.seed.SeedData 48 import com.android.healthconnect.testapps.toolbox.utils.GeneralUtils 49 import com.android.healthconnect.testapps.toolbox.viewmodels.PerformanceTestingViewModel 50 import kotlinx.coroutines.runBlocking 51 import java.time.Instant 52 import java.time.temporal.ChronoUnit 53 54 55 /** Home fragment for Health Connect Toolbox. */ 56 class HomeFragment : Fragment() { 57 58 override fun onCreateView( 59 inflater: LayoutInflater, 60 container: ViewGroup?, 61 savedInstanceState: Bundle?, 62 ): View? { 63 return inflater.inflate(R.layout.fragment_home, container, false) 64 } 65 66 private lateinit var mRequestPermissionLauncher: ActivityResultLauncher<Array<String>> 67 private lateinit var mRequestRoutePermissionLauncher: ActivityResultLauncher<String> 68 private lateinit var mNavigationController: NavController 69 private val manager by lazy { 70 requireContext().getSystemService(HealthConnectManager::class.java) 71 } 72 private val performanceTestingViewModel: PerformanceTestingViewModel by viewModels() 73 74 override fun onCreate(savedInstanceState: Bundle?) { 75 super.onCreate(savedInstanceState) 76 77 // Starting API Level 30 If permission is denied more than once, user doesn't see the dialog 78 // asking permissions again unless they grant the permission from settings. 79 mRequestPermissionLauncher = 80 registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissionMap: Map<String, Boolean> -> 81 requestPermissionResultHandler(permissionMap) 82 } 83 mRequestRoutePermissionLauncher = 84 registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> 85 if (granted) { 86 readRoute() 87 } 88 } 89 } 90 91 private fun requestPermissionResultHandler(permissionMap: Map<String, Boolean>) { 92 var numberOfPermissionsMissing = ALL_PERMISSIONS.size 93 for (value in permissionMap.values) { 94 if (value) { 95 numberOfPermissionsMissing-- 96 } 97 } 98 99 if (numberOfPermissionsMissing == 0) { 100 Toast.makeText( 101 this.requireContext(), R.string.all_permissions_success, Toast.LENGTH_SHORT) 102 .show() 103 } else { 104 Toast.makeText( 105 this.requireContext(), 106 getString( 107 R.string.number_of_permissions_not_granted, numberOfPermissionsMissing), 108 Toast.LENGTH_SHORT) 109 .show() 110 } 111 } 112 113 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 114 super.onViewCreated(view, savedInstanceState) 115 val performanceTesting = PerformanceTesting(performanceTestingViewModel) 116 childFragmentManager 117 .beginTransaction() 118 .add(performanceTesting, "PERFORMANCE_TESTING_FRAGMENT") 119 .commit() 120 view.findViewById<Button>(R.id.launch_health_connect_button).setOnClickListener { 121 launchHealthConnect() 122 } 123 view.findViewById<Button>(R.id.request_permissions_button).setOnClickListener { 124 requestPermissions() 125 } 126 view.findViewById<Button>(R.id.request_route_permissions_button).setOnClickListener { 127 requestRoutesPermissions() 128 } 129 view.findViewById<Button>(R.id.insert_update_data_button).setOnClickListener { 130 goToCategoryListPage() 131 } 132 view.findViewById<Button>(R.id.seed_random_data_button).setOnClickListener { 133 seedDataButtonPressed() 134 } 135 view.findViewById<Button>(R.id.seed_performance_read_data_button).setOnClickListener { 136 performanceTestingViewModel.beginReadingData() 137 } 138 view.findViewById<Button>(R.id.seed_performance_insert_data_button).setOnClickListener { 139 performanceTestingViewModel.beginInsertingData(false) 140 } 141 142 view.findViewById<Button>(R.id.toggle_permission_intent_filter).setOnClickListener { 143 togglePermissionIntentFilter() 144 } 145 146 // view 147 // .findViewById<Button>(R.id.seed_performance_insert_data_button_in_parallel) 148 // .setOnClickListener { performanceTestingViewModel.beginInsertingData(true) } 149 mNavigationController = findNavController() 150 } 151 152 private fun launchHealthConnect() { 153 val intent = Intent("android.health.connect.action.HEALTH_HOME_SETTINGS") 154 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK 155 startActivity(intent) 156 } 157 158 private fun seedDataButtonPressed() { 159 try { 160 SeedData(requireContext(), manager).seedData() 161 Toast.makeText(this.requireContext(), R.string.toast_seed_data_success, Toast.LENGTH_SHORT).show() 162 } catch (ex: Exception) { 163 Toast.makeText(this.requireContext(), ex.localizedMessage, Toast.LENGTH_SHORT).show() 164 } 165 } 166 167 private fun isPermissionMissing(): Boolean { 168 for (permission in ALL_PERMISSIONS) { 169 if (ContextCompat.checkSelfPermission(this.requireContext(), permission) != 170 PackageManager.PERMISSION_GRANTED) { 171 return true 172 } 173 } 174 return false 175 } 176 177 private fun togglePermissionIntentFilter() { 178 val pm = requireActivity().applicationContext.packageManager 179 val packageName = requireActivity().packageName 180 val compName = ComponentName(packageName, "$packageName.AliasMainActivity") 181 val componentState = pm.getComponentEnabledSetting(compName) 182 var desiredState = COMPONENT_ENABLED_STATE_ENABLED 183 if (componentState == COMPONENT_ENABLED_STATE_DEFAULT || componentState == COMPONENT_ENABLED_STATE_ENABLED) { 184 desiredState = COMPONENT_ENABLED_STATE_DISABLED 185 } 186 pm.setComponentEnabledSetting( 187 compName, 188 desiredState, 189 PackageManager.DONT_KILL_APP) 190 191 val toastText = if (desiredState == COMPONENT_ENABLED_STATE_ENABLED) R.string.toast_permission_filter_enabled else R.string.toast_permission_filter_disabled 192 193 Toast.makeText(this.requireContext(), toastText, Toast.LENGTH_SHORT).show() 194 195 } 196 197 private fun requestPermissions() { 198 if (isPermissionMissing()) { 199 mRequestPermissionLauncher.launch(ALL_PERMISSIONS) 200 return 201 } 202 Toast.makeText( 203 this.requireContext(), 204 R.string.all_permissions_already_granted_toast, 205 Toast.LENGTH_LONG) 206 .show() 207 } 208 209 private fun requestRoutesPermissions() { 210 if (ContextCompat.checkSelfPermission( 211 requireContext(), HealthPermissions.WRITE_EXERCISE_ROUTE) != 212 PackageManager.PERMISSION_GRANTED) { 213 mRequestRoutePermissionLauncher.launch(HealthPermissions.WRITE_EXERCISE_ROUTE) 214 return 215 } 216 readRoute() 217 } 218 219 private fun readRoute() { 220 // insert a route data 221 val start = Instant.now().truncatedTo(ChronoUnit.DAYS) 222 val end = start.plusSeconds(100_000) 223 val route = 224 ExerciseSessionRecord.Builder( 225 GeneralUtils.getMetaData(requireContext()), 226 start, 227 end, 228 ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING) 229 .setRoute(generateExerciseRouteFromLocations(WARSAW_ROUTE, start.toEpochMilli())) 230 .build() 231 runBlocking { 232 val result = GeneralUtils.insertRecords(listOf(route), manager) 233 if (result.isNotEmpty()) { 234 val record = result.first() 235 val intent = 236 Intent(HealthConnectManager.ACTION_REQUEST_EXERCISE_ROUTE).apply { 237 putExtra(HealthConnectManager.EXTRA_SESSION_ID, record.metadata.id) 238 putExtra(Intent.EXTRA_PACKAGE_NAME, requireContext().packageName) 239 } 240 startActivityForResult(intent, 1) 241 } 242 } 243 } 244 245 private fun goToCategoryListPage() { 246 mNavigationController.navigate(R.id.action_homeFragment_to_categoryList) 247 } 248 } 249