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