-
Notifications
You must be signed in to change notification settings - Fork 296
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
653bfd2
commit 843e9c7
Showing
51 changed files
with
751 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright 2024 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
apply plugin: 'com.android.application' | ||
|
||
android { | ||
namespace 'com.google.androidbrowserhelper.demos.customtabsauthview' | ||
compileSdkVersion 35 | ||
defaultConfig { | ||
applicationId "com.google.androidbrowserhelper.demos.customtabsauthview" | ||
minSdkVersion 26 | ||
targetSdkVersion 35 | ||
versionCode 1 | ||
versionName "1.0" | ||
} | ||
|
||
buildTypes { | ||
release { | ||
minifyEnabled false | ||
} | ||
} | ||
|
||
compileOptions { | ||
sourceCompatibility JavaVersion.VERSION_17 | ||
targetCompatibility JavaVersion.VERSION_17 | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation project(path: ':androidbrowserhelper') | ||
implementation fileTree(dir: "libs", include: ["*.jar"]) | ||
implementation 'androidx.appcompat:appcompat:1.7.0' | ||
implementation 'androidx.activity:activity:1.9.3' | ||
implementation 'androidx.browser:browser:1.9.0-SNAPSHOT' | ||
implementation 'com.google.android.material:material:1.12.0' | ||
implementation 'androidx.annotation:annotation:1.9.1' | ||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
package="com.google.androidbrowserhelper.demos.customtabsauthview"> | ||
<uses-permission android:name="android.permission.INTERNET" /> | ||
|
||
<application | ||
android:allowBackup="true" | ||
android:dataExtractionRules="@xml/data_extraction_rules" | ||
android:fullBackupContent="@xml/backup_rules" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="@string/app_name" | ||
android:roundIcon="@mipmap/ic_launcher_round" | ||
android:supportsRtl="true" | ||
android:theme="@style/Theme.AuthTab" | ||
tools:targetApi="31"> | ||
<activity | ||
android:name=".MainActivity" | ||
android:exported="true"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
|
||
<!-- An intent-filter for fallback to Chrome Custom Tabs --> | ||
<intent-filter android:autoVerify="true"> | ||
<action android:name="android.intent.action.VIEW" /> | ||
<category android:name="android.intent.category.DEFAULT" /> | ||
<category android:name="android.intent.category.BROWSABLE" /> | ||
<data android:scheme="auth" /> | ||
<data android:host="callback" /> | ||
</intent-filter> | ||
</activity> | ||
</application> | ||
</manifest> |
99 changes: 99 additions & 0 deletions
99
...w/src/main/java/com/google/androidbrowserhelper/demos/customtabsauthview/AuthManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.google.androidbrowserhelper.demos.customtabsauthview; | ||
|
||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.content.SharedPreferences; | ||
import android.net.Uri; | ||
import android.os.Handler; | ||
import android.os.Looper; | ||
import android.util.Log; | ||
|
||
import androidx.activity.result.ActivityResultLauncher; | ||
import androidx.annotation.NonNull; | ||
import androidx.browser.auth.AuthTabIntent; | ||
import androidx.annotation.OptIn; | ||
import androidx.browser.auth.ExperimentalAuthTab; | ||
|
||
import java.io.IOException; | ||
import java.util.UUID; | ||
|
||
@OptIn(markerClass = ExperimentalAuthTab.class) | ||
public class AuthManager { | ||
private static final String TAG = "OAuthManager"; | ||
|
||
private final String mClientId; | ||
private final String mClientSecret; | ||
private final String mAuthorizationEndpoint; | ||
private final String mRedirectScheme; | ||
|
||
public interface OAuthCallback { | ||
void auth(String accessToken, String scope, String tokenType); | ||
} | ||
|
||
public AuthManager(String clientId, String clientSecret, String authorizationEndpoint, | ||
String redirectScheme) { | ||
mClientId = clientId; | ||
mClientSecret = clientSecret; | ||
mAuthorizationEndpoint = authorizationEndpoint; | ||
mRedirectScheme = redirectScheme; | ||
} | ||
|
||
public void authorize(Context context, ActivityResultLauncher<Intent> launcher, String scope) { | ||
// Generate a random state. | ||
String state = UUID.randomUUID().toString(); | ||
|
||
// Save the state so we can verify later. | ||
SharedPreferences preferences = | ||
context.getSharedPreferences("OAUTH_STORAGE", Context.MODE_PRIVATE); | ||
preferences.edit() | ||
.putString("OAUTH_STATE", state) | ||
.apply(); | ||
|
||
// Create an authorization URI to the OAuth Endpoint. | ||
Uri uri = Uri.parse(mAuthorizationEndpoint) | ||
.buildUpon() | ||
.appendQueryParameter("response_type", "code") | ||
.appendQueryParameter("client_id", mClientId) | ||
.appendQueryParameter("scope", scope) | ||
.appendQueryParameter("state", state) | ||
.build(); | ||
|
||
// Open the Authorization URI in a Chrome Custom Auth Tab. | ||
AuthTabIntent authTabIntent = new AuthTabIntent.Builder().build(); | ||
authTabIntent.launch(launcher, uri, mRedirectScheme); | ||
} | ||
|
||
public void continueAuthFlow(@NonNull Context context, Uri uri, @NonNull OAuthCallback callback) { | ||
String code = uri.getQueryParameter("code"); | ||
SharedPreferences preferences = | ||
context.getSharedPreferences("OAUTH_STORAGE", Context.MODE_PRIVATE); | ||
String state = preferences.getString("OAUTH_STATE", ""); | ||
Uri tokenUri = Uri.parse("https://github.com/login/oauth/access_token") | ||
.buildUpon() | ||
.appendQueryParameter("client_id", mClientId) | ||
.appendQueryParameter("client_secret", mClientSecret) | ||
.appendQueryParameter("code", code) | ||
.appendQueryParameter("state", state) | ||
.build(); | ||
|
||
// Run the network request off the UI thread. | ||
new Thread(() -> { | ||
try { | ||
String response = Utils.fetch(tokenUri); | ||
// The response is a query-string. We concatenate with a valid domain to be | ||
// able to easily parse and extract values. | ||
Uri responseUri = Uri.parse("http://example.com?" + response); | ||
String accessToken = responseUri.getQueryParameter("access_token"); | ||
String tokenType = responseUri.getQueryParameter("token_type"); | ||
String scope = responseUri.getQueryParameter("scope"); | ||
|
||
// Invoke the callback in the main thread. | ||
new Handler(Looper.getMainLooper()).post( | ||
() -> callback.auth(accessToken, scope, tokenType)); | ||
|
||
} catch (IOException e) { | ||
Log.e(TAG, "Error requesting access token: " + e.getMessage()); | ||
} | ||
}).start(); | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
...iew/src/main/java/com/google/androidbrowserhelper/demos/customtabsauthview/GithubApi.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.google.androidbrowserhelper.demos.customtabsauthview; | ||
|
||
import android.net.Uri; | ||
import android.os.Handler; | ||
import android.os.Looper; | ||
import android.util.Log; | ||
|
||
import org.json.JSONException; | ||
import org.json.JSONObject; | ||
|
||
import java.io.IOException; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
|
||
public class GithubApi { | ||
private static final String TAG = "GithubAPI"; | ||
private static final String API_ENDPOINT = "https://api.github.com/user"; | ||
private static final String AUTH_HEADER_KEY = "Authorization"; | ||
|
||
public interface UserCallback { | ||
void onUserData(String username); | ||
} | ||
|
||
public static void requestGithubUsername(String token, UserCallback callback) { | ||
new Thread(() -> { | ||
try { | ||
Uri uri = Uri.parse(API_ENDPOINT); | ||
Map<String, String> headers = | ||
Collections.singletonMap(AUTH_HEADER_KEY, "token " + token); | ||
String response = Utils.fetch(uri, headers); | ||
JSONObject user = new JSONObject(response); | ||
String username = user.getString("name"); | ||
|
||
// Invoke the callback in the main thread. | ||
new Handler(Looper.getMainLooper()).post(() -> { | ||
callback.onUserData(username); | ||
}); | ||
} catch (IOException | JSONException ex) { | ||
Log.e(TAG, "Error fetching GitHub user: " + ex.getMessage()); | ||
} | ||
}).start(); | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
.../src/main/java/com/google/androidbrowserhelper/demos/customtabsauthview/MainActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/* | ||
* Copyright 2024 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.androidbrowserhelper.demos.customtabsauthview; | ||
|
||
import android.content.Intent; | ||
import android.net.Uri; | ||
import android.os.Bundle; | ||
import android.view.View; | ||
import android.widget.Button; | ||
import android.widget.ProgressBar; | ||
import android.widget.TextView; | ||
|
||
import androidx.activity.result.ActivityResultLauncher; | ||
import androidx.annotation.OptIn; | ||
import androidx.appcompat.app.AppCompatActivity; | ||
import androidx.browser.auth.AuthTabIntent; | ||
import androidx.browser.auth.ExperimentalAuthTab; | ||
|
||
@OptIn(markerClass = ExperimentalAuthTab.class) | ||
public class MainActivity extends AppCompatActivity { | ||
private static final String TAG = "MainActivity"; | ||
|
||
private static final String AUTHORIZATION_ENDPOINT = "https://github.com/login/oauth/authorize"; | ||
private static final String CLIENT_ID = "<github-client-id>"; | ||
private static final String CLIENT_SECRET = "<github-client-secret>"; | ||
private static final String REDIRECT_SCHEME = "auth"; | ||
|
||
private static final AuthManager O_AUTH_MANAGER = | ||
new AuthManager(CLIENT_ID, CLIENT_SECRET, AUTHORIZATION_ENDPOINT, REDIRECT_SCHEME); | ||
|
||
private final ActivityResultLauncher<Intent> mLauncher = | ||
AuthTabIntent.registerActivityResultLauncher(this, this::handleAuthResult); | ||
|
||
private Button mLoginButton; | ||
private TextView mUserText; | ||
private ProgressBar mProgressBar; | ||
private boolean mLoggedIn; | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
setContentView(R.layout.activity_main); | ||
|
||
mLoginButton = findViewById(R.id.login_button); | ||
mUserText = findViewById(R.id.user_text); | ||
mProgressBar = findViewById(R.id.progress_bar); | ||
|
||
Intent intent = getIntent(); | ||
if (intent != null) { | ||
Uri data = intent.getData(); | ||
if (data != null && data.getHost() != null | ||
&& data.getHost().startsWith("callback")) { | ||
mProgressBar.setVisibility(View.VISIBLE); | ||
mLoginButton.setEnabled(false); | ||
completeAuth(data); | ||
} | ||
} | ||
} | ||
|
||
public void login(View v) { | ||
if (mLoggedIn) { | ||
mLoginButton.setText(R.string.login); | ||
mUserText.setText(R.string.logged_out); | ||
mLoggedIn = false; | ||
} else { | ||
O_AUTH_MANAGER.authorize(this, mLauncher, "user"); | ||
} | ||
} | ||
|
||
private void handleAuthResult(AuthTabIntent.AuthResult result) { | ||
if (result.resultCode == AuthTabIntent.RESULT_OK) { | ||
completeAuth(result.resultUri); | ||
} | ||
} | ||
|
||
private void completeAuth(Uri uri) { | ||
O_AUTH_MANAGER.continueAuthFlow(this, uri, (accessToken, scope, tokenType) -> { | ||
GithubApi.requestGithubUsername(accessToken, (username -> { | ||
mLoginButton.setText(R.string.logout); | ||
mLoginButton.setEnabled(true); | ||
mProgressBar.setVisibility(View.INVISIBLE); | ||
mUserText.setText(getString(R.string.logged_in, username)); | ||
mLoggedIn = true; | ||
})); | ||
}); | ||
} | ||
} |
Oops, something went wrong.