diff --git a/README.md b/README.md index 56a414e7..ef477196 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ This library allows you to use accounts as well as the network stack provided by - [How to use this library](#how-to-use-this-library) - [1) Add this library to your project](#1-add-this-library-to-your-project) - - [2) To choose an account, include the following code in your login dialog](#2-to-choose-an-account-include-the-following-code-in-your-login-dialog) - - [3) To handle the result of the Account Chooser, include the following](#3-to-handle-the-result-of-the-account-chooser-include-the-following) + - [2) To choose an account, register an `ActivityResultContract` in your `Activity` or `Fragment`](#2-to-choose-an-account-register-an-activityresultcontract-in-your-activity-or-fragment) + - [3) Handle the result of the `ActivityResultContract`](#3-handle-the-result-of-the-activityresultcontract) - [4) How to get account information?](#4-how-to-get-account-information) - [5) How to make a network request?](#5-how-to-make-a-network-request) - [5.1) Using Retrofit](#51-using-retrofit) @@ -54,58 +54,38 @@ dependencies { } ``` -### 2) To choose an account, include the following code in your login dialog +### 2) To choose an account, register an [`ActivityResultContract`](https://developer.android.com/training/basics/intents/result) in your `Activity` or `Fragment` ```java -private void openAccountChooser() { - try { - AccountImporter.pickNewAccount(activityOrFragment); - } catch (NextcloudFilesAppNotInstalledException | AndroidGetAccountsPermissionNotGranted e) { - UiExceptionManager.showDialogForException(this, e); - } -} +private final ActivityResultLauncher mImportAccountLauncher = registerForActivityResult(new ImportSsoAccount(), this::handleResult); ``` -### 3) To handle the result of the Account Chooser, include the following +### 3) Handle the result of the [`ActivityResultContract`](https://developer.android.com/training/basics/intents/result) ```java -@Override -public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - AccountImporter.onActivityResult(requestCode, resultCode, data, this, new AccountImporter.IAccountAccessGranted() { - - @Override - public void accountAccessGranted(SingleSignOnAccount account) { - final var context = getApplicationContext(); - - // As this library supports multiple accounts we created some helper methods if you only want to use one. - // The following line stores the selected account as the "default" account which can be queried by using - // the SingleAccountHelper.getCurrentSingleSignOnAccount(context) method - SingleAccountHelper.commitCurrentAccount(context, account.name); - - // Get the "default" account - SingleSignOnAccount ssoAccount = null; - try { - ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); - } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { - UiExceptionManager.showDialogForException(context, e); - } - - final var nextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create()); +private void handleResult(SingleSignOnAccount ssoAccount) { + if (ssoAccount == null) { + // Import flow was canceled by user or an error + return; + } - // TODO … (see code in section 4 and below) - } - }); -} + // As this library supports multiple accounts we created some helper methods if you only want to use one. + // The following line stores the selected account as the "default" account which can be queried by using + // the SingleAccountHelper.getCurrentSingleSignOnAccount(context) method + SingleAccountHelper.commitCurrentAccount(context, account.name); -@Override -public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - AccountImporter.onRequestPermissionsResult(requestCode, permissions, grantResults, this); -} + // Get the "default" account + SingleSignOnAccount ssoAccount = null; + try { + ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); + } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { + UiExceptionManager.showDialogForException(context, e); + } -// Complete example: https://github.com/nextcloud/news-android/blob/890828441ba0c8a9b90afe56f3e08ed63366ece5/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/LoginDialogActivity.java#L470-L475 + final var nextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create()); + // TODO … (see code in section 4 and below) +} ``` ### 4) How to get account information? diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml index c5959129..0f6a5deb 100644 --- a/lib/src/main/AndroidManifest.xml +++ b/lib/src/main/AndroidManifest.xml @@ -18,4 +18,11 @@ + + + + diff --git a/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java b/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java index 2f1c5448..ca6b8c59 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java +++ b/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java @@ -31,6 +31,8 @@ import android.util.Log; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; @@ -49,8 +51,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; -import io.reactivex.annotations.NonNull; import io.reactivex.annotations.Nullable; public class AccountImporter { @@ -69,6 +71,16 @@ public static boolean accountsToImportAvailable(Context context) { return findAccounts(context).size() > 0; } + /** + * Prompt dialog to select existing or create a new account + * As soon as an account has been imported, we will continue in #onActivityResult() + *

+ * In real live applications, you won't import an account on each app start, but remember the imported account via SingleAccountHelper. + * + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} - this method will be private in the future + */ + @Deprecated(forRemoval = true) public static void pickNewAccount(Activity activity) throws NextcloudFilesAppNotInstalledException, AndroidGetAccountsPermissionNotGranted { checkAndroidAccountPermissions(activity); @@ -82,6 +94,16 @@ public static void pickNewAccount(Activity activity) throws NextcloudFilesAppNot } } + /** + * Prompt dialog to select existing or create a new account + * As soon as an account has been imported, we will continue in #onActivityResult() + *

+ * In real live applications, you won't import an account on each app start, but remember the imported account via SingleAccountHelper. + * + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} + */ + @Deprecated(forRemoval = true) public static void pickNewAccount(Fragment fragment) throws NextcloudFilesAppNotInstalledException, AndroidGetAccountsPermissionNotGranted { checkAndroidAccountPermissions(fragment.getContext()); @@ -95,12 +117,18 @@ public static void pickNewAccount(Fragment fragment) throws NextcloudFilesAppNot } } + /** + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} + */ + @Deprecated(forRemoval = true) public static void requestAndroidAccountPermissionsAndPickAccount(Activity activity) { ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.GET_ACCOUNTS}, REQUEST_GET_ACCOUNTS_PERMISSION); } - private static void checkAndroidAccountPermissions(Context context) throws AndroidGetAccountsPermissionNotGranted { + @RestrictTo(RestrictTo.Scope.LIBRARY) + public static void checkAndroidAccountPermissions(Context context) throws AndroidGetAccountsPermissionNotGranted { // https://developer.android.com/reference/android/accounts/AccountManager#getAccountsByType(java.lang.String) // Caller targeting API level below Build.VERSION_CODES.O that have not been granted the // Manifest.permission.GET_ACCOUNTS permission, will only see those accounts managed by @@ -131,7 +159,9 @@ private static boolean appInstalledOrNot(Context context) { return returnValue; } - // Find all currently installed nextcloud accounts on the phone + /** + * Find all currently installed nextcloud accounts on the phone + */ public static List findAccounts(final Context context) { final AccountManager accMgr = AccountManager.get(context); final Account[] accounts = accMgr.getAccounts(); @@ -147,7 +177,6 @@ public static List findAccounts(final Context context) { return accountsAvailable; } - public static Account getAccountForName(Context context, String name) { for (Account account : findAccounts(context)) { if (account.name.equals(name)) { @@ -181,6 +210,10 @@ public static SingleSignOnAccount getSingleSignOnAccount(Context context, final throw new NextcloudFilesAppAccountNotFoundException(context, accountName); } + /** + * @deprecated Visibility will be private in the future + */ + @Deprecated(forRemoval = true) public static SingleSignOnAccount extractSingleSignOnAccountFromResponse(Intent intent, Context context) { Bundle future = intent.getBundleExtra(NEXTCLOUD_SSO); @@ -205,23 +238,38 @@ public static SingleSignOnAccount extractSingleSignOnAccountFromResponse(Intent return ssoAccount; } - + /** + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} - Visibility will be private in the future. + */ + @Deprecated public interface IAccountAccessGranted { void accountAccessGranted(SingleSignOnAccount singleSignOnAccount); } + /** + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} + */ + @Deprecated public static void onActivityResult(int requestCode, int resultCode, Intent data, Activity activity, IAccountAccessGranted callback) throws AccountImportCancelledException { - onActivityResult(requestCode, resultCode, data, activity, null, callback); + onActivityResult(requestCode, resultCode, data, activity, null, callback, t -> {}); } + /** + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} + */ + @Deprecated public static void onActivityResult(int requestCode, int resultCode, Intent data, Fragment fragment, IAccountAccessGranted callback) throws AccountImportCancelledException { - onActivityResult(requestCode, resultCode, data, null, fragment, callback); + onActivityResult(requestCode, resultCode, data, null, fragment, callback, t -> {}); } - private static void onActivityResult(int requestCode, int resultCode, Intent data, Activity activity, - Fragment fragment, IAccountAccessGranted callback) + @RestrictTo(RestrictTo.Scope.LIBRARY) + public static void onActivityResult(int requestCode, int resultCode, Intent data, Activity activity, + Fragment fragment, IAccountAccessGranted successCallback, Consumer failureCallback) throws AccountImportCancelledException { Context context = (activity != null) ? activity : fragment.getContext(); @@ -236,12 +284,12 @@ private static void onActivityResult(int requestCode, int resultCode, Intent dat } } catch (NextcloudFilesAppNotSupportedException | NextcloudFilesAppAccountPermissionNotGrantedException e) { - UiExceptionManager.showDialogForException(context, e); + UiExceptionManager.showDialogForException(context, e, failureCallback::accept); } break; case REQUEST_AUTH_TOKEN_SSO: SingleSignOnAccount singleSignOnAccount = extractSingleSignOnAccountFromResponse(data, context); - callback.accountAccessGranted(singleSignOnAccount); + successCallback.accountAccessGranted(singleSignOnAccount); break; case REQUEST_GET_ACCOUNTS_PERMISSION: try { @@ -252,7 +300,7 @@ private static void onActivityResult(int requestCode, int resultCode, Intent dat } } catch (NextcloudFilesAppNotInstalledException | AndroidGetAccountsPermissionNotGranted e) { - UiExceptionManager.showDialogForException(context, e); + UiExceptionManager.showDialogForException(context, e, failureCallback::accept); } break; default: @@ -267,15 +315,16 @@ private static void onActivityResult(int requestCode, int resultCode, Intent dat try { handleFailedAuthRequest(context, data); } catch (SSOException e) { - UiExceptionManager.showDialogForException(context, e); + UiExceptionManager.showDialogForException(context, e, failureCallback::accept); } catch (Exception e) { Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show(); //e.printStackTrace(); Log.e(TAG, e.getMessage()); + failureCallback.accept(e); } break; case REQUEST_GET_ACCOUNTS_PERMISSION: - UiExceptionManager.showDialogForException(context, new AndroidGetAccountsPermissionNotGranted(context)); + UiExceptionManager.showDialogForException(context, new AndroidGetAccountsPermissionNotGranted(context), failureCallback::accept); break; default: break; @@ -283,15 +332,26 @@ private static void onActivityResult(int requestCode, int resultCode, Intent dat } } + /** + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} + */ + @Deprecated public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, Activity activity) { - onRequestPermissionsResult(requestCode, permissions, grantResults, activity, null); + onRequestPermissionsResult(requestCode, permissions, grantResults, activity, null, t -> {}); } + /** + * @see ActivityResultContract + * @deprecated Use {@link ImportSsoAccount} + */ + @Deprecated public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, Fragment fragment) { - onRequestPermissionsResult(requestCode, permissions, grantResults, null, fragment); + onRequestPermissionsResult(requestCode, permissions, grantResults, null, fragment, t -> {}); } - private static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, Activity activity, Fragment fragment) { + @RestrictTo(RestrictTo.Scope.LIBRARY) + public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, Activity activity, Fragment fragment, Consumer failureCallback) { Context context = (activity != null) ? activity : fragment.getContext(); switch (requestCode) { @@ -306,11 +366,11 @@ private static void onRequestPermissionsResult(int requestCode, @NonNull String[ } } catch (NextcloudFilesAppNotInstalledException | AndroidGetAccountsPermissionNotGranted e) { - UiExceptionManager.showDialogForException(context, e); + UiExceptionManager.showDialogForException(context, e, failureCallback::accept); } } else { // user declined the permission request.. - UiExceptionManager.showDialogForException(context, new AndroidGetAccountsPermissionNotGranted(context)); + UiExceptionManager.showDialogForException(context, new AndroidGetAccountsPermissionNotGranted(context), failureCallback::accept); } break; default: @@ -319,6 +379,10 @@ private static void onRequestPermissionsResult(int requestCode, @NonNull String[ } + /** + * @deprecated Will be private in the future + */ + @Deprecated public static void handleFailedAuthRequest(@NonNull Context context, @Nullable Intent data) throws SSOException { if (data != null) { String exception = data.getStringExtra(NEXTCLOUD_SSO_EXCEPTION); diff --git a/lib/src/main/java/com/nextcloud/android/sso/ImportSsoAccount.java b/lib/src/main/java/com/nextcloud/android/sso/ImportSsoAccount.java new file mode 100644 index 00000000..29bd3663 --- /dev/null +++ b/lib/src/main/java/com/nextcloud/android/sso/ImportSsoAccount.java @@ -0,0 +1,37 @@ +package com.nextcloud.android.sso; + +import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import androidx.activity.result.contract.ActivityResultContract; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.nextcloud.android.sso.model.SingleSignOnAccount; + +/** + * {@link ActivityResultContract} to import a Nextcloud account. + * The result account may be null if something went wrong or the user canceled the import. + * + * @see ActivityResultContract + */ +public class ImportSsoAccount extends ActivityResultContract { + + @NonNull + @Override + public Intent createIntent(@NonNull Context context, Void input) { + return new Intent(context, ImportSsoAccountActivity.class); + } + + @Override + public SingleSignOnAccount parseResult(int resultCode, @Nullable Intent intent) { + if (resultCode == Activity.RESULT_OK && intent != null) { + return (SingleSignOnAccount) intent.getSerializableExtra(NEXTCLOUD_SSO); + } + + return null; + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/nextcloud/android/sso/ImportSsoAccountActivity.java b/lib/src/main/java/com/nextcloud/android/sso/ImportSsoAccountActivity.java new file mode 100644 index 00000000..cb49d6aa --- /dev/null +++ b/lib/src/main/java/com/nextcloud/android/sso/ImportSsoAccountActivity.java @@ -0,0 +1,97 @@ +/* + * Nextcloud Android SingleSignOn Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.android.sso; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.nextcloud.android.sso.exceptions.AccountImportCancelledException; +import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; +import com.nextcloud.android.sso.exceptions.SSOException; +import com.nextcloud.android.sso.ui.UiExceptionManager; + +/// This is a Trampoline Activity that takes care about requesting all necessary permissions and guiding the user through the complete import flow. +/// It is extremely important to catch *any* error and finish this activity because otherwise, the Trampoline Activity will stay on top but transparent, so users can't see it and users can't interact with their (visible) 3rd party app anymore. +public class ImportSsoAccountActivity extends AppCompatActivity { + + private static final String TAG = ImportSsoAccountActivity.class.getSimpleName(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + + try { + try { + AccountImporter.checkAndroidAccountPermissions(this); + //noinspection removal + AccountImporter.pickNewAccount(this); + + } catch (AndroidGetAccountsPermissionNotGranted e) { + //noinspection removal + AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this); + } + + } catch (Throwable throwable) { + if (throwable instanceof SSOException ssoException) { + UiExceptionManager.showDialogForException(this, ssoException, t -> finish()); + } else { + throwable.printStackTrace(); + finish(); + } + } + + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + try { + AccountImporter.onRequestPermissionsResult(requestCode, permissions, grantResults, this, null, t -> finish()); + } catch (Throwable throwable) { + if (throwable instanceof SSOException ssoException) { + UiExceptionManager.showDialogForException(this, ssoException, t -> finish()); + } else { + throwable.printStackTrace(); + finish(); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + try { + AccountImporter.onActivityResult(requestCode, resultCode, data, this, null, ssoAccount -> { + final var result = new Intent(); + result.putExtra(Constants.NEXTCLOUD_SSO, ssoAccount); + setResult(RESULT_OK, result); + finish(); + }, t -> finish()); + + } catch (AccountImportCancelledException e) { + Log.i(TAG, "Account import cancelled."); + setResult(RESULT_CANCELED); + finish(); + } catch (Throwable t) { + if (t instanceof SSOException ssoException) { + UiExceptionManager.showDialogForException(this, ssoException, t1 -> finish()); + } else { + t.printStackTrace(); + finish(); + } + } + } +} diff --git a/lib/src/main/java/com/nextcloud/android/sso/ui/UiExceptionManager.java b/lib/src/main/java/com/nextcloud/android/sso/ui/UiExceptionManager.java index 4e31c969..e387b79b 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/ui/UiExceptionManager.java +++ b/lib/src/main/java/com/nextcloud/android/sso/ui/UiExceptionManager.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; @@ -22,6 +23,8 @@ import com.nextcloud.android.sso.R; import com.nextcloud.android.sso.exceptions.SSOException; +import java.util.function.Consumer; + public final class UiExceptionManager { private static final int NOTIFICATION_ID = 0; @@ -32,13 +35,20 @@ private UiExceptionManager() { public static void showDialogForException(@NonNull Context context, @NonNull SSOException exception) { + showDialogForException(context, exception, null); + } + + @RestrictTo(RestrictTo.Scope.LIBRARY) + public static void showDialogForException(@NonNull Context context, + @NonNull SSOException exception, + @Nullable Consumer onDismiss) { final int actionText = exception.getPrimaryActionTextRes().orElse(android.R.string.yes); final var optionalAction = exception.getPrimaryAction(); if (optionalAction.isPresent()) { - showDialogForException(context, exception, actionText, (dialog, which) -> context.startActivity(optionalAction.get())); + showDialogForException(context, exception, actionText, (dialog, which) -> context.startActivity(optionalAction.get()), onDismiss); } else { - showDialogForException(context, exception, actionText, null); + showDialogForException(context, exception, actionText, null, onDismiss); } } @@ -49,8 +59,22 @@ public static void showDialogForException(@NonNull Context context, @NonNull SSOException exception, @StringRes int actionText, @Nullable DialogInterface.OnClickListener callback) { + showDialogForException(context, exception, actionText, callback, null); + } + + @RestrictTo(RestrictTo.Scope.LIBRARY) + public static void showDialogForException(@NonNull Context context, + @NonNull SSOException exception, + @StringRes int actionText, + @Nullable DialogInterface.OnClickListener callback, + @Nullable Consumer onDismiss) { final var builder = new MaterialAlertDialogBuilder(context) - .setMessage(exception.getMessage()); + .setMessage(exception.getMessage()) + .setOnDismissListener(d -> { + if (onDismiss != null) { + onDismiss.accept(exception); + } + }); exception.getTitleRes().ifPresent(builder::setTitle); diff --git a/lib/src/main/res/values/themes.xml b/lib/src/main/res/values/themes.xml new file mode 100644 index 00000000..e57cd98f --- /dev/null +++ b/lib/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java b/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java index cb3fe8da..2667249d 100644 --- a/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java +++ b/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java @@ -7,22 +7,18 @@ */ package com.nextcloud.android.sso.sample; -import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.widget.TextView; -import androidx.annotation.Nullable; +import androidx.activity.result.ActivityResultLauncher; import androidx.appcompat.app.AppCompatActivity; import com.google.gson.GsonBuilder; -import com.nextcloud.android.sso.AccountImporter; +import com.nextcloud.android.sso.ImportSsoAccount; import com.nextcloud.android.sso.api.NextcloudAPI; -import com.nextcloud.android.sso.exceptions.AccountImportCancelledException; -import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; -import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import com.nextcloud.android.sso.ui.UiExceptionManager; +import com.nextcloud.android.sso.model.SingleSignOnAccount; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -34,23 +30,13 @@ public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private final ExecutorService executor = Executors.newSingleThreadExecutor(); + private final ActivityResultLauncher mImportAccountLauncher = registerForActivityResult(new ImportSsoAccount(), this::handleResult); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - findViewById(R.id.chooseAccountBtn).setOnClickListener(v -> { - try { - /* - * Prompt dialog to select existing or create a new account - * As soon as an account has been imported, we will continue in #onActivityResult() - * - * In real live applications, you won't import an account on each app start, but remember the imported account via SingleAccountHelper. - */ - AccountImporter.pickNewAccount(this); - } catch (NextcloudFilesAppNotInstalledException | AndroidGetAccountsPermissionNotGranted e) { - UiExceptionManager.showDialogForException(this, e); - } - }); + findViewById(R.id.chooseAccountBtn).setOnClickListener(v -> mImportAccountLauncher.launch(null)); /* * We can also observe the current SingleSignOnAccount (set via SingleAccountHelper) with LiveData @@ -64,60 +50,56 @@ protected void onCreate(Bundle savedInstanceState) { }); } - @Override - @SuppressWarnings("ConstantConditions") - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - try { - AccountImporter.onActivityResult(requestCode, resultCode, data, this, ssoAccount -> { - Log.i(TAG, "Imported account: " + ssoAccount.name); - - /* - * A little helper to store the currently selected account. - * We can query this later if we want to keep working with it. - */ - SingleAccountHelper.commitCurrentAccount(this, ssoAccount.name); - - /* Network requests need to be performed on a background thread */ - executor.submit(() -> { - runOnUiThread(() -> ((TextView) findViewById(R.id.result)).setText(R.string.loading)); - - /* Create local bridge API to the Nextcloud Files Android app */ - final var nextcloudAPI = new NextcloudAPI(this, ssoAccount, new GsonBuilder().create()); - - /* Create the Ocs API to talk to the server */ - final var ocsAPI = new NextcloudRetrofitApiBuilder(nextcloudAPI, "/ocs/v2.php/cloud/").create(OcsAPI.class); - - try { - /* Perform actual requests */ - final var user = ocsAPI.getUser(ssoAccount.userId).execute().body().ocs().data(); - final var serverInfo = ocsAPI.getServerInfo().execute().body().ocs.data; - - /* Show result on the UI thread */ - runOnUiThread(() -> ((TextView) findViewById(R.id.result)).setText( - getString(R.string.account_info, - user.displayName(), - serverInfo.capabilities.theming.name, - serverInfo.version.semanticVersion)) - ); - } catch (Exception e) { - runOnUiThread(() -> ((TextView) findViewById(R.id.result)).setText(e.getMessage())); - e.printStackTrace(); - } - - /* - * If you need to make multiple calls, keep the NextcloudAPI open as long as you - * can. This way the services will stay active and the connection between the - * files app and your app is already established when you make subsequent requests. - * Otherwise you'll have to bind to the service again and again for each request. - * - * @see https://github.com/nextcloud/Android-SingleSignOn/issues/120#issuecomment-540069990 - */ - nextcloudAPI.close(); - }); - }); - } catch (AccountImportCancelledException e) { - Log.i(TAG, "Account import cancelled."); + private void handleResult(SingleSignOnAccount ssoAccount) { + if (ssoAccount == null) { + Log.i(TAG, "No account imported"); + return; } + + Log.i(TAG, "Imported account: " + ssoAccount.name); + + /* + * A little helper to store the currently selected account. + * We can query this later if we want to keep working with it. + */ + SingleAccountHelper.commitCurrentAccount(this, ssoAccount.name); + + /* Network requests need to be performed on a background thread */ + executor.submit(() -> { + runOnUiThread(() -> ((TextView) findViewById(R.id.result)).setText(R.string.loading)); + + /* Create local bridge API to the Nextcloud Files Android app */ + final var nextcloudAPI = new NextcloudAPI(this, ssoAccount, new GsonBuilder().create()); + + /* Create the Ocs API to talk to the server */ + final var ocsAPI = new NextcloudRetrofitApiBuilder(nextcloudAPI, "/ocs/v2.php/cloud/").create(OcsAPI.class); + + try { + /* Perform actual requests */ + final var user = ocsAPI.getUser(ssoAccount.userId).execute().body().ocs().data(); + final var serverInfo = ocsAPI.getServerInfo().execute().body().ocs.data; + + /* Show result on the UI thread */ + runOnUiThread(() -> ((TextView) findViewById(R.id.result)).setText( + getString(R.string.account_info, + user.displayName(), + serverInfo.capabilities.theming.name, + serverInfo.version.semanticVersion)) + ); + } catch (Exception e) { + runOnUiThread(() -> ((TextView) findViewById(R.id.result)).setText(e.getMessage())); + e.printStackTrace(); + } + + /* + * If you need to make multiple calls, keep the NextcloudAPI open as long as you + * can. This way the services will stay active and the connection between the + * files app and your app is already established when you make subsequent requests. + * Otherwise you'll have to bind to the service again and again for each request. + * + * @see https://github.com/nextcloud/Android-SingleSignOn/issues/120#issuecomment-540069990 + */ + nextcloudAPI.close(); + }); } }