diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 278490e41..3383207b7 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ tools:ignore="MissingLeanbackLauncher"> + diff --git a/src/main/java/org/amahi/anywhere/AmahiModule.java b/src/main/java/org/amahi/anywhere/AmahiModule.java index a888bf068..c0b8f89fc 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; @@ -36,7 +35,9 @@ import org.amahi.anywhere.activity.WebViewActivity; 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.PINAccessFragment; import org.amahi.anywhere.fragment.ServerAppsFragment; import org.amahi.anywhere.fragment.ServerFileAudioFragment; import org.amahi.anywhere.fragment.ServerFileDownloadingFragment; @@ -76,7 +77,6 @@ CacheModule.class }, injects = { - AuthenticationActivity.class, NavigationActivity.class, ServerAppActivity.class, OfflineFilesActivity.class, @@ -87,6 +87,8 @@ NativeVideoActivity.class, RecentFilesActivity.class, ServerFileWebActivity.class, + MainLoginFragment.class, + PINAccessFragment.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..ea6fbb2aa 100644 --- a/src/main/java/org/amahi/anywhere/account/AmahiAccount.java +++ b/src/main/java/org/amahi/anywhere/account/AmahiAccount.java @@ -26,6 +26,9 @@ */ public class AmahiAccount extends Account { public static final String TYPE = "org.amahi"; + public static final String TYPE_LOCAL = "org.amahi.local"; + + public static String accountType = AmahiAccount.TYPE; 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 b4d4ec702..771b147b9 100644 --- a/src/main/java/org/amahi/anywhere/activity/AuthenticationActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/AuthenticationActivity.java @@ -21,44 +21,33 @@ import android.accounts.Account; import android.accounts.AccountManager; -import android.app.Activity; import android.content.Context; import android.os.Bundle; -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 androidx.appcompat.app.AppCompatDelegate; +import androidx.fragment.app.Fragment; -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.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.Constants; +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 { @Override protected void onCreate(Bundle savedInstanceState) { @@ -70,183 +59,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; - }); + setUpAuthenticationFragment(); } @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); + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + AmahiAccount.accountType = savedInstanceState.getString(State.ACCOUNT_TYPE, AmahiAccount.TYPE); } - @Override - public void afterTextChanged(Editable text) { - } - - @Override - public void beforeTextChanged(CharSequence text, int start, int count, int before) { - } - - private void setUpAuthenticationActionListener() { - getAuthenticationButton().setOnClickListener(this::onClick); - } - - 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 (AmahiAccount.accountType.equals(AmahiAccount.TYPE)) { + MainLoginFragment f = (MainLoginFragment) getSupportFragmentManager().findFragmentByTag(MainLoginFragment.TAG); + if (f == null) { + showMainLoginFragment(); + } } else { - startAuthentication(); - - authenticate(); + PINAccessFragment f = (PINAccessFragment) getSupportFragmentManager().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().trim(), 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(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_container, fragment, MainLoginFragment.TAG) + .commit(); } @Subscribe - public void onAuthenticationConnectionFailed(AuthenticationConnectionFailedEvent event) { - finishAuthentication(); - - showAuthenticationConnectionFailureMessage(); - } + public void onAuthenticationSucceed(AuthenticationSucceedEvent event) { + if (AmahiAccount.accountType.equals(AmahiAccount.TYPE)) { + MainLoginFragment fragment = (MainLoginFragment) getSupportFragmentManager().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) getSupportFragmentManager().findFragmentByTag(PINAccessFragment.TAG); - @Subscribe - public void onAuthenticationSucceed(AuthenticationSucceedEvent event) { - finishAuthentication(event.getAuthentication().getToken()); + finishAuthentication(event.getAuthentication().getToken(), Constants.pinAccessUsername, 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 (AmahiAccount.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); @@ -256,20 +126,42 @@ private void finishAuthentication(String authenticationToken) { setAccountAuthenticatorResult(authenticationBundle); - setResult(Activity.RESULT_OK); + setResult(RESULT_OK); finish(); } + @Subscribe + public void onPINAccess(PINAccessEvent event) { + AmahiAccount.accountType = AmahiAccount.TYPE_LOCAL; + showPINAccessFragment(); + } + + private void showPINAccessFragment() { + Fragment fragment = Fragments.Builder.buildPINFragment(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_container, fragment, PINAccessFragment.TAG) + .addToBackStack(MainLoginFragment.TAG) + .commit(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putString(State.ACCOUNT_TYPE, AmahiAccount.accountType); + } + @Override - protected void onResume() { + public void onResume() { super.onResume(); BusProvider.getBus().register(this); } @Override - protected void onPause() { + public void onPause() { super.onPause(); BusProvider.getBus().unregister(this); @@ -277,11 +169,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/activity/IntroductionActivity.java b/src/main/java/org/amahi/anywhere/activity/IntroductionActivity.java index 618bc2a65..3c7523a5c 100644 --- a/src/main/java/org/amahi/anywhere/activity/IntroductionActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/IntroductionActivity.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.graphics.Color; import android.os.Bundle; + import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.core.content.ContextCompat; @@ -62,7 +63,7 @@ public void onSkipPressed(Fragment currentFragment) { } private void launchTv() { - if(!CheckTV.isATV(this) && Preferences.getFirstRun(this)) { + if (!CheckTV.isATV(this) && Preferences.getFirstRun(this)) { Preferences.setFirstRun(this); launchNavigation(); } diff --git a/src/main/java/org/amahi/anywhere/activity/SplashActivity.java b/src/main/java/org/amahi/anywhere/activity/SplashActivity.java index 4101f61a8..bc822b4d7 100644 --- a/src/main/java/org/amahi/anywhere/activity/SplashActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/SplashActivity.java @@ -22,10 +22,13 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; + import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import org.amahi.anywhere.util.CheckTV; import org.amahi.anywhere.util.LocaleHelper; +import org.amahi.anywhere.util.Preferences; public class SplashActivity extends AppCompatActivity { @@ -39,15 +42,24 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { } private void setUpActivity() { - launchNavigation(); + if (Preferences.getFirstRun(this) && !CheckTV.isATV(this)) { + launchIntro(); + } else { + launchNavigation(); + } } private void launchNavigation() { startActivity(new Intent(this, NavigationActivity.class)); } + private void launchIntro() { + startActivity(new Intent(this, IntroductionActivity.class)); + } + @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(LocaleHelper.onAttach(newBase)); } + } 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..ceda4bb89 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/fragment/LocalLoginDialogFragment.java @@ -0,0 +1,45 @@ +/* + * 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.ProgressDialog; +import android.os.Bundle; + +import androidx.fragment.app.DialogFragment; + +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..c611a765c --- /dev/null +++ b/src/main/java/org/amahi/anywhere/fragment/MainLoginFragment.java @@ -0,0 +1,268 @@ +/* + * 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.content.Context; +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.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +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.account.AmahiAccount; +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 java.util.Objects; + +import javax.inject.Inject; + +/** + * A simple {@link Fragment } subclass. + */ + +public class MainLoginFragment extends Fragment implements TextWatcher, + View.OnClickListener { + + public static final String TAG = MainLoginFragment.class.getSimpleName(); + @Inject + AmahiClient amahiClient; + TextInputLayout username_layout, password_layout; + ActionProcessButton signInButton; + TextView 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.text_pin_access); + } + + private void setUpAuthentication(View view) { + setUpAuthenticationMessages(view); + setUpAuthenticationListeners(); + } + + private void setUpAuthenticationMessages(View view) { + TextView forgotPassword = view.findViewById(R.id.text_forgot_password); + TextView authenticationConnectionFailureMessage = view.findViewById(R.id.text_message_authentication_connection); + + forgotPassword.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(view.getId()==signInButton.getId()) { + + InputMethodManager imm = (InputMethodManager) Objects.requireNonNull(getContext()).getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + + 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().requestFocus(); + + if (getPassword().trim().isEmpty()) + getPasswordEditText().requestFocus(); + + if (getUsername().trim().isEmpty() && getPassword().trim().isEmpty()) + getUsernameEditText().requestFocus(); + + } else if (getUsername().contains(" ")) { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_authentication); + + } 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) { + hideAuthenticationFailureMessage(); + } + + @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() { + AmahiAccount.accountType = AmahiAccount.TYPE; + 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 e29cce08d..5683131b7 100644 --- a/src/main/java/org/amahi/anywhere/fragment/NavigationFragment.java +++ b/src/main/java/org/amahi/anywhere/fragment/NavigationFragment.java @@ -55,7 +55,6 @@ import org.amahi.anywhere.AmahiApplication; import org.amahi.anywhere.R; import org.amahi.anywhere.account.AmahiAccount; -import org.amahi.anywhere.activity.IntroductionActivity; import org.amahi.anywhere.adapter.NavigationDrawerAdapter; import org.amahi.anywhere.bus.AppsSelectedEvent; import org.amahi.anywhere.bus.BusProvider; @@ -91,13 +90,16 @@ /** * Navigation fragments. Shows main application sections and servers list as well. */ -public class NavigationFragment extends Fragment implements AccountManagerCallback, - OnAccountsUpdateListener { +public class NavigationFragment extends Fragment implements AccountManagerCallback, OnAccountsUpdateListener { + @Inject AmahiClient amahiClient; + @Inject ServerClient serverClient; + View view; + private Intent tvIntent; private Context mContext; private Activity mActivity; @@ -105,6 +107,8 @@ public class NavigationFragment extends Fragment implements AccountManagerCallba private boolean areServersVisible; private List serversList; + public static final String TAG = NavigationFragment.class.getSimpleName(); + @Override public void onAttach(Context context) { super.onAttach(context); @@ -155,21 +159,11 @@ private AccountManager getAccountManager() { @Override public void onAccountsUpdated(Account[] accounts) { - - if (Preferences.getFirstRun(mContext)) { - launchIntro(); - } - if (getAccounts().isEmpty()) { setUpAccount(); } } - private void launchIntro() { - startActivity(new Intent(mContext, IntroductionActivity.class)); - mActivity.finishAffinity(); - } - private void setUpContentRefreshing() { SwipeRefreshLayout refreshLayout = getRefreshLayout(); @@ -201,11 +195,20 @@ private void setUpAuthenticationToken() { public void run(AccountManagerFuture accountManagerFuture) { try { Bundle accountManagerResult = accountManagerFuture.getResult(); + Account account = getAccountManager().getAccounts()[0]; + + String isLocalUser = checkIfLocalUser(); + 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(); } @@ -216,6 +219,11 @@ public void run(AccountManagerFuture accountManagerFuture) { } } + private String checkIfLocalUser() { + Account account = getAccountManager().getAccounts()[0]; + return getAccountManager().getUserData(account, "is_local"); + } + private void tearDownActivity() { mActivity.finish(); } @@ -329,9 +337,6 @@ private void setUpAuthentication() { if (getAccounts().isEmpty()) { setUpAccount(); } else { - if (Preferences.getFirstRun(mActivity) && !checkIsATV()) { - launchIntro(); - } setUpAuthenticationToken(); } } @@ -435,17 +440,19 @@ private TextView getServerNameTextView() { } private void setUpNavigationListener() { - getNavigationListView().addOnItemTouchListener(new RecyclerItemClickListener(mContext, (view, position) -> { - getNavigationListView().dispatchSetActivated(false); + if (checkIfLocalUser().equals("F")) { + getNavigationListView().addOnItemTouchListener(new RecyclerItemClickListener(mContext, (view, position) -> { + getNavigationListView().dispatchSetActivated(false); - view.setActivated(true); + view.setActivated(true); - if (!areServersVisible) { - selectedServerListener(position); - } else { - serverClicked(position); - } - })); + if (!areServersVisible) { + selectedServerListener(position); + } else { + serverClicked(position); + } + })); + } getOfflineFilesLayout().setOnClickListener(view -> showOfflineFiles()); @@ -534,8 +541,10 @@ private void showNavigationItems() { areServersVisible = false; setUpNavigationAdapter(); - getServerNameTextView().setCompoundDrawablesWithIntrinsicBounds( - 0, 0, R.drawable.nav_arrow_down, 0); + if (checkIfLocalUser().equals("F")) { + getServerNameTextView().setCompoundDrawablesWithIntrinsicBounds( + 0, 0, R.drawable.nav_arrow_down, 0); + } getOfflineFilesLayout().setVisibility(View.VISIBLE); getRecentFilesLayout().setVisibility(View.VISIBLE); @@ -654,6 +663,11 @@ private void showRecentFiles() { BusProvider.getBus().post(new RecentFilesSelectedEvent()); } + private void setUpLocalServerApi(String auth, String ip) { + serverClient.connectLocalServer(auth, ip); + setUpServersContent(auth); + } + @Subscribe public void onServerConnectionChanged(ServerConnectionChangedEvent event) { areServersVisible = false; @@ -720,7 +734,6 @@ private void tearDownAuthenticationListener() { getAccountManager().removeOnAccountsUpdatedListener(this); } - /*Sets the adapter for navigation drawer after getting server names*/ public void showServers() { areServersVisible = true; getNavigationListView().setAdapter(null); 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..f763cd275 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/fragment/PINAccessFragment.java @@ -0,0 +1,197 @@ +/* + * 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.content.Context; +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.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.dd.processbutton.iml.ActionProcessButton; +import com.google.android.material.textfield.TextInputLayout; +import com.squareup.otto.Subscribe; + +import org.amahi.anywhere.R; +import org.amahi.anywhere.account.AmahiAccount; +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; + +import java.util.Objects; + +public class PINAccessFragment extends Fragment { + + public static final String TAG = PINAccessFragment.class.getSimpleName(); + + private TextInputLayout pinLayout; + private ActionProcessButton pinLoginButton; + private View view; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_pin_access, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + this.view = view; + setUpPINAuthentication(view); + } + + private void setUpPINAuthentication(View view) { + setUpPINViews(view); + setUpAuthenticationMessages(view); + setUpAuthenticationListeners(); + } + + private void setUpPINViews(View view) { + pinLayout = view.findViewById(R.id.pin_layout); + 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 EditText getPINEditText() { + return pinLayout.getEditText(); + } + + public String getPIN() { + return getPINEditText().getText().toString(); + } + + private void setUpAuthenticationListeners() { + getPINEditText().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) { + + } + }); + + getPINEditText().setOnEditorActionListener((v, actionId, event) -> pinLoginButton.callOnClick()); + + pinLoginButton.setOnClickListener(v -> { + if (getPIN().trim().isEmpty()) { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_pin_empty); + return; + } else if (getPIN().length() > 5 || getPIN().length() < 3) { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_pin); + return; + } + showProgress(); + startAuthentication(getPIN()); + }); + } + + private void showProgress() { + + InputMethodManager imm = (InputMethodManager) Objects.requireNonNull(getContext()).getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + + if(pinLoginButton!=null) { + pinLoginButton.setMode(ActionProcessButton.Mode.ENDLESS); + pinLoginButton.setProgress(1); + } + } + + private void hideProgress() { + if(pinLoginButton!=null) { + pinLoginButton.setProgress(0); + } + } + + private void startAuthentication(String pin) { + AmahiAccount.accountType = AmahiAccount.TYPE_LOCAL; + new LocalServerProbingTask(getActivity(), pin).execute(); + } + + @Subscribe + public void onAuthenticationFailed(AuthenticationFailedEvent event) { + hideProgress(); + enableUIControls(); + showAuthenticationFailMessage(); + } + + private void enableUIControls() { + pinLayout.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) { + hideProgress(); + enableUIControls(); + showAuthenticationConnectionFailMessage(); + } + + private void showAuthenticationConnectionFailMessage() { + ViewDirector.of(getActivity(), R.id.animator_message).show(R.id.text_message_connection); + } + + @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..c25b05121 100644 --- a/src/main/java/org/amahi/anywhere/server/client/ServerClient.java +++ b/src/main/java/org/amahi/anywhere/server/client/ServerClient.java @@ -69,7 +69,6 @@ import static org.amahi.anywhere.util.Android.loadServersFromAsset; - /** * Server API implementation. Wraps {@link org.amahi.anywhere.server.api.ProxyApi} and * {@link org.amahi.anywhere.server.api.ServerApi}. Reacts to network connection changes as well. @@ -87,6 +86,8 @@ public class ServerClient { private int network; + public static final String TAG = ServerClient.class.getSimpleName(); + @Inject public ServerClient(ApiAdapter apiAdapter) { this.apiAdapter = apiAdapter; @@ -156,7 +157,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() { @@ -339,4 +342,17 @@ public Uri getFileThumbnailUri(ServerShare share, ServerFile file) { .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..2735ce971 --- /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 android.util.Log; + +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.class.getSimpleName(); + + 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) { + 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); + try { + InetAddress ipAddress = InetAddress.getByName(testIp); + String hostName = ipAddress.getCanonicalHostName(); + Log.v(TAG, "Trying Connection: " + hostName); + 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/Constants.java b/src/main/java/org/amahi/anywhere/util/Constants.java index 24adff395..f413eb4a7 100644 --- a/src/main/java/org/amahi/anywhere/util/Constants.java +++ b/src/main/java/org/amahi/anywhere/util/Constants.java @@ -43,6 +43,7 @@ public class Constants { public static final String sharesPathAppendLoc = "shares"; + public static final String pinAccessUsername = "Server"; public static final String welcomeToAmahi = "Welcome to Amahi"; public static final String amahiAndroidUrl = "https://www.amahi.org/android"; diff --git a/src/main/java/org/amahi/anywhere/util/Fragments.java b/src/main/java/org/amahi/anywhere/util/Fragments.java index 990955450..fa4076837 100644 --- a/src/main/java/org/amahi/anywhere/util/Fragments.java +++ b/src/main/java/org/amahi/anywhere/util/Fragments.java @@ -30,8 +30,10 @@ import org.amahi.anywhere.fragment.AudioListFragment; import org.amahi.anywhere.fragment.FileOptionsDialogFragment; +import org.amahi.anywhere.fragment.MainLoginFragment; import org.amahi.anywhere.fragment.FileSortOptionsDialogFragment; 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; @@ -69,6 +71,14 @@ public static final class Builder { private Builder() { } + public static Fragment buildMainLoginFragment() { + return new MainLoginFragment(); + } + + public static 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 290e70cef..3cdfe6960 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 @@ +