diff options
author | David Luhmer <david-dev@live.de> | 2018-06-12 16:56:47 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-12 16:56:47 +0300 |
commit | 05055a82d933d1147eacdb52ce5067426566368e (patch) | |
tree | 591978c2af1aafc189eeb376927f3f283357a573 /News-Android-App | |
parent | 96efaa7d7eaca737d42c6340f7e7cffd0bba8f84 (diff) | |
parent | e48dfe87fd0e11fc561cc38f189108e515555cd0 (diff) |
Merge pull request #639 from nextcloud/sso
Proof of concept for SingSignOn
Diffstat (limited to 'News-Android-App')
12 files changed, 972 insertions, 186 deletions
diff --git a/News-Android-App/build.gradle b/News-Android-App/build.gradle index 86275f5c..abb4a14f 100644 --- a/News-Android-App/build.gradle +++ b/News-Android-App/build.gradle @@ -13,6 +13,7 @@ android { buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION defaultConfig { + multiDexEnabled true minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION) targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) @@ -89,6 +90,7 @@ repositories { jcenter() maven { url 'http://guardian.github.com/maven/repo-releases' } //needed for com.gu:option:1.3 in Android-DirectoryChooser maven { url "http://dl.bintray.com/lukaville/maven" } //Needed for com.nbsp:library:1.02 in Material File Picker + maven { url "https://jitpack.io" } } @@ -100,7 +102,7 @@ dependencies { // You must install or update the Google Repository through the SDK manager to use this dependency. // The Google Repository (separate from the corresponding library) can be found in the Extras category. // implementation 'com.google.android.gms:play-services:4.2.42' - implementation project(path: ':ownCloud-Account-Importer', configuration: 'default') + implementation "com.github.nextcloud:android-SingleSignOn:sso-SNAPSHOT" implementation "com.android.support:support-v4:${SUPPORT_VERSION}" implementation "com.android.support:support-compat:${SUPPORT_VERSION}" implementation "com.android.support:appcompat-v7:${SUPPORT_VERSION}" @@ -113,6 +115,7 @@ dependencies { implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.4' implementation 'com.google.code.gson:gson:2.8.0' implementation 'com.jakewharton:butterknife:8.8.1' + implementation 'com.android.support:multidex:1.0.3' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' compileOnly 'com.google.auto.value:auto-value:1.5.2' diff --git a/News-Android-App/src/main/AndroidManifest.xml b/News-Android-App/src/main/AndroidManifest.xml index 3bc1a936..f6394e9a 100644 --- a/News-Android-App/src/main/AndroidManifest.xml +++ b/News-Android-App/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ <uses-permission android:name="android.permission.USE_CREDENTIALS" /> <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> + <uses-permission android:name="com.owncloud.android.sso"/> <!-- <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> --> diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/LoginDialogFragment.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/LoginDialogFragment.java index 35c07cd9..c4b08055 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/LoginDialogFragment.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/LoginDialogFragment.java @@ -21,14 +21,20 @@ package de.luhmer.owncloudnewsreader; +import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; +import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.design.widget.TextInputLayout; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.text.Editable; @@ -48,7 +54,9 @@ import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageView; +import android.widget.Switch; import android.widget.TextView; +import android.widget.Toast; import java.net.MalformedURLException; import java.net.URL; @@ -57,9 +65,9 @@ import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; -import de.luhmer.owncloud.accountimporter.ImportAccountsDialogFragment; import de.luhmer.owncloud.accountimporter.helper.AccountImporter; -import de.luhmer.owncloud.accountimporter.helper.OwnCloudAccount; +import de.luhmer.owncloud.accountimporter.helper.NextcloudAPI; +import de.luhmer.owncloud.accountimporter.helper.SingleSignOnAccount; import de.luhmer.owncloud.accountimporter.interfaces.IAccountImport; import de.luhmer.owncloudnewsreader.authentication.AuthenticatorActivity; import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm; @@ -72,12 +80,18 @@ import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import static android.app.Activity.RESULT_OK; + /** * Activity which displays a login screen to the user, offering registration as * well. */ public class LoginDialogFragment extends DialogFragment implements IAccountImport { + final String TAG = LoginDialogFragment.class.getCanonicalName(); + final int CHOOSE_ACCOUNT = 12; + + static LoginDialogFragment instance; public static LoginDialogFragment getInstance() { if(instance == null) @@ -104,23 +118,39 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor // UI references. @BindView(R.id.username) EditText mUsernameView; @BindView(R.id.password) EditText mPasswordView; + @BindView(R.id.password_container) TextInputLayout mPasswordContainerView; @BindView(R.id.edt_owncloudRootPath) EditText mOc_root_path_View; @BindView(R.id.cb_AllowAllSSLCertificates) CheckBox mCbDisableHostnameVerificationView; @BindView(R.id.imgView_ShowPassword) ImageView mImageViewShowPwd; + @BindView(R.id.swSingleSignOn) Switch mSwSingleSignOn; - boolean mPasswordVisible = false; + private Account importedAccount = null; + private boolean mPasswordVisible = false; + private LoginSuccessfulListener listener; - @Override - public void accountAccessGranted(OwnCloudAccount account) { - mUsernameView.setText(account.getUsername()); - mPasswordView.setText(account.getPassword()); - mOc_root_path_View.setText(account.getUrl()); - } + @Override + public void accountAccessGranted(final Account account) { + try { + SingleSignOnAccount singleAccount = AccountImporter.BlockingGetAuthToken(getActivity(), account); + mUsernameView.setText(singleAccount.username); + mPasswordView.setText(""); + mOc_root_path_View.setText(singleAccount.url); + + mPasswordContainerView.setVisibility(View.GONE); + mImageViewShowPwd.setVisibility(View.GONE); + mCbDisableHostnameVerificationView.setVisibility(View.GONE); - public interface LoginSuccessfullListener { + this.importedAccount = account; + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + + public interface LoginSuccessfulListener { void LoginSucceeded(); } - LoginSuccessfullListener listener; + public LoginDialogFragment() { @@ -134,7 +164,7 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor /** * @param listener the listener to set */ - public void setListener(LoginSuccessfullListener listener) { + public void setListener(LoginSuccessfulListener listener) { this.listener = listener; } @@ -153,7 +183,11 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - showImportAccountButton = AccountImporter.findAccounts(getActivity()).size() > 0; + ActivityCompat.requestPermissions(getActivity(), + new String[]{Manifest.permission.GET_ACCOUNTS}, 0); + + //accountImporter = new AccountImporter(); + showImportAccountButton = AccountImporter.AccountsToImportAvailable(getActivity()); //setRetainInstance(true); @@ -179,6 +213,7 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor mUsername = mPrefs.getString(SettingsActivity.EDT_USERNAME_STRING, ""); mPassword = mPrefs.getString(SettingsActivity.EDT_PASSWORD_STRING, ""); mOc_root_path = mPrefs.getString(SettingsActivity.EDT_OWNCLOUDROOTPATH_STRING, ""); + boolean useSSO = mPrefs.getBoolean(SettingsActivity.SW_USE_SINGLE_SIGN_ON, false); mCbDisableHostnameVerification = mPrefs.getBoolean(SettingsActivity.CB_DISABLE_HOSTNAME_VERIFICATION_STRING, false); if(!mPassword.isEmpty()) { @@ -201,11 +236,52 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor } }); + if(useSSO) { + mSwSingleSignOn.setChecked(true); + syncUiElementState(); + } + + mSwSingleSignOn.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + syncUiElementState(); + + mUsernameView.setText(""); + mPasswordView.setText(""); + mOc_root_path_View.setText(""); + mCbDisableHostnameVerificationView.setChecked(false); + + mPasswordContainerView.setVisibility(View.VISIBLE); + mImageViewShowPwd.setVisibility(View.VISIBLE); + mCbDisableHostnameVerificationView.setVisibility(View.VISIBLE); + + if(isChecked) { + Intent intent = AccountManager.newChooseAccountIntent(null, null, new String[] {"nextcloud"}, + true, null, null, null, null); + startActivityForResult(intent, CHOOSE_ACCOUNT); + } else { + importedAccount = null; + } + } + }); + + + AlertDialog dialog = builder.create(); // Set dialog to resize when soft keyboard pops up dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - return dialog; + + + return dialog; + } + + private void syncUiElementState() { + boolean useSSO = mSwSingleSignOn.isChecked(); + mUsernameView.setEnabled(!useSSO); + mPasswordView.setEnabled(!useSSO); + mOc_root_path_View.setEnabled(!useSSO); + mCbDisableHostnameVerificationView.setEnabled(!useSSO); } @Override @@ -219,7 +295,9 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor neutralButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ImportAccountsDialogFragment.show(getActivity(), LoginDialogFragment.this); + mSwSingleSignOn.setChecked(false); + mSwSingleSignOn.setChecked(true); + //ImportAccountsDialogFragment.show(getActivity(), LoginDialogFragment.this); } }); // Limit button width to not push positive button out of view @@ -235,6 +313,7 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor } } + @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); @@ -304,36 +383,39 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor boolean cancel = false; View focusView = null; - // Check for a valid password. - if (TextUtils.isEmpty(mPassword)) { - mPasswordView.setError(getString(R.string.error_field_required)); - focusView = mPasswordView; - cancel = true; - } - // Check for a valid email address. - if (TextUtils.isEmpty(mUsername)) { - mUsernameView.setError(getString(R.string.error_field_required)); - focusView = mUsernameView; - cancel = true; - } + // Only run checks if we don't use sso + if(!mSwSingleSignOn.isChecked()) { + // Check for a valid password. + if (TextUtils.isEmpty(mPassword)) { + mPasswordView.setError(getString(R.string.error_field_required)); + focusView = mPasswordView; + cancel = true; + } + // Check for a valid email address. + if (TextUtils.isEmpty(mUsername)) { + mUsernameView.setError(getString(R.string.error_field_required)); + focusView = mUsernameView; + cancel = true; + } - if (TextUtils.isEmpty(mOc_root_path)) { - mOc_root_path_View.setError(getString(R.string.error_field_required)); - focusView = mOc_root_path_View; - cancel = true; - } else { - try { - URL url = new URL(mOc_root_path); - if(!url.getProtocol().equals("https")) - ShowAlertDialog(getString(R.string.login_dialog_title_security_warning), - getString(R.string.login_dialog_text_security_warning), getActivity()); - } catch (MalformedURLException e) { - mOc_root_path_View.setError(getString(R.string.error_invalid_url)); - focusView = mOc_root_path_View; - cancel = true; - //e.printStackTrace(); - } - } + if (TextUtils.isEmpty(mOc_root_path)) { + mOc_root_path_View.setError(getString(R.string.error_field_required)); + focusView = mOc_root_path_View; + cancel = true; + } else { + try { + URL url = new URL(mOc_root_path); + if (!url.getProtocol().equals("https")) + ShowAlertDialog(getString(R.string.login_dialog_title_security_warning), + getString(R.string.login_dialog_text_security_warning), getActivity()); + } catch (MalformedURLException e) { + mOc_root_path_View.setError(getString(R.string.error_invalid_url)); + focusView = mOc_root_path_View; + cancel = true; + //e.printStackTrace(); + } + } + } if (cancel) { // There was an error; don't attempt login and focus the first @@ -345,74 +427,87 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor editor.putString(SettingsActivity.EDT_OWNCLOUDROOTPATH_STRING, mOc_root_path); editor.putString(SettingsActivity.EDT_PASSWORD_STRING, mPassword); editor.putString(SettingsActivity.EDT_USERNAME_STRING, mUsername); + editor.putBoolean(SettingsActivity.SW_USE_SINGLE_SIGN_ON, importedAccount != null); editor.commit(); - // Re-init API - mApi.initApi(); - - //((NewsReaderApplication) getActivity().getApplication()).initDaggerAppComponent(); - //((NewsReaderApplication) getActivity().getApplication()).getAppComponent().injectFragment(this); - - - final String TAG = LoginDialogFragment.class.getCanonicalName(); - final ProgressDialog mDialogLogin = BuildPendingDialogWhileLoggingIn(); - mDialogLogin.show(); - - - mApi.getAPI().version() - .subscribeOn(Schedulers.newThread()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer<NextcloudNewsVersion>() { - boolean loginSuccessful = false; - - @Override - public void onSubscribe(@NonNull Disposable d) { - Log.v(TAG, "onSubscribe() called with: d = [" + d + "]"); - } + final ProgressDialog dialogLogin = BuildPendingDialogWhileLoggingIn(); + dialogLogin.show(); - @Override - public void onNext(@NonNull NextcloudNewsVersion version) { - Log.v(TAG, "onNext() called with: status = [" + version + "]"); - - loginSuccessful = true; - mPrefs.edit().putString(Constants.NEWS_WEB_VERSION_NUMBER_STRING, version.version).apply(); - - if(version.version.equals("0")) { - ShowAlertDialog(getString(R.string.login_dialog_title_error), getString(R.string.login_dialog_text_zero_version_code), getActivity()); - loginSuccessful = false; - } - } - - @Override - public void onError(@NonNull Throwable e) { - mDialogLogin.dismiss(); - Log.v(TAG, "onError() called with: e = [" + e + "]"); - - Throwable t = OkHttpSSLClient.HandleExceptions(e); - ShowAlertDialog(getString(R.string.login_dialog_title_error), t.getMessage(), getActivity()); - } - - @Override - public void onComplete() { - mDialogLogin.dismiss(); + if(mSwSingleSignOn.isChecked()) { + AccountImporter.SetCurrentAccount(getActivity(), importedAccount); + } - Log.v(TAG, "onComplete() called"); - if(loginSuccessful) { - //Reset Database - DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(getActivity()); - dbConn.resetDatabase(); + mApi.initApi(new NextcloudAPI.ApiConnectedListener() { + @Override + public void onConnected() { + Log.d(TAG, "onConnected() called"); + finishLogin(dialogLogin); + } - listener.LoginSucceeded(); - LoginDialogFragment.this.getDialog().cancel(); - if(mActivity instanceof AuthenticatorActivity) - mActivity.finish(); - } - } - }); + @Override + public void onError(Exception ex) { + Log.d(TAG, "onError() called with: ex = [" + ex + "]"); + ShowAlertDialog(getString(R.string.login_dialog_title_error), ex.getMessage(), getActivity()); + } + }); } } + private void finishLogin(final ProgressDialog dialogLogin) { + mApi.getAPI().version() + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer<NextcloudNewsVersion>() { + boolean loginSuccessful = false; + + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.v(TAG, "onSubscribe() called with: d = [" + d + "]"); + } + + @Override + public void onNext(@NonNull NextcloudNewsVersion version) { + Log.v(TAG, "onNext() called with: status = [" + version + "]"); + + loginSuccessful = true; + mPrefs.edit().putString(Constants.NEWS_WEB_VERSION_NUMBER_STRING, version.version).apply(); + + if(version.version.equals("0")) { + ShowAlertDialog(getString(R.string.login_dialog_title_error), getString(R.string.login_dialog_text_zero_version_code), getActivity()); + loginSuccessful = false; + } + } + + @Override + public void onError(@NonNull Throwable e) { + dialogLogin.dismiss(); + Log.v(TAG, "onError() called with: e = [" + e + "]"); + + Throwable t = OkHttpSSLClient.HandleExceptions(e); + ShowAlertDialog(getString(R.string.login_dialog_title_error), t.getMessage(), getActivity()); + } + + @Override + public void onComplete() { + dialogLogin.dismiss(); + + Log.v(TAG, "onComplete() called"); + + if(loginSuccessful) { + //Reset Database + DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(getActivity()); + dbConn.resetDatabase(); + + listener.LoginSucceeded(); + LoginDialogFragment.this.getDialog().cancel(); + if(mActivity instanceof AuthenticatorActivity) + mActivity.finish(); + } + } + }); + } + public static void ShowAlertDialog(String title, String text, Activity activity) { // Linkify the message @@ -429,4 +524,21 @@ public class LoginDialogFragment extends DialogFragment implements IAccountImpor // Make the textview clickable. Must be called after show() ((TextView)aDialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode == RESULT_OK) { + if (requestCode == CHOOSE_ACCOUNT) { + importedAccount = null; + String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); + + Account account = AccountImporter.GetAccountForName(getActivity(), accountName); + if(account != null) { + accountAccessGranted(account); + } + } + } + } } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java index 2ada5c97..3c0a38dd 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java @@ -69,8 +69,9 @@ import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; +import de.luhmer.owncloud.accountimporter.helper.NextcloudAPI; import de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter; -import de.luhmer.owncloudnewsreader.LoginDialogFragment.LoginSuccessfullListener; +import de.luhmer.owncloudnewsreader.LoginDialogFragment.LoginSuccessfulListener; import de.luhmer.owncloudnewsreader.adapter.NewsListRecyclerAdapter; import de.luhmer.owncloudnewsreader.adapter.RecyclerItemClickListener; import de.luhmer.owncloudnewsreader.adapter.ViewHolder; @@ -824,7 +825,9 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if(resultCode == RESULT_OK) { UpdateListView(); getSlidingListFragment().ListViewNotifyDataSetChanged(); @@ -833,17 +836,26 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements if(requestCode == RESULT_SETTINGS) { //Update settings of image Loader - mApi.initApi(); - - String oldLayout = data.getStringExtra(SettingsActivity.SP_FEED_LIST_LAYOUT); - String newLayout = PreferenceManager.getDefaultSharedPreferences(this).getString(SettingsActivity.SP_FEED_LIST_LAYOUT,"0"); + mApi.initApi(new NextcloudAPI.ApiConnectedListener() { + @Override + public void onConnected() { + String oldLayout = data.getStringExtra(SettingsActivity.SP_FEED_LIST_LAYOUT); + String newLayout = PreferenceManager.getDefaultSharedPreferences(NewsReaderListActivity.this).getString(SettingsActivity.SP_FEED_LIST_LAYOUT,"0"); + + if(ThemeChooser.getInstance(NewsReaderListActivity.this).themeRequiresRestartOfUI(NewsReaderListActivity.this) || !newLayout.equals(oldLayout)) { + finish(); + startActivity(getIntent()); + } else if(data.hasExtra(SettingsActivity.CACHE_CLEARED) && ownCloudSyncService != null) { + resetUiAndStartSync(); + } + } + + @Override + public void onError(Exception ex) { + ex.printStackTrace(); + } + }); - if(ThemeChooser.getInstance(this).themeRequiresRestartOfUI(this) || !newLayout.equals(oldLayout)) { - finish(); - startActivity(getIntent()); - } else if(data.hasExtra(SettingsActivity.CACHE_CLEARED) && ownCloudSyncService != null) { - resetUiAndStartSync(); - } } else if(requestCode == RESULT_ADD_NEW_FEED) { if(data != null) { boolean val = data.getBooleanExtra(NewFeedActivity.ADD_NEW_SUCCESS, false); @@ -867,7 +879,7 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements { LoginDialogFragment dialog = LoginDialogFragment.getInstance(); dialog.setActivity(activity); - dialog.setListener(new LoginSuccessfullListener() { + dialog.setListener(new LoginSuccessfulListener() { @Override public void LoginSucceeded() { ((NewsReaderListActivity) activity).resetUiAndStartSync(); diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java index 20e31b65..8e5b8ebf 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java @@ -90,6 +90,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { public static final String EDT_USERNAME_STRING = "edt_username"; public static final String EDT_PASSWORD_STRING = "edt_password"; public static final String EDT_OWNCLOUDROOTPATH_STRING = "edt_owncloudRootPath"; + public static final String SW_USE_SINGLE_SIGN_ON = "sw_use_single_sign_on"; public static final String EDT_CLEAR_CACHE = "edt_clearCache"; //public static final String CB_ALLOWALLSSLCERTIFICATES_STRING = "cb_AllowAllSSLCertificates"; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/di/ApiProvider.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/di/ApiProvider.java index a008cdc9..da24281a 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/di/ApiProvider.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/di/ApiProvider.java @@ -1,36 +1,24 @@ package de.luhmer.owncloudnewsreader.di; +import android.accounts.Account; import android.content.Context; import android.content.SharedPreferences; -import android.graphics.BitmapFactory; -import android.util.Base64; +import android.support.annotation.NonNull; import android.util.Log; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; -import java.lang.reflect.Type; -import java.util.List; - +import de.luhmer.owncloud.accountimporter.helper.AccountImporter; +import de.luhmer.owncloud.accountimporter.helper.NextcloudAPI; +import de.luhmer.owncloud.accountimporter.helper.SingleSignOnAccount; import de.luhmer.owncloudnewsreader.SettingsActivity; -import de.luhmer.owncloudnewsreader.database.model.Feed; -import de.luhmer.owncloudnewsreader.database.model.Folder; -import de.luhmer.owncloudnewsreader.database.model.RssItem; -import de.luhmer.owncloudnewsreader.model.UserInfo; +import de.luhmer.owncloudnewsreader.helper.GsonConfig; import de.luhmer.owncloudnewsreader.reader.OkHttpImageDownloader; import de.luhmer.owncloudnewsreader.reader.nextcloud.API; -import de.luhmer.owncloudnewsreader.reader.nextcloud.NextcloudDeserializer; -import de.luhmer.owncloudnewsreader.reader.nextcloud.Types; +import de.luhmer.owncloudnewsreader.reader.nextcloud.API_SSO; import de.luhmer.owncloudnewsreader.ssl.MemorizingTrustManager; import de.luhmer.owncloudnewsreader.ssl.OkHttpSSLClient; import okhttp3.HttpUrl; @@ -45,74 +33,71 @@ import retrofit2.converter.gson.GsonConverterFactory; public class ApiProvider { + private static final String TAG = ApiProvider.class.getCanonicalName(); private final MemorizingTrustManager mMemorizingTrustManager; private final SharedPreferences mPrefs; private API mApi; private Context context; + public ApiProvider(MemorizingTrustManager mtm, SharedPreferences sp, Context context) { this.mMemorizingTrustManager = mtm; this.mPrefs = sp; this.context = context; - initApi(); + initApi(new NextcloudAPI.ApiConnectedListener() { + @Override + public void onConnected() { + + } + + @Override + public void onError(Exception ex) { + + } + }); } - public void initApi() { + public void initApi(@NonNull NextcloudAPI.ApiConnectedListener apiConnectedListener) { String username = mPrefs.getString(SettingsActivity.EDT_USERNAME_STRING, ""); String password = mPrefs.getString(SettingsActivity.EDT_PASSWORD_STRING, ""); String baseUrlStr = mPrefs.getString(SettingsActivity.EDT_OWNCLOUDROOTPATH_STRING, "https://luhmer.de"); // We need to provide some sort of default URL here.. + Boolean useSSO = mPrefs.getBoolean(SettingsActivity.SW_USE_SINGLE_SIGN_ON, false); HttpUrl baseUrl = HttpUrl.parse(baseUrlStr).newBuilder() .addPathSegments("index.php/apps/news/api/v1-2/") .build(); - Log.d("ApiModule", "HttpUrl: " + baseUrl.toString()); + OkHttpClient client = OkHttpSSLClient.GetSslClient(baseUrl, username, password, mPrefs, mMemorizingTrustManager); + initImageLoader(mPrefs, client, context); - Type feedList = new TypeToken<List<Feed>>() {}.getType(); - Type folderList = new TypeToken<List<Folder>>() {}.getType(); - Type rssItemsList = new TypeToken<List<RssItem>>() {}.getType(); - - // Info: RssItems are handled as a stream (to be more memory efficient - see @OwnCloudSyncService and @RssItemObservable) - Gson gson = new GsonBuilder() - .setLenient() - .registerTypeAdapter(folderList, new NextcloudDeserializer<>(Types.FOLDERS.toString(), Folder.class)) - .registerTypeAdapter(feedList, new NextcloudDeserializer<>(Types.FEEDS.toString(), Feed.class)) - .registerTypeAdapter(rssItemsList, new NextcloudDeserializer<>(Types.ITEMS.toString(), RssItem.class)) - .registerTypeAdapter(UserInfo.class, new JsonDeserializer<UserInfo>() { - @Override - public UserInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - try { - JsonObject jObj = json.getAsJsonObject(); - JsonElement avatar = jObj.get("avatar"); - byte[] decodedString = {}; - if (!avatar.isJsonNull()) { - decodedString = Base64.decode(avatar.getAsJsonObject().get("data").getAsString(), Base64.DEFAULT); - } - return new UserInfo.Builder() - .setDisplayName(jObj.get("displayName").getAsString()) - .setUserId(jObj.get("userId").getAsString()) - .setAvatar(BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length)) - .setLastLoginTimestamp(jObj.get("lastLoginTimestamp").getAsLong()) - .build(); - } catch(IllegalStateException ex) { - throw OkHttpSSLClient.HandleExceptions(ex); - } - } - }) - .create(); + if(useSSO) { + Account account = AccountImporter.GetCurrentAccount(context); + initSsoApi(account, apiConnectedListener); + } else { + initRetrofitApi(baseUrl, client); + apiConnectedListener.onConnected(); + } + } - OkHttpClient client = OkHttpSSLClient.GetSslClient(baseUrl, username, password, mPrefs, mMemorizingTrustManager); + private void initRetrofitApi(HttpUrl baseUrl, OkHttpClient client) { Retrofit retrofit = new Retrofit.Builder() - .addConverterFactory(GsonConverterFactory.create(gson)) + .addConverterFactory(GsonConverterFactory.create(GsonConfig.GetGson())) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(baseUrl) .client(client) .build(); - initImageLoader(mPrefs, client, context); - mApi = retrofit.create(API.class); } + private void initSsoApi(final Account account, final NextcloudAPI.ApiConnectedListener callback) { + SingleSignOnAccount ssoAccount = AccountImporter.GetAuthTokenInSeparateThread(context, account); + NextcloudAPI nextcloudAPI = new NextcloudAPI(ssoAccount, GsonConfig.GetGson()); + nextcloudAPI.start(context, callback); + mApi = new API_SSO(nextcloudAPI); + } + + + private void initImageLoader(SharedPreferences mPrefs, OkHttpClient okHttpClient, Context context) { int diskCacheSize = Integer.parseInt(mPrefs.getString(SettingsActivity.SP_MAX_CACHE_SIZE,"500"))*1024*1024; if(ImageLoader.getInstance().isInited()) { diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/GsonConfig.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/GsonConfig.java new file mode 100644 index 00000000..12e533bd --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/GsonConfig.java @@ -0,0 +1,67 @@ +package de.luhmer.owncloudnewsreader.helper; + +import android.graphics.BitmapFactory; +import android.util.Base64; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.List; + +import de.luhmer.owncloudnewsreader.database.model.Feed; +import de.luhmer.owncloudnewsreader.database.model.Folder; +import de.luhmer.owncloudnewsreader.database.model.RssItem; +import de.luhmer.owncloudnewsreader.model.UserInfo; +import de.luhmer.owncloudnewsreader.reader.nextcloud.NextcloudDeserializer; +import de.luhmer.owncloudnewsreader.reader.nextcloud.Types; +import de.luhmer.owncloudnewsreader.ssl.OkHttpSSLClient; + +/** + * Created by david on 27.06.17. + */ + +public class GsonConfig { + + public static Gson GetGson() { + Type feedList = new TypeToken<List<Feed>>() {}.getType(); + Type folderList = new TypeToken<List<Folder>>() {}.getType(); + Type rssItemsList = new TypeToken<List<RssItem>>() {}.getType(); + + // Info: RssItems are handled as a stream (to be more memory efficient - see @OwnCloudSyncService and @RssItemObservable) + return new GsonBuilder() + .setLenient() + .registerTypeAdapter(folderList, new NextcloudDeserializer<>(Types.FOLDERS.toString(), Folder.class)) + .registerTypeAdapter(feedList, new NextcloudDeserializer<>(Types.FEEDS.toString(), Feed.class)) + .registerTypeAdapter(rssItemsList, new NextcloudDeserializer<>(Types.ITEMS.toString(), RssItem.class)) + .registerTypeAdapter(UserInfo.class, new JsonDeserializer<UserInfo>() { + @Override + public UserInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + JsonObject jObj = json.getAsJsonObject(); + JsonElement avatar = jObj.get("avatar"); + byte[] decodedString = {}; + if (!avatar.isJsonNull()) { + decodedString = Base64.decode(avatar.getAsJsonObject().get("data").getAsString(), Base64.DEFAULT); + } + return new UserInfo.Builder() + .setDisplayName(jObj.get("displayName").getAsString()) + .setUserId(jObj.get("userId").getAsString()) + .setAvatar(BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length)) + .setLastLoginTimestamp(jObj.get("lastLoginTimestamp").getAsLong()) + .build(); + } catch(IllegalStateException ex) { + throw OkHttpSSLClient.HandleExceptions(ex); + } + } + }) + .create(); + } + +}
\ No newline at end of file diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/API_SSO.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/API_SSO.java new file mode 100644 index 00000000..373fa275 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/API_SSO.java @@ -0,0 +1,216 @@ +package de.luhmer.owncloudnewsreader.reader.nextcloud; + +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.luhmer.owncloud.accountimporter.helper.NextcloudAPI; +import de.luhmer.owncloud.accountimporter.helper.NextcloudRequest; +import de.luhmer.owncloudnewsreader.database.model.Feed; +import de.luhmer.owncloudnewsreader.database.model.Folder; +import de.luhmer.owncloudnewsreader.database.model.RssItem; +import de.luhmer.owncloudnewsreader.helper.GsonConfig; +import de.luhmer.owncloudnewsreader.model.NextcloudNewsVersion; +import de.luhmer.owncloudnewsreader.model.NextcloudStatus; +import de.luhmer.owncloudnewsreader.model.UserInfo; +import io.reactivex.Completable; +import io.reactivex.Observable; +import okhttp3.ResponseBody; +import retrofit2.Call; + + +public class API_SSO implements API { + + private static final String mApiEndpoint = "/index.php/apps/news/api/v1-2/"; + private NextcloudAPI nextcloudAPI; + + public API_SSO(NextcloudAPI nextcloudAPI) { + this.nextcloudAPI = nextcloudAPI; + } + + public NextcloudAPI getNextcloudAPI() { + return nextcloudAPI; + } + + + + + @Override + public Observable<UserInfo> user() { + final Type type = UserInfo.class; + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("GET") + .setUrl(mApiEndpoint + "user") + .build(); + return nextcloudAPI.performRequestObservable(type, request); + } + + @Override + public Observable<NextcloudStatus> status() { + Type type = NextcloudStatus.class; + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("GET") + .setUrl(mApiEndpoint + "status") + .build(); + return nextcloudAPI.performRequestObservable(type, request); + } + + @Override + public Observable<NextcloudNewsVersion> version() { + Type type = NextcloudNewsVersion.class; + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("GET") + .setUrl(mApiEndpoint + "version") + .build(); + return nextcloudAPI.performRequestObservable(type, request); + } + + @Override + public Observable<List<Folder>> folders() { + Type type = new TypeToken<List<Folder>>() {}.getType(); + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("GET") + .setUrl(mApiEndpoint + "folders") + .build(); + return nextcloudAPI.performRequestObservable(type, request); + } + + @Override + public Observable<List<Feed>> feeds() { + Type type = new TypeToken<List<Feed>>() {}.getType(); + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("GET") + .setUrl(mApiEndpoint + "feeds") + .build(); + + return nextcloudAPI.performRequestObservable(type, request); + } + + @Override + public Call<List<Folder>> createFolder(Map<String, Object> folderMap) { + String body = GsonConfig.GetGson().toJson(folderMap); + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("POST") + .setUrl(mApiEndpoint + "folders") + .setRequestBody(body) + .build(); + return API_SSO_Helper.WrapInCall(nextcloudAPI, request, Folder.class); + } + + + @Override + public Call<List<Feed>> createFeed(Map<String, Object> feedMap) { + Type feedListType = new TypeToken<List<Feed>>() {}.getType(); + String body = GsonConfig.GetGson().toJson(feedMap); + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("POST") + .setUrl(mApiEndpoint + "feeds") + .setRequestBody(body) + .build(); + return API_SSO_Helper.WrapInCall(nextcloudAPI, request, feedListType); + } + + @Override + public Completable renameFeed(long feedId, Map<String, String> feedTitleMap) { + String body = GsonConfig.GetGson().toJson(feedTitleMap); + final NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("PUT") + .setUrl(mApiEndpoint + "feeds/" + feedId + "/rename") + .setRequestBody(body) + .build(); + return API_SSO_Helper.WrapInCompletable(nextcloudAPI, request); + } + + @Override + public Completable deleteFeed(long feedId) { + final NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("DELETE") + .setUrl(mApiEndpoint + "feeds/" + feedId) + .build(); + return API_SSO_Helper.WrapInCompletable(nextcloudAPI, request); + } + + + @Override + public Call<List<RssItem>> items(long batchSize, long offset, int type, long id, boolean getRead, boolean oldestFirst) { + HashMap<String, String> parameters = new HashMap<>(); + parameters.put("batchSize", String.valueOf(batchSize)); + parameters.put("offset", String.valueOf(offset)); + parameters.put("type", String.valueOf(type)); + parameters.put("id", String.valueOf(id)); + parameters.put("getRead", String.valueOf(getRead)); + parameters.put("oldestFirst", String.valueOf(oldestFirst)); + + Type resType = new TypeToken<List<RssItem>>() {}.getType(); + NextcloudRequest request = new NextcloudRequest.Builder() + .setParameter(parameters) + .setMethod("GET") + .setUrl(mApiEndpoint + "items") + .build(); + + return API_SSO_Helper.WrapInCall(nextcloudAPI, request, resType); + } + + @Override + public Observable<ResponseBody> updatedItems(long lastModified, int type, long id) { + HashMap<String, String> parameters = new HashMap<>(); + parameters.put("lastModified", String.valueOf(lastModified)); + parameters.put("type", String.valueOf(type)); + parameters.put("id", String.valueOf(id)); + + final NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("GET") + .setUrl(mApiEndpoint + "items/updated") + .setParameter(parameters) + .build(); + return Observable.just(API_SSO_Helper.getResponseBodyFromRequest(nextcloudAPI, request)); + } + + // https://github.com/owncloud/news/wiki/Items-1.2#mark-multiple-items-as-read + @Override + public Call<Void> markItemsRead(ItemIds items) { + String body = GsonConfig.GetGson().toJson(items); + return markItems("items/read/multiple", body); + } + + // https://github.com/owncloud/news/wiki/Items-1.2#mark-multiple-items-as-read + @Override + public Call<Void> markItemsUnread(ItemIds items) { + String body = GsonConfig.GetGson().toJson(items); + return markItems("items/unread/multiple", body); + } + + // https://github.com/owncloud/news/wiki/Items-1.2#mark-multiple-items-as-read + @Override + public Call<Void> markItemsStarred(ItemMap itemMap) { + String body = GsonConfig.GetGson().toJson(itemMap); + return markItems("items/star/multiple", body); + } + + // https://github.com/owncloud/news/wiki/Items-1.2#mark-multiple-items-as-read + @Override + public Call<Void> markItemsUnstarred(ItemMap itemMap) { + String body = GsonConfig.GetGson().toJson(itemMap); + return markItems("items/unstar/multiple", body); + } + + + private Call<Void> markItems(String endpoint, String body) { + NextcloudRequest request = new NextcloudRequest.Builder() + .setMethod("PUT") + .setUrl(mApiEndpoint + endpoint) + .setRequestBody(body) + .build(); + try { + nextcloudAPI.performRequest(Void.class, request); + return API_SSO_Helper.WrapVoidCall(true); + } catch (Exception e) { + e.printStackTrace(); + } + return API_SSO_Helper.WrapVoidCall(false); + } +} + diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/API_SSO_Helper.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/API_SSO_Helper.java new file mode 100644 index 00000000..db3a2fbf --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/API_SSO_Helper.java @@ -0,0 +1,138 @@ +package de.luhmer.owncloudnewsreader.reader.nextcloud; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; + +import de.luhmer.owncloud.accountimporter.helper.NextcloudAPI; +import de.luhmer.owncloud.accountimporter.helper.NextcloudRequest; +import io.reactivex.Completable; +import io.reactivex.functions.Action; +import okhttp3.Request; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class API_SSO_Helper { + + public static ResponseBody getResponseBodyFromRequest(NextcloudAPI nextcloudAPI, NextcloudRequest request) { + try { + InputStream os = nextcloudAPI.performNetworkRequest(request); + return ResponseBody.create(null, 0, new BufferedSourceSSO(os)); + } catch (Exception e) { + e.printStackTrace(); + } + return ResponseBody.create(null, ""); + } + + public static Completable WrapInCompletable(final NextcloudAPI nextcloudAPI, final NextcloudRequest request) { + return Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + nextcloudAPI.performRequest(Void.class, request); + } + }); + } + + public static <T> Call<T> WrapInCall(final NextcloudAPI nextcloudAPI, final NextcloudRequest nextcloudRequest, final Type resType) { + return new Call<T>() { + @Override + public Response<T> execute() throws IOException { + try { + T body = nextcloudAPI.performRequest(resType, nextcloudRequest); + return Response.success(body); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void enqueue(Callback<T> callback) { + try { + T body = nextcloudAPI.performRequest(resType, nextcloudRequest); + callback.onResponse(null, Response.success(body)); + } catch (Exception e) { + callback.onResponse(null, Response.<T>error(520, ResponseBody.create(null, e.toString()))); + } + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public void cancel() { + + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public Call<T> clone() { + return null; + } + + @Override + public Request request() { + return null; + } + }; + } + + public static Call<Void> WrapVoidCall(final boolean success) { + return new Call<Void>() { + @Override + public Response<Void> execute() { + if(success) { + return Response.success(null); + } else { + return Response.error(520, null); + } + } + + @Override + public void enqueue(Callback callback) { + if(success) { + callback.onResponse(null, Response.success(null)); + } else { + callback.onResponse(null, Response.error(520, null)); + } + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public void cancel() { + + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public Call<Void> clone() { + return null; + } + + @Override + public Request request() { + return null; + } + }; + + } +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/BufferedSourceSSO.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/BufferedSourceSSO.java new file mode 100644 index 00000000..4ab782bf --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/BufferedSourceSSO.java @@ -0,0 +1,249 @@ +package de.luhmer.owncloudnewsreader.reader.nextcloud; + +import android.support.annotation.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import okio.Buffer; +import okio.BufferedSource; +import okio.ByteString; +import okio.Options; +import okio.Sink; +import okio.Timeout; + +public class BufferedSourceSSO implements BufferedSource { + + private InputStream mInputStream; + + public BufferedSourceSSO(InputStream inputStream) { + this.mInputStream = inputStream; + } + + @Override + public Buffer buffer() { + return null; + } + + @Override + public boolean exhausted() throws IOException { + return false; + } + + @Override + public void require(long byteCount) throws IOException { + + } + + @Override + public boolean request(long byteCount) throws IOException { + return false; + } + + @Override + public byte readByte() throws IOException { + return 0; + } + + @Override + public short readShort() throws IOException { + return 0; + } + + @Override + public short readShortLe() throws IOException { + return 0; + } + + @Override + public int readInt() throws IOException { + return 0; + } + + @Override + public int readIntLe() throws IOException { + return 0; + } + + @Override + public long readLong() throws IOException { + return 0; + } + + @Override + public long readLongLe() throws IOException { + return 0; + } + + @Override + public long readDecimalLong() throws IOException { + return 0; + } + + @Override + public long readHexadecimalUnsignedLong() throws IOException { + return 0; + } + + @Override + public void skip(long byteCount) throws IOException { + + } + + @Override + public ByteString readByteString() throws IOException { + return null; + } + + @Override + public ByteString readByteString(long byteCount) throws IOException { + return null; + } + + @Override + public int select(Options options) throws IOException { + return 0; + } + + @Override + public byte[] readByteArray() throws IOException { + return new byte[0]; + } + + @Override + public byte[] readByteArray(long byteCount) throws IOException { + return new byte[0]; + } + + @Override + public int read(byte[] sink) throws IOException { + return 0; + } + + @Override + public void readFully(byte[] sink) throws IOException { + + } + + @Override + public int read(byte[] sink, int offset, int byteCount) throws IOException { + return 0; + } + + @Override + public void readFully(Buffer sink, long byteCount) throws IOException { + + } + + @Override + public long readAll(Sink sink) throws IOException { + return 0; + } + + @Override + public String readUtf8() throws IOException { + return null; + } + + @Override + public String readUtf8(long byteCount) throws IOException { + return null; + } + + @Nullable + @Override + public String readUtf8Line() throws IOException { + return null; + } + + @Override + public String readUtf8LineStrict() throws IOException { + return null; + } + + @Override + public String readUtf8LineStrict(long limit) throws IOException { + return null; + } + + @Override + public int readUtf8CodePoint() throws IOException { + return 0; + } + + @Override + public String readString(Charset charset) throws IOException { + return null; + } + + @Override + public String readString(long byteCount, Charset charset) throws IOException { + return null; + } + + @Override + public long indexOf(byte b) throws IOException { + return 0; + } + + @Override + public long indexOf(byte b, long fromIndex) throws IOException { + return 0; + } + + @Override + public long indexOf(byte b, long fromIndex, long toIndex) throws IOException { + return 0; + } + + @Override + public long indexOf(ByteString bytes) throws IOException { + return 0; + } + + @Override + public long indexOf(ByteString bytes, long fromIndex) throws IOException { + return 0; + } + + @Override + public long indexOfElement(ByteString targetBytes) throws IOException { + return 0; + } + + @Override + public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException { + return 0; + } + + @Override + public boolean rangeEquals(long offset, ByteString bytes) throws IOException { + return false; + } + + @Override + public boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCount) throws IOException { + return false; + } + + @Override + public InputStream inputStream() { + return mInputStream; + } + + @Override + public long read(Buffer sink, long byteCount) throws IOException { + return 0; + } + + @Override + public Timeout timeout() { + return null; + } + + @Override + public void close() throws IOException { + + } +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/RssItemObservable.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/RssItemObservable.java index 67602f8e..259ac15f 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/RssItemObservable.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/nextcloud/RssItemObservable.java @@ -51,7 +51,6 @@ public class RssItemObservable implements Publisher<Integer> { @Override public void subscribe(Subscriber<? super Integer> s) { try { - //throw new RuntimeException(""); sync(s); s.onComplete(); } catch (Exception ex) { diff --git a/News-Android-App/src/main/res/layout/dialog_signin.xml b/News-Android-App/src/main/res/layout/dialog_signin.xml index f061a621..e3d66280 100644 --- a/News-Android-App/src/main/res/layout/dialog_signin.xml +++ b/News-Android-App/src/main/res/layout/dialog_signin.xml @@ -1,11 +1,10 @@ <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - tools:context=".LoginActivity" android:id="@+id/login_form" android:layout_width="match_parent" android:layout_height="match_parent"> + <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -13,11 +12,20 @@ android:paddingRight="@dimen/abc_dialog_padding_material" android:paddingTop="@dimen/abc_dialog_padding_material"> + <Switch + android:id="@+id/swSingleSignOn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:text="Use single sign on" /> + + <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:id="@+id/username_container"> + android:id="@+id/username_container" + android:layout_marginTop="8dp" + android:layout_below="@id/swSingleSignOn"> <EditText android:id="@+id/username" @@ -34,7 +42,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/username_container" - android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:id="@+id/password_container"> @@ -56,7 +63,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/password_container" - android:layout_alignParentLeft="true" android:layout_alignParentStart="true"> <EditText @@ -84,7 +90,6 @@ android:src="@drawable/ic_action_visibility" android:layout_alignTop="@+id/password_container" android:layout_alignBottom="@+id/password_container" - android:layout_alignRight="@+id/password_container" android:layout_alignEnd="@+id/password_container" android:contentDescription="@string/content_desc_show_password"/> @@ -93,9 +98,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/pref_title_DisableHostnameVerification" - android:layout_below="@+id/url_container" - android:layout_alignRight="@+id/imgView_ShowPassword" - android:layout_alignEnd="@+id/imgView_ShowPassword" /> + android:layout_below="@+id/url_container" /> </RelativeLayout> |