1
+ package com.parse.google
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import bolts.Continuation
7
+ import bolts.Task
8
+ import com.google.android.gms.auth.api.signin.GoogleSignIn
9
+ import com.google.android.gms.auth.api.signin.GoogleSignInAccount
10
+ import com.google.android.gms.auth.api.signin.GoogleSignInClient
11
+ import com.google.android.gms.auth.api.signin.GoogleSignInOptions
12
+ import com.parse.LogInCallback
13
+ import com.parse.ParseException
14
+ import com.parse.ParseUser
15
+ import com.parse.SaveCallback
16
+
17
+ /* *
18
+ * Provides a set of utilities for using Parse with Google.
19
+ */
20
+ @Suppress(" MemberVisibilityCanBePrivate" , " unused" )
21
+ object ParseGoogleUtils {
22
+
23
+ private const val AUTH_TYPE = " google"
24
+ private var clientId: String? = null
25
+
26
+ private val lock = Any ()
27
+
28
+ private var isInitialized = false
29
+ private var googleSignInClient: GoogleSignInClient ? = null
30
+
31
+ /* *
32
+ * Just hope this doesn't clash I guess...
33
+ */
34
+ private const val REQUEST_CODE_GOOGLE_SIGN_IN = 62987
35
+
36
+ private var currentCallback: LogInCallback ? = null
37
+
38
+ /* *
39
+ * Initializes [ParseGoogleUtils] with the [clientId]. If you have Firebase configured, you can
40
+ * easily get the [clientId] value via context.getString(R.string.default_web_client_id)
41
+ * @param clientId the server clientId
42
+ */
43
+ @JvmStatic
44
+ fun initialize (clientId : String ) {
45
+ isInitialized = true
46
+ this .clientId = clientId
47
+ }
48
+
49
+ /* *
50
+ * @param user A [com.parse.ParseUser] object.
51
+ * @return `true` if the user is linked to a Facebook account.
52
+ */
53
+ @JvmStatic
54
+ fun isLinked (user : ParseUser ): Boolean {
55
+ return user.isLinked(AUTH_TYPE )
56
+ }
57
+
58
+ /* *
59
+ * Log in using a Google.
60
+ *
61
+ * @param activity The activity which passes along the result via [onActivityResult]
62
+ * @param callback The [LogInCallback] which is invoked on log in success or error
63
+ */
64
+ @JvmStatic
65
+ fun logIn (activity : Activity , callback : LogInCallback ) {
66
+ checkInitialization()
67
+ this .currentCallback = callback
68
+ val googleSignInClient = buildGoogleSignInClient(activity)
69
+ this .googleSignInClient = googleSignInClient
70
+ activity.startActivityForResult(googleSignInClient.signInIntent, REQUEST_CODE_GOOGLE_SIGN_IN )
71
+ }
72
+
73
+ /* *
74
+ * The method that should be called from the Activity's or Fragment's onActivityResult method.
75
+ *
76
+ * @param requestCode The request code that's received by the Activity or Fragment.
77
+ * @param resultCode The result code that's received by the Activity or Fragment.
78
+ * @param data The result data that's received by the Activity or Fragment.
79
+ * @return true if the result could be handled.
80
+ */
81
+ @JvmStatic
82
+ fun onActivityResult (requestCode : Int , resultCode : Int , data : Intent ? ): Boolean {
83
+ if (requestCode != REQUEST_CODE_GOOGLE_SIGN_IN ) {
84
+ return false
85
+ }
86
+ if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN ) {
87
+ if (data != null ) {
88
+ handleSignInResult(data)
89
+ } else {
90
+ onSignInCallbackResult(null , null )
91
+ }
92
+ }
93
+ return true
94
+ }
95
+
96
+ /* *
97
+ * Unlink a user from a Facebook account. This will save the user's data.
98
+ *
99
+ * @param user The user to unlink.
100
+ * @param callback A callback that will be executed when unlinking is complete.
101
+ * @return A task that will be resolved when linking is complete.
102
+ */
103
+ @JvmStatic
104
+ fun unlinkInBackground (user : ParseUser , callback : SaveCallback ): Task <Void > {
105
+ return callbackOnMainThreadAsync(unlinkInBackground(user), callback, false )
106
+ }
107
+
108
+ /* *
109
+ * Unlink a user from a Google account. This will save the user's data.
110
+ *
111
+ * @param user The user to unlink.
112
+ * @return A task that will be resolved when unlinking has completed.
113
+ */
114
+ @JvmStatic
115
+ fun unlinkInBackground (user : ParseUser ): Task <Void > {
116
+ checkInitialization()
117
+ return user.unlinkFromInBackground(AUTH_TYPE )
118
+ }
119
+
120
+ /* *
121
+ * Link an existing Parse user with a Google account using authorization credentials that have
122
+ * already been obtained.
123
+ *
124
+ * @param user The Parse user to link with.
125
+ * @param account Authorization credentials of a Google user.
126
+ * @return A task that will be resolved when linking is complete.
127
+ */
128
+ @JvmStatic
129
+ fun linkInBackground (user : ParseUser , account : GoogleSignInAccount ): Task <Void > {
130
+ return user.linkWithInBackground(AUTH_TYPE , getAuthData(account))
131
+ }
132
+
133
+ private fun checkInitialization () {
134
+ synchronized(lock) {
135
+ check(isInitialized) { " You must call ParseGoogleUtils.initialize() before using ParseGoogleUtils" }
136
+ }
137
+ }
138
+
139
+ private fun handleSignInResult (result : Intent ) {
140
+ GoogleSignIn .getSignedInAccountFromIntent(result)
141
+ .addOnSuccessListener { googleAccount ->
142
+ onSignedIn(googleAccount)
143
+ }
144
+ .addOnFailureListener { exception ->
145
+ onSignInCallbackResult(null , exception)
146
+ }
147
+ }
148
+
149
+ private fun onSignedIn (account : GoogleSignInAccount ) {
150
+ googleSignInClient?.signOut()?.addOnCompleteListener {}
151
+ val authData: Map <String , String > = getAuthData(account)
152
+ ParseUser .logInWithInBackground(AUTH_TYPE , authData)
153
+ .continueWith { task ->
154
+ when {
155
+ task.isCompleted -> {
156
+ val user = task.result
157
+ onSignInCallbackResult(user, null )
158
+ }
159
+ task.isFaulted -> {
160
+ onSignInCallbackResult(null , task.error)
161
+ }
162
+ else -> {
163
+ onSignInCallbackResult(null , null )
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ private fun getAuthData (account : GoogleSignInAccount ): Map <String , String > {
170
+ val authData = mutableMapOf<String , String >()
171
+ authData[" id" ] = account.id!!
172
+ authData[" id_token" ] = account.idToken!!
173
+ account.email?.let { authData[" email" ] = it }
174
+ account.photoUrl?.let { authData[" photo_url" ] = it.toString() }
175
+ return authData
176
+ }
177
+
178
+ private fun onSignInCallbackResult (user : ParseUser ? , exception : Exception ? ) {
179
+ val exceptionToThrow = when (exception) {
180
+ is ParseException -> exception
181
+ null -> null
182
+ else -> ParseException (exception)
183
+ }
184
+ currentCallback?.done(user, exceptionToThrow)
185
+ }
186
+
187
+ private fun buildGoogleSignInClient (context : Context ): GoogleSignInClient {
188
+ val signInOptions = GoogleSignInOptions .Builder ()
189
+ .requestId()
190
+ .requestEmail()
191
+ .requestIdToken(clientId)
192
+ .build()
193
+ return GoogleSignIn .getClient(context, signInOptions)
194
+ }
195
+
196
+ /* *
197
+ * Calls the callback after a task completes on the main thread, returning a Task that completes
198
+ * with the same result as the input task after the callback has been run.
199
+ */
200
+ private fun <T > callbackOnMainThreadAsync (
201
+ task : Task <T >, callback : SaveCallback , reportCancellation : Boolean ): Task <T > {
202
+ return callbackOnMainThreadInternalAsync(task, callback, reportCancellation)
203
+ }
204
+
205
+ /* *
206
+ * Calls the callback after a task completes on the main thread, returning a Task that completes
207
+ * with the same result as the input task after the callback has been run. If reportCancellation
208
+ * is false, the callback will not be called if the task was cancelled.
209
+ */
210
+ private fun <T > callbackOnMainThreadInternalAsync (
211
+ task : Task <T >, callback : Any? , reportCancellation : Boolean ): Task <T > {
212
+ if (callback == null ) {
213
+ return task
214
+ }
215
+ val tcs: Task <T >.TaskCompletionSource = Task .create()
216
+ task.continueWith<Void >(Continuation { task ->
217
+ if (task.isCancelled && ! reportCancellation) {
218
+ tcs.setCancelled()
219
+ return @Continuation null
220
+ }
221
+ Task .UI_THREAD_EXECUTOR .execute {
222
+ try {
223
+ var error = task.error
224
+ if (error != null && error !is ParseException ) {
225
+ error = ParseException (error)
226
+ }
227
+ if (callback is SaveCallback ) {
228
+ callback.done(error as ParseException )
229
+ } else if (callback is LogInCallback ) {
230
+ callback.done(
231
+ task.result as ParseUser , error as ParseException )
232
+ }
233
+ } finally {
234
+ when {
235
+ task.isCancelled -> {
236
+ tcs.setCancelled()
237
+ }
238
+ task.isFaulted -> {
239
+ tcs.setError(task.error)
240
+ }
241
+ else -> {
242
+ tcs.setResult(task.result)
243
+ }
244
+ }
245
+ }
246
+ }
247
+ null
248
+ })
249
+ return tcs.task
250
+ }
251
+ }
0 commit comments