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