-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add example app using SecretBox and SealedBox * remove unnecessary files * Fix build * Fix existing files * minor changes * Fix themes * Remove unnecesssary permissions * Add constants for Shared Preference keys * Fix backstack * Add missing EOF * Add missing EOF * Remove unnecessary code * Handle backstack --------- Co-authored-by: Muzzammil <[email protected]>
- Loading branch information
1 parent
4627e46
commit 2b4928d
Showing
22 changed files
with
975 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 112 additions & 1 deletion
113
android-example/src/main/java/io/xconn/androidexample/MainActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,125 @@ | ||
package io.xconn.androidexample; | ||
|
||
import static io.xconn.androidexample.util.Helpers.bytesToHex; | ||
import static io.xconn.androidexample.util.Helpers.convertTo32Bytes; | ||
|
||
import android.os.Bundle; | ||
|
||
|
||
import androidx.appcompat.app.AppCompatActivity; | ||
import androidx.fragment.app.Fragment; | ||
import androidx.fragment.app.FragmentManager; | ||
import androidx.fragment.app.FragmentTransaction; | ||
|
||
import com.google.android.material.bottomnavigation.BottomNavigationView; | ||
|
||
import java.util.Objects; | ||
|
||
import io.xconn.cryptology.KeyPair; | ||
import io.xconn.cryptology.SealedBox; | ||
import io.xconn.cryptology.SecretBox; | ||
import io.xconn.androidexample.fragment.CameraFragment; | ||
import io.xconn.androidexample.fragment.GalleryFragment; | ||
import io.xconn.androidexample.util.App; | ||
import io.xconn.androidexample.util.Helpers; | ||
|
||
public class MainActivity extends AppCompatActivity { | ||
public class MainActivity extends AppCompatActivity implements Helpers.PasswordDialogListener { | ||
|
||
private FragmentManager fragmentManager; | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
setContentView(R.layout.activity_main); | ||
|
||
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) { | ||
Fragment currentFragment = fragmentManager.findFragmentById(R.id.frameLayout); | ||
|
||
// Do nothing if already on the selected fragment | ||
if (currentFragment != null && currentFragment.getClass().equals(fragment.getClass())) { | ||
return true; | ||
} | ||
|
||
// Clear the backstack | ||
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); | ||
|
||
FragmentTransaction transaction = fragmentManager.beginTransaction() | ||
.replace(R.id.frameLayout, fragment); | ||
|
||
// Add to backstack if it's GalleryFragment | ||
if (fragment instanceof GalleryFragment) { | ||
transaction.addToBackStack(null); | ||
} | ||
|
||
transaction.commit(); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
|
||
fragmentManager.addOnBackStackChangedListener(() -> { | ||
Fragment currentFragment = fragmentManager.findFragmentById(R.id.frameLayout); | ||
|
||
if (currentFragment instanceof CameraFragment) { | ||
bottomNavigationView.setSelectedItemId(R.id.menu_camera); | ||
} else if (currentFragment instanceof GalleryFragment) { | ||
bottomNavigationView.setSelectedItemId(R.id.menu_gallery); | ||
} | ||
}); | ||
|
||
if (!App.getBoolean(App.PREF_IS_DIALOG_SHOWN)) { | ||
Helpers.showPasswordDialog(this, this, false); | ||
} | ||
} | ||
|
||
|
||
@Override | ||
public boolean onPasswordSubmit(String password) { | ||
if (password.isEmpty()) { | ||
return false; | ||
} | ||
|
||
// Generate key pair | ||
KeyPair keyPair = SealedBox.generateKeyPair(); | ||
|
||
// Convert public key to hexadecimal string and save it | ||
String publicKey = bytesToHex(keyPair.getPublicKey()); | ||
App.saveString(App.PREF_PUBLIC_KEY, publicKey); | ||
|
||
// Generate nonce and save it | ||
byte[] nonce = SecretBox.generateNonce(); | ||
App.saveString(App.PREF_NONCE, bytesToHex(nonce)); | ||
|
||
// Encrypt private key with entered password and save it | ||
byte[] encryptedPrivateKey = SecretBox.box(nonce, keyPair.getPrivateKey(), | ||
Objects.requireNonNull(convertTo32Bytes(password))); | ||
App.saveString(App.PREF_PRIVATE_KEY, bytesToHex(encryptedPrivateKey)); | ||
|
||
App.saveBoolean(App.PREF_IS_DIALOG_SHOWN, true); | ||
return true; | ||
} | ||
|
||
@Override | ||
public void onPasswordCancel() { | ||
|
||
} | ||
|
||
@Override | ||
public void onDismissed(boolean dismissedAfterSubmit) { | ||
|
||
} | ||
} |
171 changes: 171 additions & 0 deletions
171
android-example/src/main/java/io/xconn/androidexample/fragment/CameraFragment.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package io.xconn.androidexample.fragment; | ||
|
||
import static android.app.Activity.RESULT_OK; | ||
import static io.xconn.androidexample.util.Helpers.bytesToHex; | ||
import static io.xconn.androidexample.util.Helpers.hexToBytes; | ||
|
||
import android.Manifest; | ||
import android.annotation.SuppressLint; | ||
import android.content.Intent; | ||
import android.graphics.Bitmap; | ||
import android.os.Bundle; | ||
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 androidx.activity.result.ActivityResultLauncher; | ||
import androidx.activity.result.contract.ActivityResultContracts; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.fragment.app.Fragment; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.File; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.util.Objects; | ||
|
||
import io.xconn.androidexample.R; | ||
import io.xconn.androidexample.util.App; | ||
import io.xconn.cryptology.SealedBox; | ||
|
||
public class CameraFragment extends Fragment { | ||
|
||
private ActivityResultLauncher<String> cameraPermissionLauncher; | ||
private ActivityResultLauncher<Intent> cameraLauncher; | ||
private ActivityResultLauncher<Intent> 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 -> | ||
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)); | ||
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(); | ||
} | ||
} | ||
); | ||
} | ||
|
||
@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(App.PREF_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); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.