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 ae83b8f2..9c71f06a 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java +++ b/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java @@ -9,13 +9,6 @@ */ package com.nextcloud.android.sso; -import static android.app.Activity.RESULT_CANCELED; -import static android.app.Activity.RESULT_OK; -import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT; -import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO; -import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION; -import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE; - import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; @@ -31,10 +24,6 @@ import android.util.Log; import android.widget.Toast; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; - import com.nextcloud.android.sso.exceptions.AccountImportCancelledException; import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; @@ -43,18 +32,26 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException; import com.nextcloud.android.sso.exceptions.SSOException; import com.nextcloud.android.sso.exceptions.UnknownErrorException; -import com.nextcloud.android.sso.model.FilesAppType; import com.nextcloud.android.sso.model.SingleSignOnAccount; import com.nextcloud.android.sso.ui.UiExceptionManager; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; import io.reactivex.annotations.NonNull; import io.reactivex.annotations.Nullable; +import static android.app.Activity.RESULT_CANCELED; +import static android.app.Activity.RESULT_OK; +import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT; +import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO; +import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION; +import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE; + public class AccountImporter { private static final String TAG = AccountImporter.class.getCanonicalName(); @@ -67,8 +64,6 @@ public class AccountImporter { private static SharedPreferences SHARED_PREFERENCES; - private static final String[] ACCOUNT_TYPES = Arrays.stream(FilesAppType.values()).map(a -> a.accountType).toArray(String[]::new); - public static boolean accountsToImportAvailable(Context context) { return findAccounts(context).size() > 0; } @@ -78,7 +73,7 @@ public static void pickNewAccount(Activity activity) throws NextcloudFilesAppNot checkAndroidAccountPermissions(activity); if (appInstalledOrNot(activity)) { - Intent intent = AccountManager.newChooseAccountIntent(null, null, ACCOUNT_TYPES, + Intent intent = AccountManager.newChooseAccountIntent(null, null, FilesAppTypeRegistry.getInstance().getAccountTypes(), true, null, AUTH_TOKEN_SSO, null, null); activity.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); } else { @@ -91,7 +86,7 @@ public static void pickNewAccount(Fragment fragment) throws NextcloudFilesAppNot checkAndroidAccountPermissions(fragment.getContext()); if (appInstalledOrNot(fragment.requireContext())) { - Intent intent = AccountManager.newChooseAccountIntent(null, null, ACCOUNT_TYPES, + Intent intent = AccountManager.newChooseAccountIntent(null, null, FilesAppTypeRegistry.getInstance().getAccountTypes(), true, null, AUTH_TOKEN_SSO, null, null); fragment.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); } else { @@ -123,7 +118,7 @@ private static void checkAndroidAccountPermissions(Context context) throws Andro private static boolean appInstalledOrNot(Context context) { boolean returnValue = false; PackageManager pm = context.getPackageManager(); - for (final var appType : FilesAppType.values()) { + for (final var appType : FilesAppTypeRegistry.getInstance().getTypes()) { try { pm.getPackageInfo(appType.packageId, PackageManager.GET_ACTIVITIES); returnValue = true; @@ -142,7 +137,7 @@ public static List findAccounts(final Context context) { List accountsAvailable = new ArrayList<>(); for (final Account account : accounts) { - for (String accountType : ACCOUNT_TYPES) { + for (String accountType : FilesAppTypeRegistry.getInstance().getAccountTypes()) { if (accountType.equals(account.type)) { accountsAvailable.add(account); } @@ -370,7 +365,7 @@ private static Intent buildRequestAuthTokenIntent(Context context, Intent intent throw new NextcloudFilesAppAccountPermissionNotGrantedException(context); } - String componentName = FilesAppType.findByAccountType(account.type).packageId; + String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(account.type).packageId; Intent authIntent = new Intent(); authIntent.setComponent(new ComponentName(componentName, diff --git a/lib/src/main/java/com/nextcloud/android/sso/FilesAppTypeRegistry.java b/lib/src/main/java/com/nextcloud/android/sso/FilesAppTypeRegistry.java new file mode 100644 index 00000000..2a1a9426 --- /dev/null +++ b/lib/src/main/java/com/nextcloud/android/sso/FilesAppTypeRegistry.java @@ -0,0 +1,78 @@ +/* + * Nextcloud Android SingleSignOn Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Tobias Kaminsky + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.android.sso; + +import com.nextcloud.android.sso.model.FilesAppType; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class FilesAppTypeRegistry { + private static final FilesAppTypeRegistry FILES_APP_TYPE_REGISTRY = new FilesAppTypeRegistry(); + private final Set types = new HashSet<>(); + + public FilesAppTypeRegistry() { + types.add(new FilesAppType("com.nextcloud.client", "nextcloud", FilesAppType.Type.PROD)); + types.add(new FilesAppType("com.nextcloud.android.qa", "nextcloud.qa", FilesAppType.Type.QA)); + types.add(new FilesAppType("com.nextcloud.android.beta", "nextcloud.beta", FilesAppType.Type.DEV)); + } + + public static FilesAppTypeRegistry getInstance() { + return FILES_APP_TYPE_REGISTRY; + } + + public synchronized void init(FilesAppType type) { + types.clear(); + + if (type.type != FilesAppType.Type.PROD) { + throw new IllegalArgumentException("If only one FilesAppType added, this must be PROD!"); + } + + types.add(type); + } + + public synchronized void init(List types) { + this.types.clear(); + + Optional prod = types.stream().filter(t -> t.type == FilesAppType.Type.PROD).findFirst(); + if (prod.isEmpty()) { + throw new IllegalArgumentException("One provided FilesAppType must be PROD!"); + } + + this.types.addAll(types); + } + + public Set getTypes() { + return types; + } + + public String[] getAccountTypes() { + return types.stream().map(a -> a.accountType).toArray(String[]::new); + } + + + /** + * @return {@link FilesAppType.Type#PROD}, {@link FilesAppType.Type#QA} + * or {@link FilesAppType.Type#DEV} depending on {@param accountType}. + * Uses {@link FilesAppType.Type#PROD} as fallback. + */ + @NonNull + public FilesAppType findByAccountType(@Nullable String accountType) { + for (final var type : types) { + if (type.accountType.equalsIgnoreCase(accountType)) { + return type; + } + } + return types.stream().filter(t -> t.type == FilesAppType.Type.PROD).findFirst().get(); + } +} diff --git a/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java b/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java index 5c9d98e0..dd2e1e74 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java +++ b/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java @@ -9,9 +9,6 @@ */ package com.nextcloud.android.sso.api; -import static com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil.pipeFrom; -import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException; - import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -23,15 +20,12 @@ import android.os.RemoteException; import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; +import com.nextcloud.android.sso.FilesAppTypeRegistry; import com.nextcloud.android.sso.aidl.IInputStreamService; import com.nextcloud.android.sso.aidl.NextcloudRequest; import com.nextcloud.android.sso.exceptions.NextcloudApiNotRespondingException; -import com.nextcloud.android.sso.model.FilesAppType; import com.nextcloud.android.sso.model.SingleSignOnAccount; import java.io.ByteArrayInputStream; @@ -44,6 +38,12 @@ import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import static com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil.pipeFrom; +import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException; + public class AidlNetworkRequest extends NetworkRequest { private static final String TAG = AidlNetworkRequest.class.getCanonicalName(); @@ -91,7 +91,7 @@ public void connect(String type) { Log.d(TAG, "[connect] Binding to AccountManagerService for type [" + type + "]"); super.connect(type); - final String componentName = FilesAppType.findByAccountType(type).packageId; + final String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(type).packageId; Log.d(TAG, "[connect] Component name is: [" + componentName + "]"); diff --git a/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java b/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java index 75fc9782..a60addc3 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java +++ b/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java @@ -12,13 +12,16 @@ import android.content.pm.PackageManager; import android.util.Log; -import androidx.annotation.NonNull; - +import com.nextcloud.android.sso.FilesAppTypeRegistry; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException; import com.nextcloud.android.sso.model.FilesAppType; import com.nextcloud.android.sso.ui.UiExceptionManager; +import java.util.Optional; + +import androidx.annotation.NonNull; + public final class VersionCheckHelper { private static final String TAG = VersionCheckHelper.class.getCanonicalName(); @@ -38,12 +41,22 @@ public static boolean verifyMinVersion(@NonNull Context context, int minVersion, // Stable Files App is not installed at all. Therefore we need to run the test on the dev app try { - final int verCode = getNextcloudFilesVersionCode(context, FilesAppType.DEV); - // The dev app follows a different versioning schema.. therefore we can't do our normal checks + Optional dev = FilesAppTypeRegistry + .getInstance() + .getTypes() + .stream() + .filter(t -> t.type == FilesAppType.Type.DEV) + .findFirst(); + if (dev.isPresent()) { + final int verCode = getNextcloudFilesVersionCode(context, dev.get()); + // The dev app follows a different versioning schema.. therefore we can't do our normal checks - // However beta users are probably always up to date so we will just ignore it for now - Log.d(TAG, "Dev files app version is: " + verCode); - return true; + // However beta users are probably always up to date so we will just ignore it for now + Log.d(TAG, "Dev files app version is: " + verCode); + return true; + } else { + UiExceptionManager.showDialogForException(context, new NextcloudFilesAppNotInstalledException(context)); + } } catch (PackageManager.NameNotFoundException ex) { Log.e(TAG, "PackageManager.NameNotFoundException (dev files app not found): " + e.getMessage()); UiExceptionManager.showDialogForException(context, new NextcloudFilesAppNotInstalledException(context)); diff --git a/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java b/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java index b22f3907..5b45867a 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java +++ b/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java @@ -8,33 +8,19 @@ package com.nextcloud.android.sso.model; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public enum FilesAppType { - - PROD("com.nextcloud.client", "nextcloud"), - QA("com.nextcloud.android.qa", "nextcloud.qa"), - DEV("com.nextcloud.android.beta", "nextcloud.beta"); +public class FilesAppType { public final String packageId; public final String accountType; + public final Type type; - FilesAppType(@NonNull String packageId, @NonNull String accountType) { + public FilesAppType(@NonNull String packageId, @NonNull String accountType, Type type) { this.packageId = packageId; this.accountType = accountType; + this.type = type; } - /** - * @return {@link #PROD}, {@link #QA} or {@link #DEV} depending on {@param accountType}. - * Uses {@link #PROD} as fallback. - */ - @NonNull - public static FilesAppType findByAccountType(@Nullable String accountType) { - for (final var appType : FilesAppType.values()) { - if (appType.accountType.equalsIgnoreCase(accountType)) { - return appType; - } - } - return PROD; + public enum Type { + PROD, QA, DEV } }