Skip to content

Commit

Permalink
Add support for QR codes & deep links
Browse files Browse the repository at this point in the history
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
  • Loading branch information
tobiasKaminsky committed Dec 17, 2018
1 parent 4176ac2 commit 6a77bff
Show file tree
Hide file tree
Showing 12 changed files with 994 additions and 68 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ android {
compileSdkVersion 28

defaultConfig {
minSdkVersion 14
minSdkVersion 15
targetSdkVersion 28

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down Expand Up @@ -232,6 +232,7 @@ dependencies {
implementation 'com.afollestad:sectioned-recyclerview:0.5.0'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
implementation 'com.github.tobiaskaminsky:qrcodescanner:0.1.2.1-SNAPSHOT' // 'com.github.blikoon:QRCodeScanner:0.1.2'

implementation 'org.parceler:parceler-api:1.1.12'
annotationProcessor 'org.parceler:parceler:1.1.12'
Expand Down
1 change: 0 additions & 1 deletion src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@

<application
android:testOnly="false"
android:allowBackup="false"
tools:ignore="GoogleAppIndexingWarning"/>
</manifest>
16 changes: 15 additions & 1 deletion src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<!-- WRITE_EXTERNAL_STORAGE may be enabled or disabled by the user after installation in
API >= 23; the app needs to handle this -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

<!-- Next permissions are always approved in installation time, the apps needs to do nothing special in runtime -->
<uses-permission android:name="android.permission.INTERNET" />
Expand Down Expand Up @@ -235,11 +236,24 @@

<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<activity
android:name=".authentication.DeepLinkLogin"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="true"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:theme="@style/Theme.ownCloud.noActionBar.Login">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/login_data_own_scheme" android:host="login"/>

<data
android:host="login"
android:scheme="@string/login_data_own_scheme" />
</intent-filter>
</activity>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

package com.owncloud.android.authentication;

import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
Expand All @@ -50,6 +51,7 @@
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
Expand Down Expand Up @@ -83,6 +85,7 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;

import com.blikoon.qrcodescanner.QrCodeActivity;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import com.owncloud.android.MainApp;
Expand Down Expand Up @@ -110,6 +113,7 @@
import com.owncloud.android.operations.OAuth2GetAccessToken;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.activity.FirstRunActivity;
import com.owncloud.android.ui.components.CustomEditText;
import com.owncloud.android.ui.dialog.CredentialsDialogFragment;
Expand All @@ -119,6 +123,7 @@
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ErrorMessageAdapter;
import com.owncloud.android.utils.PermissionUtil;

import java.io.InputStream;
import java.net.URLDecoder;
Expand All @@ -127,6 +132,7 @@
import java.util.Locale;
import java.util.Map;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
Expand Down Expand Up @@ -191,6 +197,9 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
public static final int NO_ICON = 0;
public static final String EMPTY_STRING = "";

private static final int REQUEST_CODE_QR_SCAN = 101;


/// parameters from EXTRAs in starter Intent
private byte mAction;
private Account mAccount;
Expand Down Expand Up @@ -261,7 +270,9 @@ protected void onCreate(Bundle savedInstanceState) {
//Log_OC.e(TAG, "onCreate init");
super.onCreate(savedInstanceState);

if (savedInstanceState == null) {
Uri data = getIntent().getData();
boolean directLogin = data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme));
if (savedInstanceState == null && !directLogin) {
FirstRunActivity.runIfNeeded(this);
}

Expand Down Expand Up @@ -320,21 +331,9 @@ protected void onCreate(Bundle savedInstanceState) {
/// initialize general UI elements
initOverallUi();

findViewById(R.id.centeredRefreshButton).setOnClickListener(new View.OnClickListener() {
findViewById(R.id.centeredRefreshButton).setOnClickListener(v -> checkOcServer());

@Override
public void onClick(View v) {
checkOcServer();
}
});

findViewById(R.id.embeddedRefreshButton).setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
checkOcServer();
}
});
findViewById(R.id.embeddedRefreshButton).setOnClickListener(v -> checkOcServer());

/// initialize block to be moved to single Fragment to check server and get info about it

Expand Down Expand Up @@ -404,41 +403,35 @@ private void initWebViewLogin(String baseURL, boolean showLegacyLogin, boolean u

// show snackbar after 60s to switch back to old login method
if (showLegacyLogin) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
DisplayUtils.createSnackbar(mLoginWebView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE)
.setActionTextColor(getResources().getColor(R.color.primary_dark))
.setAction(R.string.fallback_weblogin_back, new View.OnClickListener() {
@Override
public void onClick(View v) {
mLoginWebView.setVisibility(View.INVISIBLE);
webViewLoginMethod = false;

setContentView(R.layout.account_setup);

// initialize general UI elements
initOverallUi();

mPasswordInputLayout.setVisibility(View.VISIBLE);
mUsernameInputLayout.setVisibility(View.VISIBLE);
mUsernameInput.requestFocus();
mOAuth2Check.setVisibility(View.INVISIBLE);
mAuthStatusView.setVisibility(View.INVISIBLE);
mServerStatusView.setVisibility(View.INVISIBLE);
mTestServerButton.setVisibility(View.INVISIBLE);
forceOldLoginMethod = true;
mOkButton.setVisibility(View.VISIBLE);

initServerPreFragment(null);

mHostUrlInput.setText(baseURL);

checkOcServer();
}
}).show();
}
}, 60 * 1000);
new Handler().postDelayed(() -> DisplayUtils.createSnackbar(mLoginWebView,
R.string.fallback_weblogin_text,
Snackbar.LENGTH_INDEFINITE)
.setActionTextColor(getResources().getColor(R.color.primary_dark))
.setAction(R.string.fallback_weblogin_back, v -> {
mLoginWebView.setVisibility(View.INVISIBLE);
webViewLoginMethod = false;

setContentView(R.layout.account_setup);

// initialize general UI elements
initOverallUi();

mPasswordInputLayout.setVisibility(View.VISIBLE);
mUsernameInputLayout.setVisibility(View.VISIBLE);
mUsernameInput.requestFocus();
mOAuth2Check.setVisibility(View.INVISIBLE);
mAuthStatusView.setVisibility(View.INVISIBLE);
mServerStatusView.setVisibility(View.INVISIBLE);
mTestServerButton.setVisibility(View.INVISIBLE);
forceOldLoginMethod = true;
mOkButton.setVisibility(View.VISIBLE);

initServerPreFragment(null);

mHostUrlInput.setText(baseURL);

checkOcServer();
}).show(), 60 * 1000);
}
}

