1 /*
<lambda>null2  * 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.0N
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 
17 package com.android.credentialmanager
18 
19 import android.content.Intent
20 import android.credentials.selection.BaseDialogResult
21 import androidx.lifecycle.ViewModel
22 import androidx.lifecycle.viewModelScope
23 import com.android.credentialmanager.CredentialSelectorUiState.Get
24 import com.android.credentialmanager.model.Request
25 import com.android.credentialmanager.client.CredentialManagerClient
26 import com.android.credentialmanager.model.EntryInfo
27 import com.android.credentialmanager.ui.mappers.toGet
28 import android.util.Log
29 import androidx.activity.compose.rememberLauncherForActivityResult
30 import androidx.compose.runtime.Composable
31 import com.android.credentialmanager.CredentialSelectorUiState.Cancel
32 import com.android.credentialmanager.CredentialSelectorUiState.Close
33 import com.android.credentialmanager.CredentialSelectorUiState.Create
34 import com.android.credentialmanager.CredentialSelectorUiState.Idle
35 import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
36 import com.android.credentialmanager.ktx.getIntentSenderRequest
37 import dagger.hilt.android.lifecycle.HiltViewModel
38 import kotlinx.coroutines.flow.MutableStateFlow
39 import kotlinx.coroutines.flow.SharingStarted
40 import kotlinx.coroutines.flow.StateFlow
41 import kotlinx.coroutines.flow.combine
42 import kotlinx.coroutines.flow.stateIn
43 import javax.inject.Inject
44 
45 @HiltViewModel
46 class CredentialSelectorViewModel @Inject constructor(
47     private val credentialManagerClient: CredentialManagerClient,
48 ) : FlowEngine, ViewModel() {
49     private val isPrimaryScreen = MutableStateFlow(true)
50     private val shouldClose = MutableStateFlow(false)
51     private lateinit var selectedEntry: EntryInfo
52     private var isAutoSelected: Boolean = false
53     override val uiState: StateFlow<CredentialSelectorUiState> =
54         combine(
55             credentialManagerClient.requests,
56             isPrimaryScreen,
57             shouldClose
58         ) { request, isPrimary, shouldClose ->
59             Log.d(TAG, "Request updated: " + request?.toString() +
60                     " isClose: " + shouldClose.toString() +
61                     " isPrimaryScreen: " + isPrimary.toString())
62             if (shouldClose) {
63                 return@combine Close
64             }
65 
66             when (request) {
67                 null -> Idle
68                 is Request.Cancel -> Cancel(request.appName)
69                 is Request.Close -> Close
70                 is Request.Create -> Create
71                 is Request.Get -> request.toGet(isPrimary)
72             }
73         }
74         .stateIn(
75             viewModelScope,
76             started = SharingStarted.WhileSubscribed(5000),
77             initialValue = Idle,
78         )
79 
80     fun updateRequest(intent: Intent) {
81             credentialManagerClient.updateRequest(intent = intent)
82     }
83 
84     override fun back() {
85         Log.d(TAG, "OnBackPressed")
86         when (uiState.value) {
87             is Get.MultipleEntry -> isPrimaryScreen.value = true
88             is Create, Close, is Cancel, Idle -> shouldClose.value = true
89             is Get.SingleEntry, is Get.MultipleEntryPrimaryScreen -> cancel()
90         }
91     }
92 
93     override fun cancel() {
94         credentialManagerClient.sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
95         shouldClose.value = true
96     }
97 
98     override fun openSecondaryScreen() {
99         isPrimaryScreen.value = false
100     }
101 
102     override fun sendSelectionResult(
103         entryInfo: EntryInfo,
104         resultCode: Int?,
105         resultData: Intent?,
106         isAutoSelected: Boolean,
107     ) {
108         val result = credentialManagerClient.sendEntrySelectionResult(
109             entryInfo = entryInfo,
110             resultCode = resultCode,
111             resultData = resultData,
112             isAutoSelected = isAutoSelected
113         )
114         shouldClose.value = result
115     }
116 
117     @Composable
118     override fun getEntrySelector(): (entry: EntryInfo, isAutoSelected: Boolean) -> Unit {
119         val launcher = rememberLauncherForActivityResult(
120             StartBalIntentSenderForResultContract()
121         ) {
122             sendSelectionResult(entryInfo = selectedEntry,
123                 resultCode = it.resultCode,
124                 resultData = it.data,
125                 isAutoSelected = isAutoSelected)
126         }
127         return { selected, autoSelect ->
128             selectedEntry = selected
129             isAutoSelected = autoSelect
130             selected.getIntentSenderRequest()?.let {
131                 launcher.launch(it)
132             } ?: Log.w(TAG, "Cannot parse IntentSenderRequest")
133         }
134     }
135 }
136 
137