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 @file:Suppress("deprecation") 18 19 package androidx.credentials.playservices.controllers.identityauth.createpassword 20 21 import android.content.Context 22 import android.content.Intent 23 import android.os.Bundle 24 import android.os.CancellationSignal 25 import android.os.Handler 26 import android.os.Looper 27 import android.os.ResultReceiver 28 import android.util.Log 29 import androidx.annotation.VisibleForTesting 30 import androidx.credentials.CreateCredentialResponse 31 import androidx.credentials.CreatePasswordRequest 32 import androidx.credentials.CreatePasswordResponse 33 import androidx.credentials.CredentialManagerCallback 34 import androidx.credentials.exceptions.CreateCredentialException 35 import androidx.credentials.exceptions.CreateCredentialUnknownException 36 import androidx.credentials.playservices.CredentialProviderPlayServicesImpl 37 import androidx.credentials.playservices.controllers.CredentialProviderBaseController 38 import androidx.credentials.playservices.controllers.CredentialProviderController 39 import androidx.credentials.playservices.controllers.identityauth.HiddenActivity 40 import com.google.android.gms.auth.api.identity.SavePasswordRequest 41 import com.google.android.gms.auth.api.identity.SignInPassword 42 import java.util.concurrent.Executor 43 44 /** A controller to handle the CreatePassword flow with play services. */ 45 internal class CredentialProviderCreatePasswordController(private val context: Context) : 46 CredentialProviderController< 47 CreatePasswordRequest, 48 SavePasswordRequest, 49 Unit, 50 CreateCredentialResponse, 51 CreateCredentialException 52 >(context) { 53 54 /** The callback object state, used in the protected handleResponse method. */ 55 @VisibleForTesting 56 private lateinit var callback: 57 CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> 58 59 /** The callback requires an executor to invoke it. */ 60 private lateinit var executor: Executor 61 62 /** 63 * The cancellation signal, which is shuttled around to stop the flow at any moment prior to 64 * returning data. 65 */ 66 @VisibleForTesting private var cancellationSignal: CancellationSignal? = null 67 68 private val resultReceiver = 69 object : ResultReceiver(Handler(Looper.getMainLooper())) { 70 public override fun onReceiveResult(resultCode: Int, resultData: Bundle) { 71 if ( 72 maybeReportErrorFromResultReceiver( 73 resultData, 74 CredentialProviderBaseController.Companion:: 75 createCredentialExceptionTypeToException, 76 executor = executor, 77 callback = callback, 78 cancellationSignal 79 ) 80 ) 81 return 82 handleResponse(resultData.getInt(ACTIVITY_REQUEST_CODE_TAG), resultCode) 83 } 84 } 85 86 override fun invokePlayServices( 87 request: CreatePasswordRequest, 88 callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>, 89 executor: Executor, 90 cancellationSignal: CancellationSignal? 91 ) { 92 this.cancellationSignal = cancellationSignal 93 this.callback = callback 94 this.executor = executor 95 96 if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) { 97 return 98 } 99 100 val convertedRequest: SavePasswordRequest = this.convertRequestToPlayServices(request) 101 val hiddenIntent = Intent(context, HiddenActivity::class.java) 102 hiddenIntent.putExtra(REQUEST_TAG, convertedRequest) 103 generateHiddenActivityIntent(resultReceiver, hiddenIntent, CREATE_PASSWORD_TAG) 104 try { 105 context.startActivity(hiddenIntent) 106 } catch (e: Exception) { 107 cancelOrCallbackExceptionOrResult(cancellationSignal) { 108 this.executor.execute { 109 this.callback.onError( 110 CreateCredentialUnknownException(ERROR_MESSAGE_START_ACTIVITY_FAILED) 111 ) 112 } 113 } 114 } 115 } 116 117 internal fun handleResponse(uniqueRequestCode: Int, resultCode: Int) { 118 if (uniqueRequestCode != CONTROLLER_REQUEST_CODE) { 119 Log.w( 120 TAG, 121 "Returned request code " + 122 "${CONTROLLER_REQUEST_CODE} which does not match what was given $uniqueRequestCode" 123 ) 124 return 125 } 126 if ( 127 maybeReportErrorResultCodeCreate( 128 resultCode, 129 { s, f -> cancelOrCallbackExceptionOrResult(s, f) }, 130 { e -> this.executor.execute { this.callback.onError(e) } }, 131 cancellationSignal 132 ) 133 ) 134 return 135 val response: CreateCredentialResponse = convertResponseToCredentialManager(Unit) 136 cancelOrCallbackExceptionOrResult(cancellationSignal) { 137 this.executor.execute { this.callback.onResult(response) } 138 } 139 } 140 141 @VisibleForTesting 142 public override fun convertRequestToPlayServices( 143 request: CreatePasswordRequest 144 ): SavePasswordRequest { 145 return SavePasswordRequest.builder() 146 .setSignInPassword(SignInPassword(request.id, request.password)) 147 .build() 148 } 149 150 @VisibleForTesting 151 public override fun convertResponseToCredentialManager( 152 response: Unit 153 ): CreateCredentialResponse { 154 return CreatePasswordResponse() 155 } 156 157 companion object { 158 private const val TAG = "CreatePassword" 159 160 /** 161 * Factory method for [CredentialProviderCreatePasswordController]. 162 * 163 * @param context the calling context for this controller 164 * @return a credential provider controller for CreatePasswordController 165 */ 166 @JvmStatic 167 fun getInstance(context: Context): CredentialProviderCreatePasswordController { 168 return CredentialProviderCreatePasswordController(context) 169 } 170 } 171 } 172