1 /*
<lambda>null2  * Copyright 2024 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 
17 package androidx.credentials.playservices.controllers.identityauth
18 
19 import android.app.Activity
20 import android.app.PendingIntent
21 import android.content.Intent
22 import android.content.IntentSender
23 import android.os.Bundle
24 import android.os.ResultReceiver
25 import android.util.Log
26 import androidx.annotation.RestrictTo
27 import androidx.credentials.playservices.controllers.CredentialProviderBaseController
28 import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.reportError
29 import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.reportResult
30 import com.google.android.gms.common.api.ApiException
31 import com.google.android.gms.fido.Fido
32 import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
33 
34 /** An activity used to ensure all required API versions work as intended. */
35 @RestrictTo(RestrictTo.Scope.LIBRARY)
36 @Suppress("ForbiddenSuperClass")
37 open class HiddenActivity : Activity() {
38 
39     private var resultReceiver: ResultReceiver? = null
40     private var mWaitingForActivityResult = false
41 
42     @Suppress("deprecation")
43     override fun onCreate(savedInstanceState: Bundle?) {
44         super.onCreate(savedInstanceState)
45         overridePendingTransition(0, 0)
46         val type: String? = intent.getStringExtra(CredentialProviderBaseController.TYPE_TAG)
47         resultReceiver =
48             intent.getParcelableExtra(CredentialProviderBaseController.RESULT_RECEIVER_TAG)
49 
50         if (resultReceiver == null) {
51             finish()
52         }
53 
54         restoreState(savedInstanceState)
55         if (mWaitingForActivityResult) {
56             return
57             // Past call still active
58         }
59 
60         when (type) {
61             CredentialProviderBaseController.BEGIN_SIGN_IN_TAG -> {
62                 handleBeginSignIn()
63             }
64             CredentialProviderBaseController.CREATE_PASSWORD_TAG -> {
65                 handleCreatePassword()
66             }
67             CredentialProviderBaseController.CREATE_PUBLIC_KEY_CREDENTIAL_TAG -> {
68                 handleCreatePublicKeyCredential()
69             }
70             CredentialProviderBaseController.SIGN_IN_INTENT_TAG -> {
71                 handleGetSignInIntent()
72             }
73             else -> {
74                 Log.w(TAG, "Activity handed an unsupported type")
75                 finish()
76             }
77         }
78     }
79 
80     private fun restoreState(savedInstanceState: Bundle?) {
81         if (savedInstanceState != null) {
82             mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false)
83         }
84     }
85 
86     @Suppress("deprecation")
87     private fun handleCreatePublicKeyCredential() {
88         val fidoRegistrationRequest: PublicKeyCredentialCreationOptions? =
89             intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG)
90         val requestCode: Int =
91             intent.getIntExtra(
92                 CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
93                 DEFAULT_VALUE
94             )
95         fidoRegistrationRequest?.let {
96             Fido.getFido2ApiClient(this)
97                 .getRegisterPendingIntent(fidoRegistrationRequest)
98                 .addOnSuccessListener { result: PendingIntent ->
99                     try {
100                         mWaitingForActivityResult = true
101                         startIntentSenderForResult(
102                             result.intentSender,
103                             requestCode,
104                             null,
105                             /* fillInIntent= */ 0,
106                             /* flagsMask= */ 0,
107                             /* flagsValue= */ 0,
108                             /* extraFlags= */ null
109                         /* options= */ )
110                     } catch (e: IntentSender.SendIntentException) {
111                         setupFailure(
112                             resultReceiver!!,
113                             CredentialProviderBaseController.Companion.CREATE_UNKNOWN,
114                             "During public key credential, found IntentSender " +
115                                 "failure on public key creation: ${e.message}"
116                         )
117                     }
118                 }
119                 .addOnFailureListener { e: Exception ->
120                     var errName: String = CredentialProviderBaseController.Companion.CREATE_UNKNOWN
121                     if (
122                         e is ApiException &&
123                             e.statusCode in CredentialProviderBaseController.retryables
124                     ) {
125                         errName = CredentialProviderBaseController.Companion.CREATE_INTERRUPTED
126                     }
127                     setupFailure(
128                         resultReceiver!!,
129                         errName,
130                         "During create public key credential, fido registration " +
131                             "failure: ${e.message}"
132                     )
133                 }
134         }
135             ?: run {
136                 Log.w(
137                     TAG,
138                     "During create public key credential, request is null, so nothing to " +
139                         "launch for public key credentials"
140                 )
141                 finish()
142             }
143     }
144 
145     private fun setupFailure(resultReceiver: ResultReceiver, errName: String, errMsg: String) {
146         resultReceiver.reportError(errName, errMsg)
147         finish()
148     }
149 
150     override fun onSaveInstanceState(outState: Bundle) {
151         outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult)
152         super.onSaveInstanceState(outState)
153     }
154 
155     @Suppress("deprecation")
156     private fun handleGetSignInIntent() {
157         val params: com.google.android.gms.auth.api.identity.GetSignInIntentRequest? =
158             intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG)
159         val requestCode: Int =
160             intent.getIntExtra(
161                 CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
162                 DEFAULT_VALUE
163             )
164         params?.let {
165             com.google.android.gms.auth.api.identity.Identity.getSignInClient(this)
166                 .getSignInIntent(params)
167                 .addOnSuccessListener {
168                     try {
169                         mWaitingForActivityResult = true
170                         startIntentSenderForResult(
171                             it.intentSender,
172                             requestCode,
173                             null,
174                             0,
175                             0,
176                             0,
177                             null
178                         )
179                     } catch (e: IntentSender.SendIntentException) {
180                         setupFailure(
181                             resultReceiver!!,
182                             CredentialProviderBaseController.Companion.GET_UNKNOWN,
183                             "During get sign-in intent, one tap ui intent sender " +
184                                 "failure: ${e.message}"
185                         )
186                     }
187                 }
188                 .addOnFailureListener { e: Exception ->
189                     var errName: String =
190                         CredentialProviderBaseController.Companion.GET_NO_CREDENTIALS
191                     if (
192                         e is ApiException &&
193                             e.statusCode in CredentialProviderBaseController.retryables
194                     ) {
195                         errName = CredentialProviderBaseController.Companion.GET_INTERRUPTED
196                     }
197                     setupFailure(
198                         resultReceiver!!,
199                         errName,
200                         "During get sign-in intent, failure response from one tap: ${e.message}"
201                     )
202                 }
203         }
204             ?: run {
205                 Log.i(
206                     TAG,
207                     "During get sign-in intent, params is null, nothing to launch for " +
208                         "get sign-in intent"
209                 )
210                 finish()
211             }
212     }
213 
214     @Suppress("deprecation")
215     private fun handleBeginSignIn() {
216         val pendingIntent: PendingIntent? =
217             intent.getParcelableExtra(CredentialProviderBaseController.EXTRA_GET_CREDENTIAL_INTENT)
218 
219         val requestCode: Int =
220             intent.getIntExtra(
221                 CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
222                 DEFAULT_VALUE
223             )
224 
225         if (pendingIntent != null) {
226             try {
227                 mWaitingForActivityResult = true
228                 startIntentSenderForResult(
229                     pendingIntent.intentSender,
230                     requestCode,
231                     null,
232                     0,
233                     0,
234                     0,
235                     null
236                 )
237             } catch (e: IntentSender.SendIntentException) {
238                 setupFailure(
239                     resultReceiver!!,
240                     CredentialProviderBaseController.Companion.GET_UNKNOWN,
241                     "During begin sign in, one tap ui intent sender " + "failure: ${e.message}"
242                 )
243             }
244         } else {
245             setupFailure(
246                 resultReceiver!!,
247                 CredentialProviderBaseController.Companion.GET_UNKNOWN,
248                 "internal error"
249             )
250         }
251     }
252 
253     @Suppress("deprecation")
254     private fun handleCreatePassword() {
255         val params: com.google.android.gms.auth.api.identity.SavePasswordRequest? =
256             intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG)
257         val requestCode: Int =
258             intent.getIntExtra(
259                 CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
260                 DEFAULT_VALUE
261             )
262         params?.let {
263             com.google.android.gms.auth.api.identity.Identity.getCredentialSavingClient(this)
264                 .savePassword(params)
265                 .addOnSuccessListener {
266                     try {
267                         mWaitingForActivityResult = true
268                         startIntentSenderForResult(
269                             it.pendingIntent.intentSender,
270                             requestCode,
271                             null,
272                             0,
273                             0,
274                             0,
275                             null
276                         )
277                     } catch (e: IntentSender.SendIntentException) {
278                         setupFailure(
279                             resultReceiver!!,
280                             CredentialProviderBaseController.Companion.CREATE_UNKNOWN,
281                             "During save password, found UI intent sender " +
282                                 "failure: ${e.message}"
283                         )
284                     }
285                 }
286                 .addOnFailureListener { e: Exception ->
287                     var errName: String = CredentialProviderBaseController.Companion.CREATE_UNKNOWN
288                     if (
289                         e is ApiException &&
290                             e.statusCode in CredentialProviderBaseController.retryables
291                     ) {
292                         errName = CredentialProviderBaseController.Companion.CREATE_INTERRUPTED
293                     }
294                     setupFailure(
295                         resultReceiver!!,
296                         errName,
297                         "During save password, found " +
298                             "password failure response from one tap ${e.message}"
299                     )
300                 }
301         }
302             ?: run {
303                 Log.i(
304                     TAG,
305                     "During save password, params is null, nothing to launch for create" +
306                         " password"
307                 )
308                 finish()
309             }
310     }
311 
312     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
313         super.onActivityResult(requestCode, resultCode, data)
314         resultReceiver?.reportResult(
315             requestCode = requestCode,
316             data = data,
317             resultCode = resultCode
318         )
319         mWaitingForActivityResult = false
320         finish()
321     }
322 
323     companion object {
324         private const val DEFAULT_VALUE: Int = 1
325         private const val TAG = "HiddenActivity"
326         private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT"
327     }
328 }
329