diff --git a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt index 921fd1904bb8..78708498efce 100644 --- a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt +++ b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt @@ -6,72 +6,33 @@ */ package com.nmc.android.ui -import androidx.annotation.UiThread -import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.owncloud.android.AbstractIT import com.owncloud.android.R -import com.owncloud.android.utils.EspressoIdlingResource -import org.junit.After -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LauncherActivityIT : AbstractIT() { - @Before - fun registerIdlingResource() { - IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource) - } - - @After - fun unregisterIdlingResource() { - IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource) - } - - @Test - @UiThread - fun testSplashScreenWithEmptyTitlesShouldHideTitles() { - launchActivity().use { scenario -> - scenario.onActivity { _ -> - onIdleSync { - onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed())) - onView( - withId(R.id.splashScreenBold) - ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) - onView( - withId(R.id.splashScreenNormal) - ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) - } - } - } - } + @get:Rule + val activityRule = ActivityScenarioRule(LauncherActivity::class.java) @Test - @UiThread - fun testSplashScreenWithTitlesShouldShowTitles() { - launchActivity().use { scenario -> - scenario.onActivity { - onIdleSync { - onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed())) - - EspressoIdlingResource.increment() - it.setSplashTitles("Example", "Cloud") - EspressoIdlingResource.decrement() - - val onePercentArea = ViewMatchers.isDisplayingAtLeast(1) - onView(withId(R.id.splashScreenBold)).check(matches(onePercentArea)) - onView(withId(R.id.splashScreenNormal)).check(matches(onePercentArea)) - } - } - } + fun verifyUIElements() { + onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.splashScreenBold)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.splashScreenNormal)).check(matches(isCompletelyDisplayed())) + + onView(withId(R.id.splashScreenBold)).check(matches(withText("Magenta"))) + onView(withId(R.id.splashScreenNormal)).check(matches(withText("CLOUD"))) + shortSleep() } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2d3bfe327d1e..bc7acc9ef5d8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -122,7 +122,6 @@ android:supportsRtl="true" android:enableOnBackInvokedCallback="false" android:theme="@style/Theme.ownCloud.Toolbar" - android:usesCleartextTraffic="true" tools:ignore="UnusedAttribute" tools:replace="android:allowBackup"> diff --git a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java index 9e0be399fc9f..b174e3739579 100644 --- a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java +++ b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -45,17 +45,13 @@ import android.webkit.WebView; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.LinearLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; import com.blikoon.qrcodescanner.QrCodeActivity; -import com.google.android.material.button.MaterialButton; import com.google.android.material.snackbar.Snackbar; import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import com.nextcloud.android.common.ui.color.ColorUtil; import com.nextcloud.android.common.ui.theme.utils.ColorRole; @@ -63,12 +59,9 @@ import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.device.DeviceInfo; import com.nextcloud.client.di.Injectable; -import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.onboarding.FirstRunActivity; import com.nextcloud.client.onboarding.OnboardingService; import com.nextcloud.client.preferences.AppPreferences; -import com.nextcloud.common.PlainClient; -import com.nextcloud.operations.PostMethod; import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.MainApp; @@ -116,8 +109,6 @@ import com.owncloud.android.utils.theme.CapabilityUtils; import com.owncloud.android.utils.theme.ViewThemeUtils; -import org.json.JSONObject; - import java.io.InputStream; import java.net.URLDecoder; import java.util.ArrayList; @@ -127,8 +118,6 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -144,12 +133,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.ProcessLifecycleOwner; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import okhttp3.FormBody; -import okhttp3.RequestBody; import static com.owncloud.android.utils.PermissionUtil.PERMISSIONS_CAMERA; @@ -187,17 +171,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity private static final String KEY_USERNAME = "USERNAME"; private static final String KEY_PASSWORD = "PASSWORD"; private static final String KEY_ASYNC_TASK_IN_PROGRESS = "AUTH_IN_PROGRESS"; - - /** - * Login Flow v1 - */ - // public static final String WEB_LOGIN = "/index.php/login/flow"; - - /** - * Login Flow v2 - */ - public static final String WEB_LOGIN = "/index.php/login/v2"; - + public static final String WEB_LOGIN = "/index.php/login/flow"; public static final String PROTOCOL_SUFFIX = "://"; public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"; public static final String HTTPS_PROTOCOL = "https://"; @@ -248,9 +222,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity @Inject PassCodeManager passCodeManager; @Inject ViewThemeUtils.Factory viewThemeUtilsFactory; @Inject ColorUtil colorUtil; - @Inject ClientFactory clientFactory; - - private String token; private boolean onlyAdd = false; @SuppressLint("ResourceAsColor") @ColorInt @@ -342,8 +313,7 @@ protected void onCreate(Bundle savedInstanceState) { if (webViewLoginMethod) { accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater()); setContentView(accountSetupWebviewBinding.getRoot()); - anonymouslyPostLoginRequest(webloginUrl); - // initWebViewLogin(webloginUrl, false); + initWebViewLogin(webloginUrl, false); } else { accountSetupBinding = AccountSetupBinding.inflate(getLayoutInflater()); setContentView(accountSetupBinding.getRoot()); @@ -359,65 +329,57 @@ protected void onCreate(Bundle savedInstanceState) { } else { showEnforcedServers(); } - + initServerPreFragment(savedInstanceState); - ProcessLifecycleOwner.get().getLifecycle().addObserver(lifecycleEventObserver); // webViewUtil.checkWebViewVersion(); } } - - private void showEnforcedServers() { - showAuthStatus(); - accountSetupBinding.hostUrlFrame.setVisibility(View.GONE); - accountSetupBinding.hostUrlInputHelperText.setVisibility(View.GONE); - accountSetupBinding.scanQr.setVisibility(View.GONE); - accountSetupBinding.serversSpinner.setVisibility(View.VISIBLE); + private void showEnforcedServers() { - ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.enforced_servers_spinner); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - ArrayList servers = new ArrayList<>(); - servers.add(""); - adapter.add(getString(R.string.please_select_a_server)); - - ArrayList t = new Gson().fromJson(getString(R.string.enforce_servers), - new TypeToken>() { - } - .getType()); - - for (EnforcedServer e : t) { - adapter.add(e.getName()); - servers.add(e.getUrl()); - } - - accountSetupBinding.serversSpinner.setAdapter(adapter); - accountSetupBinding.serversSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){ + showAuthStatus(); + accountSetupBinding.hostUrlFrame.setVisibility(View.GONE); + accountSetupBinding.hostUrlInputHelperText.setVisibility(View.GONE); + accountSetupBinding.scanQr.setVisibility(View.GONE); + accountSetupBinding.serversSpinner.setVisibility(View.VISIBLE); + + ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.enforced_servers_spinner); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + ArrayList servers = new ArrayList<>(); + servers.add(""); + adapter.add(getString(R.string.please_select_a_server)); + + ArrayList t = new Gson().fromJson(getString(R.string.enforce_servers), + new TypeToken>() { + } + .getType()); + + for (EnforcedServer e : t) { + adapter.add(e.getName()); + servers.add(e.getUrl()); + } - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String url = servers.get(position); + accountSetupBinding.serversSpinner.setAdapter(adapter); + accountSetupBinding.serversSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - if (URLUtil.isValidUrl(url)) { - accountSetupBinding.hostUrlInput.setText(url); - checkOcServer(); - } - } + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String url = servers.get(position); - @Override - public void onNothingSelected(AdapterView parent) { - // do nothing + if (URLUtil.isValidUrl(url)) { + accountSetupBinding.hostUrlInput.setText(url); + checkOcServer(); } - }); - } + } - private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> { - if (event == Lifecycle.Event.ON_START && token != null) { - Log_OC.d(TAG, "Start poolLogin"); - poolLogin(); - } - }); + @Override + public void onNothingSelected(AdapterView parent) { + // do nothing + } + }); + } private void deleteCookies() { try { @@ -428,72 +390,11 @@ private void deleteCookies() { } } - private String baseUrl; - - /** - * This function facilitates the login process by anonymously posting a login request to a specified URL. - * After posting the request, it retrieves the login URL for completing the login flow. - * The login flow version used is v2. - * - * @param url The URL where the login request is to be anonymously posted. - * This URL should handle the login request and return the login URL. - * It's typically the entry point for the login process. - * Example: "..." - */ - private void anonymouslyPostLoginRequest(String url) { - baseUrl = url; - - Thread thread = new Thread(() -> { - String response = getResponseOfAnonymouslyPostLoginRequest(); - - try { - JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); - String loginUrl = getLoginUrl(jsonObject); - runOnUiThread(() -> { - initLoginInfoView(); - launchDefaultWebBrowser(loginUrl); - }); - token = jsonObject.getAsJsonObject("poll").get("token").getAsString(); - } catch (Throwable t) { - Log_OC.d(TAG, "Error caught at anonymouslyPostLoginRequest: " + t); - DisplayUtils.showSnackMessage(this, R.string.authenticator_activity_login_error); - } - }); - - thread.start(); - } - - private String getResponseOfAnonymouslyPostLoginRequest() { - PostMethod post = new PostMethod(baseUrl, false, new FormBody.Builder().build()); - PlainClient client = clientFactory.createPlainClient(); - post.execute(client); - return post.getResponseBodyAsString(); - } - - private String getLoginUrl(JsonObject response) { - String result = response.get("login").getAsString(); - if (result == null) { - result = getResources().getString(R.string.webview_login_url); - } - - return result; - } - - private void launchDefaultWebBrowser(String url) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - private static String getWebLoginUserAgent() { return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL + " (Android)"; } - /** - * @Deprecated This function is deprecated. Please use the {@link #anonymouslyPostLoginRequest(String)} method instead, which utilizes the improved login flow v2. - */ - @Deprecated @SuppressFBWarnings("ANDROID_WEB_VIEW_JAVASCRIPT") @SuppressLint("SetJavaScriptEnabled") private void initWebViewLogin(String baseURL, boolean useGenericUserAgent) { @@ -835,8 +736,7 @@ protected void onNewIntent(Intent intent) { if (intent.getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) { accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater()); setContentView(accountSetupWebviewBinding.getRoot()); - anonymouslyPostLoginRequest(getString(R.string.provider_registration_server)); - // initWebViewLogin(getString(R.string.provider_registration_server), true); + initWebViewLogin(getString(R.string.provider_registration_server), true); } } @@ -1073,16 +973,7 @@ private void onGetServerInfoFinish(RemoteOperationResult result) { accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater()); setContentView(accountSetupWebviewBinding.getRoot()); - - if (!isLoginProcessCompleted) { - if (!isRedirectedToTheDefaultBrowser) { - anonymouslyPostLoginRequest(mServerInfo.mBaseUrl + WEB_LOGIN); - isRedirectedToTheDefaultBrowser = true; - } else { - initLoginInfoView(); - } - // initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false); - } + initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false); } } else { updateServerStatusIconAndText(result); @@ -1095,20 +986,6 @@ private void onGetServerInfoFinish(RemoteOperationResult result) { } } - // region LoginInfoView - private void initLoginInfoView() { - LinearLayout loginFlowLayout = accountSetupWebviewBinding.loginFlowV2.getRoot(); - MaterialButton cancelButton = accountSetupWebviewBinding.loginFlowV2.cancelButton; - loginFlowLayout.setVisibility(View.VISIBLE); - - cancelButton.setOnClickListener(v -> { - loginFlowExecutorService.shutdown(); - ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver); - recreate(); - }); - } - // endregion - /** * Chooses the right icon and text to show to the user for the received operation result. * @@ -1350,8 +1227,7 @@ public void onAuthenticatorTaskCallback(RemoteOperationResult result) } else { // authorization fail due to client side - probably wrong credentials if (accountSetupWebviewBinding != null) { - anonymouslyPostLoginRequest(mServerInfo.mBaseUrl + WEB_LOGIN); - // initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false); + initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false); DisplayUtils.showSnackMessage(this, accountSetupWebviewBinding.loginWebview, R.string.auth_access_failed, result.getLogMessage()); @@ -1700,71 +1576,6 @@ public void onServiceDisconnected(ComponentName component) { } } - private final ScheduledExecutorService loginFlowExecutorService = Executors.newSingleThreadScheduledExecutor(); - private boolean isLoginProcessCompleted = false; - private boolean isRedirectedToTheDefaultBrowser = false; - - private void poolLogin() { - loginFlowExecutorService.scheduleWithFixedDelay(() -> { - if (!isLoginProcessCompleted) { - performLoginFlowV2(); - } - }, 0, 30, TimeUnit.SECONDS); - } - - private void performLoginFlowV2() { - String postRequestUrl = baseUrl + "/poll"; - - RequestBody requestBody = new FormBody.Builder() - .add("token", token) - .build(); - - PlainClient client = clientFactory.createPlainClient(); - PostMethod post = new PostMethod(postRequestUrl, false, requestBody); - int status = post.execute(client); - String response = post.getResponseBodyAsString(); - - Log_OC.d(TAG, "performLoginFlowV2 status: " + status); - Log_OC.d(TAG, "performLoginFlowV2 response: " + response); - - if (!response.isEmpty()) { - runOnUiThread(() -> completeLoginFlow(response, status)); - } - } - - private void completeLoginFlow(String response, int status) { - try { - JSONObject jsonObject = new JSONObject(response); - - String server = jsonObject.getString("server"); - String loginName = jsonObject.getString("loginName"); - String appPassword = jsonObject.getString("appPassword"); - - LoginUrlInfo loginUrlInfo = new LoginUrlInfo(); - loginUrlInfo.serverAddress = server; - loginUrlInfo.username = loginName; - loginUrlInfo.password = appPassword; - - isLoginProcessCompleted = (status == 200 && !server.isEmpty() && !loginName.isEmpty() && !appPassword.isEmpty()); - - if (accountSetupBinding != null) { - accountSetupBinding.hostUrlInput.setText(""); - } - mServerInfo.mBaseUrl = AuthenticatorUrlUtils.INSTANCE.normalizeUrlSuffix(loginUrlInfo.serverAddress); - webViewUser = loginUrlInfo.username; - webViewPassword = loginUrlInfo.password; - } catch (Exception e) { - Log_OC.d(TAG, "Error caught at completeLoginFlow: " + e); - mServerStatusIcon = R.drawable.ic_alert; - mServerStatusText = getString(R.string.qr_could_not_be_read); - showServerStatus(); - } - - checkOcServer(); - loginFlowExecutorService.shutdown(); - ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver); - } - /** * Called from SslValidatorDialog when a new server certificate was correctly saved. */ diff --git a/app/src/main/res/drawable/ic_magentacloud_splash_logo.xml b/app/src/main/res/drawable/ic_magentacloud_splash_logo.xml new file mode 100644 index 000000000000..dfc85e1d1b34 --- /dev/null +++ b/app/src/main/res/drawable/ic_magentacloud_splash_logo.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/account_setup_webview.xml b/app/src/main/res/layout/account_setup_webview.xml index 583aa20126ea..54c520c617a0 100644 --- a/app/src/main/res/layout/account_setup_webview.xml +++ b/app/src/main/res/layout/account_setup_webview.xml @@ -7,31 +7,21 @@ ~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only --> + android:layout_height="match_parent"> - - - - + android:indeterminate="true"/> diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml index c068a3904a41..8c7f5e91e98c 100644 --- a/app/src/main/res/layout/activity_splash.xml +++ b/app/src/main/res/layout/activity_splash.xml @@ -18,11 +18,12 @@ app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:srcCompat="@drawable/nextcloud_splash_logo" /> + app:srcCompat="@drawable/ic_magentacloud_splash_logo" /> 400dp 24dp 24dp - 180dp + 116dp 20sp 5 0 diff --git a/app/src/main/res/values/setup.xml b/app/src/main/res/values/setup.xml index e141c71a34be..261a5a7e48fa 100644 --- a/app/src/main/res/values/setup.xml +++ b/app/src/main/res/values/setup.xml @@ -142,8 +142,8 @@ - - + Magenta + CLOUD https://nominatim.openstreetmap.org/