1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.healthconnect.controller.route 17 18 import android.content.Context 19 import android.content.pm.PackageManager 20 import android.health.connect.HealthPermissions.READ_EXERCISE_ROUTES 21 import android.health.connect.HealthPermissions.WRITE_EXERCISE_ROUTE 22 import android.health.connect.datatypes.ExerciseSessionRecord 23 import android.util.Log 24 import androidx.lifecycle.LiveData 25 import androidx.lifecycle.MutableLiveData 26 import androidx.lifecycle.ViewModel 27 import androidx.lifecycle.viewModelScope 28 import com.android.healthconnect.controller.permissions.api.GetGrantedHealthPermissionsUseCase 29 import com.android.healthconnect.controller.permissions.api.GetHealthPermissionsFlagsUseCase 30 import com.android.healthconnect.controller.permissions.api.GrantHealthPermissionUseCase 31 import com.android.healthconnect.controller.permissions.api.LoadAccessDateUseCase 32 import com.android.healthconnect.controller.shared.app.AppInfoReader 33 import com.android.healthconnect.controller.shared.app.AppMetadata 34 import com.android.healthconnect.controller.shared.usecase.UseCaseResults 35 import dagger.hilt.android.lifecycle.HiltViewModel 36 import dagger.hilt.android.qualifiers.ApplicationContext 37 import java.util.Objects 38 import javax.inject.Inject 39 import kotlinx.coroutines.launch 40 41 /** View model for reading an exercise route for the given session. */ 42 @HiltViewModel 43 class ExerciseRouteViewModel 44 @Inject 45 constructor( 46 @ApplicationContext private val context: Context, 47 private val loadExerciseRouteUseCase: LoadExerciseRouteUseCase, 48 private val getGrantedHealthPermissionsUseCase: GetGrantedHealthPermissionsUseCase, 49 private val getHealthPermissionsFlagsUseCase: GetHealthPermissionsFlagsUseCase, 50 private val grantHealthPermissionUseCase: GrantHealthPermissionUseCase, 51 private val loadAccessDateUseCase: LoadAccessDateUseCase, 52 private val appInfoReader: AppInfoReader, 53 ) : ViewModel() { 54 55 companion object { 56 private const val TAG = "ExerciseRouteViewModel" 57 } 58 59 private val _exerciseSession = MutableLiveData<SessionWithAttribution?>() 60 val exerciseSession: LiveData<SessionWithAttribution?> 61 get() = _exerciseSession 62 getExerciseWithRoutenull63 fun getExerciseWithRoute(sessionId: String) { 64 viewModelScope.launch { 65 when (val result = loadExerciseRouteUseCase.invoke(sessionId)) { 66 is UseCaseResults.Success -> { 67 if (!Objects.equals(result.data, null)) { 68 val record: ExerciseSessionRecord = result.data!! 69 _exerciseSession.postValue( 70 SessionWithAttribution( 71 record, 72 appInfoReader.getAppMetadata( 73 record.metadata.dataOrigin.packageName))) 74 } else { 75 _exerciseSession.postValue(null) 76 } 77 } 78 is UseCaseResults.Failed -> { 79 Log.e(TAG, result.exception.message!!) 80 _exerciseSession.postValue(null) 81 } 82 } 83 } 84 } 85 isRouteReadOrWritePermissionGrantednull86 fun isRouteReadOrWritePermissionGranted(packageName: String): Boolean { 87 val grantedPermissions = getGrantedHealthPermissionsUseCase(packageName) 88 val routePermissions = listOf(READ_EXERCISE_ROUTES, WRITE_EXERCISE_ROUTE) 89 return grantedPermissions.any { it in routePermissions } 90 } 91 isReadRoutesPermissionGrantednull92 fun isReadRoutesPermissionGranted(packageName: String): Boolean { 93 val grantedPermissions = getGrantedHealthPermissionsUseCase(packageName) 94 return grantedPermissions.contains(READ_EXERCISE_ROUTES) 95 } 96 isReadRoutesPermissionUserFixednull97 fun isReadRoutesPermissionUserFixed(packageName: String): Boolean { 98 val permission = READ_EXERCISE_ROUTES 99 val flags = getHealthPermissionsFlagsUseCase(packageName, listOf(READ_EXERCISE_ROUTES)) 100 101 return flags[permission]!!.and(PackageManager.FLAG_PERMISSION_USER_FIXED) != 0 102 } 103 isReadRoutesPermissionDeclarednull104 fun isReadRoutesPermissionDeclared(packageName: String): Boolean { 105 return try { 106 val appInfo = 107 context.packageManager.getPackageInfo( 108 packageName, 109 PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())) 110 111 if (appInfo.requestedPermissions == null) { 112 Log.e(TAG, "isPermissionDeclared error: no permissions") 113 return false 114 } 115 116 appInfo.requestedPermissions!!.contains(READ_EXERCISE_ROUTES) 117 } catch (e: PackageManager.NameNotFoundException) { 118 Log.e(TAG, "isPermissionDeclared error", e) 119 false 120 } 121 } 122 grantReadRoutesPermissionnull123 fun grantReadRoutesPermission(packageName: String) { 124 grantHealthPermissionUseCase.invoke(packageName, READ_EXERCISE_ROUTES) 125 } 126 isSessionInaccessiblenull127 fun isSessionInaccessible(packageName: String, session: ExerciseSessionRecord): Boolean { 128 if (packageName == session.metadata.dataOrigin.packageName) { 129 return false 130 } 131 132 val accessLimit = loadAccessDateUseCase(packageName) 133 134 return session.startTime.isBefore(accessLimit) 135 } 136 137 data class SessionWithAttribution(val session: ExerciseSessionRecord, val appInfo: AppMetadata) 138 } 139