Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ We want to make contributing to this project as easy and transparent as possible
- [Parse Coroutines](/coroutines)
- [ParseUI](https://github.com/parse-community/ParseUI-Android)
- [ParseLiveQuery](https://github.com/parse-community/ParseLiveQuery-Android)
- [ParseGoogleUtils](/google)
- [ParseFacebookUtils](https://github.com/parse-community/ParseFacebookUtils-Android)
- [ParseTwitterUtils](https://github.com/parse-community/ParseTwitterUtils-Android)

Expand Down
1 change: 1 addition & 0 deletions google/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
56 changes: 56 additions & 0 deletions google/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Parse Google Utils for Android
A utility library to authenticate `ParseUser`s with the Google SDK.

## Setup

## Dependency

After including JitPack:
```gradle
dependencies {
implementation "com.github.parse-community.Parse-SDK-Android:google:latest.version.here"
}
```

## Usage
Here we will show the basic steps for logging in/signing up with Google. First:
```java
// in Application.onCreate(); or somewhere similar
ParseGoogleUtils.initialize(getString(R.string.default_web_client_id));
```
If you have already configured [Firebase](https://firebase.google.com/docs/android/setup) in your project, the above will work. Otherwise, you might instead need to replace `getString(R.id.default_web_client_id` with a web configured OAuth 2.0 API client ID.

Within the activity where your user is going to log in with Google, include the following:
```java
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
ParseGoogleUtils.onActivityResult(requestCode, resultCode, data);
}
```
Then elsewhere, when your user taps the login button:
```java
ParseGoogleUtils.logInWithReadPermissionsInBackground(this, permissions, new LogInCallback() {
@Override
public void done(ParseUser user, ParseException err) {
if (user == null) {
Log.d("MyApp", "Uh oh. The user cancelled the Google login.");
} else if (user.isNew()) {
Log.d("MyApp", "User signed up and logged in through Google!");
} else {
Log.d("MyApp", "User logged in through Google!");
}
}
});
```

## How Do I Contribute?
We want to make contributing to this project as easy and transparent as possible. Please refer to the [Contribution Guidelines](https://github.com/parse-community/Parse-SDK-Android/blob/master/CONTRIBUTING.md).

## License
Copyright (c) 2015-present, Parse, LLC.
All rights reserved.

This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
39 changes: 39 additions & 0 deletions google/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion rootProject.ext.compileSdkVersion

defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

packagingOptions {
exclude "**/BuildConfig.class"
}

lintOptions {
abortOnError false
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

}

dependencies {
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "com.google.android.gms:play-services-auth:17.0.0"
implementation project(":parse")
}

apply from: "https://raw.githubusercontent.com/Commit451/gradle-android-javadocs/1.1.0/gradle-android-javadocs.gradle"
21 changes: 21 additions & 0 deletions google/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
1 change: 1 addition & 0 deletions google/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.parse.google" />
251 changes: 251 additions & 0 deletions google/src/main/java/com/parse/google/ParseGoogleUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package com.parse.google

import android.app.Activity
import android.content.Context
import android.content.Intent
import bolts.Continuation
import bolts.Task
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.parse.LogInCallback
import com.parse.ParseException
import com.parse.ParseUser
import com.parse.SaveCallback

/**
* Provides a set of utilities for using Parse with Google.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ParseGoogleUtils {

private const val AUTH_TYPE = "google"
private var clientId: String? = null

private val lock = Any()

private var isInitialized = false
private var googleSignInClient: GoogleSignInClient? = null

/**
* Just hope this doesn't clash I guess...
*/
private const val REQUEST_CODE_GOOGLE_SIGN_IN = 62987

private var currentCallback: LogInCallback? = null

/**
* Initializes [ParseGoogleUtils] with the [clientId]. If you have Firebase configured, you can
* easily get the [clientId] value via context.getString(R.string.default_web_client_id)
* @param clientId the server clientId
*/
@JvmStatic
fun initialize(clientId: String) {
isInitialized = true
this.clientId = clientId
}

/**
* @param user A [com.parse.ParseUser] object.
* @return `true` if the user is linked to a Facebook account.
*/
@JvmStatic
fun isLinked(user: ParseUser): Boolean {
return user.isLinked(AUTH_TYPE)
}

/**
* Log in using a Google.
*
* @param activity The activity which passes along the result via [onActivityResult]
* @param callback The [LogInCallback] which is invoked on log in success or error
*/
@JvmStatic
fun logIn(activity: Activity, callback: LogInCallback) {
checkInitialization()
this.currentCallback = callback
val googleSignInClient = buildGoogleSignInClient(activity)
this.googleSignInClient = googleSignInClient
activity.startActivityForResult(googleSignInClient.signInIntent, REQUEST_CODE_GOOGLE_SIGN_IN)
}

/**
* The method that should be called from the Activity's or Fragment's onActivityResult method.
*
* @param requestCode The request code that's received by the Activity or Fragment.
* @param resultCode The result code that's received by the Activity or Fragment.
* @param data The result data that's received by the Activity or Fragment.
* @return true if the result could be handled.
*/
@JvmStatic
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode != REQUEST_CODE_GOOGLE_SIGN_IN) {
return false
}
if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) {
if (data != null) {
handleSignInResult(data)
} else {
onSignInCallbackResult(null, null)
}
}
return true
}

/**
* Unlink a user from a Facebook account. This will save the user's data.
*
* @param user The user to unlink.
* @param callback A callback that will be executed when unlinking is complete.
* @return A task that will be resolved when linking is complete.
*/
@JvmStatic
fun unlinkInBackground(user: ParseUser, callback: SaveCallback): Task<Void> {
return callbackOnMainThreadAsync(unlinkInBackground(user), callback, false)
}

/**
* Unlink a user from a Google account. This will save the user's data.
*
* @param user The user to unlink.
* @return A task that will be resolved when unlinking has completed.
*/
@JvmStatic
fun unlinkInBackground(user: ParseUser): Task<Void> {
checkInitialization()
return user.unlinkFromInBackground(AUTH_TYPE)
}

/**
* Link an existing Parse user with a Google account using authorization credentials that have
* already been obtained.
*
* @param user The Parse user to link with.
* @param account Authorization credentials of a Google user.
* @return A task that will be resolved when linking is complete.
*/
@JvmStatic
fun linkInBackground(user: ParseUser, account: GoogleSignInAccount): Task<Void> {
return user.linkWithInBackground(AUTH_TYPE, getAuthData(account))
}

private fun checkInitialization() {
synchronized(lock) {
check(isInitialized) { "You must call ParseGoogleUtils.initialize() before using ParseGoogleUtils" }
}
}

private fun handleSignInResult(result: Intent) {
GoogleSignIn.getSignedInAccountFromIntent(result)
.addOnSuccessListener { googleAccount ->
onSignedIn(googleAccount)
}
.addOnFailureListener { exception ->
onSignInCallbackResult(null, exception)
}
}

private fun onSignedIn(account: GoogleSignInAccount) {
googleSignInClient?.signOut()?.addOnCompleteListener {}
val authData: Map<String, String> = getAuthData(account)
ParseUser.logInWithInBackground(AUTH_TYPE, authData)
.continueWith { task ->
when {
task.isCompleted -> {
val user = task.result
onSignInCallbackResult(user, null)
}
task.isFaulted -> {
onSignInCallbackResult(null, task.error)
}
else -> {
onSignInCallbackResult(null, null)
}
}
}
}

private fun getAuthData(account: GoogleSignInAccount): Map<String, String> {
val authData = mutableMapOf<String, String>()
authData["id"] = account.id!!
authData["id_token"] = account.idToken!!
account.email?.let { authData["email"] = it }
account.photoUrl?.let { authData["photo_url"] = it.toString() }
return authData
}

private fun onSignInCallbackResult(user: ParseUser?, exception: Exception?) {
val exceptionToThrow = when (exception) {
is ParseException -> exception
null -> null
else -> ParseException(exception)
}
currentCallback?.done(user, exceptionToThrow)
}

private fun buildGoogleSignInClient(context: Context): GoogleSignInClient {
val signInOptions = GoogleSignInOptions.Builder()
.requestId()
.requestEmail()
.requestIdToken(clientId)
.build()
return GoogleSignIn.getClient(context, signInOptions)
}

/**
* Calls the callback after a task completes on the main thread, returning a Task that completes
* with the same result as the input task after the callback has been run.
*/
private fun <T> callbackOnMainThreadAsync(
task: Task<T>, callback: SaveCallback, reportCancellation: Boolean): Task<T> {
return callbackOnMainThreadInternalAsync(task, callback, reportCancellation)
}

/**
* Calls the callback after a task completes on the main thread, returning a Task that completes
* with the same result as the input task after the callback has been run. If reportCancellation
* is false, the callback will not be called if the task was cancelled.
*/
private fun <T> callbackOnMainThreadInternalAsync(
task: Task<T>, callback: Any?, reportCancellation: Boolean): Task<T> {
if (callback == null) {
return task
}
val tcs: Task<T>.TaskCompletionSource = Task.create()
task.continueWith<Void>(Continuation { task ->
if (task.isCancelled && !reportCancellation) {
tcs.setCancelled()
return@Continuation null
}
Task.UI_THREAD_EXECUTOR.execute {
try {
var error = task.error
if (error != null && error !is ParseException) {
error = ParseException(error)
}
if (callback is SaveCallback) {
callback.done(error as ParseException)
} else if (callback is LogInCallback) {
callback.done(
task.result as ParseUser, error as ParseException)
}
} finally {
when {
task.isCancelled -> {
tcs.setCancelled()
}
task.isFaulted -> {
tcs.setError(task.error)
}
else -> {
tcs.setResult(task.result)
}
}
}
}
null
})
return tcs.task
}
}
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
include ':parse', ':fcm', ':gcm', ':ktx', ':coroutines'
include ':parse', ':fcm', ':gcm', ':ktx', ':coroutines', ':google'