1 /**
2  * 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.controller.permissiontypes
17 
18 import android.util.Log
19 import androidx.lifecycle.LiveData
20 import androidx.lifecycle.MutableLiveData
21 import androidx.lifecycle.ViewModel
22 import androidx.lifecycle.viewModelScope
23 import com.android.healthconnect.controller.permissions.data.HealthPermissionType
24 import com.android.healthconnect.controller.permissiontypes.api.FilterPermissionTypesUseCase
25 import com.android.healthconnect.controller.permissiontypes.api.LoadContributingAppsUseCase
26 import com.android.healthconnect.controller.permissiontypes.api.LoadPermissionTypesUseCase
27 import com.android.healthconnect.controller.permissiontypes.api.LoadPriorityListUseCase
28 import com.android.healthconnect.controller.permissiontypes.api.UpdatePriorityListUseCase
29 import com.android.healthconnect.controller.shared.HealthDataCategoryInt
30 import com.android.healthconnect.controller.shared.app.AppInfoReader
31 import com.android.healthconnect.controller.shared.app.AppMetadata
32 import com.android.healthconnect.controller.shared.usecase.UseCaseResults
33 import dagger.hilt.android.lifecycle.HiltViewModel
34 import javax.inject.Inject
35 import kotlinx.coroutines.launch
36 
37 @HiltViewModel
38 class HealthPermissionTypesViewModel
39 @Inject
40 constructor(
41     private val loadPermissionTypesUseCase: LoadPermissionTypesUseCase,
42     private val loadPriorityListUseCase: LoadPriorityListUseCase,
43     private val updatePriorityListUseCase: UpdatePriorityListUseCase,
44     private val appInfoReader: AppInfoReader,
45     private val loadContributingAppsUseCase: LoadContributingAppsUseCase,
46     private val filterPermissionTypesUseCase: FilterPermissionTypesUseCase
47 ) : ViewModel() {
48 
49     companion object {
50         private const val TAG = "PriorityListView"
51     }
52 
53     private val _permissionTypesData = MutableLiveData<PermissionTypesState>()
54     private val _priorityList = MutableLiveData<PriorityListState>()
55     private val _appsWithData = MutableLiveData<AppsWithDataFragmentState>()
56     private val _selectedAppFilter = MutableLiveData("All apps")
57     private val _editedPriorityList = MutableLiveData<List<AppMetadata>>()
58     private val _categoryLabel = MutableLiveData<String>()
59 
60     /** Provides a list of [HealthPermissionType]s displayed in [HealthPermissionTypesFragment]. */
61     val permissionTypesData: LiveData<PermissionTypesState>
62         get() = _permissionTypesData
63 
64     /**
65      * Provides a list of [AppMetadata]s used in [HealthPermissionTypesFragment] and in
66      * [com.android.healthconnect.controller.permissiontypes.prioritylist.PriorityListDialogFragment].
67      */
68     val priorityList: LiveData<PriorityListState>
69         get() = _priorityList
70 
71 
72     /** Provides a list of apps with data in [HealthPermissionTypesFragment]. */
73     val appsWithData: LiveData<AppsWithDataFragmentState>
74         get() = _appsWithData
75 
76     /** Stores currently selected app filter. */
77     val selectedAppFilter: LiveData<String>
78         get() = _selectedAppFilter
79 
80     /**
81      * Provides a reordered version of [priorityList] that is shown on the priority dialog but is
82      * not saved yet by the user.
83      *
84      * If it is empty then the original [priorityList] should be shown on the dialog.
85      */
86     val editedPriorityList: LiveData<List<AppMetadata>>
87         get() = _editedPriorityList
88 
89     val categoryLabel: LiveData<String>
90         get() = _categoryLabel
91 
setAppFilternull92     fun setAppFilter(selectedAppFilter: String) {
93         _selectedAppFilter.postValue(selectedAppFilter)
94     }
95 
setEditedPriorityListnull96     fun setEditedPriorityList(newList: List<AppMetadata>) {
97         _editedPriorityList.postValue(newList)
98     }
99 
setCategoryLabelnull100     fun setCategoryLabel(label: String) {
101         _categoryLabel.postValue(label)
102     }
103 
loadDatanull104     fun loadData(category: @HealthDataCategoryInt Int) {
105         _permissionTypesData.postValue(PermissionTypesState.Loading)
106         _priorityList.postValue(PriorityListState.Loading)
107 
108         viewModelScope.launch {
109             val permissionTypes = loadPermissionTypesUseCase.invoke(category)
110             _permissionTypesData.postValue(PermissionTypesState.WithData(permissionTypes))
111 
112             when (val result = loadPriorityListUseCase.invoke(category)) {
113                 is UseCaseResults.Success -> {
114                     _priorityList.postValue(
115                         if (result.data.isEmpty()) {
116                             PriorityListState.WithData(listOf())
117                         } else {
118                             PriorityListState.WithData(result.data)
119                         })
120                 }
121                 is UseCaseResults.Failed -> {
122                     Log.e(TAG, "Load error ", result.exception)
123                     _priorityList.postValue(PriorityListState.LoadingFailed)
124                 }
125             }
126         }
127     }
128 
loadAppsWithDatanull129     fun loadAppsWithData(category: @HealthDataCategoryInt Int) {
130         _appsWithData.postValue(AppsWithDataFragmentState.Loading)
131         viewModelScope.launch {
132             val appsWithHealthPermissions = loadContributingAppsUseCase.invoke(category)
133             _appsWithData.postValue(AppsWithDataFragmentState.WithData(appsWithHealthPermissions))
134         }
135     }
136 
filterPermissionTypesnull137     fun filterPermissionTypes(
138         category: @HealthDataCategoryInt Int,
139         selectedAppPackageName: String
140     ) {
141         _permissionTypesData.postValue(PermissionTypesState.Loading)
142         viewModelScope.launch {
143             val permissionTypes =
144                 filterPermissionTypesUseCase.invoke(category, selectedAppPackageName)
145             if (permissionTypes.isNotEmpty()) {
146                 _permissionTypesData.postValue(PermissionTypesState.WithData(permissionTypes))
147             } else {
148                 val allPermissionTypes = loadPermissionTypesUseCase.invoke(category)
149                 _permissionTypesData.postValue(PermissionTypesState.WithData(allPermissionTypes))
150             }
151         }
152     }
153 
updatePriorityListnull154     fun updatePriorityList(category: @HealthDataCategoryInt Int, newPriorityList: List<String>) {
155         _priorityList.postValue(PriorityListState.Loading)
156 
157         viewModelScope.launch {
158             updatePriorityListUseCase.invoke(newPriorityList, category)
159             val appMetadataList: List<AppMetadata> =
160                 newPriorityList.map { appInfoReader.getAppMetadata(it) }
161             _priorityList.postValue(PriorityListState.WithData(appMetadataList))
162         }
163     }
164 
165     sealed class PermissionTypesState {
166         object Loading : PermissionTypesState()
167         data class WithData(val permissionTypes: List<HealthPermissionType>) :
168             PermissionTypesState()
169     }
170 
171     sealed class PriorityListState {
172         object Loading : PriorityListState()
173         object LoadingFailed : PriorityListState()
174         data class WithData(val priorityList: List<AppMetadata>) : PriorityListState()
175     }
176 
177     sealed class AppsWithDataFragmentState {
178         object Loading : AppsWithDataFragmentState()
179         data class WithData(val appsWithData: List<AppMetadata>) : AppsWithDataFragmentState()
180     }
181 }
182