diff --git a/app-k9mail/badging/fossRelease-badging.txt b/app-k9mail/badging/fossRelease-badging.txt
index cb9192fab86..a2f2128f7b7 100644
--- a/app-k9mail/badging/fossRelease-badging.txt
+++ b/app-k9mail/badging/fossRelease-badging.txt
@@ -14,6 +14,7 @@ uses-permission: name='android.permission.FOREGROUND_SERVICE'
uses-permission: name='android.permission.FOREGROUND_SERVICE_DATA_SYNC' maxSdkVersion='33'
uses-permission: name='android.permission.FOREGROUND_SERVICE_SPECIAL_USE'
uses-permission: name='android.permission.SCHEDULE_EXACT_ALARM'
+uses-permission: name='android.permission.CAMERA'
uses-permission: name='android.permission.USE_BIOMETRIC'
uses-permission: name='android.permission.USE_FINGERPRINT'
uses-permission: name='com.fsck.k9.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION'
@@ -84,6 +85,7 @@ property: name='android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE' value='This servic
uses-library-not-required:'androidx.window.extensions'
uses-library-not-required:'androidx.window.sidecar'
feature-group: label=''
+ uses-feature-not-required: name='android.hardware.camera'
uses-feature-not-required: name='android.hardware.touchscreen'
provides-component:'app-widget'
main
diff --git a/app-k9mail/badging/fullRelease-badging.txt b/app-k9mail/badging/fullRelease-badging.txt
index 999834cd6db..8d12d4f38de 100644
--- a/app-k9mail/badging/fullRelease-badging.txt
+++ b/app-k9mail/badging/fullRelease-badging.txt
@@ -14,6 +14,7 @@ uses-permission: name='android.permission.FOREGROUND_SERVICE'
uses-permission: name='android.permission.FOREGROUND_SERVICE_DATA_SYNC' maxSdkVersion='33'
uses-permission: name='android.permission.FOREGROUND_SERVICE_SPECIAL_USE'
uses-permission: name='android.permission.SCHEDULE_EXACT_ALARM'
+uses-permission: name='android.permission.CAMERA'
uses-permission: name='android.permission.USE_BIOMETRIC'
uses-permission: name='android.permission.USE_FINGERPRINT'
uses-permission: name='com.android.vending.BILLING'
@@ -85,6 +86,7 @@ property: name='android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE' value='This servic
uses-library-not-required:'androidx.window.extensions'
uses-library-not-required:'androidx.window.sidecar'
feature-group: label=''
+ uses-feature-not-required: name='android.hardware.camera'
uses-feature-not-required: name='android.hardware.touchscreen'
provides-component:'app-widget'
main
diff --git a/core/android/common/src/main/AndroidManifest.xml b/core/android/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..bdf29cb63ef
--- /dev/null
+++ b/core/android/common/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt
index 5a0b5994759..56bf2de5c47 100644
--- a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt
+++ b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt
@@ -1,5 +1,6 @@
package app.k9mail.core.android.common
+import app.k9mail.core.android.common.camera.cameraModule
import app.k9mail.core.android.common.contact.contactModule
import app.k9mail.core.common.coreCommonModule
import org.koin.core.module.Module
@@ -9,4 +10,6 @@ val coreCommonAndroidModule: Module = module {
includes(coreCommonModule)
includes(contactModule)
+
+ includes(cameraModule)
}
diff --git a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/CameraCaptureHandler.kt b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/CameraCaptureHandler.kt
new file mode 100644
index 00000000000..257747aed78
--- /dev/null
+++ b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/CameraCaptureHandler.kt
@@ -0,0 +1,57 @@
+package app.k9mail.core.android.common.camera
+
+import android.Manifest.permission
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.provider.MediaStore
+import androidx.core.app.ActivityCompat
+import androidx.core.app.ActivityCompat.startActivityForResult
+import androidx.core.content.ContextCompat
+import app.k9mail.core.android.common.camera.io.CaptureImageFileWriter
+
+class CameraCaptureHandler(
+ private val captureImageFileWriter: CaptureImageFileWriter,
+) {
+
+ private lateinit var capturedImageUri: Uri
+
+ companion object {
+ const val REQUEST_IMAGE_CAPTURE: Int = 6
+ const val CAMERA_PERMISSION_REQUEST_CODE: Int = 100
+ }
+
+ fun getCapturedImageUri(): Uri {
+ if (::capturedImageUri.isInitialized) {
+ return capturedImageUri
+ } else {
+ throw UninitializedPropertyAccessException("Image Uri not initialized")
+ }
+ }
+
+ fun canLaunchCamera(context: Context) =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
+
+ fun openCamera(activity: Activity) {
+ val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ capturedImageUri = captureImageFileWriter.getFileUri()
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri)
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ startActivityForResult(activity, intent, REQUEST_IMAGE_CAPTURE, null)
+ }
+
+ fun requestCameraPermission(activity: Activity) {
+ ActivityCompat.requestPermissions(
+ activity,
+ arrayOf(permission.CAMERA),
+ CAMERA_PERMISSION_REQUEST_CODE,
+ )
+ }
+
+ fun hasCameraPermission(context: Context): Boolean {
+ val hasPermission = ContextCompat.checkSelfPermission(context, permission.CAMERA)
+ return hasPermission == PackageManager.PERMISSION_GRANTED
+ }
+}
diff --git a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/CameraKoinModule.kt b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/CameraKoinModule.kt
new file mode 100644
index 00000000000..8d4996618be
--- /dev/null
+++ b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/CameraKoinModule.kt
@@ -0,0 +1,13 @@
+package app.k9mail.core.android.common.camera
+
+import app.k9mail.core.android.common.camera.io.CaptureImageFileWriter
+import org.koin.dsl.module
+
+internal val cameraModule = module {
+ single { CaptureImageFileWriter(context = get()) }
+ single {
+ CameraCaptureHandler(
+ captureImageFileWriter = get(),
+ )
+ }
+}
diff --git a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/io/CaptureImageFileWriter.kt b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/io/CaptureImageFileWriter.kt
new file mode 100644
index 00000000000..72ab8fa621e
--- /dev/null
+++ b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/io/CaptureImageFileWriter.kt
@@ -0,0 +1,31 @@
+package app.k9mail.core.android.common.camera.io
+
+import android.content.Context
+import android.net.Uri
+import androidx.core.content.FileProvider
+import java.io.File
+
+class CaptureImageFileWriter(private val context: Context) {
+
+ fun getFileUri(): Uri {
+ val file = getCaptureImageFile()
+ return FileProvider.getUriForFile(context, "${context.packageName}.activity", file)
+ }
+
+ private fun getCaptureImageFile(): File {
+ val fileName = "IMG_${System.currentTimeMillis()}$FILE_EXT"
+ return File(getDirectory(), fileName)
+ }
+
+ private fun getDirectory(): File {
+ val directory = File(context.cacheDir, DIRECTORY_NAME)
+ directory.mkdirs()
+
+ return directory
+ }
+
+ companion object {
+ private const val FILE_EXT = ".jpg"
+ private const val DIRECTORY_NAME = "captureImage"
+ }
+}
diff --git a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/provider/CaptureImageFileProvider.kt b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/provider/CaptureImageFileProvider.kt
new file mode 100644
index 00000000000..06e8e3ad612
--- /dev/null
+++ b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/camera/provider/CaptureImageFileProvider.kt
@@ -0,0 +1,5 @@
+package app.k9mail.core.android.common.camera.provider
+
+import androidx.core.content.FileProvider
+
+class CaptureImageFileProvider : FileProvider()
diff --git a/core/android/common/src/main/res/xml/capture_image_file_provider_paths.xml b/core/android/common/src/main/res/xml/capture_image_file_provider_paths.xml
new file mode 100644
index 00000000000..aa70953d5f4
--- /dev/null
+++ b/core/android/common/src/main/res/xml/capture_image_file_provider_paths.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/legacy/ui/legacy/build.gradle.kts b/legacy/ui/legacy/build.gradle.kts
index 955e71ff9b1..bf07b41df4a 100644
--- a/legacy/ui/legacy/build.gradle.kts
+++ b/legacy/ui/legacy/build.gradle.kts
@@ -17,6 +17,7 @@ dependencies {
implementation(projects.core.featureflags)
implementation(projects.core.ui.theme.api)
implementation(projects.feature.launcher)
+ implementation(projects.core.common)
implementation(projects.feature.navigation.drawer.api)
implementation(projects.feature.navigation.drawer.dropdown)
implementation(projects.feature.navigation.drawer.siderail)
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java
index 60014129ca8..d6562344657 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java
@@ -7,8 +7,8 @@
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
-
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
@@ -17,6 +17,7 @@
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -27,6 +28,7 @@
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
@@ -36,6 +38,7 @@
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.PopupMenu;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -57,6 +60,7 @@
import com.fsck.k9.activity.compose.AttachmentPresenter;
import com.fsck.k9.activity.compose.AttachmentPresenter.AttachmentMvpView;
import com.fsck.k9.activity.compose.AttachmentPresenter.WaitingAction;
+import app.k9mail.core.android.common.camera.CameraCaptureHandler;
import com.fsck.k9.activity.compose.ComposeCryptoStatus;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState;
import com.fsck.k9.activity.compose.IdentityAdapter;
@@ -120,6 +124,9 @@
import org.openintents.openpgp.OpenPgpApiManager;
import org.openintents.openpgp.util.OpenPgpIntentStarter;
import timber.log.Timber;
+import static com.fsck.k9.activity.compose.AttachmentPresenter.REQUEST_CODE_ATTACHMENT_URI;
+import static app.k9mail.core.android.common.camera.CameraCaptureHandler.CAMERA_PERMISSION_REQUEST_CODE;
+import static app.k9mail.core.android.common.camera.CameraCaptureHandler.REQUEST_IMAGE_CAPTURE;
@SuppressWarnings("deprecation") // TODO get rid of activity dialogs and indeterminate progress bars
@@ -171,6 +178,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private static final int REQUEST_MASK_ATTACHMENT_PRESENTER = (1 << 10);
private static final int REQUEST_MASK_MESSAGE_BUILDER = (1 << 11);
+
+
/**
* Regular expression to remove the first localized "Re:" prefix in subjects.
*
@@ -188,6 +197,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private final Contacts contacts = DI.get(Contacts.class);
+ private final CameraCaptureHandler cameraCaptureHandler = DI.get(CameraCaptureHandler.class);
+
private QuotedMessagePresenter quotedMessagePresenter;
private MessageLoaderHelper messageLoaderHelper;
private AttachmentPresenter attachmentPresenter;
@@ -324,6 +335,7 @@ public void onCreate(Bundle savedInstanceState) {
EditText upperSignature = findViewById(R.id.upper_signature);
EditText lowerSignature = findViewById(R.id.lower_signature);
+
QuotedMessageMvpView quotedMessageMvpView = new QuotedMessageMvpView(this);
quotedMessagePresenter = new QuotedMessagePresenter(this, quotedMessageMvpView, account);
attachmentPresenter = new AttachmentPresenter(getApplicationContext(), attachmentMvpView,
@@ -503,7 +515,6 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
setProgressBarIndeterminateVisibility(true);
currentMessageBuilder.reattachCallback(this);
}
-
}
@Override
@@ -796,11 +807,31 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
attachmentPresenter.onActivityResult(resultCode, requestCode, data);
return;
}
+
+ if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
+ Intent intent = new Intent();
+ intent.setData(cameraCaptureHandler.getCapturedImageUri());
+ attachmentPresenter.onActivityResult(resultCode, REQUEST_CODE_ATTACHMENT_URI, intent);
+ return;
+ }
}
super.onActivityResult(requestCode, resultCode, data);
}
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ cameraCaptureHandler.openCamera(this);
+ } else {
+ Toast.makeText(this,R.string.camera_permission_denied,Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+
private void onAccountChosen(LegacyAccount account, Identity identity) {
if (!this.account.equals(account)) {
Timber.v("Switching account from %s to %s", this.account, account);
@@ -846,6 +877,29 @@ private void onAccountChosen(LegacyAccount account, Identity identity) {
switchToIdentity(identity);
}
+ private void showPopupMenu(View view) {
+ PopupMenu popup = new PopupMenu(this, view);
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.message_attachment_option, popup.getMenu());
+ popup.getMenu().findItem(R.id.open_camera).setVisible(cameraCaptureHandler.canLaunchCamera(this));
+ popup.setOnMenuItemClickListener(item -> {
+ int itemId = item.getItemId();
+ if (itemId == R.id.open_camera) {
+ if(!cameraCaptureHandler.hasCameraPermission(this)){
+ cameraCaptureHandler.requestCameraPermission(this);
+ }else {
+ cameraCaptureHandler.openCamera(this);
+ }
+ return true;
+ } else if (itemId == R.id.attach_file) {
+ attachmentPresenter.onClickAddAttachment(recipientPresenter);
+ return true;
+ }
+ return false;
+ });
+ popup.show();
+ }
+
private void switchToIdentity(Identity identity) {
this.identity = identity;
identityChanged = true;
@@ -952,7 +1006,8 @@ public boolean onOptionsItemSelected(MenuItem item) {
} else if (id == R.id.openpgp_sign_only_disable) {
recipientPresenter.onMenuSetSignOnly(false);
} else if (id == R.id.add_attachment) {
- attachmentPresenter.onClickAddAttachment(recipientPresenter);
+ View attachmentMenuAnchor = findViewById(com.fsck.k9.ui.base.R.id.toolbar).findViewById(R.id.add_attachment);
+ showPopupMenu(attachmentMenuAnchor);
} else if (id == R.id.read_receipt) {
onReadReceipt();
} else {
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java
index bef1a763b60..d429900317f 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java
@@ -39,7 +39,7 @@ public class AttachmentPresenter {
private static final String LOADER_ARG_ATTACHMENT = "attachment";
private static final int LOADER_ID_MASK = 1 << 6;
private static final int MAX_TOTAL_LOADERS = LOADER_ID_MASK - 1;
- private static final int REQUEST_CODE_ATTACHMENT_URI = 1;
+ public static final int REQUEST_CODE_ATTACHMENT_URI = 1;
// injected state
diff --git a/legacy/ui/legacy/src/main/res/menu/message_attachment_option.xml b/legacy/ui/legacy/src/main/res/menu/message_attachment_option.xml
new file mode 100644
index 00000000000..50a3ff00b25
--- /dev/null
+++ b/legacy/ui/legacy/src/main/res/menu/message_attachment_option.xml
@@ -0,0 +1,16 @@
+
+
diff --git a/legacy/ui/legacy/src/main/res/values-ar/strings.xml b/legacy/ui/legacy/src/main/res/values-ar/strings.xml
index a37cc8389bc..ff8145c0d58 100644
--- a/legacy/ui/legacy/src/main/res/values-ar/strings.xml
+++ b/legacy/ui/legacy/src/main/res/values-ar/strings.xml
@@ -853,5 +853,5 @@
الاسم وعنوان البريد الإلكتروني
-فريق ثَندَربِرْد للهواتف الذكيّة
+ فريق ثَندَربِرْد للهواتف الذكيّة
diff --git a/legacy/ui/legacy/src/main/res/values-az/strings.xml b/legacy/ui/legacy/src/main/res/values-az/strings.xml
index a43f465349b..274295a9432 100644
--- a/legacy/ui/legacy/src/main/res/values-az/strings.xml
+++ b/legacy/ui/legacy/src/main/res/values-az/strings.xml
@@ -164,4 +164,4 @@
Bitir
Poçtu yoxla
Mesajları göndər
-
\ No newline at end of file
+
diff --git a/legacy/ui/legacy/src/main/res/values-br/strings.xml b/legacy/ui/legacy/src/main/res/values-br/strings.xml
index dd850e893a5..8575ae5fc91 100644
--- a/legacy/ui/legacy/src/main/res/values-br/strings.xml
+++ b/legacy/ui/legacy/src/main/res/values-br/strings.xml
@@ -756,7 +756,7 @@ Gallout a rit mirout ar gemennadenn-mañ hag implij anezhi evel un enrolladenn e
-Skipailh Thunderbird evit ardivinkoù hezoug
+ Skipailh Thunderbird evit ardivinkoù hezoug
Bezañ skoazellet
N\'eus ket bet gallet kargañ marilh ar c\'hemmoù.
Petra nevez
diff --git a/legacy/ui/legacy/src/main/res/values-gd/strings.xml b/legacy/ui/legacy/src/main/res/values-gd/strings.xml
index 6519fd99afc..6ea2ec29ccd 100644
--- a/legacy/ui/legacy/src/main/res/values-gd/strings.xml
+++ b/legacy/ui/legacy/src/main/res/values-gd/strings.xml
@@ -744,7 +744,7 @@
-Sgioba mobile Thunderbird
+ Sgioba mobile Thunderbird
Faigh cobhair
Sàbhail an ceanglachan
Crìoch air an fho-sgrìobhadh
diff --git a/legacy/ui/legacy/src/main/res/values-hi/strings.xml b/legacy/ui/legacy/src/main/res/values-hi/strings.xml
index 41e393261e0..22e9eeaef3f 100644
--- a/legacy/ui/legacy/src/main/res/values-hi/strings.xml
+++ b/legacy/ui/legacy/src/main/res/values-hi/strings.xml
@@ -160,4 +160,4 @@
एक्सपोर्ट पूरा नहीं हुआ।
सब मिटाएं
Thunderbird Mobile Team
-
\ No newline at end of file
+
diff --git a/legacy/ui/legacy/src/main/res/values/strings.xml b/legacy/ui/legacy/src/main/res/values/strings.xml
index ec990bc04b4..c8933d17071 100644
--- a/legacy/ui/legacy/src/main/res/values/strings.xml
+++ b/legacy/ui/legacy/src/main/res/values/strings.xml
@@ -5,6 +5,9 @@
The K-9 Dog Walkers
Thunderbird Mobile Team
+ Open Camera
+ Attach File
+ Camera Permission Denied! Unable to take photo
Source code
Apache License, Version 2.0
Open Source Project