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