1 /*
2  * 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
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.os.Bundle
22 import android.os.Parcel
23 import android.os.ResultReceiver
24 import androidx.credentials.exceptions.CreateCredentialCancellationException
25 import androidx.credentials.exceptions.CreateCredentialException
26 import androidx.credentials.exceptions.CreateCredentialInterruptedException
27 import androidx.credentials.exceptions.CreateCredentialUnknownException
28 import androidx.credentials.exceptions.GetCredentialCancellationException
29 import androidx.credentials.exceptions.GetCredentialException
30 import androidx.credentials.exceptions.GetCredentialInterruptedException
31 import androidx.credentials.exceptions.GetCredentialUnknownException
32 import androidx.credentials.exceptions.NoCredentialException
33 import com.google.android.gms.common.api.CommonStatusCodes
34 
35 /** Holds all non type specific details shared by the controllers. */
36 internal open class CredentialProviderBaseController(private val context: Context) {
37     companion object {
38 
39         // Common retryable status codes from the play modules found
40         // https://developers.google.com/android/reference/com/google/android/gms/common/api/CommonStatusCodes
41         val retryables: Set<Int> =
42             setOf(
43                 CommonStatusCodes.NETWORK_ERROR,
44                 CommonStatusCodes.CONNECTION_SUSPENDED_DURING_CALL
45             )
46 
47         // Generic controller request code used by all controllers
48         @JvmStatic internal val CONTROLLER_REQUEST_CODE: Int = 1
49 
50         /** -- Used to avoid reflection, these constants map errors from HiddenActivity -- */
51         const val GET_CANCELED = "GET_CANCELED_TAG"
52         const val GET_INTERRUPTED = "GET_INTERRUPTED"
53         const val GET_NO_CREDENTIALS = "GET_NO_CREDENTIALS"
54         const val GET_UNKNOWN = "GET_UNKNOWN"
55 
56         const val CREATE_CANCELED = "CREATE_CANCELED"
57         const val CREATE_INTERRUPTED = "CREATE_INTERRUPTED"
58         const val CREATE_UNKNOWN = "CREATE_UNKNOWN"
59 
60         /** ---- Data Constants to pass between the controllers and the hidden activity---- */
61 
62         // Key to indicate type sent from controller to hidden activity
63         const val TYPE_TAG = "TYPE"
64 
65         // Value for the specific begin sign in type
66         const val BEGIN_SIGN_IN_TAG = "BEGIN_SIGN_IN"
67 
68         // Key for the Sign-in Intent flow
69         const val SIGN_IN_INTENT_TAG = "SIGN_IN_INTENT"
70 
71         // Value for the specific create password type
72         const val CREATE_PASSWORD_TAG = "CREATE_PASSWORD"
73 
74         // Value for the specific create public key credential type
75         const val CREATE_PUBLIC_KEY_CREDENTIAL_TAG = "CREATE_PUBLIC_KEY_CREDENTIAL"
76 
77         // Key for the actual parcelable type sent to the hidden activity
78         const val REQUEST_TAG = "REQUEST_TYPE"
79 
80         // Key for the result intent to send back to the controller
81         const val RESULT_DATA_TAG = "RESULT_DATA"
82 
83         // Key for the actual parcelable type sent to the hidden activity
84         const val EXTRA_GET_CREDENTIAL_INTENT = "EXTRA_GET_CREDENTIAL_INTENT"
85 
86         // Key for the failure boolean sent back from hidden activity to controller
87         const val FAILURE_RESPONSE_TAG = "FAILURE_RESPONSE"
88 
89         // Key for the exception type sent back from hidden activity to controllers if error
90         const val EXCEPTION_TYPE_TAG = "EXCEPTION_TYPE"
91 
92         // Key for an error message propagated from hidden activity to controllers
93         const val EXCEPTION_MESSAGE_TAG = "EXCEPTION_MESSAGE"
94 
95         // Key for the activity request code from controllers to activity
96         const val ACTIVITY_REQUEST_CODE_TAG = "ACTIVITY_REQUEST_CODE"
97 
98         // Key for the result receiver sent from controller to activity
99         const val RESULT_RECEIVER_TAG = "RESULT_RECEIVER"
100 
101         /** Shuttles back exceptions only related to the hidden activity that can't be parceled */
getCredentialExceptionTypeToExceptionnull102         internal fun getCredentialExceptionTypeToException(
103             typeName: String?,
104             msg: String?
105         ): GetCredentialException {
106             return when (typeName) {
107                 GET_CANCELED -> {
108                     GetCredentialCancellationException(msg)
109                 }
110                 GET_INTERRUPTED -> {
111                     GetCredentialInterruptedException(msg)
112                 }
113                 GET_NO_CREDENTIALS -> {
114                     NoCredentialException(msg)
115                 }
116                 else -> {
117                     GetCredentialUnknownException(msg)
118                 }
119             }
120         }
121 
reportErrornull122         internal fun ResultReceiver.reportError(errName: String, errMsg: String) {
123             val bundle = Bundle()
124             bundle.putBoolean(FAILURE_RESPONSE_TAG, true)
125             bundle.putString(EXCEPTION_TYPE_TAG, errName)
126             bundle.putString(EXCEPTION_MESSAGE_TAG, errMsg)
127             this.send(Integer.MAX_VALUE, bundle)
128         }
129 
reportResultnull130         internal fun ResultReceiver.reportResult(requestCode: Int, resultCode: Int, data: Intent?) {
131             val bundle = Bundle()
132             bundle.putBoolean(FAILURE_RESPONSE_TAG, false)
133             bundle.putInt(ACTIVITY_REQUEST_CODE_TAG, requestCode)
134             bundle.putParcelable(RESULT_DATA_TAG, data)
135             this.send(resultCode, bundle)
136         }
137 
createCredentialExceptionTypeToExceptionnull138         internal fun createCredentialExceptionTypeToException(
139             typeName: String?,
140             msg: String?
141         ): CreateCredentialException {
142             return when (typeName) {
143                 CREATE_CANCELED -> {
144                     CreateCredentialCancellationException(msg)
145                 }
146                 CREATE_INTERRUPTED -> {
147                     CreateCredentialInterruptedException(msg)
148                 }
149                 else -> {
150                     CreateCredentialUnknownException(msg)
151                 }
152             }
153         }
154     }
155 
toIpcFriendlyResultReceivernull156     fun <T : ResultReceiver?> toIpcFriendlyResultReceiver(resultReceiver: T): ResultReceiver? {
157         val parcel: Parcel = Parcel.obtain()
158         resultReceiver!!.writeToParcel(parcel, 0)
159         parcel.setDataPosition(0)
160         val ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel)
161         parcel.recycle()
162         return ipcFriendly
163     }
164 
generateHiddenActivityIntentnull165     fun generateHiddenActivityIntent(
166         resultReceiver: ResultReceiver,
167         hiddenIntent: Intent,
168         typeTag: String
169     ) {
170         hiddenIntent.putExtra(TYPE_TAG, typeTag)
171         hiddenIntent.putExtra(ACTIVITY_REQUEST_CODE_TAG, CONTROLLER_REQUEST_CODE)
172         hiddenIntent.putExtra(RESULT_RECEIVER_TAG, toIpcFriendlyResultReceiver(resultReceiver))
173         hiddenIntent.flags = Intent.FLAG_ACTIVITY_NO_ANIMATION
174     }
175 }
176