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