From 2476cdc52d0be85b7d97cd96866645e05745749b Mon Sep 17 00:00:00 2001 From: Arihant Jain Date: Sat, 14 Jul 2018 22:52:50 +0530 Subject: [PATCH 01/19] adds local server login for secondary user # Conflicts: # src/main/java/org/amahi/anywhere/AmahiModule.java # src/main/java/org/amahi/anywhere/activity/AuthenticationActivity.java # src/main/java/org/amahi/anywhere/fragment/NavigationFragment.java # src/main/java/org/amahi/anywhere/server/client/ServerClient.java # src/main/res/layout/layout_authentication.xml # src/main/res/values/strings.xml --- src/main/AndroidManifest.xml | 1 + .../java/org/amahi/anywhere/AmahiModule.java | 4 +- .../amahi/anywhere/account/AmahiAccount.java | 1 + .../activity/AuthenticationActivity.java | 259 +++++---------- .../amahi/anywhere/bus/PINAccessEvent.java | 23 ++ .../fragment/LocalLoginDialogFragment.java | 44 +++ .../anywhere/fragment/MainLoginFragment.java | 297 ++++++++++++++++++ .../anywhere/fragment/NavigationFragment.java | 24 +- .../anywhere/fragment/PINAccessFragment.java | 180 +++++++++++ .../anywhere/server/client/ServerClient.java | 22 +- .../anywhere/server/model/Authentication.java | 2 +- .../amahi/anywhere/server/model/Server.java | 10 + .../anywhere/task/LocalServerProbingTask.java | 135 ++++++++ .../org/amahi/anywhere/util/Fragments.java | 10 + .../org/amahi/anywhere/util/Preferences.java | 8 + .../res/layout-land/layout_authentication.xml | 8 + .../res/layout/activity_authentication.xml | 7 +- src/main/res/layout/fragment_main_login.xml | 32 ++ src/main/res/layout/fragment_pin_access.xml | 90 ++++++ src/main/res/layout/layout_authentication.xml | 205 ++++++------ src/main/res/values/strings.xml | 20 +- 21 files changed, 1089 insertions(+), 293 deletions(-) create mode 100644 src/main/java/org/amahi/anywhere/bus/PINAccessEvent.java create mode 100644 src/main/java/org/amahi/anywhere/fragment/LocalLoginDialogFragment.java create mode 100644 src/main/java/org/amahi/anywhere/fragment/MainLoginFragment.java create mode 100644 src/main/java/org/amahi/anywhere/fragment/PINAccessFragment.java create mode 100644 src/main/java/org/amahi/anywhere/task/LocalServerProbingTask.java create mode 100644 src/main/res/layout/fragment_main_login.xml create mode 100644 src/main/res/layout/fragment_pin_access.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index fd718f4bf..22aa0a779 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ android:installLocation="auto"> + diff --git a/src/main/java/org/amahi/anywhere/AmahiModule.java b/src/main/java/org/amahi/anywhere/AmahiModule.java index 7dbf6598a..1a0704b8f 100644 --- a/src/main/java/org/amahi/anywhere/AmahiModule.java +++ b/src/main/java/org/amahi/anywhere/AmahiModule.java @@ -22,7 +22,6 @@ import android.app.Application; import android.content.Context; -import org.amahi.anywhere.activity.AuthenticationActivity; import org.amahi.anywhere.activity.NativeVideoActivity; import org.amahi.anywhere.activity.NavigationActivity; import org.amahi.anywhere.activity.OfflineFilesActivity; @@ -35,6 +34,7 @@ import org.amahi.anywhere.activity.ServerFilesActivity; import org.amahi.anywhere.cache.CacheModule; import org.amahi.anywhere.fragment.AudioListFragment; +import org.amahi.anywhere.fragment.MainLoginFragment; import org.amahi.anywhere.fragment.NavigationFragment; import org.amahi.anywhere.fragment.ServerAppsFragment; import org.amahi.anywhere.fragment.ServerFileAudioFragment; @@ -74,7 +74,6 @@ CacheModule.class }, injects = { - AuthenticationActivity.class, NavigationActivity.class, ServerAppActivity.class, OfflineFilesActivity.class, @@ -85,6 +84,7 @@ NativeVideoActivity.class, RecentFilesActivity.class, ServerFileWebActivity.class, + MainLoginFragment.class, NavigationFragment.class, ServerSharesFragment.class, ServerAppsFragment.class, diff --git a/src/main/java/org/amahi/anywhere/account/AmahiAccount.java b/src/main/java/org/amahi/anywhere/account/AmahiAccount.java index cb940e13e..342423951 100644 --- a/src/main/java/org/amahi/anywhere/account/AmahiAccount.java +++ b/src/main/java/org/amahi/anywhere/account/AmahiAccount.java @@ -26,6 +26,7 @@ */ public class AmahiAccount extends Account { public static final String TYPE = "org.amahi"; + public static final String TYPE_LOCAL = "org.amahi.local"; public static final String TYPE_TOKEN = String.format("%s.FULL", TYPE); diff --git a/src/main/java/org/amahi/anywhere/activity/AuthenticationActivity.java b/src/main/java/org/amahi/anywhere/activity/AuthenticationActivity.java index 827fc2ea8..de5364e4c 100644 --- a/src/main/java/org/amahi/anywhere/activity/AuthenticationActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/AuthenticationActivity.java @@ -21,46 +21,34 @@ import android.accounts.Account; import android.accounts.AccountManager; -import android.app.Activity; +import android.app.Fragment; import android.content.Context; import android.os.Bundle; -import com.google.android.material.textfield.TextInputLayout; - import androidx.appcompat.app.AppCompatDelegate; -import android.text.Editable; -import android.text.TextWatcher; -import android.text.method.LinkMovementMethod; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.TextView; - -import com.dd.processbutton.iml.ActionProcessButton; import com.squareup.otto.Subscribe; import org.amahi.anywhere.AmahiApplication; import org.amahi.anywhere.R; import org.amahi.anywhere.account.AccountAuthenticatorAppCompatActivity; import org.amahi.anywhere.account.AmahiAccount; -import org.amahi.anywhere.bus.AuthenticationConnectionFailedEvent; -import org.amahi.anywhere.bus.AuthenticationFailedEvent; import org.amahi.anywhere.bus.AuthenticationSucceedEvent; import org.amahi.anywhere.bus.BusProvider; -import org.amahi.anywhere.server.client.AmahiClient; +import org.amahi.anywhere.bus.PINAccessEvent; +import org.amahi.anywhere.fragment.MainLoginFragment; +import org.amahi.anywhere.fragment.PINAccessFragment; +import org.amahi.anywhere.util.Fragments; import org.amahi.anywhere.util.LocaleHelper; -import org.amahi.anywhere.util.ViewDirector; - -import javax.inject.Inject; +import org.amahi.anywhere.util.Preferences; /** * Authentication activity. Allows user authentication. If operation succeed * the authentication token is saved at the {@link android.accounts.AccountManager}. */ -public class AuthenticationActivity extends AccountAuthenticatorAppCompatActivity implements TextWatcher { - @Inject - AmahiClient amahiClient; +public class AuthenticationActivity extends AccountAuthenticatorAppCompatActivity { + + private String accountType = AmahiAccount.TYPE; @Override protected void onCreate(Bundle savedInstanceState) { @@ -72,182 +60,64 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_authentication); - setUpInjections(); - - setUpAuthentication(); - } - - private void setUpInjections() { - AmahiApplication.from(this).inject(this); - } - - private void setUpAuthentication() { - setUpAuthenticationMessages(); - setUpAuthenticationListeners(); - } - - private String getUsername() { - return getUsernameEdit().getText().toString(); - } - - private EditText getUsernameEdit() { - TextInputLayout username_layout = findViewById(R.id.username_layout); - return username_layout.getEditText(); - } - - private String getPassword() { - return getPasswordEdit().getText().toString(); - } - - private EditText getPasswordEdit() { - TextInputLayout password_layout = findViewById(R.id.password_layout); - return password_layout.getEditText(); - } - - private ActionProcessButton getAuthenticationButton() { - return findViewById(R.id.button_authentication); - } - - private void setUpAuthenticationMessages() { - TextView forgotPassword = findViewById(R.id.text_forgot_password); - TextView authenticationConnectionFailureMessage = findViewById(R.id.text_message_authentication_connection); - forgotPassword.setMovementMethod(LinkMovementMethod.getInstance()); - authenticationConnectionFailureMessage.setMovementMethod(LinkMovementMethod.getInstance()); - } - - private void setUpAuthenticationListeners() { - setUpAuthenticationTextListener(); - setUpAuthenticationActionListener(); - } - - private void setUpAuthenticationTextListener() { - getUsernameEdit().addTextChangedListener(this); - getPasswordEdit().addTextChangedListener(this); - getPasswordEdit().setOnEditorActionListener((v, actionId, event) -> { - boolean handled = false; - if (actionId == EditorInfo.IME_ACTION_GO) { - onClick(getAuthenticationButton()); - handled = true; - } - return handled; - }); - } - - @Override - public void onTextChanged(CharSequence text, int after, int before, int count) { - hideAuthenticationFailureMessage(); - } - - private void hideAuthenticationFailureMessage() { - ViewDirector.of(this, R.id.animator_message).show(R.id.view_message_empty); - } - - @Override - public void afterTextChanged(Editable text) { + setUpAuthenticationFragment(); } @Override - public void beforeTextChanged(CharSequence text, int start, int count, int before) { - } - - private void setUpAuthenticationActionListener() { - getAuthenticationButton().setOnClickListener(this::onClick); + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + accountType = savedInstanceState.getString(State.ACCOUNT_TYPE, AmahiAccount.TYPE); } - public void onClick(View view) { - if (getUsername().trim().isEmpty() || getPassword().trim().isEmpty()) { - ViewDirector.of(this, R.id.animator_message).show(R.id.text_message_authentication_empty); - - if (getUsername().trim().isEmpty()) - getUsernameEdit().requestFocus(); - - if (getPassword().trim().isEmpty()) - getPasswordEdit().requestFocus(); - - if (getUsername().trim().isEmpty() && getPassword().trim().isEmpty()) - getUsernameEdit().requestFocus(); - + private void setUpAuthenticationFragment() { + if (accountType.equals(AmahiAccount.TYPE)) { + MainLoginFragment f = (MainLoginFragment) getFragmentManager().findFragmentByTag(MainLoginFragment.TAG); + if (f == null) { + showMainLoginFragment(); + } } else { - startAuthentication(); - - authenticate(); + PINAccessFragment f = (PINAccessFragment) getFragmentManager().findFragmentByTag(PINAccessFragment.TAG); + if (f == null) { + showPINAccessFragment(); + } } } - private void startAuthentication() { - hideAuthenticationText(); - - showProgress(); - - hideAuthenticationFailureMessage(); - } - - private void hideAuthenticationText() { - getUsernameEdit().setEnabled(false); - getPasswordEdit().setEnabled(false); - } - - private void showProgress() { - ActionProcessButton authenticationButton = getAuthenticationButton(); - - authenticationButton.setMode(ActionProcessButton.Mode.ENDLESS); - authenticationButton.setProgress(1); - } - - private void authenticate() { - amahiClient.authenticate(getUsername(), getPassword()); - } - - @Subscribe - public void onAuthenticationFailed(AuthenticationFailedEvent event) { - finishAuthentication(); - - showAuthenticationFailureMessage(); - } - - private void finishAuthentication() { - showAuthenticationText(); - - hideProgress(); - } - - private void showAuthenticationText() { - getUsernameEdit().setEnabled(true); - getPasswordEdit().setEnabled(true); - } - - private void hideProgress() { - getAuthenticationButton().setProgress(0); - } - - private void showAuthenticationFailureMessage() { - ViewDirector.of(this, R.id.animator_message).show(R.id.text_message_authentication); + private void showMainLoginFragment() { + Fragment fragment = Fragments.Builder.buildMainLoginFragment(); + getFragmentManager() + .beginTransaction() + .replace(R.id.main_container, fragment, MainLoginFragment.TAG) + .commit(); } @Subscribe - public void onAuthenticationConnectionFailed(AuthenticationConnectionFailedEvent event) { - finishAuthentication(); - - showAuthenticationConnectionFailureMessage(); - } + public void onAuthenticationSucceed(AuthenticationSucceedEvent event) { + if (accountType.equals(AmahiAccount.TYPE)) { + MainLoginFragment fragment = (MainLoginFragment) getFragmentManager().findFragmentByTag(MainLoginFragment.TAG); - private void showAuthenticationConnectionFailureMessage() { - ViewDirector.of(this, R.id.animator_message).show(R.id.text_message_authentication_connection); - } + finishAuthentication(event.getAuthentication().getToken(), fragment.getUsername(), fragment.getPassword()); + } else { + PINAccessFragment fragment = (PINAccessFragment) getFragmentManager().findFragmentByTag(PINAccessFragment.TAG); - @Subscribe - public void onAuthenticationSucceed(AuthenticationSucceedEvent event) { - finishAuthentication(event.getAuthentication().getToken()); + finishAuthentication(event.getAuthentication().getToken(), "Server", fragment.getPIN()); + } } - private void finishAuthentication(String authenticationToken) { + private void finishAuthentication(String authenticationToken, String username, String password) { AccountManager accountManager = AccountManager.get(this); Bundle authenticationBundle = new Bundle(); - Account account = new AmahiAccount(getUsername()); + Account account = new AmahiAccount(username); + if (accountType.equals(AmahiAccount.TYPE_LOCAL)) { + authenticationBundle.putString("ip", Preferences.getLocalServerIP(this)); + authenticationBundle.putString("is_local", "T"); + } else { + authenticationBundle.putString("is_local", "F"); + } - if (accountManager.addAccountExplicitly(account, getPassword(), null)) { + if (accountManager.addAccountExplicitly(account, password, authenticationBundle)) { authenticationBundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); authenticationBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); authenticationBundle.putString(AccountManager.KEY_AUTHTOKEN, authenticationToken); @@ -257,20 +127,42 @@ private void finishAuthentication(String authenticationToken) { setAccountAuthenticatorResult(authenticationBundle); - setResult(Activity.RESULT_OK); + setResult(RESULT_OK); finish(); } + @Subscribe + public void onPINAccess(PINAccessEvent event) { + accountType = AmahiAccount.TYPE_LOCAL; + showPINAccessFragment(); + } + + private void showPINAccessFragment() { + Fragment fragment = Fragments.Builder.buildPINFragment(); + getFragmentManager() + .beginTransaction() + .replace(R.id.main_container, fragment, PINAccessFragment.TAG) + .addToBackStack(MainLoginFragment.TAG) + .commit(); + } + @Override - protected void onResume() { + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putString(State.ACCOUNT_TYPE, accountType); + } + + @Override + public void onResume() { super.onResume(); BusProvider.getBus().register(this); } @Override - protected void onPause() { + public void onPause() { super.onPause(); BusProvider.getBus().unregister(this); @@ -278,11 +170,18 @@ protected void onPause() { @Override public void onBackPressed() { - finishAffinity(); + super.onBackPressed(); } @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(LocaleHelper.onAttach(newBase)); } + + private static final class State { + static final String ACCOUNT_TYPE = "account_type"; + + private State() { + } + } } diff --git a/src/main/java/org/amahi/anywhere/bus/PINAccessEvent.java b/src/main/java/org/amahi/anywhere/bus/PINAccessEvent.java new file mode 100644 index 000000000..a8c3fcfb6 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/bus/PINAccessEvent.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.bus; + +public class PINAccessEvent implements BusEvent { +} diff --git a/src/main/java/org/amahi/anywhere/fragment/LocalLoginDialogFragment.java b/src/main/java/org/amahi/anywhere/fragment/LocalLoginDialogFragment.java new file mode 100644 index 000000000..d8c3e7200 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/fragment/LocalLoginDialogFragment.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.fragment; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.ProgressDialog; +import android.os.Bundle; + +public class LocalLoginDialogFragment extends DialogFragment { + + private ProgressDialog dialog; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + dialog = new ProgressDialog(getActivity()); + dialog.setTitle("Logging In"); + dialog.setCancelable(false); + dialog.setIndeterminate(false); + dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + return dialog; + } + + public void setProgress(int progress) { + dialog.setProgress(progress); + } +} diff --git a/src/main/java/org/amahi/anywhere/fragment/MainLoginFragment.java b/src/main/java/org/amahi/anywhere/fragment/MainLoginFragment.java new file mode 100644 index 000000000..20eb555e2 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/fragment/MainLoginFragment.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.fragment; + +import android.app.Fragment; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import com.dd.processbutton.iml.ActionProcessButton; +import com.google.android.material.textfield.TextInputLayout; +import com.squareup.otto.Subscribe; + +import org.amahi.anywhere.AmahiApplication; +import org.amahi.anywhere.R; +import org.amahi.anywhere.bus.AuthenticationConnectionFailedEvent; +import org.amahi.anywhere.bus.AuthenticationFailedEvent; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.bus.PINAccessEvent; +import org.amahi.anywhere.server.client.AmahiClient; +import org.amahi.anywhere.util.ViewDirector; + +import javax.inject.Inject; + +/** + * A simple {@link Fragment} subclass. + */ + +public class MainLoginFragment extends Fragment implements TextWatcher, + View.OnClickListener { + + public static final String TAG = "MainLoginFragment"; + @Inject + AmahiClient amahiClient; + TextInputLayout username_layout, password_layout; + ActionProcessButton signInButton; + Button pinLoginButton; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.layout_authentication, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setUpInjections(); + setUpLayout(view); + setUpAuthentication(view); + } + + private void setUpInjections() { + AmahiApplication.from(getActivity()).inject(this); + } + + + private void setUpLayout(View view) { + username_layout = view.findViewById(R.id.username_layout); + password_layout = view.findViewById(R.id.password_layout); + signInButton = view.findViewById(R.id.button_authentication); + pinLoginButton = view.findViewById(R.id.button_pin_access); + } + + private void setUpAuthentication(View view) { + setUpAuthenticationMessages(view); + setUpAuthenticationListeners(); + } + + private void setUpAuthenticationMessages(View view) { + TextView authenticationFailureMessage = view.findViewById(R.id.text_message_authentication); + TextView authenticationConnectionFailureMessage = view.findViewById(R.id.text_message_authentication_connection); + + authenticationFailureMessage.setMovementMethod(LinkMovementMethod.getInstance()); + authenticationConnectionFailureMessage.setMovementMethod(LinkMovementMethod.getInstance()); + } + + private void setUpAuthenticationListeners() { + setUpAuthenticationTextListener(); + setUpPINAccessListener(); + } + + private void setUpAuthenticationTextListener() { + getUsernameEditText().addTextChangedListener(this); + getPasswordEditText().addTextChangedListener(this); + getPasswordEditText().setOnEditorActionListener((v, actionId, event) -> { + boolean handled = false; + if (actionId == EditorInfo.IME_ACTION_GO) { + signInButton.callOnClick(); + handled = true; + } + return handled; + }); + signInButton.setOnClickListener(this); + } + + private EditText getUsernameEditText() { + return username_layout.getEditText(); + } + + private EditText getPasswordEditText() { + return password_layout.getEditText(); + } + + @Override + public void onClick(View view) { + if (getUsername().trim().isEmpty() || getPassword().trim().isEmpty()) { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_authentication_empty); + + if (getUsername().trim().isEmpty()) + getUsernameEditText().getBackground().setColorFilter(ContextCompat.getColor(getActivity(), android.R.color.holo_red_light), PorterDuff.Mode.SRC_ATOP); + if (getPassword().trim().isEmpty()) + getPasswordEditText().getBackground().setColorFilter(ContextCompat.getColor(getActivity(), android.R.color.holo_red_light), PorterDuff.Mode.SRC_ATOP); + + getUsernameEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + if (!getUsername().trim().isEmpty()) + getUsernameEditText().getBackground().setColorFilter(ContextCompat.getColor(getActivity(), R.color.blue_normal), PorterDuff.Mode.SRC_ATOP); + else + getUsernameEditText().getBackground().setColorFilter(ContextCompat.getColor(getActivity(), R.color.holo_red_light), PorterDuff.Mode.SRC_ATOP); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + getPasswordEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + if (!getPassword().trim().isEmpty()) + getPasswordEditText().getBackground().setColorFilter(ContextCompat.getColor(getActivity(), R.color.blue_normal), PorterDuff.Mode.SRC_ATOP); + else + getPasswordEditText().getBackground().setColorFilter(ContextCompat.getColor(getActivity(), android.R.color.holo_red_light), PorterDuff.Mode.SRC_ATOP); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + } else { + startAuthentication(); + + authenticate(); + } + } + + public String getUsername() { + return username_layout.getEditText().getText().toString(); + } + + public String getPassword() { + return password_layout.getEditText().getText().toString(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + + } + + private void startAuthentication() { + hideAuthenticationText(); + + showProgress(); + + hideAuthenticationFailureMessage(); + } + + private void hideAuthenticationFailureMessage() { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.view_message_empty); + } + + private void hideAuthenticationText() { + getUsernameEditText().setEnabled(false); + getPasswordEditText().setEnabled(false); + } + + private void showProgress() { + signInButton.setMode(ActionProcessButton.Mode.ENDLESS); + signInButton.setProgress(1); + } + + private void authenticate() { + amahiClient.authenticate(getUsername(), getPassword()); + } + + @Subscribe + public void onAuthenticationFailed(AuthenticationFailedEvent event) { + finishAuthentication(); + + showAuthenticationFailureMessage(); + } + + private void finishAuthentication() { + showAuthenticationText(); + + hideProgress(); + } + + private void showAuthenticationText() { + getUsernameEditText().setEnabled(true); + getPasswordEditText().setEnabled(true); + } + + private void hideProgress() { + signInButton.setProgress(0); + } + + private void showAuthenticationFailureMessage() { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_authentication); + } + + @Subscribe + public void onAuthenticationConnectionFailed(AuthenticationConnectionFailedEvent event) { + finishAuthentication(); + + showAuthenticationConnectionFailureMessage(); + } + + private void showAuthenticationConnectionFailureMessage() { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_authentication_connection); + } + + private void setUpPINAccessListener() { + pinLoginButton.setOnClickListener((v) -> { + BusProvider.getBus().post(new PINAccessEvent()); + }); + } + + @Override + public void onResume() { + super.onResume(); + + BusProvider.getBus().register(this); + } + + @Override + public void onPause() { + super.onPause(); + + BusProvider.getBus().unregister(this); + } +} diff --git a/src/main/java/org/amahi/anywhere/fragment/NavigationFragment.java b/src/main/java/org/amahi/anywhere/fragment/NavigationFragment.java index fb4d36671..f6de481f8 100644 --- a/src/main/java/org/amahi/anywhere/fragment/NavigationFragment.java +++ b/src/main/java/org/amahi/anywhere/fragment/NavigationFragment.java @@ -33,10 +33,6 @@ import android.os.Bundle; import android.os.Parcelable; import android.preference.PreferenceManager; -import androidx.fragment.app.Fragment; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -47,6 +43,11 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.squareup.otto.Subscribe; import org.amahi.anywhere.AmahiApplication; @@ -196,11 +197,20 @@ private void setUpAuthenticationToken() { public void run(AccountManagerFuture accountManagerFuture) { try { Bundle accountManagerResult = accountManagerFuture.getResult(); + Account account = getAccountManager().getAccounts()[0]; + + String isLocalUser = getAccountManager().getUserData(account, "is_local"); + String ip = getAccountManager().getUserData(account, "ip"); String authenticationToken = accountManagerResult.getString(AccountManager.KEY_AUTHTOKEN); if (authenticationToken != null) { - setUpServers(authenticationToken); + if (isLocalUser.equals("F")) { + setUpServers(authenticationToken); + } else { + BusProvider.getBus().post(new SharesSelectedEvent()); + setUpLocalServerApi(authenticationToken, ip); + } } else { setUpAuthenticationToken(); } @@ -629,6 +639,10 @@ private void showRecentFiles() { BusProvider.getBus().post(new RecentFilesSelectedEvent()); } + private void setUpLocalServerApi(String auth, String ip) { + serverClient.connectLocalServer(auth, ip); + } + @Subscribe public void onServerConnectionChanged(ServerConnectionChangedEvent event) { areServersVisible = false; diff --git a/src/main/java/org/amahi/anywhere/fragment/PINAccessFragment.java b/src/main/java/org/amahi/anywhere/fragment/PINAccessFragment.java new file mode 100644 index 000000000..3b8a97051 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/fragment/PINAccessFragment.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.fragment; + +import android.app.Fragment; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.squareup.otto.Subscribe; + +import org.amahi.anywhere.R; +import org.amahi.anywhere.bus.AuthenticationConnectionFailedEvent; +import org.amahi.anywhere.bus.AuthenticationFailedEvent; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.task.LocalServerProbingTask; +import org.amahi.anywhere.util.ViewDirector; + +public class PINAccessFragment extends Fragment { + + public static final String TAG = "PINAccessFragment"; + + private EditText pinEditText; + private Button pinLoginButton; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_pin_access, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpPINAuthentication(view); + } + + private void setUpPINAuthentication(View view) { + setUpPINViews(view); + setUpAuthenticationMessages(view); + setUpAuthenticationListeners(); + } + + private void setUpPINViews(View view) { + pinEditText = view.findViewById(R.id.edit_text_pin); + pinLoginButton = view.findViewById(R.id.button_pin_sign_in); + } + + private void setUpAuthenticationMessages(View view) { + TextView pinWrongMessage = view.findViewById(R.id.text_message_pin); + TextView connectionFailureMessage = view.findViewById(R.id.text_message_connection); + TextView pinEmptyMessage = view.findViewById(R.id.text_message_pin_empty); + + pinWrongMessage.setMovementMethod(LinkMovementMethod.getInstance()); + connectionFailureMessage.setMovementMethod(LinkMovementMethod.getInstance()); + pinEmptyMessage.setMovementMethod(LinkMovementMethod.getInstance()); + } + + private void setUpAuthenticationListeners() { + pinEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.view_message_empty); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + pinEditText.setOnEditorActionListener((v, actionId, event) -> pinLoginButton.callOnClick()); + + pinLoginButton.setOnClickListener(v -> { + String pin = pinEditText.getText().toString(); + if (TextUtils.isEmpty(pin)) { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_pin_empty); + return; + } + showProgress(); + startAuthentication(pin); + }); + } + + private void showProgress() { + LocalLoginDialogFragment fragment = (LocalLoginDialogFragment) getChildFragmentManager().findFragmentByTag("log_in"); + if (fragment == null) { + fragment = new LocalLoginDialogFragment(); + fragment.show(getChildFragmentManager(), "log_in"); + } + } + + private void startAuthentication(String pin) { + new LocalServerProbingTask(getActivity(), pin).execute(); + } + + @Subscribe + public void onAuthenticationFailed(AuthenticationFailedEvent event) { + dismissDialog(); + enableUIControls(); + showAuthenticationFailMessage(); + } + + private void dismissDialog() { + LocalLoginDialogFragment fragment = (LocalLoginDialogFragment) getChildFragmentManager().findFragmentByTag("log_in"); + if (fragment != null) { + fragment.dismiss(); + } + } + + private void enableUIControls() { + pinEditText.setEnabled(true); + pinLoginButton.setEnabled(true); + } + + private void showAuthenticationFailMessage() { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_pin); + } + + @Subscribe + public void onAuthenticationConnectionFail(AuthenticationConnectionFailedEvent event) { + dismissDialog(); + enableUIControls(); + showAuthenticationConnectionFailMessage(); + } + + private void showAuthenticationConnectionFailMessage() { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_connection); + } + + public String getPIN() { + return pinEditText.getText().toString(); + } + + @Override + public void onResume() { + super.onResume(); + + BusProvider.getBus().register(this); + } + + @Override + public void onPause() { + super.onPause(); + + BusProvider.getBus().unregister(this); + } +} diff --git a/src/main/java/org/amahi/anywhere/server/client/ServerClient.java b/src/main/java/org/amahi/anywhere/server/client/ServerClient.java index 32d705c58..c528a4f4b 100644 --- a/src/main/java/org/amahi/anywhere/server/client/ServerClient.java +++ b/src/main/java/org/amahi/anywhere/server/client/ServerClient.java @@ -51,7 +51,6 @@ import org.amahi.anywhere.server.response.ServerSharesResponse; import org.amahi.anywhere.task.ServerConnectionDetectingTask; import org.amahi.anywhere.util.ProgressRequestBody; -import org.amahi.anywhere.util.Time; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -156,7 +155,9 @@ public boolean isConnected() { } public boolean isConnected(Server server) { - return (this.server != null) && (this.serverRoute != null) && (this.server.getSession().equals(server.getSession())); + return (this.server != null) && (this.serverRoute != null) + && (this.server.getSession().equals(server.getSession()) + && server.getAuthToken() != null); } public boolean isConnectedLocal() { @@ -312,8 +313,6 @@ public Uri getFileUri(ServerShare share, ServerFile file) { .path("files") .appendQueryParameter("s", share.getName()) .appendQueryParameter("p", file.getPath()) - .appendQueryParameter("mtime", Time.getEpochTimeString(file.getModificationTime())) - .appendQueryParameter("session", server.getSession()) .build(); } @@ -334,9 +333,20 @@ public Uri getFileThumbnailUri(ServerShare share, ServerFile file) { .path("cache") .appendQueryParameter("s", share.getName()) .appendQueryParameter("p", file.getPath()) - .appendQueryParameter("mtime", Time.getEpochTimeString(file.getModificationTime())) - .appendQueryParameter("session", server.getSession()) .build(); } + + public void connectLocalServer(String auth, String ip) { + serverRoute = new ServerRoute(); + serverRoute.setLocalAddress(getLocalAddress(ip)); + serverAddress = serverRoute.getLocalAddress(); + server = new Server(auth); + server.setAuthToken(auth); + serverApi = buildServerApi(); + } + + private String getLocalAddress(String ip) { + return "http://" + ip + ":4563/"; + } } diff --git a/src/main/java/org/amahi/anywhere/server/model/Authentication.java b/src/main/java/org/amahi/anywhere/server/model/Authentication.java index 37ec3b38a..cf787e0ea 100644 --- a/src/main/java/org/amahi/anywhere/server/model/Authentication.java +++ b/src/main/java/org/amahi/anywhere/server/model/Authentication.java @@ -25,7 +25,7 @@ * Authentication API resource. */ public class Authentication { - @SerializedName("access_token") + @SerializedName(value = "access_token", alternate = "auth_token") private String token; public String getToken() { diff --git a/src/main/java/org/amahi/anywhere/server/model/Server.java b/src/main/java/org/amahi/anywhere/server/model/Server.java index 07f0f48c2..8575abddc 100644 --- a/src/main/java/org/amahi/anywhere/server/model/Server.java +++ b/src/main/java/org/amahi/anywhere/server/model/Server.java @@ -48,6 +48,8 @@ public Server[] newArray(int size) { private String session; @SerializedName("active") private boolean active; + + private String authToken; private boolean debug = false; private int index; @@ -69,6 +71,14 @@ public Server(Parcel parcel) { this.active = Boolean.valueOf(parcel.readString()); } + public void setAuthToken(String authToken) { + this.authToken = authToken; + } + + public String getAuthToken() { + return authToken; + } + public static List filterActiveServers(List servers) { List activeServers = new ArrayList<>(); for (Server server : servers) { diff --git a/src/main/java/org/amahi/anywhere/task/LocalServerProbingTask.java b/src/main/java/org/amahi/anywhere/task/LocalServerProbingTask.java new file mode 100644 index 000000000..4872a732c --- /dev/null +++ b/src/main/java/org/amahi/anywhere/task/LocalServerProbingTask.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.task; + +import android.content.Context; +import android.net.DhcpInfo; +import android.net.wifi.WifiManager; +import android.os.AsyncTask; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; + +import org.amahi.anywhere.bus.AuthenticationConnectionFailedEvent; +import org.amahi.anywhere.bus.AuthenticationFailedEvent; +import org.amahi.anywhere.bus.AuthenticationSucceedEvent; +import org.amahi.anywhere.bus.BusEvent; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.server.model.Authentication; +import org.amahi.anywhere.util.Preferences; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.net.InetAddress; +import java.util.concurrent.TimeUnit; + +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class LocalServerProbingTask extends AsyncTask { + + private static final String TAG = "LocalServerProbingTask"; + + private WeakReference context; + private OkHttpClient httpClient; + private RequestBody body; + + public LocalServerProbingTask(Context context, @NonNull String pin) { + this.context = new WeakReference<>(context); + this.httpClient = buildHttpClient(); + this.body = buildRequestBody(pin); + } + + private OkHttpClient buildHttpClient() { + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + clientBuilder.connectTimeout(1, TimeUnit.SECONDS); + httpClient = clientBuilder.build(); + return httpClient; + } + + private RequestBody buildRequestBody(String pin) { + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + return RequestBody.create(JSON, "{\"pin\":\"" + pin + "\"}"); + } + + @Override + protected BusEvent doInBackground(Void... voids) { +// String ipString = "192.168.54.129"; + WifiManager wm = (WifiManager) context.get().getApplicationContext().getSystemService(Context.WIFI_SERVICE); + if (wm != null) { + DhcpInfo d = wm.getDhcpInfo(); + String ip = intToIp(d.dns1); + String prefix = ip.substring(0, ip.lastIndexOf(".") + 1); + for (int i = 0; i < 255; i++) { + String testIp = prefix + String.valueOf(i); +// testIp = "192.168.54.129"; + try { + InetAddress ipAddress = InetAddress.getByName(testIp); + String hostName = ipAddress.getCanonicalHostName(); + if (ipAddress.isReachable(200)) { + Request httpRequest = new Request.Builder() + .url(getConnectionUrl(hostName)) + .post(body) + .build(); + + Response response = httpClient + .newCall(httpRequest) + .execute(); + + if (response.code() == 200) { + Gson gson = new Gson(); + String json = response.body().string(); + Authentication authentication = gson.fromJson(json, Authentication.class); + Preferences.setLocalServerIP(context.get().getApplicationContext(), testIp); + return new AuthenticationSucceedEvent(authentication); + } else if (response.code() == 403) { + return new AuthenticationFailedEvent(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return new AuthenticationConnectionFailedEvent(); + } + + private String intToIp(int i) { + return (i & 0xFF) + "." + + ((i >> 8) & 0xFF) + "." + + ((i >> 16) & 0xFF) + "." + + ((i >> 24) & 0xFF); + } + + private String getConnectionUrl(String hostName) { + return "http://" + hostName + ":4563/auth"; + } + + @Override + protected void onPostExecute(BusEvent busEvent) { + super.onPostExecute(busEvent); + + BusProvider.getBus().post(busEvent); + } +} diff --git a/src/main/java/org/amahi/anywhere/util/Fragments.java b/src/main/java/org/amahi/anywhere/util/Fragments.java index 97d52d05e..3b6416307 100644 --- a/src/main/java/org/amahi/anywhere/util/Fragments.java +++ b/src/main/java/org/amahi/anywhere/util/Fragments.java @@ -28,7 +28,9 @@ import org.amahi.anywhere.fragment.AudioListFragment; import org.amahi.anywhere.fragment.FileOptionsDialogFragment; +import org.amahi.anywhere.fragment.MainLoginFragment; import org.amahi.anywhere.fragment.NavigationFragment; +import org.amahi.anywhere.fragment.PINAccessFragment; import org.amahi.anywhere.fragment.ServerAppsFragment; import org.amahi.anywhere.fragment.ServerFileAudioFragment; import org.amahi.anywhere.fragment.ServerFileImageFragment; @@ -65,6 +67,14 @@ public static final class Builder { private Builder() { } + public static android.app.Fragment buildMainLoginFragment() { + return new MainLoginFragment(); + } + + public static android.app.Fragment buildPINFragment() { + return new PINAccessFragment(); + } + public static Fragment buildNavigationFragment() { return new NavigationFragment(); } diff --git a/src/main/java/org/amahi/anywhere/util/Preferences.java b/src/main/java/org/amahi/anywhere/util/Preferences.java index ec02b65c9..80b4f32f0 100644 --- a/src/main/java/org/amahi/anywhere/util/Preferences.java +++ b/src/main/java/org/amahi/anywhere/util/Preferences.java @@ -94,6 +94,14 @@ public static String getServerName(Context context) { return context.getSharedPreferences(Constants.preference, MODE_PRIVATE).getString(context.getString(R.string.pref_server_select_key), null); } + public static void setLocalServerIP(Context context, String ip) { + context.getSharedPreferences(context.getString(R.string.preference), MODE_PRIVATE).edit().putString(context.getString(R.string.local_server_ip), ip).apply(); + } + + public static String getLocalServerIP(Context context) { + return context.getSharedPreferences(context.getString(R.string.preference), MODE_PRIVATE).getString(context.getString(R.string.local_server_ip), null); + } + public static Preferences ofCookie(Context context) { return new Preferences(context, Locations.COOKIE); } diff --git a/src/main/res/layout-land/layout_authentication.xml b/src/main/res/layout-land/layout_authentication.xml index 39f496e16..cac2e53e1 100644 --- a/src/main/res/layout-land/layout_authentication.xml +++ b/src/main/res/layout-land/layout_authentication.xml @@ -154,6 +154,14 @@ +