/*
* AndFHEM - Open Source Android application to control a FHEM home automation
* server.
*
* Copyright (c) 2011, Matthias Klass or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU GENERAL PUBLIC LICENSE, as published by the Free Software Foundation.
*
* This program 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 this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package li.klass.fhem.fragments;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.support.v4.app.FragmentActivity;
import android.text.method.PasswordTransformationMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import com.github.angads25.filepicker.controller.DialogSelectionListener;
import com.github.angads25.filepicker.model.DialogConfigs;
import com.github.angads25.filepicker.model.DialogProperties;
import com.github.angads25.filepicker.view.FilePickerDialog;
import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import li.klass.fhem.R;
import li.klass.fhem.constants.Actions;
import li.klass.fhem.constants.BundleExtraKeys;
import li.klass.fhem.constants.ResultCodes;
import li.klass.fhem.dagger.ApplicationComponent;
import li.klass.fhem.fhem.connection.FHEMServerSpec;
import li.klass.fhem.fhem.connection.ServerType;
import li.klass.fhem.fragments.core.BaseFragment;
import li.klass.fhem.service.intent.ConnectionsIntentService;
import li.klass.fhem.util.FhemResultReceiver;
import li.klass.fhem.util.PermissionUtil;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.newArrayList;
import static li.klass.fhem.constants.BundleExtraKeys.CONNECTION;
import static li.klass.fhem.constants.BundleExtraKeys.CONNECTION_ALTERNATE_URL;
import static li.klass.fhem.constants.BundleExtraKeys.CONNECTION_CLIENT_CERTIFICATE_PASSWORD;
import static li.klass.fhem.constants.BundleExtraKeys.CONNECTION_CLIENT_CERTIFICATE_PATH;
import static li.klass.fhem.constants.BundleExtraKeys.CONNECTION_URL;
import static li.klass.fhem.constants.BundleExtraKeys.CONNECTION_USERNAME;
import static li.klass.fhem.constants.ResultCodes.SUCCESS;
import static li.klass.fhem.util.DialogUtil.showAlertDialog;
public class ConnectionDetailFragment extends BaseFragment {
private static final String TAG = ConnectionDetailFragment.class.getName();
private String connectionId;
private boolean isModify = false;
private ServerType connectionType;
private ConnectionTypeDetailChangedListener detailChangedListener = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void setArguments(Bundle args) {
super.setArguments(args);
if (args.containsKey(BundleExtraKeys.CONNECTION_ID)) {
connectionId = args.getString(BundleExtraKeys.CONNECTION_ID);
isModify = true;
}
}
@Override
protected void inject(ApplicationComponent applicationComponent) {
applicationComponent.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (view != null) {
return view;
}
view = inflater.inflate(R.layout.connection_detail, container, false);
assert (view != null);
Spinner connectionTypeSpinner = (Spinner) view.findViewById(R.id.connectionType);
if (isModify) {
connectionTypeSpinner.setEnabled(false);
}
final List<ServerType> connectionTypes = getServerTypes();
ArrayAdapter<ServerType> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_spinner_dropdown_item, connectionTypes);
connectionTypeSpinner.setAdapter(adapter);
connectionTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
handleConnectionTypeChange(connectionTypes.get(position));
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.connection_menu, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.save) {
handleSave();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected boolean mayPullToRefresh() {
return false;
}
private List<ServerType> getServerTypes() {
final List<ServerType> connectionTypes = newArrayList();
connectionTypes.addAll(Arrays.asList(ServerType.values()));
connectionTypes.remove(ServerType.DUMMY);
return connectionTypes;
}
@SuppressLint("InflateParams")
private void handleConnectionTypeChange(ServerType connectionType) {
if (getView() == null) return;
this.connectionType = checkNotNull(connectionType);
FragmentActivity activity = checkNotNull(getActivity());
LayoutInflater inflater = checkNotNull(activity.getLayoutInflater());
View view;
if (connectionType == ServerType.FHEMWEB) {
view = inflater.inflate(R.layout.connection_fhemweb, null);
handleFHEMWEBView(view);
} else if (connectionType == ServerType.TELNET) {
view = inflater.inflate(R.layout.connection_telnet, null);
} else {
throw new IllegalArgumentException("cannot handle connection type " + connectionType);
}
assert view != null;
CheckBox showPasswordCheckbox = (CheckBox) view.findViewById(R.id.showPasswordCheckbox);
final EditText passwordView = (EditText) view.findViewById(R.id.password);
if (showPasswordCheckbox != null && passwordView != null) {
showPasswordCheckbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CheckBox radio = (CheckBox) view;
boolean checked = radio.isChecked();
if (checked) {
passwordView.setTransformationMethod(null);
} else {
passwordView.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
}
});
if (isModify) showPasswordCheckbox.setEnabled(false);
}
CheckBox showCertificatePasswordCheckbox = (CheckBox) view.findViewById(R.id.showCertificatePasswordCheckbox);
final EditText passwordClientCertificateView = (EditText) view.findViewById(R.id.clientCertificatePassword);
if (showCertificatePasswordCheckbox != null && passwordClientCertificateView != null) {
showCertificatePasswordCheckbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CheckBox radio = (CheckBox) view;
boolean checked = radio.isChecked();
if (checked) {
passwordClientCertificateView.setTransformationMethod(null);
} else {
passwordClientCertificateView.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
}
});
if (isModify) showCertificatePasswordCheckbox.setEnabled(false);
}
ViewGroup connectionPreferences = (ViewGroup) checkNotNull(getView()).findViewById(R.id.connectionPreferences);
checkNotNull(connectionPreferences);
connectionPreferences.removeAllViews();
connectionPreferences.addView(view);
if (detailChangedListener != null) detailChangedListener.onChanged();
}
private void handleSave() {
Intent intent = new Intent();
intent.setClass(getActivity(), ConnectionsIntentService.class);
if (isModify) {
intent.setAction(Actions.CONNECTION_UPDATE);
intent.putExtra(BundleExtraKeys.CONNECTION_ID, connectionId);
} else {
intent.setAction(Actions.CONNECTION_CREATE);
}
intent.putExtra(BundleExtraKeys.CONNECTION_TYPE, connectionType.name());
String name = getTextViewContent(R.id.connectionName);
if (enforceNotEmpty(R.string.connectionName, name)) return;
intent.putExtra(BundleExtraKeys.CONNECTION_NAME, name);
intent.putExtra(BundleExtraKeys.CONNECTION_PASSWORD, getTextViewContent(R.id.password));
switch (connectionType) {
case TELNET:
if (!handleTelnetSave(intent)) {
return;
}
break;
case FHEMWEB:
if (!handleFHEMWEBSave(intent)) {
return;
}
break;
}
intent.putExtra(BundleExtraKeys.RESULT_RECEIVER, new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
if (resultCode != ResultCodes.SUCCESS) {
Log.e(TAG, "could not save! resultCode=" + resultCode + ",resultData=" + resultData);
return;
}
Intent intent = new Intent(Actions.BACK);
getActivity().sendBroadcast(intent);
}
});
getActivity().startService(intent);
}
private void handleFHEMWEBView(View view) {
ImageButton setClientCertificate = (ImageButton) view.findViewById(R.id.setClientCertificatePath);
setClientCertificate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (getView() == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
PermissionUtil.checkPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE);
}
final TextView clientCertificatePath = (TextView) getView().findViewById(R.id.clientCertificatePath);
File initialPath = new File(clientCertificatePath.getText().toString());
DialogProperties properties = new DialogProperties();
properties.selection_mode = DialogConfigs.SINGLE_MODE;
properties.selection_type = DialogConfigs.FILE_SELECT;
properties.root = initialPath;
FilePickerDialog dialog = new FilePickerDialog(getActivity(), properties);
dialog.setTitle(R.string.selectFile);
dialog.setDialogSelectionListener(new DialogSelectionListener() {
@Override
public void onSelectedFilePaths(String[] files) {
if (files.length > 0) {
clientCertificatePath.setText(files[0]);
}
}
});
dialog.show();
}
});
}
private String getTextViewContent(int id) {
View view = getView();
if (view == null) return null;
TextView textView = (TextView) view.findViewById(id);
if (textView == null) {
Log.e(TAG, "cannot find " + id);
return null;
}
return String.valueOf(textView.getText());
}
private boolean enforceNotEmpty(int fieldName, String value) {
if (value != null && value.trim().length() > 0) {
return false;
}
Context context = getActivity();
String emptyError = context.getString(R.string.connectionEmptyError);
String errorMessage = String.format(emptyError, context.getString(fieldName));
showAlertDialog(context, R.string.error, errorMessage);
return true;
}
private boolean handleTelnetSave(Intent intent) {
String ip = getTextViewContent(R.id.ip);
if (enforceNotEmpty(R.string.connectionIP, ip)) return false;
intent.putExtra(BundleExtraKeys.CONNECTION_IP, ip);
String port = getTextViewContent(R.id.port);
if (enforceNotEmpty(R.string.connectionPort, port)) return false;
intent.putExtra(BundleExtraKeys.CONNECTION_PORT, port);
return true;
}
private boolean handleFHEMWEBSave(Intent intent) {
if (getView() == null) return false;
String url = getTextViewContent(R.id.url);
if (enforceNotEmpty(R.string.connectionURL, url)) return false;
if (enforceUrlStartsWithHttp(url)) return false;
String alternateUrl = getTextViewContent(R.id.alternate_url);
if (!isNullOrEmpty(alternateUrl) && enforceUrlStartsWithHttp(alternateUrl)) return false;
String username = getTextViewContent(R.id.username);
String clientCertificatePath = getTextViewContent(R.id.clientCertificatePath);
intent
.putExtra(CONNECTION_URL, url)
.putExtra(CONNECTION_ALTERNATE_URL, alternateUrl)
.putExtra(CONNECTION_USERNAME, username)
.putExtra(CONNECTION_CLIENT_CERTIFICATE_PATH, clientCertificatePath)
.putExtra(CONNECTION_CLIENT_CERTIFICATE_PASSWORD, getTextViewContent(R.id.clientCertificatePassword));
return true;
}
private boolean enforceUrlStartsWithHttp(String url) {
if (!url.startsWith("http")) {
Context context = getActivity();
String emptyError = context.getString(R.string.connectionUrlHttp);
showAlertDialog(context, R.string.error, emptyError);
return true;
}
return false;
}
@Override
public CharSequence getTitle(Context context) {
// FIXME: maybe better 'Edit connection'?
return context.getString(R.string.connectionManageTitle);
}
@Override
public void update(boolean doUpdate) {
if (!isModify) {
Log.e(TAG, "I can only update if a connection is being modified!");
getActivity().sendBroadcast(new Intent(Actions.DISMISS_EXECUTING_DIALOG));
return;
}
Intent intent = new Intent(Actions.CONNECTION_GET)
.setClass(getActivity(), ConnectionsIntentService.class)
.putExtra(BundleExtraKeys.CONNECTION_ID, connectionId)
.putExtra(BundleExtraKeys.RESULT_RECEIVER, new FhemResultReceiver() {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode != SUCCESS || !resultData.containsKey(CONNECTION)) {
return;
}
Serializable serializable = resultData.getSerializable(CONNECTION);
if (!(serializable instanceof FHEMServerSpec)) {
Log.e(TAG, "expected an FHEMServerSpec, but got " + serializable);
return;
}
setValuesForCurrentConnection((FHEMServerSpec) serializable);
FragmentActivity activity = getActivity();
if (activity != null)
activity.sendBroadcast(new Intent(Actions.DISMISS_EXECUTING_DIALOG));
}
});
getActivity().startService(intent);
}
private void setValuesForCurrentConnection(final FHEMServerSpec connection) {
final View view = getView();
if (view == null) return;
// We do not need to change the type selector here, as the right one is already selected.
// We just overwrite values within the edit fields.
if (connection.getServerType() == connectionType) {
fillDetail(connection);
} else {
// We have to change the detail view to the one which is right for the current
// connection type. However, we do not know when the selection changed listener
// of the combo box fires. This is why we register a global listener, which is called
// when the new view has been attached to the root view.
// Afterwards we can continue with filling the fields with the respective values
// of the current connection!
detailChangedListener = new ConnectionTypeDetailChangedListener() {
@Override
public void onChanged() {
detailChangedListener = null;
fillDetail(connection);
}
};
Spinner connectionTypeSpinner = (Spinner) view.findViewById(R.id.connectionType);
connectionTypeSpinner.setSelection(selectionIndexFor(connection.getServerType()), true);
}
}
private void fillDetail(FHEMServerSpec connection) {
setTextViewContent(R.id.connectionName, connection.getName());
switch (connectionType) {
case FHEMWEB:
fillFHEMWEB(connection);
break;
case TELNET:
fillTelnet(connection);
break;
}
}
private int selectionIndexFor(ServerType serverType) {
List<ServerType> serverTypes = getServerTypes();
for (int i = 0; i < serverTypes.size(); i++) {
if (serverType == serverTypes.get(i)) return i;
}
return -1;
}
private void setTextViewContent(int id, String value) {
setTextViewContent(getView(), id, value);
}
private void fillFHEMWEB(FHEMServerSpec connection) {
View view = getView();
if (view == null) return;
setTextViewContent(view, R.id.url, connection.getUrl());
setTextViewContent(view, R.id.alternate_url, connection.getAlternateUrl());
setTextViewContent(view, R.id.username, connection.getUsername() + "");
setTextViewContent(view, R.id.password, connection.getPassword());
setTextViewContent(view, R.id.clientCertificatePath, connection.getClientCertificatePath());
setTextViewContent(view, R.id.clientCertificatePassword, connection.getClientCertificatePassword());
}
private void fillTelnet(FHEMServerSpec connection) {
View view = getView();
if (view == null) return;
setTextViewContent(view, R.id.ip, connection.getIp());
setTextViewContent(view, R.id.port, connection.getPort() + "");
setTextViewContent(view, R.id.password, connection.getPassword());
}
private void setTextViewContent(View view, int id, String value) {
if (view == null) return;
TextView textView = (TextView) view.findViewById(id);
if (textView != null) {
textView.setText(value);
}
}
private interface ConnectionTypeDetailChangedListener {
void onChanged();
}
}