From 14de958d8687dd92e8212cf6d273eee9ba64ac9e Mon Sep 17 00:00:00 2001 From: A117870935 Date: Mon, 24 Apr 2023 11:47:04 +0530 Subject: [PATCH] Login related customization. NMC-3250 -> opening login flow in internal webview instead of external browser. NMC-1885: Splash screen customized NMC-571 -- removed usesCleartextTraffic from Manifest. NMC-3773 -- close app on access denied error comes NMC-3964 -- fix app crash on logout for Xiaomi Android 15 devices NMC-3936 & NMC-3813 -- Enabled edge-to-edge for Api level 35. NMC-4351 -- fix redirection to login screen on timeout NMC-4743 -- fix logout crash from trashbin tab --- .../com/nmc/android/ui/LauncherActivityIT.kt | 65 +-- app/src/debug/res/values/setup.xml | 6 + app/src/main/AndroidManifest.xml | 1 - .../java/com/owncloud/android/MainApp.java | 15 + .../AccountAuthenticatorActivity.java | 18 + .../authentication/AuthenticatorActivity.java | 380 ++++++------------ .../android/ui/activity/DrawerActivity.java | 3 +- .../ui/activity/FileDisplayActivity.kt | 10 +- .../drawable/ic_magentacloud_splash_logo.xml | 23 ++ .../main/res/layout/account_setup_webview.xml | 14 +- app/src/main/res/layout/activity_splash.xml | 3 +- app/src/main/res/values/dims.xml | 2 +- app/src/main/res/values/setup.xml | 6 +- app/src/release/res/values/setup.xml | 5 + 14 files changed, 216 insertions(+), 335 deletions(-) create mode 100644 app/src/debug/res/values/setup.xml create mode 100644 app/src/main/res/drawable/ic_magentacloud_splash_logo.xml create mode 100644 app/src/release/res/values/setup.xml 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/debug/res/values/setup.xml b/app/src/debug/res/values/setup.xml new file mode 100644 index 000000000000..046f7830a774 --- /dev/null +++ b/app/src/debug/res/values/setup.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7d0bf6464568..f4002bdec4ba 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -123,7 +123,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/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index 9358a3cca0b7..6a47743822ab 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -37,6 +37,7 @@ import android.os.StrictMode; import android.text.TextUtils; import android.view.WindowManager; +import android.webkit.WebView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.nextcloud.appReview.InAppReviewHelper; @@ -384,6 +385,8 @@ public void onCreate() { networkChangeReceiver = new NetworkChangeReceiver(this, connectivityService); registerNetworkChangeReceiver(); + configureWebViewForMultiProcess(); + if (!MDMConfig.INSTANCE.sendFilesSupport(this)) { disableDocumentsStorageProvider(); } @@ -399,6 +402,18 @@ public void disableDocumentsStorageProvider() { packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } + // NMC-3964 fix + // crash was happening for Xiaomi Android 15 devices + private void configureWebViewForMultiProcess(){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + String processName = getProcessName(); + if (processName != null && !processName.equals(getPackageName())) { + // this ensures each process uses a unique directory, preventing conflicts. + WebView.setDataDirectorySuffix(processName); + } + } + } + private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> { if (event == Lifecycle.Event.ON_START) { Log_OC.d(TAG, "APP IN FOREGROUND"); diff --git a/app/src/main/java/com/owncloud/android/authentication/AccountAuthenticatorActivity.java b/app/src/main/java/com/owncloud/android/authentication/AccountAuthenticatorActivity.java index bf90cf0784c7..9319ee76d9d7 100644 --- a/app/src/main/java/com/owncloud/android/authentication/AccountAuthenticatorActivity.java +++ b/app/src/main/java/com/owncloud/android/authentication/AccountAuthenticatorActivity.java @@ -8,10 +8,15 @@ import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; +import android.graphics.Color; +import android.os.Build; import android.os.Bundle; import com.nextcloud.utils.extensions.IntentExtensionsKt; +import com.nextcloud.android.common.ui.util.extensions.WindowExtensionsKt; +import androidx.activity.EdgeToEdge; +import androidx.activity.SystemBarStyle; import androidx.appcompat.app.AppCompatActivity; /* @@ -46,6 +51,14 @@ public final void setAccountAuthenticatorResult(Bundle result) { */ @Override protected void onCreate(Bundle savedInstanceState) { + // NMC-3936 and NMC-3813 fix + boolean isApiLevel35OrHigher = (Build.VERSION.SDK_INT >= 35); + + if (isApiLevel35OrHigher) { + enableEdgeToEdge(); + WindowExtensionsKt.addSystemBarPaddings(getWindow()); + } + super.onCreate(savedInstanceState); mAccountAuthenticatorResponse = @@ -58,6 +71,11 @@ protected void onCreate(Bundle savedInstanceState) { } } + private void enableEdgeToEdge() { + final var style = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT); + EdgeToEdge.enable(this, style, style); + } + /** * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present. */ 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 7a1648ee2611..daa0a928b6c6 100644 --- a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java +++ b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -24,7 +24,10 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Color; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -36,18 +39,16 @@ import android.view.inputmethod.EditorInfo; import android.webkit.CookieManager; import android.webkit.URLUtil; -import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; 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; @@ -59,12 +60,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.BuildConfig; @@ -74,6 +72,8 @@ import com.owncloud.android.databinding.AccountSetupWebviewBinding; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.lib.common.OwnCloudAccount; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudCredentials; import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; @@ -87,6 +87,9 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.status.GetCapabilitiesRemoteOperation; +import com.owncloud.android.lib.resources.status.NextcloudVersion; +import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.lib.resources.status.OwnCloudVersion; import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation; import com.owncloud.android.operations.DetectAuthenticationMethodOperation.AuthenticationMethod; @@ -118,13 +121,12 @@ import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -132,16 +134,12 @@ import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; 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; @@ -179,9 +177,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"; - - 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://"; @@ -232,11 +228,11 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity @Inject PassCodeManager passCodeManager; @Inject ViewThemeUtils.Factory viewThemeUtilsFactory; @Inject ColorUtil colorUtil; - @Inject ClientFactory clientFactory; - private AuthObject authObject = null; - private String fallbackToken; private boolean onlyAdd = false; + @SuppressLint("ResourceAsColor") @ColorInt + private int primaryColor = R.color.primary; + private boolean strictMode = false; private final Gson gson = new Gson(); @@ -324,7 +320,7 @@ protected void onCreate(Bundle savedInstanceState) { if (webViewLoginMethod) { accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater()); setContentView(accountSetupWebviewBinding.getRoot()); - anonymouslyPostLoginRequest(webloginUrl); + initWebViewLogin(webloginUrl, false); } else { accountSetupBinding = AccountSetupBinding.inflate(getLayoutInflater()); setContentView(accountSetupBinding.getRoot()); @@ -340,11 +336,9 @@ protected void onCreate(Bundle savedInstanceState) { } else { showEnforcedServers(); } - + initServerPreFragment(savedInstanceState); } - - ProcessLifecycleOwner.get().getLifecycle().addObserver(lifecycleEventObserver); } private void showEnforcedServers() { @@ -399,198 +393,62 @@ private void deleteCookies() { } } - // region LoginFlow - private final ScheduledExecutorService loginFlowExecutorService = Executors.newSingleThreadScheduledExecutor(); - private boolean isLoginProcessCompleted = false; - private boolean isRedirectedToTheDefaultBrowser = false; - private String baseUrl; - - private void poolLogin() { - loginFlowExecutorService.scheduleWithFixedDelay(() -> { - if (!isLoginProcessCompleted) { - performLoginFlowV2(); - } - }, 0, 30, TimeUnit.SECONDS); - } - - /** - * 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) { - if (TextUtils.isEmpty(url)) { - DisplayUtils.showSnackMessage(this, R.string.authenticator_activity_empty_base_url); - return; - } - baseUrl = url; - - singleThreadExecutor.execute(() -> { - String response = getResponseOfAnonymouslyPostLoginRequest(); - if (TextUtils.isEmpty(response)) { - DisplayUtils.showSnackMessage(AuthenticatorActivity.this, R.string.authenticator_activity_empty_response_message); - return; - } - - String loginUrl = extractLoginUrl(response); - runOnUiThread(() -> { - initLoginInfoView(); - launchDefaultWebBrowser(loginUrl); - }); - }); - } - - private String extractLoginUrl(String response) { - try { - authObject = gson.fromJson(response, AuthObject.class); - if (authObject != null && !TextUtils.isEmpty(authObject.getLogin())) { - return authObject.getLogin(); - } else { - Log_OC.e(TAG, "AuthObject parsing failed or login empty, trying JSONObject fallback"); - } - } catch (Exception e) { - Log_OC.e(TAG, "Error parsing AuthObject: " + e.getMessage(), e); - } - - try { - String fallbackUrl = getLoginFromJsonObject(response); - if (!TextUtils.isEmpty(fallbackUrl)) { - return fallbackUrl; - } else { - Log_OC.e(TAG, "Fallback JSONObject parsing failed or login empty"); - } - } catch (Exception e) { - Log_OC.e(TAG, "Error parsing fallback JSONObject: " + e.getMessage(), e); - } - - Log_OC.e(TAG, "Both AuthObject and fallback parsing failed, returning default login URL"); - DisplayUtils.showSnackMessage(this, R.string.authenticator_activity_login_error); - return getResources().getString(R.string.webview_login_url); + private static String getWebLoginUserAgent() { + return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + + Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL + " (Android)"; } - private String getLoginFromJsonObject(String response) { - JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); - fallbackToken = jsonObject.getAsJsonObject("poll").get("token").getAsString(); - return jsonObject.get("login").getAsString(); - } + @SuppressFBWarnings("ANDROID_WEB_VIEW_JAVASCRIPT") + @SuppressLint("SetJavaScriptEnabled") + private void initWebViewLogin(String baseURL, boolean useGenericUserAgent) { + viewThemeUtils.platform.colorCircularProgressBar(accountSetupWebviewBinding.loginWebviewProgressBar, ColorRole.ON_PRIMARY_CONTAINER); + accountSetupWebviewBinding.loginWebview.setVisibility(View.GONE); + new WebViewUtil().setProxyKKPlus(accountSetupWebviewBinding.loginWebview); - private String getResponseOfAnonymouslyPostLoginRequest() { - PostMethod post = new PostMethod(baseUrl, false, new FormBody.Builder().build()); - PlainClient client = clientFactory.createPlainClient(); - post.execute(client); - return post.getResponseBodyAsString(); - } + accountSetupWebviewBinding.loginWebview.getSettings().setAllowFileAccess(false); + accountSetupWebviewBinding.loginWebview.getSettings().setJavaScriptEnabled(true); + accountSetupWebviewBinding.loginWebview.getSettings().setDomStorageEnabled(true); - private void launchDefaultWebBrowser(String url) { - if (url == null || url.isBlank()) { - DisplayUtils.showSnackMessage(this, R.string.invalid_url); - return; + if (useGenericUserAgent) { + accountSetupWebviewBinding.loginWebview.getSettings().setUserAgentString(MainApp.getUserAgent()); + } else { + accountSetupWebviewBinding.loginWebview.getSettings().setUserAgentString(getWebLoginUserAgent()); } + accountSetupWebviewBinding.loginWebview.getSettings().setSaveFormData(false); + accountSetupWebviewBinding.loginWebview.getSettings().setSavePassword(false); - try { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PackageManager packageManager = getPackageManager(); + Map headers = new HashMap<>(); + headers.put(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE); - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent); - } else { - DisplayUtils.showSnackMessage(this, R.string.authenticator_activity_no_web_browser_found); - } - } catch (Exception e) { - Log_OC.e(TAG, "Exception launchDefaultWebBrowser: " + e); - DisplayUtils.showSnackMessage(this, R.string.authenticator_activity_login_error); + String url; + if (baseURL != null && !baseURL.isEmpty()) { + url = baseURL; + } else { + url = getResources().getString(R.string.webview_login_url); } - } - - private Pair extractPollUrlAndToken() { - if (authObject != null) { - final var poll = authObject.getPoll(); - String pollUrl = poll.getEndpoint(); - String token = poll.getToken(); - - if (TextUtils.isEmpty(pollUrl)) { - Log_OC.e(TAG, "auth object poll url is empty."); - } - if (TextUtils.isEmpty(token)) { - Log_OC.e(TAG, "auth object token is empty."); - } - if (!TextUtils.isEmpty(pollUrl) && !TextUtils.isEmpty(token)) { - return new Pair<>(pollUrl, token); - } + new WebViewUtil().setProxyKKPlus(accountSetupWebviewBinding.loginWebview); + if (url.startsWith(HTTPS_PROTOCOL)) { + strictMode = true; } - return new Pair<>(baseUrl + "/poll", fallbackToken); - } - - private void performLoginFlowV2() { - final var pollUrlAndToken = extractPollUrlAndToken(); + accountSetupWebviewBinding.loginWebview.loadUrl(url, headers); - RequestBody requestBody = new FormBody.Builder() - .add("token", pollUrlAndToken.second) - .build(); - - PlainClient client = clientFactory.createPlainClient(); - PostMethod post = new PostMethod(pollUrlAndToken.first, 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 { - LoginUrlInfo loginUrlInfo = gson.fromJson(response, LoginUrlInfo.class); - if (loginUrlInfo == null) { - Log_OC.e(TAG, "cannot complete login flow loginUrl is null"); - return; - } - isLoginProcessCompleted = loginUrlInfo.isValid(status); - - if (accountSetupBinding != null) { - accountSetupBinding.hostUrlInput.setText(""); - } - - mServerInfo.mBaseUrl = AuthenticatorUrlUtils.INSTANCE.normalizeUrlSuffix(loginUrlInfo.getServer()); - webViewUser = loginUrlInfo.getLoginName(); - webViewPassword = loginUrlInfo.getAppPassword(); - } catch (Exception e) { - Log_OC.d(TAG, "Error 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); + setClient(); } - private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> { - if (event == Lifecycle.Event.ON_START && authObject != null && !TextUtils.isEmpty(authObject.getPoll().getToken())) { - Log_OC.d(TAG, "Start poolLogin"); - poolLogin(); - } - }); - // endregion - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (accountSetupWebviewBinding != null && event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) { if (accountSetupWebviewBinding.loginWebview.canGoBack()) { - accountSetupWebviewBinding.loginWebview.goBack(); + // NMC-2602 Fix + // On back press "Webpage not available" error comes + // because login urls doesn't maintain the backstack hierarchy + // to solve it we are recreating the activity with the actual login url + // if user presses back from other urls which is not first or login url + // it will recreate the activity else it will finish the activity + recreate(); } else { finish(); } @@ -602,8 +460,29 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { private void setClient() { accountSetupWebviewBinding.loginWebview.setWebViewClient(new NextcloudWebViewClient(getSupportFragmentManager()) { @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - String url = request.getUrl().toString(); + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + return super.shouldInterceptRequest(view, request); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + // NMC-3773 fix: close activity if access_denied error comes + if (url.endsWith("&error=access_denied")){ + finish(); + return true; + } + + // NMC-4351 fix: recreate activity to show login screen when timeout occurs + if (url.contains("error=interaction_required")) { + recreate(); + return true; + } + if (url.startsWith(getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/")) { parseAndLoginFromWebView(url); return true; @@ -615,12 +494,24 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); + //scroll to top when url loads + //because directly loading Telekom login page it scrolls down automatically + view.scrollTo(0,0); + accountSetupWebviewBinding.loginWebviewProgressBar.setVisibility(View.GONE); accountSetupWebviewBinding.loginWebview.setVisibility(View.VISIBLE); + + if (mServerInfo.mVersion != null && mServerInfo.mVersion.isOlderThan(NextcloudVersion.nextcloud_25)) { + viewThemeUtils.platform.colorStatusBar(AuthenticatorActivity.this, primaryColor); + getWindow().setNavigationBarColor(primaryColor); + } else { + viewThemeUtils.platform.resetStatusBar(AuthenticatorActivity.this); + getWindow().setNavigationBarColor(ContextCompat.getColor(AuthenticatorActivity.this, R.color.bg_default)); + } } @Override - public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { accountSetupWebviewBinding.loginWebviewProgressBar.setVisibility(View.GONE); accountSetupWebviewBinding.loginWebview.setVisibility(View.VISIBLE); @@ -872,36 +763,10 @@ protected void onNewIntent(Intent intent) { if (intent.getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) { accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater()); setContentView(accountSetupWebviewBinding.getRoot()); - initSimpleSignupLogin(); + initWebViewLogin(getString(R.string.provider_registration_server), true); } } - @SuppressFBWarnings("ANDROID_WEB_VIEW_JAVASCRIPT") - @SuppressLint("SetJavaScriptEnabled") - private void initSimpleSignupLogin() { - viewThemeUtils.platform.colorCircularProgressBar(accountSetupWebviewBinding.loginWebviewProgressBar, ColorRole.ON_PRIMARY_CONTAINER); - accountSetupWebviewBinding.loginWebview.setVisibility(View.GONE); - new WebViewUtil().setProxyKKPlus(accountSetupWebviewBinding.loginWebview); - - accountSetupWebviewBinding.loginWebview.getSettings().setAllowFileAccess(false); - accountSetupWebviewBinding.loginWebview.getSettings().setJavaScriptEnabled(true); - accountSetupWebviewBinding.loginWebview.getSettings().setDomStorageEnabled(true); - - accountSetupWebviewBinding.loginWebview.getSettings().setUserAgentString(MainApp.getUserAgent()); - accountSetupWebviewBinding.loginWebview.getSettings().setSaveFormData(false); - accountSetupWebviewBinding.loginWebview.getSettings().setSavePassword(false); - - Map headers = new HashMap<>(); - headers.put(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE); - - new WebViewUtil().setProxyKKPlus(accountSetupWebviewBinding.loginWebview); - - accountSetupWebviewBinding.loginWebview.loadUrl(getString(R.string.provider_registration_server), headers); - accountSetupWebviewBinding.loginFlowV2.loginFlowInfoV2.setVisibility(View.GONE); - - setClient(); - } - private boolean checkIfViaSSO(Intent intent) { Bundle extras = intent.getExtras(); if (extras == null) { @@ -1115,17 +980,27 @@ private void onGetServerInfoFinish(RemoteOperationResult result) { webViewPassword != null && !webViewPassword.isEmpty()) { checkBasicAuthorization(webViewUser, webViewPassword); } else { + new Thread(() -> { + OwnCloudClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mServerInfo.mBaseUrl), + this, + true); + RemoteOperationResult remoteOperationResult = new GetCapabilitiesRemoteOperation().execute(client); + + if (remoteOperationResult.isSuccess() && + remoteOperationResult.getData() != null && + remoteOperationResult.getData().size() > 0) { + OCCapability capability = (OCCapability) remoteOperationResult.getData().get(0); + try { + primaryColor = Color.parseColor(capability.getServerColor()); + } catch (Exception e) { + // falls back to primary color + } + } + }).start(); + 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); } } else { updateServerStatusIconAndText(result); @@ -1138,31 +1013,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); - - // add margin bottom to prevent overlapping with system bars - ViewCompat.setOnApplyWindowInsetsListener(loginFlowLayout, (view, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - view.setPadding( - view.getPaddingLeft(), - view.getPaddingTop(), - view.getPaddingRight(), - systemBars.bottom); - return insets; - }); - - 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. * @@ -1374,6 +1224,10 @@ public void onAuthenticatorTaskCallback(RemoteOperationResult result) accountManager.setCurrentOwnCloudAccount(mAccount.name); getUserCapabilitiesAndFinish(); } else { + // init webView again + if (accountSetupWebviewBinding != null) { + accountSetupWebviewBinding.loginWebview.setVisibility(View.GONE); + } accountSetupBinding = AccountSetupBinding.inflate(getLayoutInflater()); setContentView(accountSetupBinding.getRoot()); initOverallUi(); @@ -1400,7 +1254,10 @@ 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); + DisplayUtils.showSnackMessage(this, + accountSetupWebviewBinding.loginWebview, R.string.auth_access_failed, + result.getLogMessage(this)); } else { DisplayUtils.showSnackMessage(this, R.string.auth_access_failed, result.getLogMessage(this)); @@ -1746,7 +1603,6 @@ public void onServiceDisconnected(ComponentName component) { } } - /** * Called from SslValidatorDialog when a new server certificate was correctly saved. */ diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 29aa804614f2..9a867ea30489 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -1305,7 +1305,8 @@ protected void onStop() { @Subscribe(threadMode = ThreadMode.MAIN) public void onAccountRemovedEvent(AccountRemovedEvent event) { - restart(); + //NMC customization + finish(); } /** diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 87ef694000b3..d7a71d2d293e 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -2781,9 +2781,13 @@ class FileDisplayActivity : } } - val newLastDisplayedAccountName = optionalUser.orElse(null).accountName - preferences.lastDisplayedAccountName = newLastDisplayedAccountName - lastDisplayedAccountName = newLastDisplayedAccountName + // NMC-4743 crash fix + // added a user present check + if (optionalUser.isPresent) { + val newLastDisplayedAccountName = optionalUser.orElse(null).accountName + preferences.lastDisplayedAccountName = newLastDisplayedAccountName + lastDisplayedAccountName = newLastDisplayedAccountName + } EventBus.getDefault().post(TokenPushEvent()) checkForNewDevVersionNecessary(applicationContext) 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 177640a00c19..4063c6083445 100644 --- a/app/src/main/res/layout/account_setup_webview.xml +++ b/app/src/main/res/layout/account_setup_webview.xml @@ -7,30 +7,22 @@ ~ 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 d0e1302fd6e6..3cf413a6bf2a 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 afed2dafe23a..261a5a7e48fa 100644 --- a/app/src/main/res/values/setup.xml +++ b/app/src/main/res/values/setup.xml @@ -73,7 +73,7 @@ #7fC0E3 - true + false false @@ -142,8 +142,8 @@ - - + Magenta + CLOUD https://nominatim.openstreetmap.org/ diff --git a/app/src/release/res/values/setup.xml b/app/src/release/res/values/setup.xml new file mode 100644 index 000000000000..a4600f0ff88a --- /dev/null +++ b/app/src/release/res/values/setup.xml @@ -0,0 +1,5 @@ + + + + https://magentacloud.de/index.php/login/flow + \ No newline at end of file