Secure IoT command sending (Android Client). There are many ways to secure and authenticate a networking communication, but not all solutions will run on a microcontroller, where processing power and memory is a scarce resource.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

262 lines
11 KiB

package com.skulixlabs.iotcontrol;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreferenceCompat;
import com.skulixlabs.iotcontrol.util.jackson.JsonUtils;
import java.io.InputStream;
import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
import static com.skulixlabs.iotcontrol.util.jackson.JsonUtils.REQUEST_READ_FILE;
import static com.skulixlabs.iotcontrol.util.jackson.JsonUtils.REQUEST_READ_STORAGE;
/** Preference screens does not currently support Data Binding
* because it is not a xml layout file
*/
public class SettingsFragment extends PreferenceFragmentCompat {
@ColorInt public static Integer primaryColor = null;
public SettingsFragment() {
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
((Preference) findPreference("load_json_commands")).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
showImportDialog();
return true;
};
});
((SwitchPreferenceCompat) findPreference("dark_theme")).setOnPreferenceChangeListener(updateThemeListener(themeAction()));
((ListPreference) findPreference("theme_color")).setOnPreferenceChangeListener(updateThemeListener(themeAction()));
((Preference) findPreference("version")).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment).navigate(R.id.action_settingsFragment_to_aboutFragment);
return true;
};
});
shouldShowImportDialog();
}
private void shouldShowImportDialog() {
if (SettingsFragmentArgs.fromBundle(getArguments()).getImportCmds()) {
getArguments().putBoolean("importCmds", false);
showImportDialog();
}
}
private void showImportDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = requireActivity().getLayoutInflater();
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(inflater.inflate(R.layout.dialog_import_method, null));
final AlertDialog alertDialog = builder.create();
alertDialog.show();
alertDialog.findViewById(R.id.btnFromClipboard).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
String json = "";
// If the clipboard doesn't contain data, else start the process after validation
if (!(clipboard.hasPrimaryClip()) || !(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {
Toast.makeText(getActivity(), "Not valid!", Toast.LENGTH_SHORT).show();
((Preference) findPreference("load_json_commands")).setIcon(R.drawable.ic_error_outline_red_48dp);
} else {
try {
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); // Since the clipboard contains plain text.
json = item.getText().toString(); // Gets the clipboard as text.
new JsonUtils().saveCommands(getActivity(), json);
((Preference) findPreference("load_json_commands")).setIcon(R.drawable.ic_check_green_48dp);
alertDialog.cancel();
} catch (Exception e) {
Toast.makeText(getActivity(), "Not valid!", Toast.LENGTH_SHORT).show();
((Preference) findPreference("load_json_commands")).setIcon(R.drawable.ic_error_outline_red_48dp);
}
}
}
});
alertDialog.findViewById(R.id.btnFromFile).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
selectFile();
}
});
}
public Runnable themeAction() {
return new Runnable() {
@Override
public void run() {
primaryColor = null;
((MainActivity) getActivity()).theme();
getActivity().recreate();
}
};
}
public static synchronized int getPrimaryColor(Context context) {
if (primaryColor == null) {
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
primaryColor = typedValue.data;
}
return primaryColor;
}
private String FRAGMENT_DIALOG = "dialog";
public void selectFile() {
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
requestReadStoragePermission();
return;
}
openFileManager();
}
private void openFileManager() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, REQUEST_READ_FILE);
}
private void requestReadStoragePermission() {
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
} else {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_READ_STORAGE);
}
}
public static class ConfirmationDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Fragment parent = getParentFragment();
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.request_read_external_storage_permission)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
parent.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_READ_STORAGE);
}
})
.create();
}
}
public static class ErrorDialog extends DialogFragment {
private static final String ARG_MESSAGE = "message";
public static ErrorDialog newInstance(String message) {
ErrorDialog dialog = new ErrorDialog();
Bundle args = new Bundle();
args.putString(ARG_MESSAGE, message);
dialog.setArguments(args);
return dialog;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
return new AlertDialog.Builder(activity)
.setMessage(getArguments().getString(ARG_MESSAGE))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
getDialog().cancel();
}
})
.create();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_READ_STORAGE) {
if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { // one requested permission, one result expected (in results array)
ErrorDialog.newInstance(getString(R.string.request_read_external_storage_permission))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
} else {
openFileManager();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_READ_FILE && resultCode == Activity.RESULT_OK) {
if (data != null) {
Uri uri = data.getData();
try {
InputStream is = getActivity().getContentResolver().openInputStream(uri);
JsonUtils jsonUtils = new JsonUtils();
jsonUtils.saveCommands(getActivity(), is);
((Preference) findPreference("load_json_commands")).setIcon(R.drawable.ic_check_green_48dp);
System.out.println();
} catch (Exception e) {
((Preference) findPreference("load_json_commands")).setIcon(R.drawable.ic_error_outline_red_48dp);
e.printStackTrace();
}
}
}
}
public static Preference.OnPreferenceChangeListener updateThemeListener(final Runnable action) {
return new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
action.run();
return true;
}
};
}
}