diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..65ec49c --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,45 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace 'io.xconn.cryptologyexample' + compileSdk 34 + + defaultConfig { + applicationId "io.xconn.cryptologyexample" + minSdk 24 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation libs.appcompat + implementation libs.activity + implementation libs.constraintlayout + + + implementation libs.cryptology + implementation libs.navigation.fragment.ktx + implementation libs.navigation.ui.ktx + implementation libs.material + implementation libs.drawerlayout + + +} \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c1c2424 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/io/xconn/cryptologyexample/MainActivity.java b/android/app/src/main/java/io/xconn/cryptologyexample/MainActivity.java new file mode 100644 index 0000000..36564a0 --- /dev/null +++ b/android/app/src/main/java/io/xconn/cryptologyexample/MainActivity.java @@ -0,0 +1,133 @@ +package io.xconn.cryptologyexample; + +import static io.xconn.cryptologyexample.util.Helpers.bytesToHex; +import static io.xconn.cryptologyexample.util.Helpers.convertTo32Bytes; + + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.Objects; + +import io.xconn.cryptology.KeyPair; +import io.xconn.cryptology.SealedBox; +import io.xconn.cryptology.SecretBox; +import io.xconn.cryptologyexample.fragment.CameraFragment; +import io.xconn.cryptologyexample.fragment.GalleryFragment; +import io.xconn.cryptologyexample.util.App; + +public class MainActivity extends AppCompatActivity { + + + private androidx.appcompat.app.AlertDialog passwordDialog; + + private FragmentManager fragmentManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (!App.getBoolean("isDialogShown")) { + showPasswordDialog(); + } + fragmentManager = getSupportFragmentManager(); + BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation); + + Fragment cameraFragment = new CameraFragment(); + fragmentManager.beginTransaction().replace(R.id.frameLayout, cameraFragment).commit(); + + bottomNavigationView.setOnItemSelectedListener(item -> { + Fragment fragment = null; + if (item.getItemId() == R.id.menu_camera) { + fragment = new CameraFragment(); + } else if (item.getItemId() == R.id.menu_gallery) { + fragment = new GalleryFragment(); + } + + if (fragment != null) { + fragmentManager.beginTransaction().replace(R.id.frameLayout, fragment).commit(); + return true; + } + return false; + }); + } + + private void showPasswordDialog() { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + LayoutInflater inflater = getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.custom_dialog_box, null); + TextInputLayout textInputLayoutPassword = dialogView.findViewById(R.id.enterPassword); + EditText editTextPassword = textInputLayoutPassword.getEditText(); + + builder.setView(dialogView) + .setTitle("Enter Password") + .setPositiveButton("Submit", null) + .setCancelable(false); + + passwordDialog = builder.create(); + passwordDialog.show(); + + passwordDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) + .setOnClickListener(v -> { + String enteredPassword = editTextPassword != null ? + editTextPassword.getText().toString().trim() : ""; + + if (enteredPassword.isEmpty()) { + textInputLayoutPassword.setError("Please enter a password"); + textInputLayoutPassword.requestFocus(); + } else { + KeyPair keyPair = SealedBox.generateKeyPair(); + String publicKey = bytesToHex(keyPair.getPublicKey()); + App.saveString(App.PREF_PUBLIC_KEY, publicKey); + + byte[] nonce = SecretBox.generateNonce(); + App.saveString("nonce", bytesToHex(nonce)); + + byte[] encryptedPrivateKey = SecretBox.box(nonce, keyPair.getPrivateKey(), + Objects.requireNonNull(convertTo32Bytes(enteredPassword))); + App.saveString(App.PREF_PRIVATE_KEY, bytesToHex(encryptedPrivateKey)); + + App.saveBoolean("isDialogShown", true); + passwordDialog.dismiss(); + + System.out.println("------passSaved "); + } + }); + + assert editTextPassword != null; + editTextPassword.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // No action needed + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + passwordDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) + .setEnabled(!TextUtils.isEmpty(s)); + } + + @Override + public void afterTextChanged(Editable s) { + // No action needed + } + }); + + passwordDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) + .setEnabled(false); + } +} diff --git a/android/app/src/main/java/io/xconn/cryptologyexample/fragment/CameraFragment.java b/android/app/src/main/java/io/xconn/cryptologyexample/fragment/CameraFragment.java new file mode 100644 index 0000000..edc6b2e --- /dev/null +++ b/android/app/src/main/java/io/xconn/cryptologyexample/fragment/CameraFragment.java @@ -0,0 +1,189 @@ +package io.xconn.cryptologyexample.fragment; + +import static android.app.Activity.RESULT_OK; + +import static io.xconn.cryptologyexample.util.Helpers.bytesToHex; +import static io.xconn.cryptologyexample.util.Helpers.hexToBytes; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.os.Bundle; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; + +import android.provider.MediaStore; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Objects; + +import io.xconn.cryptology.SealedBox; +import io.xconn.cryptologyexample.R; +import io.xconn.cryptologyexample.util.App; + + +public class CameraFragment extends Fragment { + + + private ActivityResultLauncher cameraPermissionLauncher; + private ActivityResultLauncher cameraLauncher; + private ActivityResultLauncher galleryLauncher; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_camera, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + view.findViewById(R.id.button_capture).setOnClickListener(v -> dispatchTakePictureIntent()); + view.findViewById(R.id.button_select_photo).setOnClickListener(v -> openGallery()); + + // Initialize ActivityResultLaunchers + cameraLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) { + handleCameraResult(result.getData()); + } + } + ); + + galleryLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) { + handleGalleryResult(result.getData()); + } + } + ); + + // Initialize camera permission launcher + cameraPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + isGranted -> { + if (isGranted) { + startCamera(); + } else { + Toast.makeText(requireContext(), "Camera permission denied", + Toast.LENGTH_SHORT).show(); + } + } + ); + } + + private void dispatchTakePictureIntent() { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + requestCameraPermission(); + } else { + startCamera(); + } + } + + private void requestCameraPermission() { + cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + } + + @SuppressLint("QueryPermissionsNeeded") + private void startCamera() { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (takePictureIntent.resolveActivity(requireActivity().getPackageManager()) != null) { + cameraLauncher.launch(takePictureIntent); + } else { + Toast.makeText(requireContext(), "No camera app found", Toast.LENGTH_SHORT).show(); + } + } + + private void openGallery() { + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + galleryLauncher.launch(intent); + } + + private void handleCameraResult(@Nullable Intent data) { + assert data != null; + Bitmap bitmap = (Bitmap) Objects.requireNonNull(data.getExtras()).get("data"); + assert bitmap != null; + byte[] imageData = bitmapToByteArray(bitmap); + + byte[] publicKey = hexToBytes(App.getString("public_key")); + Log.d("PublicKey", "Public Key: " + bytesToHex(publicKey)); + + byte[] encryptedImageData = SealedBox.seal(imageData, publicKey); + saveImageToFile(encryptedImageData); + } + + private void handleGalleryResult(@Nullable Intent data) { + try { + if (data != null && data.getData() != null) { + Bitmap bitmap = MediaStore.Images.Media.getBitmap( + requireActivity().getContentResolver(), data.getData()); + byte[] imageData = bitmapToByteArray(bitmap); + + byte[] publicKey = hexToBytes(App.getString(App.PREF_PUBLIC_KEY)); + + byte[] encryptedImageData = SealedBox.seal(imageData, publicKey); + saveImageToFile(encryptedImageData); + } + } catch (IOException e) { + Log.w("IOException", e.getMessage(), e); + } + } + + private byte[] bitmapToByteArray(Bitmap bitmap) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); + return baos.toByteArray(); + } + + private void saveImageToFile(byte[] data) { + File directory = new File(requireContext().getFilesDir(), "cryptology"); + if (!directory.exists()) { + if (!directory.mkdirs()) { + Toast.makeText(requireContext(), "Failed to create directory", + Toast.LENGTH_SHORT).show(); + return; + } + } + + String fileName = "image_" + System.currentTimeMillis() + ".dat"; + File file = new File(directory, fileName); + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + fos.write(data); + Toast.makeText(requireContext(), "Image saved: " + file.getAbsolutePath(), + Toast.LENGTH_SHORT).show(); + Log.d("ImagePath", "Image saved: " + file.getAbsolutePath()); + } catch (IOException e) { + Log.w("IOException", e.getMessage(), e); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + Log.w("IOException", e.getMessage(), e); + } + } + } + } +} diff --git a/android/app/src/main/java/io/xconn/cryptologyexample/fragment/GalleryFragment.java b/android/app/src/main/java/io/xconn/cryptologyexample/fragment/GalleryFragment.java new file mode 100644 index 0000000..bff7e18 --- /dev/null +++ b/android/app/src/main/java/io/xconn/cryptologyexample/fragment/GalleryFragment.java @@ -0,0 +1,306 @@ +package io.xconn.cryptologyexample.fragment; + +import static io.xconn.cryptologyexample.util.Helpers.convertTo32Bytes; +import static io.xconn.cryptologyexample.util.Helpers.hexToBytes; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import android.os.Handler; +import android.os.Looper; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.EditText; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.Toast; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.xconn.cryptology.SealedBox; +import io.xconn.cryptology.SecretBox; +import io.xconn.cryptologyexample.R; +import io.xconn.cryptologyexample.util.App; + +public class GalleryFragment extends Fragment { + + private GridView gridView; + private static byte[] privateKey; + + private final ExecutorService executorService = Executors.newFixedThreadPool(4); + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + private androidx.appcompat.app.AlertDialog passwordDialog; + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_gallery, container, false); + gridView = view.findViewById(R.id.gridView); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + showPasswordDialog(); + } + + private void showPasswordDialog() { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); + LayoutInflater inflater = requireActivity().getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.custom_dialog_box, null); + TextInputLayout textInputLayoutPassword = dialogView.findViewById(R.id.enterPassword); + EditText editTextPassword = textInputLayoutPassword.getEditText(); + + builder.setView(dialogView) + .setTitle("Enter Password") + .setPositiveButton("Submit", null) + .setNegativeButton("Cancel", null) // Removed cancel button listener + .setCancelable(false); + + passwordDialog = builder.create(); + passwordDialog.show(); + + passwordDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) + .setOnClickListener(v -> { + if (editTextPassword != null) { + String password = editTextPassword.getText().toString(); + if (TextUtils.isEmpty(password)) { + Toast.makeText(requireContext(), "Please enter a password", + Toast.LENGTH_SHORT).show(); + } else { + if (decryptPrivateKey(password)) { + loadImages(); + passwordDialog.dismiss(); + } else { + Toast.makeText(requireContext(), "Incorrect password", + Toast.LENGTH_SHORT).show(); + } + } + } + }); + + passwordDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE) + .setOnClickListener(v -> { + passwordDialog.dismiss(); + // Navigate back to CameraFragment + navigateToCameraFragment(); + + }); + + assert editTextPassword != null; + editTextPassword.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // No action needed + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + passwordDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) + .setEnabled(!TextUtils.isEmpty(s)); + } + + @Override + public void afterTextChanged(Editable s) { + // No action needed + } + }); + + passwordDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) + .setEnabled(false); + } + + private boolean decryptPrivateKey(String password) { + String encryptedPrivateKeyHex = App.getString(App.PREF_PRIVATE_KEY); + String nonceHex = App.getString("nonce"); + + if (!TextUtils.isEmpty(encryptedPrivateKeyHex) && !TextUtils.isEmpty(nonceHex)) { + byte[] encryptedPrivateKey = hexToBytes(encryptedPrivateKeyHex); + byte[] nonce = hexToBytes(nonceHex); + + try { + privateKey = SecretBox.boxOpen(nonce, encryptedPrivateKey, + Objects.requireNonNull(convertTo32Bytes(password))); + return true; + } catch (Exception e) { + Log.e("DecryptPrivateKey", "Incorrect password", e); + return false; + } + } else { + Toast.makeText(requireContext(), "Private key or nonce not found", + Toast.LENGTH_SHORT).show(); + return false; + } + } + + private void loadImages() { + File directory = new File(requireContext().getFilesDir(), "cryptology"); + if (!directory.exists()) { + if (!directory.mkdirs()) { + Toast.makeText(requireContext(), "Failed to create directory", + Toast.LENGTH_SHORT).show(); + return; + } + } + + List imageFiles = new ArrayList<>(); + File[] files = directory.listFiles(); + + if (files != null) { + for (File file : files) { + if (file.isFile()) { + imageFiles.add(file); + } + } + } + + ImageAdapter adapter = new ImageAdapter( + requireContext(), + imageFiles, + executorService, + mainHandler); + gridView.setAdapter(adapter); + } + + private static class ImageAdapter extends BaseAdapter { + + private final List imageFiles; + private final LayoutInflater inflater; + private final ExecutorService executorService; + private final Handler mainHandler; + + ImageAdapter(Context context, + List imageFiles, + ExecutorService executorService, + Handler mainHandler) { + this.imageFiles = imageFiles; + this.inflater = LayoutInflater.from(context); + this.executorService = executorService; + this.mainHandler = mainHandler; + } + + @Override + public int getCount() { + return imageFiles.size(); + } + + @Override + public Object getItem(int position) { + return imageFiles.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView == null) { + convertView = inflater.inflate(R.layout.gallery_item, parent, false); + holder = new ViewHolder(); + holder.imageView = convertView.findViewById(R.id.imageView); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + holder.imageView.setImageResource(R.drawable.placeholder); + + File imageFile = imageFiles.get(position); + decodeAndDecryptImageDataAsync(imageFile, holder.imageView); + + return convertView; + } + + private static class ViewHolder { + ImageView imageView; + } + + private void decodeAndDecryptImageDataAsync(final File imageFile, final ImageView imageView) + { + executorService.execute(() -> { + Bitmap bitmap = decryptImageData(imageFile); + mainHandler.post(() -> { + if (bitmap != null) { + imageView.setImageBitmap(bitmap); + } else { + imageView.setImageResource(R.drawable.error); + } + }); + }); + } + + private Bitmap decryptImageData(File imageFile) { + try (FileInputStream fis = new FileInputStream(imageFile)) { + byte[] encryptedData = new byte[(int) imageFile.length()]; + int bytesRead = fis.read(encryptedData); + if (bytesRead == -1) { + Log.e("FileInputStream", "No bytes were read from the file"); + return null; + } + + byte[] decryptedData = SealedBox.sealOpen(encryptedData, privateKey); + return BitmapFactory.decodeByteArray(decryptedData, 0, decryptedData.length); + } catch (IOException e) { + Log.e("IOException", "Error reading or decrypting image data", e); + return null; + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + executorService.shutdown(); + } + + @Override + public void onResume() { + super.onResume(); + requireActivity().getOnBackPressedDispatcher().addCallback(this, + new androidx.activity.OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + + navigateToCameraFragment(); + } + }); + } + + private void navigateToCameraFragment() { + FragmentManager fragmentManager = requireActivity().getSupportFragmentManager(); + FragmentTransaction transaction = fragmentManager.beginTransaction(); + transaction.replace(R.id.frameLayout, new CameraFragment()); + transaction.addToBackStack(null); + transaction.commit(); + } +} + diff --git a/android/app/src/main/java/io/xconn/cryptologyexample/util/App.java b/android/app/src/main/java/io/xconn/cryptologyexample/util/App.java new file mode 100644 index 0000000..d4afc3e --- /dev/null +++ b/android/app/src/main/java/io/xconn/cryptologyexample/util/App.java @@ -0,0 +1,51 @@ +package io.xconn.cryptologyexample.util; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; + +public class App extends Application { + @SuppressLint("StaticFieldLeak") + public static Context context; + + public static final String PREF_PUBLIC_KEY = "public_key"; + public static final String PREF_PRIVATE_KEY = "private_key"; + + + @Override + public void onCreate() { + super.onCreate(); + + context = getApplicationContext(); + } + + public static Context getContext() { + return context; + } + + public static SharedPreferences getPreferenceManager() { + return getContext().getSharedPreferences("shared_prefs", MODE_PRIVATE); + } + + public static void saveString(String key, String value) { + SharedPreferences sharedPreferences = getPreferenceManager(); + sharedPreferences.edit().putString(key, value).apply(); + } + + public static String getString(String key) { + SharedPreferences sharedPreferences = getPreferenceManager(); + return sharedPreferences.getString(key, ""); + } + + public static void saveBoolean(String key, boolean value) { + SharedPreferences sharedPreferences = getPreferenceManager(); + sharedPreferences.edit().putBoolean(key, value).apply(); + } + + public static boolean getBoolean(String key) { + SharedPreferences sharedPreferences = getPreferenceManager(); + return sharedPreferences.getBoolean(key, false); + } + +} \ No newline at end of file diff --git a/android/app/src/main/java/io/xconn/cryptologyexample/util/Helpers.java b/android/app/src/main/java/io/xconn/cryptologyexample/util/Helpers.java new file mode 100644 index 0000000..ec1fa92 --- /dev/null +++ b/android/app/src/main/java/io/xconn/cryptologyexample/util/Helpers.java @@ -0,0 +1,46 @@ +package io.xconn.cryptologyexample.util; + +import android.app.Application; +import android.util.Log; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Helpers extends Application { + + // Function to convert hexadecimal string to bytes + public static byte[] hexToBytes(String hexString) { + int len = hexString.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + + Character.digit(hexString.charAt(i + 1), 16)); + } + return data; + } + + // Function to convert bytes to hexadecimal string + public static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X", b)); + } + return sb.toString(); + } + + public static byte[] convertTo32Bytes(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes()); + + // If the hash is less than 32 bytes, we pad it with zeros + byte[] result = new byte[32]; + System.arraycopy(hash, 0, result, 0, Math.min(hash.length, 32)); + + return result; + } catch (NoSuchAlgorithmException e) { + Log.w("IOException", e.getMessage(), e); + return null; + } + } +} diff --git a/android/app/src/main/res/drawable/camera.xml b/android/app/src/main/res/drawable/camera.xml new file mode 100644 index 0000000..e5d01a2 --- /dev/null +++ b/android/app/src/main/res/drawable/camera.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/drawable/edittext_background.xml b/android/app/src/main/res/drawable/edittext_background.xml new file mode 100644 index 0000000..d27c6a1 --- /dev/null +++ b/android/app/src/main/res/drawable/edittext_background.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/error.png b/android/app/src/main/res/drawable/error.png new file mode 100644 index 0000000..fbc831b Binary files /dev/null and b/android/app/src/main/res/drawable/error.png differ diff --git a/android/app/src/main/res/drawable/gallery.xml b/android/app/src/main/res/drawable/gallery.xml new file mode 100644 index 0000000..d710d27 --- /dev/null +++ b/android/app/src/main/res/drawable/gallery.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/placeholder.png b/android/app/src/main/res/drawable/placeholder.png new file mode 100644 index 0000000..f0e8d47 Binary files /dev/null and b/android/app/src/main/res/drawable/placeholder.png differ diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4a49be3 --- /dev/null +++ b/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/custom_dialog_box.xml b/android/app/src/main/res/layout/custom_dialog_box.xml new file mode 100644 index 0000000..4f30155 --- /dev/null +++ b/android/app/src/main/res/layout/custom_dialog_box.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_camera.xml b/android/app/src/main/res/layout/fragment_camera.xml new file mode 100644 index 0000000..03885de --- /dev/null +++ b/android/app/src/main/res/layout/fragment_camera.xml @@ -0,0 +1,53 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_gallery.xml b/android/app/src/main/res/layout/fragment_gallery.xml new file mode 100644 index 0000000..80f518d --- /dev/null +++ b/android/app/src/main/res/layout/fragment_gallery.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/android/app/src/main/res/layout/gallery_item.xml b/android/app/src/main/res/layout/gallery_item.xml new file mode 100644 index 0000000..e261698 --- /dev/null +++ b/android/app/src/main/res/layout/gallery_item.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/android/app/src/main/res/menu/bottom_navigation_menu.xml b/android/app/src/main/res/menu/bottom_navigation_menu.xml new file mode 100644 index 0000000..3076ea2 --- /dev/null +++ b/android/app/src/main/res/menu/bottom_navigation_menu.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/values-night/themes.xml b/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..d9d8c6e --- /dev/null +++ b/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..51027e3 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #FF000000 + #FFFFFFFF + #039BE5 + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..f84a50b --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + Cryptology Java + + open + close + + Hello blank fragment + Select Photo + Capture + Password: + Enter password... + OK + Password: + Enter password... + Enter + \ No newline at end of file diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..b51cc94 --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,10 @@ + + + + +