Expand Down Expand Up @@ -583,14 +576,19 @@ public static LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) t
}

private void initAuthTokenType() {
mAuthTokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
Bundle extras = getIntent().getExtras();
mAuthTokenType = null;

if (extras != null) {
extras.getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
}

if (mAuthTokenType == null) {
if (mAccount != null) {
boolean oAuthRequired = mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2) != null;
boolean samlWebSsoRequired = mAccountMgr.getUserData
(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null;
mAuthTokenType = chooseAuthTokenType(oAuthRequired, samlWebSsoRequired);

} else {
boolean oAuthSupported = AUTH_ON.equals(getString(R.string.auth_method_oauth2));
boolean samlWebSsoSupported = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));
Expand Down Expand Up @@ -627,6 +625,8 @@ private void initOverallUi() {
mOkButton = findViewById(R.id.buttonOK);
mOkButton.setOnClickListener(v -> onOkClick());

findViewById(R.id.scanQR).setOnClickListener(v -> onScan());

setupInstructionMessage();

mTestServerButton.setVisibility(mAction == ACTION_CREATE ? View.VISIBLE : View.GONE);
Expand Down Expand Up @@ -763,17 +763,14 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {


// TODO find out if this is really necessary, or if it can done in a different way
findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN &&
AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(
MainApp.getAccountType(getBaseContext())).equals(mAuthTokenType) &&
mHostUrlInput.hasFocus()) {
checkOcServer();
}
return false;
findViewById(R.id.scroll).setOnTouchListener((view, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN &&
AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(
MainApp.getAccountType(getBaseContext())).equals(mAuthTokenType) &&
mHostUrlInput.hasFocus()) {
checkOcServer();
}
return false;
});
}
}
Expand Down Expand Up @@ -980,6 +977,10 @@ protected void onNewIntent(Intent intent) {
mNewCapturedUriFromOAuth2Redirection = data;
}

if (data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme))) {
parseAndLoginFromWebView(data.toString());
}

if (intent.getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
webViewLoginMethod = true;
setContentView(R.layout.account_setup_webview);
Expand Down Expand Up @@ -1757,6 +1758,14 @@ public void onAuthenticatorTaskCallback(RemoteOperationResult result) {

if (success) {
finish();

AccountUtils.setCurrentOwnCloudAccount(this, mAccount.name);

Intent i = new Intent(this, FileDisplayActivity.class);
i.setAction(FileDisplayActivity.RESTART);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);

} else {
// init webView again
if (mLoginWebView != null) {
Expand Down Expand Up @@ -1978,6 +1987,40 @@ private boolean createAccount(RemoteOperationResult authResult) {
}
}

public void onScan() {
if (PermissionUtil.checkSelfPermission(this, Manifest.permission.CAMERA)) {
startQRScanner();
} else {
PermissionUtil.requestCameraPermission(this);
}
}

private void startQRScanner() {
Intent i = new Intent(AuthenticatorActivity.this, QrCodeActivity.class);
startActivityForResult(i, REQUEST_CODE_QR_SCAN);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
@NonNull int[] grantResults) {
switch (requestCode) {
case PermissionUtil.PERMISSIONS_CAMERA: {
// If request is cancelled, result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted
startQRScanner();
} else {
// permission denied
return;
}
return;
}

default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

/**
/**
* Updates the content and visibility state of the icon and text associated
Expand Down Expand Up @@ -2256,7 +2299,21 @@ public void onServiceConnected(ComponentName component, IBinder service) {
)) {
mOperationsServiceBinder = (OperationsServiceBinder) service;

doOnResumeAndBound();
Uri data = getIntent().getData();
if (data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme))) {
String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, data.toString());

if (loginUrlInfo != null) {
mServerInfo.mBaseUrl = AuthenticatorUrlUtils.normalizeUrlSuffix(loginUrlInfo.serverAddress);
webViewUser = loginUrlInfo.username;
webViewPassword = loginUrlInfo.password;
doOnResumeAndBound();
}

} else {
doOnResumeAndBound();
}

}

Expand Down Expand Up @@ -2302,4 +2359,21 @@ public void createAuthenticationDialog(WebView webView, HttpAuthHandler handler)
public void doNegativeAuthenticationDialogClick() {
mIsFirstAuthAttempt = true;
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_CODE_QR_SCAN) {
if (data == null) {
return;
}

String result = data.getStringExtra("com.blikoon.qrcodescanner.got_qr_scan_relult");

if (!result.startsWith(getString(R.string.login_data_own_scheme))) {
return;
}

parseAndLoginFromWebView(result);
}
}
}
Loading

0 comments on commit 6a77bff

Please sign in to comment.