Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app-k9mail/badging/fossRelease-badging.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions app-k9mail/badging/fullRelease-badging.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions core/android/common/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">

<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.CAMERA"/>

<application android:supportsRtl="true">
<provider
android:name=".camera.provider.CaptureImageFileProvider"
android:authorities="${applicationId}.activity"
android:exported="false"
android:grantUriPermissions="true"
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/capture_image_file_provider_paths"
/>
<meta-data
android:name="de.cketti.safecontentresolver.ALLOW_INTERNAL_ACCESS"
android:value="true"
/>
</provider>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -9,4 +10,6 @@ val coreCommonAndroidModule: Module = module {
includes(coreCommonModule)

includes(contactModule)

includes(cameraModule)
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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> {
CameraCaptureHandler(
captureImageFileWriter = get(),
)
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package app.k9mail.core.android.common.camera.provider

import androidx.core.content.FileProvider

class CaptureImageFileProvider : FileProvider()
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<paths>
<cache-path
name="captureImage"
path="captureImage"
/>
</paths>
1 change: 1 addition & 0 deletions legacy/ui/legacy/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -503,7 +515,6 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
setProgressBarIndeterminateVisibility(true);
currentMessageBuilder.reattachCallback(this);
}

}

@Override
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>

<item
android:id="@+id/open_camera"
android:title="@string/open_camera"
/>
<item
android:id="@+id/attach_file"
android:title="@string/attach_file"
/>

</menu>
2 changes: 1 addition & 1 deletion legacy/ui/legacy/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -853,5 +853,5 @@
<string name="clipboard_label_name_and_email_address">الاسم وعنوان البريد الإلكتروني </string>
<!--Might be displayed in a notification when deleting an account (in the background)-->
<!--For unread messages the first line of a message list item is modified by this to improve the experience for people with screen readers.-->
<string name="about_app_authors_thunderbird">فريق ثَندَربِرْد للهواتف الذكيّة</string>
<string name="about_app_authors_thunderbird">فريق ثَندَربِرْد للهواتف الذكيّة</string>
</resources>
2 changes: 1 addition & 1 deletion legacy/ui/legacy/src/main/res/values-az/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,4 @@
<string name="done_action">Bitir</string>
<string name="check_mail_action">Poçtu yoxla</string>
<string name="send_messages_action">Mesajları göndər</string>
</resources>
</resources>
Loading